# Taking payments (the user's own shop) (https://lazyfrog.dev/docs/guide/taking-payments-the-user-s-own-shop)

{/* Generated by scripts/generate-docs.ts from services/gateway/src/guide.md. Do not edit. */}

- Customers pay the **user's own** payment account (Stripe; or PayPal, Paddle, Lemon Squeezy):
  the user is the seller, and LazyFrog never holds their money. That has nothing to do with
  LazyFrog billing (Account › Billing is the user's LazyFrog plan): say so if they mix them up.
  These providers are reachable on every plan.
- **Where the business is**: Stripe serves about 46 countries (the UK, the EU, the US and
  more), but not South Africa: there, use Paystack (owned by Stripe; Checkout-like hosted payments, its
  own webhooks signed with `x-paystack-signature`), or PayFast, Yoco or Peach. Ask where the
  business is registered before choosing.
- **Marketplaces** (a commission on payments between users): Stripe Connect with destination
  charges (`application_fee_amount`, `transfer_data.destination`), Express onboarding with
  `account.updated`, bookable only once the seller's `payouts_enabled` is true, a payout schedule,
  a webhook for connected accounts' events, and refunds that reverse the fee. Who is the merchant
  of record, and tax, are the user's decisions: ask. Stripe Connect doesn't serve every country
  (South Africa: Paystack split payments, PayFast, Yoco, Peach; reachable on every plan).
- **A SaaS whose customers are the sellers** (a ticketing app for promoters, a booking app for
  salons): each customer connects their own Stripe account, so they are the merchant and the
  money never passes through the user. Stripe Connect with Standard accounts and direct charges
  (the `stripeAccount` option on each call), Account Links for onboarding, sales open only once
  `account.updated` says `charges_enabled`, and each event's `account` checked against the
  tenant it's for. The user's own platform key is the one `payments connect` takes (a restricted
  key with Write on Connected accounts, Account links, Checkout Sessions and Refunds);
  `payments webhook` with `connected_accounts: true` makes the endpoint for their customers'
  accounts (its secret is `STRIPE_CONNECT_WEBHOOK_SECRET`).
- **Selling limited places** (tickets, seats, classes): hold the places in the database with
  the Checkout session (row locks or a constraint, so two buyers never get the last one). A
  session's `expires_at` is at least 30 minutes away, so a hold lasts at least that long; release
  holds on `checkout.session.expired` and from a cron (missed events), and when a payment arrives
  after its places went to someone else, refund it at once and say so.
- **Subscriptions** (a SaaS's plans): Checkout in `subscription` mode, with `trial_period_days`
  (or a trial without a card, ended by the app); handle `checkout.session.completed`,
  `customer.subscription.created/updated/deleted` and `invoice.payment_failed`, and tie each to the
  tenant through `client_reference_id` or metadata; send people to Stripe's Billing Portal to
  change cards or cancel; decide what a lapsed trial does (read-only, a banner) and run it from a
  cron. Test mode works against the live address before launch.
- Use a hosted checkout (Stripe Checkout) so card details never touch the app; Payment Links suit
  a handful of products with no code. Prices come from the database, never from the browser.
- Stripe through the `payments` tool: `payments connect` (env preview) gives the user a link to
  paste a **restricted key** in test mode (`rk_test_…`), and says what to tick at Stripe; the key
  never passes through you. `payments status` checks it with Stripe (test or live, restricted or
  full, the account's country) and warns about a live key on preview or a test key on production.
  When the app has its webhook route, `payments webhook` makes the endpoint on their Stripe
  account for that environment's address and keeps its signing secret as `STRIPE_WEBHOOK_SECRET`.
  For the live site, the same with env production and a live key (`rk_live_…`) when they're ready
  to sell. Until a key is set, the app says "payments aren't set up yet" instead of failing.
  Paystack and others: `secrets request` for their keys, and their dashboard for the webhook.
- The webhook route: read the raw body, check `Stripe-Signature` with `STRIPE_WEBHOOK_SECRET`,
  keep the event ids so a repeat does nothing, mark orders paid only from it, and answer 2xx
  quickly (a sleeping site wakes for it; Stripe retries for 3 days). A preview password blocks
  webhooks to the preview: test without one, or on the live site. The preview and the live site
  each have their own endpoint and signing secret (`payments webhook` for each env), and
  `rotate_preview_url` changes the preview's address: run `payments webhook` again after it.
- Tell them in plain words what to do at Stripe: make an account (testing needs no bank details),
  stay in Test mode, create the restricted key `payments connect` describes and paste it into the
  link (never into the chat), test with card 4242 4242 4242 4242, and switch to live mode to sell.
- A shop needs terms of sale, refunds and privacy pages: placeholders the owner fills in.
