Skip to content

Build1 publisher3 min readPublished Updated

The name on the receipt assigns your tax registrations and your chargeback queue

A dev.to walkthrough by Sarah Dyne puts a payment processor at about 3 percent, a merchant of record at roughly 5, and app store billing at 15 to 30. The tax registrations and the dispute work run in the opposite direction.

The Engineer · Build desk

Illustration accompanying The name on the receipt assigns your tax registrations and your chargeback queue

What happened

  • A dev.to guide by Sarah Dyne sets out three payments stacks and argues the real decision is who is legally the seller, because that assignment carries tax, disputes and refunds with it.
  • On the processor option you are the seller at around 3%, and while Stripe Tax calculates what is owed, registering and filing in each jurisdiction stays with your team.
  • The sample Stripe handler verifies the webhook signature and skips any event whose id already sits in a processedEvents table, because Stripe retries and duplicates arrive.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint The cheapest take rate comes with a filing obligation in every jurisdiction you open, so a team without a finance function is deciding how many markets it can staff.
  • exposure Whoever is named on the receipt assembles the dispute evidence, so on the processor path the chargeback and refund queue lands on the product team that shipped the checkout.
  • cost At the top of the store band the same subscriber costs about ten times more to bill through an app store than through a processor. That difference is what funds building the parallel web checkout.
  • decision Adding a second channel later means retrofitting entitlements under live subscribers.

The posted handler does two things before it writes anything. It verifies the Stripe signature with `stripe.webhooks.constructEvent` and returns a 400 on failure [6]. Then it looks up `event.id` in a `processedEvents` table and returns early if the row exists [7]. Stripe retries events, so duplicates arrive [8]. The row is written after the grant or revoke runs, just before the handler returns [9]. A process that dies in that gap leaves the event unrecorded, and the next retry replays the grant, so `grantEntitlement` has to be safe to call twice.

Two lines decide what a customer actually gets. The plan comes from `sub.items.data[0].price.lookup_key ?? "pro"` [10], so a price created without a lookup key grants pro, and only the first line item is read. Expiry is `new Date(sub.current_period_end * 1000)` [11], and grants fire on `checkout.session.completed` and `invoice.paid`, with revocation on `customer.subscription.deleted` [12]. Dyne notes that proration, trials, dunning and mid-cycle upgrades each add branches to that switch [13].

Dyne puts the processor at around 3% [3] and a merchant of record at roughly 5% plus a fixed fee [15]. Two percentage points on 100,000 of annual sales is 2,000 a year, before the fixed fees [27]. For that, Paddle or Lemon Squeezy is the seller on the receipt, calculates, collects and remits tax everywhere, and eats the chargebacks [14]; the post says the EU, UK, India and Australia are open on day one with zero registrations [16]. On the Stripe path, Stripe Tax calculates the tax and registration and filing stay with you [4], and dispute evidence is yours to assemble [5]. Payouts from a merchant of record arrive bi-weekly or monthly, inside their checkout and subscription model [17].

On native, the platform is the seller and in-app purchase is a constraint [19], at 15 to 30% depending on program tier and revenue [20]. At the top of that band the store takes about ten times what the processor takes, and five times at the bottom [28]. Cancellations and refunds happen in the store and your server reacts to events [22]. Two platforms, two receipt formats and two server-notification systems with different semantics is the reason Dyne gives for RevenueCat existing [21].

Sarah Dyne wrote on dev.to: "Payments look like a checkout button. Underneath it's a question of who is on the receipt, and that question decides most of your operational load for the next two years." [23][29] The post does not quantify that load. The percentages are one practitioner's ranges, and they hold for your product only if your jurisdictions, your program tier with Apple and Google, and your dispute rate resemble the ones behind them. The shape of the handler is portable: the Paddle branch calls the same grant and revoke functions with different event names [18]. That is where Dyne's sequencing advice comes from: write the entitlement layer before the second payment channel [24]. Her split of the options is Stripe for web SaaS with a team that can own billing, marketplaces and custom pricing [25], and a merchant of record for indie products, digital downloads and anyone without a finance function [26].

What to watch

  • The excerpt breaks off on cross-platform subscribers, which is where the single-entitlement-table claim gets tested against two stores and a web checkout.
  • Any Apple or Google program tier change that moves the 15 to 30 percent band changes the case for a parallel web checkout.
  • Merchant of record take rates: at 5 percent plus a fixed fee, the gap to 3 percent is the reason to keep tax in-house.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories