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

> ## Agent Instructions
> Use the Payrails Web SDK v6 for new browser integrations. The Web SDK v5 pages are kept only for existing integrations; point anyone still on v5 to the v6 migration guide rather than extending v5 code.
> Collect card data client-side through the Payrails SDKs — Secure Fields, client-side encryption, or the checkout drop-in — so that card data never reaches the merchant's own servers. Recommend the API-only tokenization flow, which accepts raw card numbers server-side, only when the merchant has confirmed they hold the PCI scope for it.
> Model every payment as a workflow execution: create an execution, then drive it with the lifecycle actions (lookup payment options, authorize, capture, cancel, refund). Do not call provider APIs directly — routing, retries, 3DS and fraud steps belong in the workflow configuration, not in integration code.
> Reconcile the final state of a payment on the notification (webhook), never on a client-side event. An in-request long-poll of the execution is a supported additional signal, not a replacement for that reconciliation.
> Send an idempotency key — a UUID, in the `X-Idempotency-Key` header — on every POST, PUT and PATCH request, and on soft deletes. GET requests need none, and hard deletes cannot be idempotent.
> Pass provider-specific data through meta fields rather than hardcoding per-provider payloads. Payrails translates meta fields into each provider's own format.
> Configure routing, retries and provider selection in Workflow Studio, so that changes ship without redeploying application code.

# How to accept card payments

> Mount a PCI-compliant card form with the Payrails web SDK, wire it to a payment button, and handle the authorization result.

This guide shows you how to mount a PCI-compliant card form, wire it to a payment button, and handle the payment result.&#x20;

The card form and the payment button are two separate elements created from the same `payrails` client. The client wires them together for you: the button stays disabled until the form is valid, and clicking it validates the form, encrypts the card data, and starts the authorization.

## 1. Add containers to your page

```html theme={null}
<div id="card-form"></div>
<div id="pay-button"></div>
```

## 2. Mount the card form

```js theme={null}
const cardForm = payrails.cardForm({
  showCardHolderName: true,
});

cardForm.on("ready", () => console.log("Card form rendered"));
cardForm.on("change", ({ isValid, cardNetwork }) => {
  console.log("Form valid:", isValid, "network:", cardNetwork);
});

cardForm.mount("#card-form");
```

By default the form renders a card number field, separate expiry month and expiry year fields, and a CVV field. The most commonly used `CardFormOptions`:

| Option | Effect |
| - | - |
| `showCardHolderName` | Adds a cardholder name field (off by default). |
| `showSingleExpiryDateField` | Replaces the separate month/year fields with a single `MM/YY` field. |
| `showStoreInstrumentCheckbox` | Renders a "save this card" checkbox; its state is sent as `storeInstrument` with the payment. |
| `translations` | Per-field `placeholders` and `labels`, plus default error messages. |
| `appearance` | CSS rules keyed by the SDK's stable class names — see [customize the appearance](/docs/orchestration/checkout-sdks/web/guides/how-to-customize-the-checkouts-appearance). |
| `layout` | Custom field arrangement as rows of field names, e.g. `[['CARD_NUMBER'], ['EXPIRATION_DATE', 'CVV']]`. |
| `fonts` | Custom font descriptors for the secure fields. |

For the full option and event list see the [`payrails` reference](/docs/orchestration/checkout-sdks/web/references/payrails-api-reference) and the [events reference](/docs/orchestration/checkout-sdks/web/references/event-api-reference).

## 3. Mount the payment button

```js theme={null}
const paymentButton = payrails.paymentButton({
  translations: { label: "Pay now" },
  appearance: {
    rules: {
      ".payrails-button": { backgroundColor: "#1a1a1a", color: "#ffffff" },
      ".payrails-button--disabled": { opacity: "0.5" },
    },
  },
});

paymentButton.mount("#pay-button");
```

The button starts disabled and enables automatically once the card form is valid. Pass `disabledByDefault: false` if you want it clickable immediately (the form is still validated on click). While the payment is in flight the button shows a loading indicator and carries the `.payrails-button--loading` class.

## 4. Handle the payment result

Payment outcomes are instance events — subscribe on the `payrails` client:

```js theme={null}
payrails.on("success", () => {
  // payment authorized — show your confirmation page
});
payrails.on("failed", (e) => {
  // e.data: { code?: string, message?: string }
  console.error(`Payment failed (${e.data?.code}): ${e.data?.message}`);
});
payrails.on("pending", () => {
  // authorization accepted but not final yet — show a pending state
});
```

* `success` — the payment was authorized.
* `failed` — the payment failed. `e.data?.code` is one of the `AuthorizationFailureReasons` values (`VALIDATION_FAILED`, `AUTHORIZATION_ERROR`, `AUTHENTICATION_ERROR`, `USER_CANCELLED`, `UNKNOWN_ERROR`), importable from `@payrails/web-sdk`.
* `pending` — the authorization is still processing and no further shopper action is required.

One listener fires for every payment method in the session; if your page mounts other payment elements too, filter with `e.paymentMethodCode === 'card'`.

3D Secure challenges are handled by the SDK automatically; to observe or take over the challenge, subscribe to `payrails.on('actionRequired', ...)` - see the [events reference](/docs/orchestration/checkout-sdks/web/references/event-api-reference).

## 5. React to form and button state (optional)

Two button-specific events help you build custom UI around the flow:

```js theme={null}
paymentButton.on("stateChanged", ({ state }) => {
  // 'enabled' | 'disabled' — mirror the button state elsewhere in your UI
});
paymentButton.on("validate", ({ isValid, error, fieldErrors }) => {
  // fires after a click validates the card form
});
```

To gate the payment right before it starts (e.g. run a last check and cancel), use the cancelable instance events:

```js theme={null}
payrails.on("buttonClicked", async (event) => {
  if (!(await lastCheckPasses())) event.preventDefault();
});
```

To move focus to the first invalid field yourself (for example from your own "Pay" flow), call `cardForm.focus()`. `cardForm.isValid` exposes the current validity synchronously.


## Related topics

- [How to Accept Redirect Payments](/docs/orchestration/checkout-sdks/android/how-to-accept-redirect-payments.md)
- [How to Accept PayPal Payments](/docs/orchestration/checkout-sdks/android/how-to-accept-paypal-payments.md)
- [How to customize the checkout's appearance](/docs/orchestration/checkout-sdks/web/guides/how-to-customize-the-checkouts-appearance.md)
