Skip to content

[ING-683] feat(wallets): expose connections - #6401

Open
mariohd wants to merge 1 commit into
mainfrom
ing-683
Open

mariohd wants to merge 1 commit into
mainfrom
ing-683

Conversation

@mariohd

@mariohd mariohd commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Context

ING-683. ING-466 (REST) and ING-467 (GraphQL) added the write path for per-object connections on wallets and recurring transaction rules, but nothing reads it back. ING-527 needs that routing to render the wallet's "External apps" tab, and ING-667 covers only the invoice snapshot. Branched from main — the read depends on nothing in those two PRs.

Description

Both surfaces report every category with the behaviour the object holds and the code of the connection actually in effect:

connections { category behavior code }
# behavior: specific | skip | inherit
"connections": {
  "payment":    {"behavior": "specific", "code": "stripe_us"},
  "tax":        {"behavior": "inherit",  "code": "anrok_main"},
  "crm":        {"behavior": "skip",     "code": null}
}

inherit means the object makes no choice of its own, and the code then shows the customer default that applies. That pairing is the point: a client can render the drawer state and a connection chip from one payload, without reimplementing the cascade. The REST shape mirrors the object already accepted on create/update.

One derivation feeds both — ConnectionResolvable#connection_routing, which loads overrides once rather than per category. Subscription includes the same concern, so ING-463's read half is already done at the model layer.

The wallet index eager-loads the overrides plus the customer connection lists the inherited path resolves through; without that the existing N+1 guard fails.

Two calls worth a reviewer's eye

Wallet webhooks gain the key. wallet.created, wallet.updated, wallet.terminated and wallet.depleted_ongoing_balance all share V1::WalletSerializer, so their payloads now carry connections and run the resolution queries. It's additive and consistent with payment_method, which is also unconditional and also in those payloads — but the alternative is to gate it behind include?(:connections) so only the REST endpoints opt in. Happy to switch.

The read is not gated behind multi_connection, contrary to the ticket text. The resolution cascade is ungated by design (ING-456 shipped "ungated, no behaviour change"), and with the flag off the read reports the customer default, which is accurate — gating would hide true information rather than protect anything.

Before merging

The ticket asked for the payload shape to be agreed with FE (ING-527 / ING-528) first, and that hasn't happened. If the tab needs the stored override separately from the resolved connection, ConnectionRouting changes shape — cheap now, a breaking change once it's merged and codegen'd.

Testing

481 examples, 0 failures. New coverage: the cascade at model level across wallet, rule and subscription (15), a real GraphQL query on the wallet and its nested rules, the REST show endpoint for inherit / specific / skip, and the rule serializer. The GraphQL query matters specifically — category is a String against an enum built from Symbol keys, which would raise at query time if they didn't coerce. The index N+1 guard and the schema-dump spec both pass; Rubocop clean.

## Context

ING-466 and ING-467 added the write path for per-object connections on
wallets and recurring transaction rules, but nothing read it back.
ING-527 needs that routing to render the wallet's external apps tab.

## Description

Both surfaces report every category with the behaviour the object
holds, and the code of the connection actually in effect. The
behaviour is specific, skip, or inherit when the object makes no
choice of its own. A client can therefore render the drawer state and
a connection chip from one payload, without reimplementing the
customer-default cascade.

One derivation feeds both: connection_routing on ConnectionResolvable,
which loads the overrides once rather than per category, so a
preloaded collection stays at a single query. Subscriptions include
the same concern, so their read comes with it.

GraphQL gains a ConnectionRouting type on the wallet and recurring
rule objects. REST gains a connections key on both serializers, shaped
like the object already accepted on create and update. Since the
wallet webhooks share that serializer, their payloads gain the key
too.

The wallet index eager-loads the overrides and the customer connection
lists the inherited path resolves through, without which the existing
N+1 guard fails.

The read is deliberately not gated behind multi_connection. The
resolution cascade is ungated by design, and with the flag off the
read reports the customer default, which is accurate. Gating it would
hide true information rather than protect anything.
@mariohd
mariohd marked this pull request as ready for review September 14, 2026 16:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant