Migrating from Stripe
This guide is for teams moving an existing Stripe integration to Flint without rebuilding every payment flow at once.
The main shift is structural:
- Stripe integrations often start from
PaymentIntent,Checkout Session,Invoice, orSubscription. - Flint starts from the business object that should stay authoritative:
order,invoice,payment_link, orsubscription.
Flint still uses Stripe.js and Stripe Elements for card entry in embedded and saved-card flows. The migration is mostly about changing your backend resources, hosted entry points, and webhook handling.
Stripe to Flint Object Map#
| Stripe | Flint |
|---|---|
| Customer | Customer |
| SetupIntent | POST /v1/payment-methods |
| PaymentMethod | Payment Method |
| PaymentIntent | Payment Intent |
| Checkout Session | Checkout Session |
| Payment Link | Payment Link |
| Product + recurring Price | Subscription Plan |
| Subscription | Subscription |
| Invoice | Invoice |
| Refund | Refund |
| Webhook endpoint | Webhook endpoint |
Pick the Right Flint Primitive#
Use:
checkout-sessionswhen your app creates one hosted payment flow for one buyer or one orderpayment-linkswhen you want one reusable public URL for many buyerspayment-intentswhen you keep checkout on your own site with Stripe Elementssubscription-plansplussubscriptionswhen you own recurring billing in your appinvoiceswhen the problem is accounts receivable, due dates, reminders, PDFs, or manual payment recording
1. Migrate Stripe Billing Subscriptions#
Common Stripe shape:
- Product
- recurring Price
- Customer
- PaymentMethod
- Subscription
Flint equivalent:
- Subscription Plan
- Customer
- Payment Method
- Subscription
If your current Stripe flow creates subscriptions directly from your backend for known customers, the Flint migration is:
- Create or map the Flint customer.
- Save a payment method for that customer.
- Confirm card setup on the frontend and wait for setup completion.
- Create a subscription plan.
- Create the subscription.
The payment_method_id returned by POST /v1/payment-methods starts in pending status. It is not ready for subscription billing until frontend setup confirmation completes and Flint processes Stripe's webhook, which promotes it to active.
If you set that finished payment method as the customer's default, later POST /v1/subscriptions calls can omit payment_method_id and Flint will use the customer's default payment method instead.
Create the Plan#
curl -X POST https://api.withflintpay.com/v1/subscription-plans \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: create-plan-pro-monthly-v1" \
-d '{
"name": "Pro Monthly",
"billing_interval": "monthly",
"billing_interval_count": 1,
"currency": "USD",
"line_items": [
{
"name": "Pro Plan",
"unit_price_money": { "amount": 2999, "currency": "USD" },
"quantity": 1
}
],
"trial_period_days": 14
}'
Create the Subscription#
curl -X POST https://api.withflintpay.com/v1/subscriptions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: subscription-cus-plan-001" \
-d '{
"plan_id": "plan_xxx",
"customer_id": "cus_xxx",
"payment_method_id": "pm_xxx"
}'
If your current Stripe flow uses hosted subscription signup, do not create subscriptions directly from your frontend. Use one of these Flint hosted entry points instead:
POST /v1/checkout-sessionswithplan_idfor app-driven hosted signupPOST /v1/payment-linkswithplan_idfor a reusable public signup URL
2. Migrate Stripe One-Time Hosted Checkout#
Common Stripe shape:
- create a Checkout Session with
mode=payment - redirect the buyer to the hosted Stripe page
Flint equivalent:
- create a Checkout Session with
quick_payororder_id - redirect the buyer to
data.url
Use quick_pay when you just need a one-time amount and Flint can create the backing order for you:
curl -X POST https://api.withflintpay.com/v1/checkout-sessions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: checkout-session-consulting-001" \
-d '{
"quick_pay": {
"name": "Consulting Session",
"amount_money": { "amount": 15000, "currency": "USD" }
},
"customer": {
"require_email": true
},
"redirects": {
"success_redirect_url": "https://example.com/success",
"cancel_redirect_url": "https://example.com/cancel"
}
}'
Use order_id when your app already knows the cart or order and you want that order to stay authoritative through payment, refunds, and reporting:
curl -X POST https://api.withflintpay.com/v1/checkout-sessions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: checkout-session-order-001" \
-d '{
"order_id": "ord_xxx",
"customer": {
"customer_id": "cus_xxx",
"require_email": true
},
"redirects": {
"success_redirect_url": "https://example.com/orders/success"
}
}'
If you currently reuse one Stripe-hosted URL across many buyers, that is not a checkout-session migration. That is a payment-link migration.
3. Migrate Stripe Payment Links#
Common Stripe shape:
- one reusable public URL
- shared from a site, campaign, QR code, or sales email
Flint equivalent:
POST /v1/payment-links
Example one-time payment link:
curl -X POST https://api.withflintpay.com/v1/payment-links \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: payment-link-drop-001" \
-d '{
"name": "Spring merch drop",
"description": "Limited release t-shirt",
"line_items": [
{
"key": "shirt",
"name": "Limited Tee",
"quantity": 1,
"amount_money": { "amount": 3500, "currency": "USD" },
"allow_quantity_adjustment": true,
"min_quantity": 1,
"max_quantity": 5
}
],
"customer": {
"require_email": true
},
"redirects": {
"success_redirect_url": "https://example.com/orders/success"
}
}'
For recurring signup pages, use the same endpoint but send plan_id instead of line_items.
Flint payment links also support popular hosted flows beyond standard one-time payments:
- recurring signup with
plan_id - donation pages with
mode: "donation" - event or ticketing pages with
mode: "event"
4. Migrate Stripe PaymentIntents plus Elements#
Common Stripe shape:
- create a PaymentIntent
- mount Stripe Elements or the Payment Element
- confirm payment on the frontend
Flint equivalent:
- Create an order.
- Create a Flint payment intent.
- Mount Stripe Elements with the returned Flint-managed
client_secret. - Confirm payment with Stripe.js on the frontend.
- Verify the final order state from your backend.
Create the Flint Payment Intent#
curl -X POST https://api.withflintpay.com/v1/payment-intents \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: pi-order-001" \
-d '{
"order_id": "ord_xxx"
}'
The response includes:
data.payment_intent.payment_intent_iddata.client_confirmation.stripe.client_secretdata.client_confirmation.stripe.account_id
That means your frontend can still use Stripe.js:
const stripe = Stripe("pk_test_xxx", {
stripeAccount: accountIdFromBackend,
});
const elements = stripe.elements({
clientSecret: clientSecretFromBackend,
});
const paymentElement = elements.create("payment");
paymentElement.mount("#payment-element");
Confirm payment on the frontend:
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: "https://example.com/orders/success",
},
});
Then verify the final state from your backend:
curl -X GET https://api.withflintpay.com/v1/orders/ord_xxx \
-H "Authorization: Bearer YOUR_API_KEY"
Use this migration when you want to keep checkout embedded on your own domain instead of moving to Flint-hosted checkout.
5. Migrate Stripe Invoicing#
Common Stripe shape:
- create draft invoice
- finalize and send invoice
- collect payment from hosted invoice page
Flint equivalent:
- create invoice draft with
POST /v1/invoices - send with
POST /v1/invoices/{invoice_id}/send - collect with
POST /v1/invoices/{invoice_id}/checkout-session - optionally record offline payments with
POST /v1/invoices/{invoice_id}/manual-paymentsafter shutting down any active online collection path for the invoice
Example draft invoice:
curl -X POST https://api.withflintpay.com/v1/invoices \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: inv-order-001" \
-d '{
"order_id": "ord_xxx",
"recipient_email": "billing@example.com",
"due_at": "2026-04-15T00:00:00Z",
"reference": "PO-1048"
}'
Send it:
curl -X POST https://api.withflintpay.com/v1/invoices/inv_xxx/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: send-inv-001" \
-d '{}'
Create or reuse the invoice-owned hosted checkout flow:
curl -X POST https://api.withflintpay.com/v1/invoices/inv_xxx/checkout-session \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{}'
Use Flint invoices when the job is receivables management, not just immediate checkout.
6. Migrate Saved Cards and Future Use Flows#
Common Stripe shape:
- create a SetupIntent
- confirm card setup with Stripe.js
- reuse the saved payment method later
Flint equivalent:
POST /v1/payment-methods- confirm with Stripe.js using the returned
client_secretandaccount_id - use the resulting Flint
payment_method_idfor subscriptions or payment intents
Create the Flint payment method setup:
curl -X POST https://api.withflintpay.com/v1/payment-methods \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: save-pm-customer-001" \
-d '{
"customer_id": "cus_xxx"
}'
The response includes:
data.payment_method.payment_method_iddata.client_setup.stripe.client_secretdata.client_setup.stripe.account_id
Create Stripe.js with the returned connected account before confirming setup:
const stripe = Stripe("pk_test_xxx", {
stripeAccount: accountIdFromBackend,
});
If you are using a card element flow, confirm setup like this:
await stripe.confirmCardSetup(clientSecretFromBackend, {
payment_method: {
card: cardElementFromYourUI,
},
});
If your frontend uses the newer elements-based setup flow, use stripe.confirmSetup(...) instead.
After the webhook confirms the setup, reuse the returned Flint payment_method_id in:
POST /v1/subscriptionsPOST /v1/payment-intentsPOST /v1/payment-methods/{payment_method_id}/set-default
7. Migrate Refunds#
Common Stripe shape:
- refund a charge or payment intent
Flint equivalent:
- refund the
order_idorpayment_intent_id
Example:
curl -X POST https://api.withflintpay.com/v1/refunds \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: refund-order-001" \
-d '{
"order_id": "ord_xxx",
"reason": "requested_by_customer",
"reason_message": "Customer canceled before fulfillment."
}'
Flint refunds stay attached to the commerce object that matters downstream instead of making your system reconcile a separate processor object graph.
8. Migrate Webhooks#
Common Stripe shape:
- register a webhook endpoint
- verify
Stripe-Signature - deduplicate on the Stripe event ID
Flint equivalent:
- register a Flint webhook endpoint
- verify
webhook-signature - deduplicate on
webhook-id
Register the endpoint:
curl -X POST https://api.withflintpay.com/v1/webhook-endpoints \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: webhook-prod-001" \
-d '{
"url": "https://example.com/webhooks/flint",
"enabled_events": [
"payment_intent.succeeded",
"order.payment_succeeded",
"subscription.payment_succeeded",
"invoice.paid",
"refund.created"
]
}'
Store data.secret immediately. Flint only returns it at creation time and when you rotate the secret.
Migration Checklist#
- Create Flint customers for every active Stripe customer you still need to charge.
- Store your old Stripe IDs in Flint metadata or
merchant_customer_idfor reconciliation during cutover. - Recreate recurring prices as Flint subscription plans.
- Replace Stripe-hosted payment entry points with Flint checkout sessions or payment links.
- Replace direct Stripe invoice flows with Flint invoices if you need due dates, reminders, PDFs, or manual payment recording.
- Keep Stripe.js on the frontend for embedded payments and saved-card flows, but switch backend intent and setup creation to Flint.
- Replace Stripe webhook verification and event routing with Flint webhook verification and Flint event names.
- Use Flint idempotency keys on the write paths that support them during the migration window, and treat the Idempotency guide as the source of truth for the current route list.
