ACH Debit Payments
ACH debit lets a buyer pay once from a US bank account. Flint owns the payment lifecycle and exposes the rail as ach_debit. Stripe.js is used only where your browser must collect bank details and mandate acceptance.
Phase 1 is intentionally narrow:
- USD, automatic capture, and an on-session buyer only.
- Instant bank verification only. A bank that cannot verify instantly is not offered a microdeposit fallback.
- One-time payments only. The bank account is not saved for later charges.
- Checkout Sessions, Payment Links, and standalone PaymentIntents.
ACH can remain processing after the buyer finishes. Do not fulfill, close an order yourself, or tell the buyer the order is paid until Flint reports succeeded or the order reports paid.
Check Availability#
ACH is default-off. The platform and the merchant payment account must both be ready. Flint must also have current evidence that the direct-charge account uses standard settlement and that mandate-email delivery is enabled and tested.
curl "https://api.withflintpay.com/v1/capabilities?capability=accept_ach_debit_payments" \
-H "Authorization: Bearer YOUR_API_KEY"
Require status: "ready" before rollout. For transaction-specific availability, use POST /v1/payment-options/resolve with the amount, currency, capture method, and source. A missing or rejected option is authoritative. Do not silently replace ACH with card.
Hosted Checkout#
Enable ach_debit in the dashboard payment settings or in the checkout resource's payments.enabled_payment_options snapshot:
{
"order_id": "ord_1kmn0aExample",
"payments": {
"enabled_payment_options": ["card", "ach_debit"]
},
"customer_collection": {
"require_email": true
},
"redirects": {
"success_redirect_url": "https://example.com/thanks"
}
}
Flint Checkout renders the bank collection flow only when ach_debit remains available for the final amount and order. It collects billing name and email, runs instant verification, presents the hosted mandate, and sends the resulting ConfirmationToken to Flint.
Payment Links use the same payment configuration and lifecycle. A link-generated checkout is not paid while its payment is merely processing.
Standalone PaymentIntent#
Create the intent from your backend. A standalone intent must declare a non-empty payment_options list. When that list contains ach_debit, it must also declare why the debit is being made.
curl -X POST https://api.withflintpay.com/v1/payment-intents \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ach-order-1001" \
-d '{
"amount_money": {"amount": 12500, "currency": "USD"},
"payment_options": ["ach_debit"],
"transaction_purpose": "goods",
"receipt_email": "ada@example.com"
}'
transaction_purpose accepts goods, services, or other. It is frozen for the payment. It is required whenever ACH is requested and rejected for a pure-card intent.
The response contains data.payment_intent and data.payment_collection. Initialize deferred Stripe Elements from payment_collection.stripe, including its account, publishable key, amount, currency, payment method types, and the complete payment method options object. Do not rebuild or omit nested options. Stripe requires the deferred Elements options to match Flint's server-side confirmation options exactly.
In the browser, require an accurate billing name and email, submit Elements, and create a ConfirmationToken:
const {error: submitError} = await elements.submit();
if (submitError) throw submitError;
const {error, confirmationToken} = await stripe.createConfirmationToken({
elements,
params: {
payment_method_data: {
billing_details: {
name: billingName,
email: billingEmail,
},
},
},
});
if (error) throw error;
Send only the token ID to your backend. Your backend confirms through Flint:
curl -X POST https://api.withflintpay.com/v1/payment-intents/pi_1kmn0aExample/confirm \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ach-order-1001-confirm" \
-d '{"confirmation_token":"ctoken_Example"}'
Do not call a processor confirmation API from the browser. Do not put a payment credential on create or update. Flint is the confirmation authority for both card and ACH.
Status and Fulfillment#
| PaymentIntent status | Meaning | What your integration should do |
|---|---|---|
requires_payment_method | Collection failed or a new source is required. | Collect a new source only when Flint offers collection again. |
requires_action | The buyer must complete a typed client action. | Execute current_payment_action, then continue through Flint. |
processing | The bank debit was submitted and has no final outcome yet. | Keep the order unfulfilled and wait for a webhook or re-fetch. |
succeeded | The payment completed. | Fulfill once, using your durable event handling. |
canceled | The payment was canceled before submission. | Stop collection. |
Once ACH is processing, it cannot be canceled or replaced. Flint retains the order's payment-collection ownership while the outcome is pending. A long-running payment remains processing; Flint opens an internal operational review without changing its economic state.
Subscribe to payment_intent.processing, payment_intent.succeeded, and payment_intent.payment_failed. For hosted commerce, order.paid is the best fulfillment signal because it means the order, not just one payment leg, is fully settled.
Failures, Returns, and Refunds#
A failure before settlement returns a safe Flint last_payment_error. Provider return codes are not exposed. Retry only when Flint returns to a collectable state and the buyer supplies a new credential.
An ACH return after success is represented by the existing Dispute resource (see Bank Returns in the disputes guide):
payment_optionisach_debit.case_typeisbank_return.reasonis an allowlisted Flint value.evidence_response_allowedandaction_requiredarefalsefor a final return.
Returns reduce refundable money. A full return blocks a new refund. A refund and a return that race are serialized so the same funds cannot be moved twice. Refund creation can fail with REFUND_INSUFFICIENT_AVAILABLE_BALANCE; retry the same logical request with the same idempotency key after the balance is available.
Processing Fees on ACH#
ACH is priced by its own rule, so the card rate does not apply. Because settlement is delayed, so is the fee: processing_fee_money is final only when the payment succeeds, not when you confirm it. A payment that fails before success is never charged a processing fee.
A return after success does not refund the original processing fee. It posts a separate balance movement for the returned principal, and it also carries a $15.00 event fee, assessed against your merchant account rather than deducted from that movement. A payment that fails after Flint has submitted it to the bank carries a $4.00 event fee instead; one that fails before submission carries none. See Processing Fees.
Sandbox Accounts#
Use routing number 110000000 with these Stripe-supported test account numbers:
| Account number | Outcome |
|---|---|
000123456789 | Succeeds. |
000222222227 | Fails for insufficient funds. |
000111111113 | Fails because the account is closed. |
000111111116 | Fails because no account exists. |
000333333335 | Fails because the debit is not authorized. |
000555555559 | Succeeds, then creates a return dispute. |
000000000009 | Remains processing for pending-state tests. |
Do not use microdeposit account numbers or verification codes. Microdeposit verification is not part of this release.
Go-Live Checklist#
accept_ach_debit_paymentsis ready in the correct test or live environment.- The direct-charge payment account matches the environment you are using.
- Flint support has recorded current standard-settlement configuration evidence for that account.
- Flint support has recorded current mandate-email configuration and delivery-test evidence for that account.
- Checkout and standalone code require billing name and email.
- Fulfillment waits for
order.paidorpayment_intent.succeeded, never a redirect orprocessing. - Webhooks include processing, success, failure, dispute, and refund events.
- Your support and accounting teams understand that a successful ACH payment can later return.
- Rollback disables
ach_debitfor new payments but keeps all webhooks and reconciliation active for in-flight payments.
For the browser and backend confirmation sequence, see Server-Confirmed Payments. For durable delivery handling, see Webhooks.
