Mobile Dev

Stripe Partial Refunds for Split Payments in Flutter

Learn how to handle Stripe partial refunds for incomplete split payments with time limits in Flutter apps. Use PaymentIntents, flutter_stripe, cron jobs, webhooks, and Stripe refund API for auto-refunds on timeouts.

1 answer 1 view

How to handle partial refunds in Stripe for incomplete split payments with a time limit in a Flutter app?

Use case:

  • A user makes a booking (e.g., total $10) and pays their share immediately ($5).
  • A friend must pay the remaining $5 within 2 days.

Problem: If the friend does not complete payment within the time limit, automatically refund the original user’s $5.

What are the best practices, Stripe APIs, or Flutter implementation steps for this scenario?

Handling partial refunds in Stripe for incomplete split payments in a Flutter app means ditching any hope of a built-in “timed split” feature—Stripe doesn’t offer it natively. Instead, create separate PaymentIntents for each user’s share (like $5 for the original user, $5 for the friend), track them in your database with a 2-day expiration, and use server-side cron jobs or webhooks to trigger a stripe partial refund via the Stripe refund API if the friend’s payment doesn’t succeed. The flutter stripe package handles the client-side magic smoothly, while your backend ensures automatic refunds without manual intervention.


Contents


Understanding Stripe Partial Refunds for Split Payments

Picture this: your user books something for $10, pays their $5 upfront via Flutter, and invites a friend for the rest. Friend ghosts after 48 hours? Boom—auto-refund that original $5. Stripe’s refunds documentation makes partial refunds straightforward, but only after the payment is captured and your account has available balance.

Why separate payments? Stripe treats split payments as individual charges, not a single pot. Lumping them risks full refunds or messy disputes. Each share gets its own PaymentIntent—one succeeds immediately, the other times out. If it fails, refund the first via API. Simple? Not quite. Fees might stick (Stripe doesn’t always refund them), and pending balances can delay things.

Stripe Connect could split to vendors later, but for peer-to-peer like this? Stick to direct Payments API. No native timers, so you build that.


Stripe APIs for Refunds and PaymentIntents

Core to this: PaymentIntents API. Create one per user:

bash
curl https://api.stripe.com/v1/payment_intents \
 -u sk_test_...: \
 -d amount=500 \
 -d currency=usd \
 -d metadata[booking_id]=booking_123 \
 -d metadata[user_type]=original

Confirm client-side in Flutter, capture on success. For refunds, hit the Refunds API:

bash
curl https://api.stripe.com/v1/refunds \
 -u sk_test_...: \
 -d payment_intent=pi_123 \
 -d amount=500 # Partial: refund $5 of $5

This issues a partial refund if specified (full by default). Response includes status: succeeded or pending. Idempotency keys prevent duplicates—crucial for cron retries.

PaymentIntents track statuses: requires_payment_method, succeeded, canceled. Metadata links shares: friend_pi: pi_friend_456.

What about uncaptured intents? Cancel them first with POST /v1/payment_intents/{id}/cancel.


Setting Up Split Payments in Flutter

flutter_stripe is your go-to. Add it to pubspec.yaml, init Stripe:

dart
import 'package:flutter_stripe/flutter_stripe.dart';

await Stripe.publishableKey = 'pk_test_...';
await Stripe.init();

User pays share:

dart
final paymentIntent = await _createPaymentIntent(500, 'usd', 'booking_123');
await Stripe.instance.initPaymentSheet(
 paymentSheetParameters: SetupPaymentSheetParameters(
 paymentIntentClientSecret: paymentIntent['client_secret'],
 merchantDisplayName: 'Your App',
 ),
);
await Stripe.instance.presentPaymentSheet();

Server creates the PI first (Node/Python/whatever), returns client_secret. Friend gets a deep link or push for theirs. Boom—split payments without Stripe Checkout’s limits.


Implementing Time-Limited Payments and Auto-Refunds

No magic timeout button. Set expires_at in metadata (your DB field), poll or webhook-watch.

Flow:

  1. User pays → PI succeeds → Store timeout_at = now + 2 days.
  2. Friend pays → Both succeed → Mark complete.
  3. Timeout hits → If friend PI not succeeded, refund original.

Use cron (e.g., node-cron) or Stripe webhooks (payment_intent.succeeded for friend triggers “complete”).

Refund call from server:

javascript
const stripe = require('stripe')('sk_test_...');
const refund = await stripe.refunds.create({
 payment_intent: 'pi_original_123',
 amount: 500,
});

Partial only if original PI amount > refund (here, full for $5 share).


Backend Logic: Cron Jobs, Webhooks, and Database Tracking

Database schema? Key fields:

Field Type Purpose
booking_id string Links shares
user_pi_id string Original PI
friend_pi_id string Friend PI
timeout_at timestamp 2 days from original
status enum pending/complete/refunded

Cron job (every hour):

javascript
cron.schedule('0 * * * *', async () => {
 const expired = await db.query('SELECT * WHERE timeout_at < NOW() AND status = "pending"');
 for (const booking of expired) {
 if (!(await stripe.paymentIntents.retrieve(booking.friend_pi_id)).status === 'succeeded') {
 await stripe.refunds.create({ payment_intent: booking.user_pi_id, amount: 500 });
 await db.update(booking.id, { status: 'refunded' });
 }
 }
});

Webhooks beat cron—listen for payment_intent.succeeded on friend PI, update DB. Fallback cron for safety.

Idempotency: Use booking_id as key.


Handling Edge Cases

Funds low? Balances API checks available before refund—pending holds 7 days sometimes.

Friend pays late? Extend timeout via DB update.

Fees: Original charge fee (~2.9% + 30¢) isn’t refunded. Warn users.

Disputes? Metadata proves intent.

Multicurrency? Match currencies exactly.

Partial on larger shares: Refund $5 of $10 PI with amount=500.

What if friend partially pays? Rare—intents are atomic.


Step-by-Step Flutter Implementation

  1. Server: Create original PI → Return client_secret.
  2. Flutter: Present sheet → On success, POST /complete-share with PI ID.
  3. Server: Store PIs, set timeout → Generate friend link (deep link with PI params).
  4. Friend app: Same flow → Webhook updates.
  5. Cron/Webhook: Check & refund.

Full Flutter service:

dart
Future<String> payShare(String bookingId, int amount) async {
 final res = await http.post('/create-pi', body: {'bookingId': bookingId, 'amount': amount});
 final data = json.decode(res.body);
 await Stripe.instance.presentPaymentSheet(
 paymentSheetParameters: SetupPaymentSheetParameters(paymentIntentClientSecret: data['client_secret']),
 );
 await http.post('/confirm-share', body: {'piId': data['id'], 'bookingId': bookingId});
}

Server webhook:

javascript
app.post('/webhook', (req, res) => {
 const event = req.body;
 if (event.type === 'payment_intent.succeeded') {
 const pi = event.data.object;
 db.completeBooking(pi.metadata.booking_id);
 }
});

Testing and Best Practices

Test mode: Use Stripe CLI for webhooks (stripe listen --forward-to localhost:3000/webhook).

Dashboard: Simulate refunds, check balances.

Best practices:

  • Always verify webhook sigs.
  • Retry failed refunds (exponential backoff).
  • Notify users via push/email on refund.
  • Reserves: High-risk? Hold payouts.
  • SCA: flutter_stripe handles it.

Scale with queues (BullMQ) for crons. Monitor via Stripe Sigma.


Sources

  1. Stripe Refunds — Details on partial refunds, balances, and Dashboard/API usage: https://docs.stripe.com/refunds
  2. Stripe API Refunds — API endpoint for creating partial refunds on PaymentIntents: https://docs.stripe.com/api/refunds
  3. Payment Intents — Managing separate intents for split payments and statuses: https://docs.stripe.com/payments/payment-intents
  4. flutter_stripe Package — Official Flutter integration for Stripe PaymentSheets: https://pub.dev/packages/flutter_stripe
  5. Stripe Balances — Checking available funds before issuing refunds: https://docs.stripe.com/payments/balances
  6. Split Payment Systems — Best practices for refunds in shared payment scenarios: https://stripe.com/resources/more/how-to-implement-split-payment-systems-what-businesses-need-to-do-to-make-it-work

Conclusion

Nail stripe split payments in Flutter by leaning on separate PaymentIntents, smart DB tracking, and automated refunds via cron or webhooks—no waiting for Stripe to invent timers. You’ve got the APIs, the flutter stripe flows, and edge-case armor. Test ruthlessly in sandbox, roll it out, and watch those incomplete bookings self-heal. Users stay happy, disputes drop.

Authors
Verified by moderation
Stripe Partial Refunds for Split Payments in Flutter