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
PaymentIntent→PaymentSheetconfirms → only thenPOST /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)
PaymentSheetAPI (rememberPaymentSheet,presentWithPaymentIntent) shifts, and parts are flagged deprecated. We use the real code, but at authoring time confirm the currentPaymentSheetflow and test keys.