Affirm Payments

Affirm is a one-time, buyer-present payment option for eligible US consumer purchases in USD from $50.00 through $30,000.00, or through $25,000.00 on a Payment Link. It uses the same Flint PaymentIntent, order, Refund, Return, Dispute, and webhook resources as other payment options.

Enabling Affirm is the last step, not the first. PATCH /v1/settings with affirm in checkout.enabled_payment_options fails with PAYMENT_OPTION_NOT_READY until the merchant environment has Affirm pricing, the connected account's Affirm capability is active, and merchant onboarding requirements are complete. Read GET /v1/capabilities?domain=payments&capability=accept_affirm_payments for which of those is outstanding, then enable it. Each transaction must still satisfy Affirm's eligibility rules.

Transaction Eligibility#

Offer Affirm only when all of these facts are true:

  • The buyer and charging account are in the United States.
  • The currency is USD and the amount is between 5000 and 3000000 minor units. On a Payment Link the self-service ceiling is 2500000 ($25,000.00) unless Flint has approved a higher limit for your account.
  • The payment is one-time, on-session, and uses automatic capture or one full manual capture.
  • The purchase is consumer commerce, not B2B.
  • The goods or services are available for fulfillment. Do not offer Affirm for a pre-order or goods that are not available.
  • One PaymentIntent collects the full transaction balance. Split tender and partial collection are not supported.
  • Card remains available because Affirm approval is not guaranteed.

Flint enforces the authoritative facts present on its stored resources and the retrieved ConfirmationToken. Your integration remains responsible for facts Flint does not receive, including B2B classification, pre-order status, and goods availability.

Review Stripe's maintained Affirm prohibited and restricted business categories before offering Affirm. Stripe or Affirm may impose additional requirements or reject a transaction even when the connected account capability is active.

Affirm is supported for hosted checkout, standalone and direct-order PaymentIntents, fixed-price Payment Links, one-time invoice collection, and eligible Return balance collection. Do not use it for pay-what-you-want or donation links, subscription invoices or recurring collection, saved credentials, off-session collection, virtual terminal, or split tender.

Create a PaymentIntent#

Set a clean HTTPS payment_return_url whenever the immutable option list contains affirm. The URL cannot contain credentials, a query, or a fragment.

Bash
curl -X POST https://api.withflintpay.com/v1/payment-intents \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: affirm-order-1042" \
  -d '{
    "amount_money": {"amount": 9900, "currency": "USD"},
    "payment_options": ["card", "affirm"],
    "payment_return_url": "https://shop.example.com/orders/1042/payment-return"
  }'

Use the returned payment_collection.stripe object exactly as returned. It includes one publishable key and connected account_id, the allowed Element method types, and Flint's exact return_url. The merchant URL is never passed to Stripe.

Collect a ConfirmationToken#

Initialize Stripe.js for the returned connected account. Use that same Stripe instance for Elements, ConfirmationToken creation, and every pending client action.

JavaScript
const response = await fetch("/api/payment-intents/affirm", {method: "POST"});
if (!response.ok) throw new Error("Could not create the payment.");

const {data} = await response.json();
const collection = data.payment_collection.stripe;
const stripe = Stripe(collection.publishable_key, {
  stripeAccount: collection.account_id,
});

const guidance = collection.elements;
const elements = stripe.elements({
  mode: "payment",
  amount: guidance.amount_money.amount,
  currency: guidance.amount_money.currency.toLowerCase(),
  paymentMethodCreation: "manual",
  paymentMethodTypes: guidance.payment_method_types,
});

elements.create("payment", {
  layout: "tabs",
  paymentMethodOrder: ["card", "affirm"],
}).mount("#payment-element");

After elements.submit(), create a ConfirmationToken with Flint's exact relay URL and complete billing details. Include authoritative shipping only when the purchase has a real shipping recipient. Do not copy billing into a synthetic shipping address.

JavaScript
const {error, confirmationToken} = await stripe.createConfirmationToken({
  elements,
  params: {
    return_url: collection.return_url,
    payment_method_data: {
      billing_details: {
        name: buyer.name,
        email: buyer.email,
        address: buyer.billingAddress,
      },
    },
    ...(order.shipping ? {shipping: order.shipping} : {}),
  },
});

On a checkout session, confirm with time to spare. Affirm needs at least 15 minutes left in the session's payment action window, and a confirm submitted inside that last 15 minutes fails with PAYMENT_ACTION_WINDOW_TOO_SHORT (409) before the buyer is sent to Affirm. Recover by starting a new payment attempt or by offering card. Direct PaymentIntents that are not backed by a checkout session are not subject to this window.

Send confirmation_token to Flint from your backend:

Bash
curl -X POST https://api.withflintpay.com/v1/payment-intents/pi_Example/confirm \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: affirm-confirm-1042" \
  -d '{"confirmation_token":"ctoken_Example"}'

Complete the Redirect#

When confirmation returns a payment_authentication action, initialize Stripe.js from the action's publishable key and account_id, call the typed handle_next_action, then re-read or resume the existing Flint attempt. Never create a second attempt because the browser returned.

The provider redirects to a Flint relay first. The relay discards provider query parameters and sends a clean 303 redirect to your stored payment_return_url. A browser return is not proof of success. Read the PaymentIntent, or use order.paid for an order-backed purchase.

requires_payment_method makes the prior attempt terminal. Start one deliberate new submission with a new ConfirmationToken. Present card first after every terminal Affirm failure. Do not automatically resubmit Affirm. A provider decline should remove Affirm from the immediate retry; an incomplete redirect may leave it available for explicit buyer selection.

Payment Method Messaging#

You may use Stripe's Payment Method Messaging Element on eligible product, cart, and payment pages. Bind it to the same connected-account Stripe instance and the authoritative amount and currency.

JavaScript
const messagingElements = stripe.elements();
messagingElements.create("paymentMethodMessaging", {
  amount: order.total_money.amount,
  currency: "USD",
  countryCode: "US",
  paymentMethodTypes: ["affirm"],
}).mount("#affirm-message");

The component explains plans that may be available. It does not guarantee approval. Do not write your own installment amount, APR, or approval claim. Disclose Stripe's use of cookies, IP addresses, and interaction data and obtain the rights or consent required for your page.

Manual Capture#

Affirm supports one full capture within the authorization deadline returned on the PaymentIntent. Partial, multiple, over, incremental, decremental, and reauthorization operations are rejected before the processor call.

Refunds#

Submit an Affirm refund within 120 days of the original payment. Refunds are asynchronous and can remain pending for up to two days. A refund changes Flint's refunded order principal, not the original processing fee.

Affirm applies the refund to the buyer's loan. Remaining payments may change, and interest already paid is not refunded. If an Affirm refund reaches failed, arrange another reimbursement method. Do not create a fresh provider Refund for that failed refund.

Payment Identity#

After selection, selected_payment_option is affirm and payment_source.type is affirm:

JSON
{
  "payment_source": {"type": "affirm"},
  "selected_payment_option": "affirm",
  "support_reference": "N7XBTQJ4KMEXAMPLE"
}

support_reference on a completed Affirm payment is the Affirm transaction reference, which is what Affirm support will ask a buyer for. It falls back to the PaymentIntent ID when no Affirm reference exists yet, so treat it as an opaque string and do not parse it or assume a pi_ prefix.

Affirm payments are Not assessed by Flint risk. Processor and operational controls still apply.

Rate this doc