Stripe Payment Service

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, ledger
  • payment with 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:

  1. find the matching payment record
  2. find the linked topup
  3. call balance.finishTopup()
  4. 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.