Crediting Balance
Why the Stripe service should not update wallet balance directly, and how it splits responsibility with the balance service.
The runtime-critical part of the Stripe service in this version is not call-time gating. It is how wallet crediting is finalized.
Why it should not update balance directly
If the Stripe service updates the wallet balance table on its own, several problems appear quickly:
- topup state and wallet credit can drift apart
- ledger entries may be missing or duplicated
- debugging loses a single crediting entry point
The more stable rule is:
the Stripe service decides whether payment succeeded, and real crediting always goes through
balance.finishTopup().
Relationship with the balance service
A useful split is:
balance: wallet, topups, ledgerpaymentwith the Stripe provider: Checkout, webhook, payment-result syncing
So the two services are not competitors. They are adjacent layers in one recharge flow.
A common end-to-end scenario
Create a topup and start payment
const topup = await user.service("balance").action("topups/create").invoke({
amount: 500,
});
const checkout = await user.service("payment").action("checkout/create").invoke({
method_id: "stripe",
topup_id: topup.topup_id,
});Credit automatically after success
When Stripe sends checkout.session.completed to the webhook, the service will:
- find the matching payment record
- find the linked topup
- call
balance.finishTopup() - mark the payment as
paid
Common misunderstandings
The Stripe service is not the wallet service
It does not own the primary balance table or ledger.
The more unified crediting is, the better
As soon as multiple paths can mutate balance directly, debugging and reconciliation become much harder.
Downcity is for attribution, not wallet ownership
If you want to remember which downcity page initiated a recharge, store it in metadata rather than splitting the wallet itself.