Stripe Payments — Frontend Integration Guide
Stripe Payments — Frontend Integration Guide
Overview
Card payments use a two-step flow:
- Backend creates a PaymentIntent — returns a
stripe_client_secret - Frontend confirms the payment — uses the Stripe SDK with the
stripe_client_secretto collect card details and process the charge
The backend handles Stripe communication server-side. The frontend only needs the publishable key and the stripe_client_secret from the create payment response.
Publishable Keys
| Environment | Key |
|---|---|
| Development | pk_test_3XxXqkTDvgkh8MxL8OdliMYq00KuYHGdTu |
| Staging | Contact backend team |
| Production | Contact backend team |
Never use the secret key (
sk_*) on the frontend.
API Flow
1. Create a Payment
Request
POST /api/v1/rides/:ride_id/payments
Authorization: Bearer <jwt_token>
Idempotency-Key: <uuid>
Content-Type: application/json
{ "payment_method": "card" }
Response 201 Created
{
"data": {
"attributes": {
"id": 1182,
"ride_id": 2544,
"stripe_payment_intent_id": "pi_3T80f1IShcAMEstE2sdDxrwL",
"stripe_client_secret": "pi_3T80f1IShcAMEstE2sdDxrwL_secret_Qr9ybne3az7Cv8uHdxnUvziLT",
"amount_cents": 2363,
"amount_dollars": 23.63,
"status": "requires_payment_method",
"payment_method": "card"
}
}
}
If a payment already exists for the ride, 200 OK is returned with the existing payment. Check status before presenting the payment sheet — if already succeeded, skip to step 3.
Store id (payment ID) and stripe_client_secret from this response.
2. Confirm the Payment (Stripe SDK)
Use the stripe_client_secret to present the payment sheet via the Stripe Flutter SDK:
// Initialize Stripe once at app startup
Stripe.publishableKey = 'pk_test_3XxXqkTDvgkh8MxL8OdliMYq00KuYHGdTu';
// In the payment screen:
Future<void> confirmPayment(String clientSecret) async {
await Stripe.instance.initPaymentSheet(
paymentSheetParameters: SetupPaymentSheetParameters(
paymentIntentClientSecret: clientSecret,
merchantDisplayName: 'SafeRide',
),
);
await Stripe.instance.presentPaymentSheet();
// If no exception is thrown, the payment was confirmed by Stripe
}
Do not pass the full stripe_client_secret to the backend confirm endpoint — the SDK handles confirmation directly with Stripe.
3. Notify the Backend
After the Stripe SDK confirms the payment, call the backend confirm endpoint to sync the payment status:
Request
POST /api/v1/payments/:payment_id/confirm
Authorization: Bearer <jwt_token>
Idempotency-Key: <uuid>
Content-Type: application/json
{ "payment_method": "<payment_method_id_from_stripe_sdk>" }
The payment_method is the Stripe payment method ID returned by the SDK after confirmation (e.g. pm_...). If unavailable from the SDK result, the backend will also sync via webhook automatically — see Real-Time Updates below.
Response 200 OK — returns the updated payment with status: "succeeded".
Payment Status Reference
| Status | Meaning |
|---|---|
requires_payment_method |
PaymentIntent created, awaiting card details |
requires_confirmation |
Card attached, awaiting confirmation |
requires_action |
3DS or other action required from the user |
processing |
Stripe is processing the charge |
succeeded |
Payment complete — ride can be completed |
failed |
Charge failed — prompt user to retry |
canceled |
Payment canceled or refunded |
Ride Fields
The ride object includes two payment-related fields:
{
"requires_payment": true,
"payment_completed": false
}
requires_payment—truewhen the ride haspayment_method: "card"and the fare is non-zeropayment_completed—truewhen the associated payment hasstatus: "succeeded"
The driver’s Complete Ride button must be disabled when requires_payment == true && payment_completed != true.
Real-Time Updates
The backend broadcasts a ride_updated ActionCable event when payment status changes. Subscribe to RideChannel and listen for payment_completed: true rather than polling:
// On the ride detail screen, listen for ActionCable ride_updated events
// The payload includes: payment_completed, requires_payment, status
// When payment_completed flips to true, re-enable the Complete Ride button
// and dismiss any payment-pending UI
If ActionCable is not yet integrated, poll GET /api/v1/rides/:id every 2–3 seconds for up to 30 seconds after the SDK confirms, stopping when payment_completed is true.
Error Handling
| HTTP Status | Error | Action |
|---|---|---|
402 |
insufficient_funds |
Show “Your card has insufficient funds” |
402 |
payment_error |
Show “Payment failed, please try a different card” |
402 |
requires_action |
Trigger 3DS flow via Stripe SDK |
422 |
invalid_request |
Payment intent issue — create a new payment |
503 |
stripe_error |
Stripe temporarily unavailable — retry later |
Testing Without a Real Card
Use Stripe test payment method tokens in development. With the Stripe Flutter SDK in test mode, use test card numbers directly in the payment sheet:
| Card Number | Scenario |
|---|---|
4242 4242 4242 4242 |
Success |
4000 0000 0000 9995 |
Insufficient funds |
4000 0025 0000 3155 |
3DS required |
4000 0000 0000 0002 |
Generic decline |
Use any future expiry, any 3-digit CVC, and any billing ZIP.
Full list: https://stripe.com/docs/testing#cards