Theory

Why the client never touches the card

The order has pizzas and a table — it's missing payment. But payments are dangerous territory: if your app touches a card number, PCI DSS requirements, audits and huge liability land on you. So the rule: the client never touches card data.

The solution is Stripe and its PaymentSheet. The division of labor:

Your server  ──(1) creates a PaymentIntent──▶  Stripe        (amount, currency)
Your server  ◀──(2) returns client_secret──                  (a one-time "permit")
The app      ──(3) client_secret──▶  Stripe PaymentSheet     (collects the card)
PaymentSheet ──(4) confirms directly with Stripe──▶          (the card never goes through you)
The app      ──(5) success → record the order in your DB──▶

The key: card data goes straight from PaymentSheet to Stripe, bypassing both your app and your server. You only ever see the client_secret (a one-time id for this payment) and, in the end, "succeeded / failed."

Two principles:

  • The server sets the amount. The price comes from the server (POST /create-payment-intent), not the client — otherwise a user could pay €0.01 for a €20 order.
  • The order is recorded ONLY after payment. Create a PaymentIntentPaymentSheet confirms → only then POST /orders. Never the other way around (or you'd have unpaid orders).

In this lesson everything is test mode (Stripe test keys + the test card 4242 4242 4242 4242). No real money.

Verify-docs. The Stripe Android SDK (23.11.1) PaymentSheet API (rememberPaymentSheet, presentWithPaymentIntent) shifts, and parts are flagged deprecated. We use the real code, but at authoring time confirm the current PaymentSheet flow and test keys.