- Merchant onboarding — Connect, plus an OAuth onramp for merchants who already have a Stripe account
- Customer payments —
PaymentIntentper order, attached to the merchant’s connected account or to the platform’s primary account when the merchant isn’t connected - Subscription billing — Stripe Checkout sessions for Droplinked’s own SaaS plans
Configuration
Merchant onboarding
There are three Stripe onramps, and they do not all produce the same kind of account. Because the account type determines merchant of record, chargeback liability and payout ownership, here is the precise mapping:
Who is merchant of record, and who answers a chargeback, follows the account the charge
landed in — see
Merchant PSP onboarding
for the full table.
Create an account + onboarding link (platform-created path)
1
Look up shop + user
Resolve the user’s email and the shop’s existing connected-account id (if any).
2
Reject if already onboarded
If the shop already has a connected account, throws
BadRequestException (“already
onboarded”).3
Create the Stripe account
stripe.accounts.create({ type, email }) — standard on the legacy route, express
on the Connect-module route. Save the new account ID against the shop.4
Return the onboarding link
stripe.accountLinks.create() with a return URL appropriate for the environment.
Return the URL to the merchant.A connected account is not automatically a chargeable one. Until Stripe enables charges,
the checkout intent resolver reports
PSP_ACCOUNT_NOT_READY / STRIPE_CHARGES_DISABLED and the card slot is withheld. Resolve
availability at request time.Webhook: account.updated
Mounted at POST /stripe/account-onboarding/webhook. The platform listens for
account.updated events. When charges_enabled and
payouts_enabled are both true, the shop’s Stripe status is flipped to active.
- Fetches the account-update endpoint secret from config
- Constructs the event with
stripe.webhooks.constructEvent(body, sig, secret)— verifying authenticity - If the event is
account.updatedand both capabilities are enabled, marks the shop active - Returns
trueon success; throwsBadRequestExceptionon verification failure
Customer payments
Per-order payments use the standardPaymentIntent flow. See Order lifecycle
for how this integrates with the order-confirmation saga.
Connected-account vs platform charges
- Merchant connected —
payment_intent.create({ on_behalf_of, transfer_data })so funds settle into the merchant’s Stripe account; platform retains an application fee - Merchant not connected — Payment captured into the platform’s primary Stripe account; merchant payout reconciled out-of-band
Subscription billing
Droplinked’s own SaaS plans bill through Stripe Checkout (subscription mode). The subscription gateway is exposed as integration-service endpoints called server-to-server from the backend.Create a subscription checkout session
{ checkoutUrl, sessionId }. Redirect the merchant to checkoutUrl.
Stripe metadata values must be strings — the gateway drops
null/undefined entries
before sending.Webhook signature verification
Droplinked mounts four Stripe webhook routes:
All of them use
stripe.webhooks.constructEvent to validate the Stripe-Signature header
against the endpoint secret, over the raw request body. A failed verification returns
400 and is never processed. A missing signing secret returns 503, not 400, so
Stripe retries rather than dropping a genuine event — it re-delivers for up to three days.
Full contract, including every non-Stripe source: Webhooks.
For replay safety and the broader webhook test matrix, see
Checkout stability.