> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gr4vy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Affirm

> Connect to Affirm to accept buy now, pay later payments.

export const connector = {
  displayName: "Affirm",
  method: "affirm",
  features: "create_transaction delayed_capture direct_integration_create partial_capture partial_refunds redirect_requires_popup refunds requires_void_after_final_capture requires_void_after_partial_capture transaction_sync verify_credentials void",
  supportedCountries: "US",
  supportedCurrencies: "USD"
};

export const ConnectorRegions = ({data, kind, name: nameOverride}) => {
  const [query, setQuery] = useState("");
  const [open, setOpen] = useState(false);
  const isCountries = kind === "countries";
  const raw = data && (isCountries ? data.supportedCountries : data.supportedCurrencies);
  const codes = typeof raw === "string" ? raw.split(/\s+/).filter(Boolean) : Array.isArray(raw) ? raw : [];
  const DISPLAY_NAME_OVERRIDES = {
    authorizenet: "Authorize.net",
    cardpointe: "Fiserv CardPointe",
    dlocal: "dLocal",
    shift4i4go: "Shift4 i4go",
    tokenex: "TokenEx"
  };
  const rawName = data && data.displayName || "";
  const name = nameOverride || DISPLAY_NAME_OVERRIDES[rawName.toLowerCase()] || rawName || "This connector";
  const verb = isCountries ? "supports transactions from buyers in" : "supports processing payments in";
  const noun = isCountries ? "countries" : "currencies";
  if (codes.length === 0) return null;
  let displayNames = null;
  try {
    displayNames = new Intl.DisplayNames(["en"], {
      type: isCountries ? "region" : "currency"
    });
  } catch (e) {
    displayNames = null;
  }
  const resolve = code => {
    if (!displayNames) return null;
    try {
      const resolved = displayNames.of(code);
      return resolved && resolved !== code ? resolved : null;
    } catch (e) {
      return null;
    }
  };
  const MAJOR_CURRENCIES = ["USD", "EUR", "GBP", "CAD", "AUD", "JPY", "CHF", "CNY", "SGD", "HKD", "NZD", "SEK", "NOK", "DKK", "MXN", "BRL", "INR"];
  const items = codes.map(code => ({
    code,
    label: resolve(code)
  }));
  if (isCountries) {
    items.sort((a, b) => (a.label || a.code).localeCompare(b.label || b.code));
  } else {
    const rank = code => {
      const i = MAJOR_CURRENCIES.indexOf(code);
      return i === -1 ? MAJOR_CURRENCIES.length : i;
    };
    items.sort((a, b) => rank(a.code) - rank(b.code) || a.code.localeCompare(b.code));
  }
  if (codes.length <= 3) {
    const parts = items.map(it => isCountries || !it.label ? it.label || it.code : `${it.label} (${it.code})`);
    const joined = parts.length === 1 ? parts[0] : parts.length === 2 ? `${parts[0]} and ${parts[1]}` : `${parts.slice(0, -1).join(", ")}, and ${parts[parts.length - 1]}`;
    return <p>
        {name} {verb} {joined}.
      </p>;
  }
  const chipStyle = {
    display: "inline-flex",
    alignItems: "baseline",
    gap: "0.4rem",
    padding: "0.15rem 0.55rem",
    borderRadius: "0.375rem",
    border: "1px solid rgba(128, 128, 128, 0.25)",
    fontSize: "0.875rem",
    lineHeight: 1.5
  };
  const codeStyle = {
    fontFamily: "var(--font-mono, ui-monospace, monospace)",
    fontWeight: 600,
    fontSize: "0.8125rem"
  };
  const controlStyle = {
    color: "inherit",
    background: "transparent",
    border: "1px solid rgba(128, 128, 128, 0.3)",
    borderRadius: "0.5rem",
    fontSize: "0.875rem"
  };
  const renderChip = it => <span key={it.code} style={chipStyle} title={isCountries ? it.code : it.label || it.code}>
      {isCountries ? it.label || it.code : <span style={codeStyle}>{it.code}</span>}
      {!isCountries && it.label ? <span style={{
    opacity: 0.7
  }}>{it.label}</span> : null}
    </span>;
  const PREVIEW = 5;
  const collapsible = items.length > PREVIEW;
  const q = query.trim().toLowerCase();
  const filtered = q ? items.filter(it => it.code.toLowerCase().includes(q) || it.label && it.label.toLowerCase().includes(q)) : items;
  const expanded = open || q !== "";
  const visible = !collapsible ? items : expanded ? filtered : items.slice(0, PREVIEW);
  const toggle = () => {
    const next = !open;
    setOpen(next);
    if (!next) setQuery("");
  };
  return <div>
      <p>
        {name} {verb} the following {codes.length} {noun}:
      </p>

      {collapsible ? <input type="text" value={query} onChange={e => setQuery(e.target.value)} placeholder={`Filter ${noun}…`} aria-label={`Filter ${noun}`} style={{
    ...controlStyle,
    display: "block",
    width: "100%",
    maxWidth: "22rem",
    padding: "0.4rem 0.7rem",
    margin: "0 0 0.75rem"
  }} /> : null}

      <div style={{
    display: "flex",
    flexWrap: "wrap",
    gap: "0.4rem"
  }}>
        {visible.map(renderChip)}
      </div>

      {q && filtered.length === 0 ? <p style={{
    opacity: 0.7,
    marginTop: "0.6rem"
  }}>
          No {noun} match “{query.trim()}”.
        </p> : null}
      {q && filtered.length > 0 ? <p style={{
    opacity: 0.6,
    fontSize: "0.8125rem",
    marginTop: "0.6rem"
  }}>
          Showing {filtered.length} of {items.length}.
        </p> : null}

      {collapsible && !q ? <button type="button" aria-expanded={open} onClick={toggle} style={{
    ...controlStyle,
    display: "inline-flex",
    alignItems: "center",
    gap: "0.4rem",
    padding: "0.35rem 0.75rem",
    marginTop: "0.75rem",
    cursor: "pointer"
  }}>
          <span aria-hidden="true" style={{
    display: "inline-block",
    transform: open ? "rotate(90deg)" : "none",
    transition: "transform 0.15s ease"
  }}>
            ›
          </span>
          {open ? "Show fewer" : `and ${items.length - PREVIEW} more`}
        </button> : null}
    </div>;
};

export const ConnectorCapabilities = ({data}) => {
  const CAPABILITIES = [{
    keys: ["three_d_secure_hosted"],
    label: "3-D Secure (hosted)",
    description: "Gr4vy-hosted 3DS authentication flow.",
    cardOnly: true
  }, {
    keys: ["three_d_secure_pass_through"],
    label: "3-D Secure (pass-through)",
    description: "Pass through 3DS data authenticated by a third party.",
    cardOnly: true
  }, {
    keys: ["partial_authorization"],
    label: "Partial authorization",
    description: "Support partial approval responses."
  }, {
    keys: ["zero_auth"],
    label: "Zero auth",
    description: "Verify a card without charging it."
  }, {
    keys: ["void"],
    label: "Void",
    description: "Cancel an authorized transaction before capture."
  }, {
    keys: ["direct_capture"],
    label: "Direct capture",
    description: "Capture a payment immediately at authorization.",
    hideWhenUnsupported: true
  }, {
    keys: ["delayed_capture"],
    label: "Delayed capture",
    description: "Authorize a payment and capture it at a later time."
  }, {
    keys: ["partial_capture"],
    label: "Partial capture",
    description: "Capture a portion of the authorized amount."
  }, {
    keys: ["over_capture"],
    label: "Over capture",
    description: "Capture more than the originally authorized amount."
  }, {
    keys: ["refunds"],
    label: "Refunds",
    description: "Refund a captured payment."
  }, {
    keys: ["partial_refunds"],
    label: "Partial refunds",
    description: "Refund a portion of the captured amount."
  }, {
    keys: ["settlement_reporting"],
    label: "Settlement reporting",
    description: "Automatic settlement and reconciliation reporting."
  }, {
    keys: ["create_session"],
    label: "Create session",
    description: "Create a connector session for client-side flows."
  }, {
    keys: ["network_tokens_default", "network_tokens_toggle"],
    label: "Network tokens",
    description: "Network-level tokenization for improved approval rates.",
    cardOnly: true
  }, {
    keys: ["digital_wallets"],
    label: "Digital wallets",
    description: "Apple Pay, Google Pay, and other wallet integrations."
  }, {
    keys: ["payment_method_tokenization", "payment_method_tokenization_toggle"],
    label: "Payment method tokenization",
    description: "Store payment methods outside of transactions."
  }, {
    keys: ["transaction_sync"],
    label: "Transaction sync",
    description: "Synchronize transaction state from the connector."
  }, {
    keys: ["create_token"],
    label: "Tokenization",
    description: "Create a token from card details collected via Secure Fields.",
    hideWhenUnsupported: true
  }, {
    keys: ["delete_token"],
    label: "Delete token",
    description: "Delete a stored token.",
    hideWhenUnsupported: true
  }, {
    keys: ["verify_credentials"],
    label: "Verify credentials",
    description: "Validate the configured credentials against the connector.",
    hideWhenUnsupported: true
  }];
  const raw = data && data.features;
  const enabled = typeof raw === "string" ? new Set(raw.split(/\s+/).filter(Boolean)) : Array.isArray(raw) ? new Set(raw) : new Set(Object.keys(raw || ({})).filter(key => raw[key]));
  const isOn = entry => entry.keys.some(key => enabled.has(key));
  const isNonCard = data && data.method && data.method !== "card";
  const renderGroup = (title, entries, supported) => {
    if (entries.length === 0) return null;
    const mark = supported ? "✓" : "✕";
    const markColor = supported ? "#16a34a" : "#9ca3af";
    return <div style={{
      marginTop: "1rem"
    }}>
        <div style={{
      fontSize: "0.75rem",
      fontWeight: 600,
      letterSpacing: "0.05em",
      textTransform: "uppercase",
      opacity: 0.6,
      marginBottom: "0.25rem"
    }}>
          {title}
        </div>
        {}
        <div role="list">
          {entries.map(entry => <div role="listitem" key={entry.label} style={{
      display: "flex",
      gap: "0.5rem",
      alignItems: "baseline",
      padding: "0.3rem 0",
      opacity: supported ? 1 : 0.7
    }}>
              <span aria-hidden="true" style={{
      color: markColor,
      fontWeight: 700,
      flexShrink: 0
    }}>
                {mark}
              </span>
              <span>
                <strong>{entry.label}</strong>
                {entry.description ? <span style={{
      opacity: 0.85
    }}> — {entry.description}</span> : null}
              </span>
            </div>)}
        </div>
      </div>;
  };
  const visible = isNonCard ? CAPABILITIES.filter(entry => !entry.cardOnly) : CAPABILITIES;
  const supported = visible.filter(isOn);
  const unsupported = visible.filter(entry => !isOn(entry) && !entry.hideWhenUnsupported);
  return <div>
      {renderGroup("Supported", supported, true)}
      {renderGroup("Not supported", unsupported, false)}
    </div>;
};

Affirm is a buy now, pay later (BNPL) payment method that allows eligible buyers to split purchases into installments. Affirm uses a redirect flow where the buyer approves the payment on the Affirm hosted page.

## Setup

To sign up for an account visit the [sign-up page](https://www.affirm.com/dashboard/onboarding) and fill in the details.

## Credentials

When setting up Affirm in the dashboard, configure the following credentials. Contact your Affirm representative to obtain these values.

* **Public key** - Your Affirm public key for client-side authentication.
* **Private key** - Your Affirm private key for server-side authentication.
* **Merchant name** - Your merchant name as registered with Affirm.

## Capabilities

<ConnectorCapabilities data={connector} />

## Supported countries

<ConnectorRegions data={connector} kind="countries" />

## Supported currencies

<ConnectorRegions data={connector} kind="currencies" />

## Identifier limitations

`payment_service_transaction_id` is null on transaction creation and is populated after the buyer approves the transaction.

## Required fields

Affirm requires cart items to be included with every transaction. Up to 249 cart items can be provided.

Based on the account setup, Affirm might also require buyer billing and shipping details. When not provided, the Affirm transaction might fail with an `invalid_request_parameters` error code, more specifically with a raw `missing_fields` error from Affirm.

## Integration

The default integration for Affirm uses a redirect to a hosted payments page.

Start by creating a new transaction with the following required fields.

<CodeGroup>
  ```csharp C# theme={"system"}
  var transaction = await client.Transactions.CreateAsync(
    transactionCreate: new TransactionCreate()
    {
      Amount = 1299,
      Currency = "USD",
      Country = "US",
      PaymentMethod =
        TransactionCreatePaymentMethod.CreateRedirectPaymentMethodCreate(
          new RedirectPaymentMethodCreate()
          {
            Method = "affirm",
            Country = "US",
            Currency = "USD",
            RedirectUrl = "https://example.com/callback",
          }
        ),
    }
  );
  ```

  ```go Go theme={"system"}
  amount := int64(1299)
  currency := "USD"
  country := "US"
  method := components.RedirectPaymentMethodCreateMethodAffirm
  redirectUrl := "https://example.com/callback"

  redirectPaymentMethodCreate := components.RedirectPaymentMethodCreate{
    Method: method,
    Country: country,
    Currency: currency,
    RedirectURL: redirectUrl,
  }
  paymentMethod := components.CreateTransactionCreatePaymentMethodRedirectPaymentMethodCreate(redirectPaymentMethodCreate)

  transactionCreate := components.TransactionCreate{
    Amount:        amount,
    Currency:      currency,
    Country:       &country,
    PaymentMethod: &paymentMethod,
  }

  transaction, err := client.Transactions.Create(ctx, transactionCreate, nil, nil, nil)
  ```

  ```java Java theme={"system"}
  CreateTransactionResponse transactionResponse = gr4vyClient.transactions().create()
    .transactionCreate(TransactionCreate.builder()
      .amount(1299L)
      .currency("USD")
      .country("US")
      .paymentMethod(TransactionCreatePaymentMethod.of(RedirectPaymentMethodCreate.builder()
        .method(RedirectPaymentMethodCreateMethod.AFFIRM)
        .country("US")
        .currency("USD")
        .redirectUrl("https://example.com/callback")
        .build()))
      .build())
    .call();

  Transaction transaction = transactionResponse.transaction().orElse(null);
  ```

  ```php PHP theme={"system"}
  $transactionCreate = new TransactionCreate(
    amount: 1299,
    currency: 'USD',
    country: 'US',
    paymentMethod: new RedirectPaymentMethodCreate(
      method: 'affirm',
      country: 'US',
      currency: 'USD',
      redirectUrl: 'https://example.com/callback'
    )
  );
  $response = $client->transactions->create($transactionCreate);
  $transaction = $response->transaction;
  ```

  ```python Python theme={"system"}
  transaction: models.Transaction = client.transactions.create(
    amount=1299,
    currency="USD",
    country="US",
    payment_method={
      "method": "affirm",
      "country": "US",
      "currency": "USD",
      "redirect_url": "https://example.com/callback",
    }
  )
  ```

  ```ts TypeScript theme={"system"}
  const transaction = await client.transactions.create({
    amount: 1299,
    currency: "USD",
    country: "US",
    paymentMethod: {
      method: "affirm",
      country: "US",
      currency: "USD",
      redirectUrl: "https://example.com/callback"
    }
  })
  ```
</CodeGroup>

After the transaction is created, the API response includes a `payment_method.approval_url` and the status is set to `buyer_approval_pending`. The approval URL expires after 30 minutes.

```json theme={"system"}
{
  "type": "transaction",
  "id": "ea1efdd0-20f9-44d9-9b0b-0a3d71e9b625",
  "payment_method": {
    "type": "payment-method",
    "approval_url": "https://cdn.gr4vy.com/connectors/..."
  },
  "method": "affirm"
}
```

Redirect the buyer to the `approval_url` (open in a browser or Webview), where they can complete the payment. Once the buyer approves, the transaction progresses to an `authorization_succeeded` or `capture_succeeded` state.

### Connection options

Affirm supports the pass through of the following connection options when creating a request. These options allow you to include itinerary details and discount information.

<CodeGroup>
  ```csharp C# highlight={16-42} theme={"system"}
  var transaction = await client.Transactions.CreateAsync(
    transactionCreate: new TransactionCreate()
    {
      Amount = 1299,
      Currency = "USD",
      Country = "US",
      PaymentMethod =
        TransactionCreatePaymentMethod.CreateRedirectPaymentMethodCreate(
          new RedirectPaymentMethodCreate()
          {
            Method = "affirm",
            Country = "US",
            Currency = "USD",
            RedirectUrl = "https://example.com/callback",
          }
        ),
      ConnectionOptions = new Dictionary<string, object>()
      {
        ["affirm-affirm"] = new Dictionary<string, object>()
        {
          ["itinerary"] = new Dictionary<string, string>()
          {
            ["type"] = "event",
            ["sku"] = "ABC123",
            ["display_name"] = "Bad Bunny at Petco Park",
            ["venue"] = "Petco Park",
            ["location"] = "100 Park Blvd, San Diego, CA 92101, US",
            ["date_start"] = "2022-12-06T03:00:00.000Z UTC"
          },
          ["discounts"] = new Dictionary<string, object>()
          {
            ["RETURN5"] = new Dictionary<string, object>()
            {
              ["discount_amount"] = 500,
              ["discount_display_name"] = "Returning customer 5% discount"
            },
            ["PRESDAY10"] = new Dictionary<string, object>()
            {
              ["discount_amount"] = 1000,
              ["discount_display_name"] = "President's Day 10% off"
            }
          }
        }
      }
    }
  );
  ```

  ```go Go highlight={19-39} theme={"system"}
  amount := int64(1299)
  currency := "USD"
  country := "US"
  method := components.RedirectPaymentMethodCreateMethodAffirm
  redirectUrl := "https://example.com/callback"

  redirectPaymentMethodCreate := components.RedirectPaymentMethodCreate{
    Method: method,
    Country: country,
    Currency: currency,
    RedirectURL: redirectUrl,
  }
  paymentMethod := components.CreateTransactionCreatePaymentMethodRedirectPaymentMethodCreate(redirectPaymentMethodCreate)

  transactionCreate := components.TransactionCreate{
    Amount:        amount,
    Currency:      currency,
    Country:       &country,
    PaymentMethod: &paymentMethod,
    ConnectionOptions: map[string]any{
      "affirm-affirm": map[string]any{
        "itinerary": map[string]string{
          "type":         "event",
          "sku":          "ABC123",
          "display_name": "Bad Bunny at Petco Park",
          "venue":        "Petco Park",
          "location":     "100 Park Blvd, San Diego, CA 92101, US",
          "date_start":   "2022-12-06T03:00:00.000Z UTC",
        },
        "discounts": map[string]any{
          "RETURN5": map[string]any{
            "discount_amount":       500,
            "discount_display_name": "Returning customer 5% discount",
          },
          "PRESDAY10": map[string]any{
            "discount_amount":       1000,
            "discount_display_name": "President's Day 10% off",
          },
        },
      },
    },
  }

  transaction, err := client.Transactions.Create(ctx, transactionCreate, nil, nil, nil)
  ```

  ```java Java highlight={1-27,38} theme={"system"}
  Map<String, String> itinerary = new HashMap<>();
  itinerary.put("type", "event");
  itinerary.put("sku", "ABC123");
  itinerary.put("display_name", "Bad Bunny at Petco Park");
  itinerary.put("venue", "Petco Park");
  itinerary.put("location", "100 Park Blvd, San Diego, CA 92101, US");
  itinerary.put("date_start", "2022-12-06T03:00:00.000Z UTC");

  Map<String, Object> return5 = new HashMap<>();
  return5.put("discount_amount", 500);
  return5.put("discount_display_name", "Returning customer 5% discount");

  Map<String, Object> presday10 = new HashMap<>();
  presday10.put("discount_amount", 1000);
  presday10.put("discount_display_name", "President's Day 10% off");

  Map<String, Object> discounts = new HashMap<>();
  discounts.put("RETURN5", return5);
  discounts.put("PRESDAY10", presday10);

  Map<String, Object> affirmOptions = new HashMap<>();
  affirmOptions.put("itinerary", itinerary);
  affirmOptions.put("discounts", discounts);

  Map<String, Object> connectionOptions = new HashMap<>();
  connectionOptions.put("affirm-affirm", affirmOptions);

  CreateTransactionResponse transactionResponse = gr4vyClient.transactions().create()
    .transactionCreate(TransactionCreate.builder()
      .amount(1299L)
      .currency("USD")
      .country("US")
      .paymentMethod(TransactionCreatePaymentMethod.of(RedirectPaymentMethodCreate.builder()
        .method(RedirectPaymentMethodCreateMethod.AFFIRM)
        .country("US")
        .currency("USD")
        .redirectUrl("https://example.com/callback")
        .build()))
      .connectionOptions(connectionOptions)
      .build())
    .call();

  Transaction transaction = transactionResponse.transaction().orElse(null);
  ```

  ```php PHP highlight={9-30} theme={"system"}
  $transactionCreate = new TransactionCreate(
    amount: 1299,
    currency: 'USD',
    country: 'US',
    paymentMethod: new RedirectPaymentMethodCreate(
      method: 'affirm',
      country: 'US',
      currency: 'USD',
      redirectUrl: 'https://example.com/callback'
    ),
    connectionOptions: [
      'affirm-affirm' => [
        'itinerary' => [
          'type' => 'event',
          'sku' => 'ABC123',
          'display_name' => 'Bad Bunny at Petco Park',
          'venue' => 'Petco Park',
          'location' => '100 Park Blvd, San Diego, CA 92101, US',
          'date_start' => '2022-12-06T03:00:00.000Z UTC'
        ],
        'discounts' => [
          'RETURN5' => [
            'discount_amount' => 500,
            'discount_display_name' => 'Returning customer 5% discount'
          ],
          'PRESDAY10' => [
            'discount_amount' => 1000,
            'discount_display_name' => "President's Day 10% off"
          ]
        ]
      ]
    ]
  );
  $response = $client->transactions->create($transactionCreate);
  $transaction = $response->transaction;
  ```

  ```python Python highlight={9-31} theme={"system"}
  transaction: models.Transaction = client.transactions.create(
    amount=1299,
    currency="USD",
    country="US",
    payment_method={
      "method": "affirm",
      "country": "US",
      "currency": "USD",
      "redirect_url": "https://example.com/callback",
    },
    connection_options={
      "affirm-affirm": {
        "itinerary": {
          "type": "event",
          "sku": "ABC123",
          "display_name": "Bad Bunny at Petco Park",
          "venue": "Petco Park",
          "location": "100 Park Blvd, San Diego, CA 92101, US",
          "date_start": "2022-12-06T03:00:00.000Z UTC"
        },
        "discounts": {
          "RETURN5": {
            "discount_amount": 500,
            "discount_display_name": "Returning customer 5% discount"
          },
          "PRESDAY10": {
            "discount_amount": 1000,
            "discount_display_name": "President's Day 10% off"
          }
        }
      }
    }
  )
  ```

  ```ts TypeScript highlight={9-31} theme={"system"}
  const transaction = await client.transactions.create({
    amount: 1299,
    currency: "USD",
    country: "US",
    paymentMethod: {
      method: "affirm",
      country: "US",
      currency: "USD",
      redirectUrl: "https://example.com/callback"
    },
    connectionOptions: {
      "affirm-affirm": {
        itinerary: {
          type: "event",
          sku: "ABC123",
          display_name: "Bad Bunny at Petco Park",
          venue: "Petco Park",
          location: "100 Park Blvd, San Diego, CA 92101, US",
          date_start: "2022-12-06T03:00:00.000Z UTC"
        },
        discounts: {
          RETURN5: {
            discount_amount: 500,
            discount_display_name: "Returning customer 5% discount"
          },
          PRESDAY10: {
            discount_amount: 1000,
            discount_display_name: "President's Day 10% off"
          }
        }
      }
    }
  })
  ```
</CodeGroup>
