Practitionist / Practitionist/elluminar_web
Dodo Payments provider — Merchant of Record for international sales
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 0
- Avg merge
- 17h 2m
- Merged PRs (30d)
- 18
Description
Why / Context
Razorpay handles all MVP payments (INR-first). Post-MVP we add Dodo Payments as Merchant of Record for international expansion (per the locked decision: "Razorpay pre-MVP, Dodo Payments post-MVP"). MoR means Dodo owns global tax/compliance remittance for international sales, which pairs with the Regional/PPP pricing issue. The schema and payment layer were designed provider-agnostic from day one, so this is an adapter implementation, not a migration.
Schema already in place — NO migration required
PaymentProviderKind.DODOenum value onPayment.provider/Subscription.providerPayment.providerOrderRef/providerPaymentRef(unique per provider) +raw JsonRefund.providerRefundRefSubscriptionPlan.providerRefs Json— adddodo.productIdper interval×currency alongside existingrazorpayidsUserProviderIdentity(provider: DODO, externalRef)for Dodo customer idsWebhookEvent(provider: DODO, eventRef)— idempotency backbone already handles multi-provider
Implementation plan
- Create Dodo merchant account; configure products mirroring
SubscriptionPlanintervals and one-time SKUs; store ids inSubscriptionPlan.providerRefs.dodo. - Implement
src/lib/payments/dodo.tssatisfying the existingPaymentProviderinterface (createCheckout,createSubscription,cancelSubscription,pauseSubscription,refund,verifyWebhook,parseEvent) using thedodopaymentsNode SDK. - Add
/api/webhooks/dodoroute handler: verify per Standard Webhooks spec (svix-style signature headers), insertWebhookEvent(provider: DODO), dispatch to the same fulfillment service Razorpay uses (payment.succeeded,subscription.active,subscription.renewed,refund.succeeded, failure events →PAST_DUE). - Provider selection policy in
src/lib/payments/index.ts: route by order currency/region (INR → Razorpay, non-INR → Dodo) behind aFeatureFlag('dodo-payments'). - Evaluate the official BetterAuth dodopayments plugin (better-auth.com/docs/plugins/dodopayments) for customer mapping/portal; adopt only if it doesn't fight our
Subscriptiontables (decision note in PR). - Customer portal link (Dodo-hosted) on the billing page for Dodo-billed users.
- Invoice handling: Dodo is MoR and issues its own invoices for international sales — store Dodo invoice refs in
Payment.raw; suppress our GSTInvoicegeneration for MoR payments (flag inInvoice.data). - Subscription migration playbook (docs/payments-dodo.md): existing Razorpay subscribers stay on Razorpay until cancellation; new international signups go to Dodo; write the support runbook.
- Unit tests for
parseEventmapping + E2E in Dodo test mode.
Acceptance criteria
- International (non-INR) checkout completes via Dodo test mode →
OrderPAID, fulfillment identical to Razorpay path - Dodo subscription lifecycle events transition
Subscription.statuscorrectly (active → past_due → cancelled) - Webhook replay is a no-op (WebhookEvent dedupe)
- Refund via Dodo updates
Refund.status=PROCESSEDand writes ledger reversals - Razorpay flows unaffected (regression suite green)
Dependencies / sequencing
- Builds on MVP M7 (commerce core) + M8 (subscriptions)
- Pairs with issue "Regional/PPP pricing + multi-currency GA" (#21 in plan) — ship this first or together
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the existing PaymentProvider interface and Razorpay flow, then read src/lib/payments/index.ts and implement the adapter in src/lib/payments/dodo.ts. Review the /api/webhooks/dodo route requirements and docs/payments-dodo.md, and add the specified parseEvent unit tests plus Dodo test-mode E2E coverage. Done means international checkout, subscription events, webhook deduplication, refunds, and the Razorpay regression suite all pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- api, backend, database, payments
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100