Mikalai SiliukMS Development
All articles
Article··9 min read

Mobile Payment Provider Abstraction from Day One

Mobile payment provider abstraction is one file, four verbs, and the day-one call that makes swapping RevenueCat or Adapty a one-day change.

paymentsarchitectureMVP

Build the wrapper before you have a paywall. Before paying users. Before you have finalised whether the paywall SDK behind it will be RevenueCat, Adapty, or Qonversion. That is the argument, and it is the exact opposite of what most non-technical founders hear from their engineer, which is usually a version of "we'll just use RevenueCat, it handles everything." Mobile payment provider abstraction is not what RevenueCat does. Mobile payment provider abstraction is what stands between your app and RevenueCat — one file, four verbs, and a boundary that decides whether swapping paywall SDKs is a one-day adapter change or a quarter of touching every screen. The eight-area architectural map names payments as Area 2 and points here. This is the drill-down.

The single-sentence thesis: a PaymentService interface — one file that knows what the payment system needs to do (start a subscription, restore purchases, check entitlement, cancel) and hides which provider does it — is the single most valuable line of defence against the year-two subscription rewrite, and it belongs in the codebase before you have picked the SDK. The SDK is the implementation detail. The interface is the design decision. Get the interface wrong and the SDK choice will still look easy in the demo — and cost you a quarter of engineering the first time you need to change it.

What a mobile payment provider abstraction actually is — one file, four verbs, zero SDK types anywhere else

The concrete shape is small. One file, usually called PaymentService or payment_service.dart or PaymentService.swift, that exposes an interface with four verbs the app needs and hides everything else:

  • startSubscription(productId) — begin the purchase flow for a subscription product. Whatever the SDK requires (product identifier lookup, StoreKit or Play Billing handshakes, receipt collection) happens inside this call.
  • restorePurchases() — the App-Store-mandated flow that lets a returning user get their subscription back on a new device.
  • checkEntitlement() — returns whether the current user has access. Not "is there a purchase in local state" — whether the server says the user is entitled. This is where server-side receipt validation mobile MVP correctness lives.
  • cancel() — opens the platform's cancellation surface. On iOS this is a deep link to Manage Subscriptions; on Android, the Play Store subscription page.

Optional verbs land alongside when the product needs them: a purchaseOneTime(productId) for lifetime or consumable purchases, a getAvailableProducts() when the paywall screen wants to render prices from the server. The four load-bearing verbs are the minimum.

The invariant that makes the interface real is what happens in the rest of the codebase: no other file imports RevenueCat, Adapty, Qonversion, StoreKit, or Play Billing types. The paywall screen imports PaymentService. The settings screen imports PaymentService. The account-restore modal imports PaymentService. The provider lives in one adapter file — behind the interface — and everything else in the codebase reaches payments through the interface. That is the payment provider abstraction layer, and it is the whole thing. This is the same shape as in-app purchase abstraction layer patterns you may have seen in Stripe-first web codebases: the SDK is the payload, the interface is the pattern.

Server-side entitlement is the second load-bearing beat. The checkEntitlement() verb returns whatever the server says, not what the local purchase state claims. The paywall SDK forwards a receipt to your backend; the backend validates it and writes entitlement to your database; the client reads that. Trust the phone for entitlement and you are one refund, one grace period, or one shared Apple ID away from a support ticket you cannot resolve. This is the correctness anchor for server-side receipt validation mobile MVP work — the boundary between "what the SDK returns" and "what the app trusts" is what the interface enforces.

Building this before you have a paywall is cheap. AI writes the RevenueCat, Adapty, or Qonversion SDK wiring in minutes — the initialisation, purchase callbacks, receipt forwarding, all of it. What AI does not do is decide where to draw the interface, which verbs to expose, or whether entitlement lives on the phone or the server. That judgement — and the migration cost of getting it wrong — is what the senior engineer is paid for. See how AI-augmented delivery works in practice for the workflow behind it.

Why paying for a paywall SDK is orthogonal to whether the abstraction exists

The counter-intuitive point most founders get wrong in the payments area: paying for a RevenueCat plan does not mean you have a mobile payment provider abstraction. RevenueCat is a very good implementation of the adapter. It is not the interface. Founders skip the wrapper because their engineer said "RevenueCat handles all the platform stuff for us" — which is true, and irrelevant to whether swapping RevenueCat for Adapty next year will cost you one file or twelve.

The revenuecat adapty qonversion abstraction confusion is worth naming directly. All three SDKs remove the platform-specific pain: StoreKit calls, Play Billing calls, iOS-Android receipt-format mismatches, sandbox-vs-production edge cases, subscription state machines. They earn their monthly fee for that alone. None of them removes the provider-specific pain of migrating between themselves, or of adding a second monetisation surface — a Stripe-powered web funnel next to App Store IAP, a lifetime purchase on top of a subscription, an enterprise deal that skips the App Store entirely. That pain is what the wrapper exists to remove, and no paywall SDK, by definition, can remove it — the wrapper's job is to sit above the SDK, not to be it.

The layered picture makes the point clearer. The paywall SDK is Layer 3, the vendor-specific adapter. The wrapper is Layer 2, the app-facing interface. The callers — paywall, settings, restore flow, business logic — are Layer 1. All three layers exist regardless of your architectural choices; skipping Layer 2 collapses Layer 1 into Layer 3 and turns every screen into a migration touch point. Adding a payment provider abstraction layer costs one file up front and returns every provider swap for the life of the product.

Concrete proof under production pressure: Boyfi shipped with dual monetisation surfaces from the start — App Store IAP via Adapty and a custom Stripe-powered WebFunnels API for the web funnel — sitting behind one PaymentService interface. The app store iap and stripe web funnel dual-subscription pattern is exactly the migration most codebases cannot survive without a rewrite; Boyfi added a third AI provider eight months later by touching one module, without touching a paywall screen. MyCoach is the opposite proof: Qonversion behind the same wrapper shape, two years and sixty updates without touching the payment layer once. The wrapper is not less valuable when you do not swap providers — it is what makes the code readable and the test surface small when the payment side stays stable and everything around it moves.

The discipline generalises. Hairly uses the same shape on image providers — three (OpenAI, RapidAPI, Lightx) behind one interface, two more added by writing one adapter each. The same pattern applies to auth — see the sibling deep-dive on auth-provider abstraction for the mirror argument. The wrapper is the pattern; the SDK is the payload.

I have cleaned up codebases where the paywall SDK was called from twelve different screens. Adding a web-based subscription funnel alongside App Store IAP required touching every one of them — a rewrite, not a feature addition. The engineer who built that codebase held the same title, used the same SDK, and delivered on time. The difference between that codebase and one that survives the year-two migration is one file — the interface — that was never written.

The founder-side test: three questions to ask before the first paywall ships

You do not need to read the code to verify the wrapper exists. You need to hear the answer to three questions. This is the founder-side test of a mobile payment provider abstraction, and it works in an architectural review or a hiring first call.

Question 1 — "Show me the file that hides the payment provider from the rest of the app."

If the engineer opens PaywallScreen.tsx (or .dart, or .swift) and starts pointing at RevenueCat imports and product-identifier lookups mixed with the pricing layout, the wrapper does not exist. If they open PaymentService.ts or payment_service.dart and it imports the SDK, exposes four verbs, and returns app-native types (a Result<Entitlement, PaymentError> or an equivalent — not raw SDK objects), the wrapper exists. The file name is not the point. The point is a single answer: "here is the one file that touches the provider, and nothing else does."

Question 2 — "If I asked you tomorrow to swap RevenueCat for Adapty, or add Stripe alongside App Store IAP, what would change?"

This is the how to swap payment provider mobile app question, and the answer is the receipt for whether the interface was drawn. The right answer names one file — the adapter behind PaymentService — plus webhook re-wiring on the backend. That is a one-day change. The wrong answer describes "touching the paywall screen, the settings screen, the restore flow, the account screen…" — that answer is the rewrite quote your engineer will present in month nine when the growth team asks for a web funnel. Ask it in week one instead.

Question 3 — "Where does entitlement come from — the phone or the server?"

The right answer is the server. Webhook-driven, validated on your own backend, written to your own database, read by the client as a boolean or a small JSON blob. The wrong answer is "we trust the local purchase state" or "we read it from the SDK on app launch." The wrong answer ships fine for the first hundred users; it produces duplicate subscriptions, grace-period edge cases, and refund-window support tickets you cannot resolve at a hundred paying users and above.

Three questions. Zero code-reading required. Together they surface whether the mobile payment provider abstraction was designed or skipped — and whether the rewrite tax is compounding quietly in a codebase that looks fine in the demo. It is fixable in month nine. It is far cheaper to fix in week one.

The engagement, if you want the boundary drawn for you

The wrapper is cheap before the SDK is picked, expensive to retrofit after the twelfth screen calls the SDK directly. If you are staring at a paywall SDK decision, a dual-monetisation question, or a codebase where you suspect Layer 2 got skipped — tell me about your project and I will tell you whether the mobile payment provider abstraction is already implied by your scope, or the decision your engineer is about to skip. For the full engagement model, the services page is where payments — and the other seven architectural areas — are owned end-to-end.