> ## 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.

# Overview

> Accept payments with Payrails as the legal seller — tax calculation, invoicing, and a compliant checkout surface rendered by the drop-in.

When Payrails acts as a merchant of record (MoR), Payrails becomes the legal seller for the transaction. Payrails calculates the tax, charges the customer, and issues the invoice in its own name. You keep the integration you already have: the same `client/init` and authorize endpoints, the same web SDK, and the
same notification contract.

This page gets you to a first MoR payment. It covers the order data you add to
your client session, the [drop-in](https://docs.payrails.com/docs/orchestration/checkout-sdks/web) that renders the
checkout surface, and the limits of the current beta.

## What changes

MoR is not a new integration model, a new SDK, or a new set of endpoints. It is
the same client-session-then-authorize flow, running on a workspace that
Payrails configures to act as merchant of record.

These stay the same:

* The endpoints: `POST /merchant/client/init` and
  `POST /merchant/workflows/{workflowCode}/executions/{executionId}/authorize`.
* The `@payrails/web-sdk` package, `Payrails.init()`, and `dropin().mount()`.
* The notification contract, so your existing success and failure consumers keep
  working.

These change:

| Change | Reason |
| - | - |
| MoR requires the drop-in. Elements and secure fields do not support MoR. | As the legal seller, Payrails must show the customer the tax breakdown and terms. The drop-in is the integration where Payrails renders them. |
| Every `client/init` call on the workspace carries `meta.order`, with a billing address. | Payrails uses it to calculate tax and issue an invoice. A call without it fails with `order is required` |
| The `client/init` response carries MoR data — tax lines, billing form, and terms — that the drop-in renders. | Pass the response to the SDK unmodified. |
| Payrails becomes the seller of record and handles tax calculation and invoicing. | You do not need your own tax or invoicing logic for MoR volume. |
| MoR applies to a whole workspace, and Payrails enables it. | You cannot switch it on per request or per rule. Your non-MoR traffic stays in your existing workspace. |

## Before you start

* An MoR workspace, provisioned by your Payrails onboarding contact.
* A completed and cleared KYB and KYC submission.
* A signed MoR agreement.
* The `@payrails/web-sdk` package on its latest version.
* At least one **active** product in your product catalog. Payrails reviews every
  product before you can sell it. See [Create your products](#create-your-products).

## Create your products

On MoR, every line in an order points to a product in your product catalog. The
product tells Payrails what you sell, so Payrails can tax it, describe it on the
invoice, and screen it before the first sale. Create and manage products in the
Payrails Portal.

1. Open the Portal and select **Merchant of record** > **Products**.
2. Create a product and fill in three steps:
   * **Product:** the buyer-facing name and description, the product category
     (the tax code), the product URL, and optionally the image, terms, and
     refund conditions URLs, an invoice brand, and your own external reference.
   * **Pricing:** the pricing model, the price range, and the currency.
   * **Compliance:** four yes/no screening questions, and additional notes.
3. Submit the product. It starts as **Pending review**.
4. Wait for Payrails to review it. The status changes to **Active** when Payrails
   approves it, or **Rejected** when it needs changes.

![The Create product form on the Product step, with the name, description, category, and product URL filled in and the Product, Pricing, and Compliance steps listed on the left](https://mintcdn.com/payrails-42074109/pi9z08YDj7ldbKam/images/docs/merchant-of-record/create-product-step-1-product.jpg?fit=max\&auto=format\&n=pi9z08YDj7ldbKam\&q=85\&s=38f251a509fa331b65306ed02abfd169)

Step 1: Product

![The Create product form on the Pricing step, showing the pricing model and the minimum price, maximum price, and currency fields](https://mintcdn.com/payrails-42074109/pi9z08YDj7ldbKam/images/docs/merchant-of-record/create-product-step-2-pricing.jpg?fit=max\&auto=format\&n=pi9z08YDj7ldbKam\&q=85\&s=249eea81f60ae74abe2f421ca7d0a50b)

Step 2: Pricing

![The Create product form on the Compliance step, showing four yes/no screening questions and an additional notes field](https://mintcdn.com/payrails-42074109/pi9z08YDj7ldbKam/images/docs/merchant-of-record/create-product-step-3-compliance.jpg?fit=max\&auto=format\&n=pi9z08YDj7ldbKam\&q=85\&s=bdfcc12bda3db4680f51840541a34397)

Step 3: Compliance

![The Products list under Merchant of record, with a product in Pending review status and its Product ID, price range, and version](https://mintcdn.com/payrails-42074109/pi9z08YDj7ldbKam/images/docs/merchant-of-record/products-list.jpg?fit=max\&auto=format\&n=pi9z08YDj7ldbKam\&q=85\&s=80c84d3d87d9535b6728e6389804f098)

The Products list. A new product starts as Pending review.

Copy the **Product ID** from the list, or from the product's details page, and
the **Version** next to it. You send both in the order line.

![A product details page showing the Product ID, name, description, category, and links, with the Pending review status at the top](https://mintcdn.com/payrails-42074109/pi9z08YDj7ldbKam/images/docs/merchant-of-record/product-details.jpg?fit=max\&auto=format\&n=pi9z08YDj7ldbKam\&q=85\&s=3d70d514491f78c7603e7f6d3f0219f3)

The product details page, with the Product ID.

### Product fields

| Field | Required | What to enter |
| - | - | - |
| Product name | Yes | The name the buyer sees on the invoice email. Up to 255 characters. |
| Product description | Yes | What the buyer purchases, clear enough to identify without visiting your site. Up to 2,000 characters. |
| Product category | Yes | The Payrails tax code, for example `businessSaas`. Payrails uses it to calculate tax. |
| Product URL | Yes | The page where you sell the product. |
| Pricing model | Yes | One-time, subscription (monthly, annual, or other), usage-based, free, or free with in-app purchases. |
| Price range | Yes | A minimum and maximum price, with a currency. The price you send in each order must fall inside it. |
| Screening questions | Yes | Whether the product has user-generated content, adult content, or digital-asset features, and whether it needs a regulatory licence. |
| Additional notes | Depends | Required when the product needs a regulatory licence. |
| Invoice brand | No | The app or brand the buyer recognises. Its name replaces Payrails' on the invoice and receipt email. English letters and digits only. Spaces and symbols are rejected. |
| External reference | No | Your own identifier, such as a SKU. Use it to find the product in the Portal. You can't send it in an order. |

A product has no fixed price. One product covers every variant you sell, such as
several plan sizes, so set the range to span them all.

### Change a product

You can't edit a product after you submit it. To change one, open it and select
**Edit as new version**. The new version goes through review as well. Each
active version works independently, so several versions of one product can be
sold at the same time. Send the `version` you want in each order line. A new
version only becomes usable once it's active.

### Skip manual review in the test environment

In the test environment, Payrails can review a product automatically, so you
can test the flow end to end without waiting for approval. End the product
description with one of these markers:

| Description ends with | Result |
| - | - |
| `(accepted)` | The product becomes **Active** right away. |
| `(rejected)` or `(declined)` | The product becomes **Rejected**, with the reason `missing_information`. |
| No marker | The product stays **Pending review** until Payrails reviews it. |

The markers have no effect in the live environment.

## Create the client session

Call `POST /merchant/client/init` from your backend, as you do today. Send a
`Bearer` token carrying the `sdk:read:config` scope, and an `x-idempotency-key`
header holding a UUID — Payrails rejects the call without it. The MoR-specific
additions sit inside `meta.order`.

**Checkout**

Use normal checkout when the customer must explicitly provide their billing address during the payment.

1. The customer must provide a complete billing address before they can pay.
2. You can provide the full or partial billing address in the request. Payrails pre-populates it for the customer in the address form.

```json theme={null}
{
  "type": "dropIn",
  "workflowCode": "payment-acceptance",
  "workspaceId": "<your MoR workspace id>",
  "holderReference": "holder-12345",
  "merchantReference": "order-12345",
  "amount": { "value": "19.99", "currency": "USD" },
  "meta": {
    "customer": {
      "reference": "holder-12345",
      "name": "Jane",
      "lastName": "Doe",
      "email": "jane.doe@example.com"
    },
    "order": {
      "billingAddress": {
        "email": "jane.doe@example.com",
        "street": "500 Market Street",
        "city": "San Francisco",
        "state": "CA",
        "postalCode": "94105",
        "country": { "code": "US" }
      },
      "lines": [
        {
          "id": "pro-monthly",
          "type": "lineItem",
          "quantity": 1,
          "product": {
            "id": "3f2b8c1e-6a4d-4e0b-9d57-2c1a7e5b9f40",
            "version": 1
          },
          "unitPrice": { "value": "19.99", "currency": "USD" }
        }
      ]
    }
  }
}
```

**Express Checkout**

Express Checkout is an optimised experience which can use the capabilities of Apple Pay and Google Pay to directly collect the customers billing address from their wallet profile instead of requiring the customer to manually fill the address form.

**Required fields**

1. You must pass `meta.expressCheckout: true`.
2. Express Checkout is only allowed for inclusive tax behavior, so you must pass `meta.order.taxBehavior: "inclusive"`.

```json theme={null}
{
  "type": "dropIn",
  "workflowCode": "payment-acceptance",
  "workspaceId": "<your MoR workspace id>",
  "holderReference": "holder-12345",
  "merchantReference": "order-12345",
  "amount": { "value": "19.99", "currency": "USD" },
  "meta": {
    "expressCheckout": true,
    "customer": {
      "reference": "holder-12345",
      "name": "Jane",
      "lastName": "Doe",
      "email": "jane.doe@example.com"
    },
    "order": {
      "taxBehavior": "inclusive",
      "billingAddress": {
        "country": { "code": "US" }
      },
      "lines": [
        {
          "id": "pro-monthly",
          "type": "lineItem",
          "quantity": 1,
          "product": {
            "id": "3f2b8c1e-6a4d-4e0b-9d57-2c1a7e5b9f40",
            "version": 1
          },
          "unitPrice": { "value": "19.99", "currency": "USD" }
        }
      ]
    }
  }
}
```

### Order fields

The API enforces very little. Send the rest anyway: it drives the tax
calculation and the invoice, which is what matters in practice.

| Field | Enforced by the API | Why to send it |
| - | - | - |
| `meta.order` | Yes | Its absence fails the call on an MoR workspace. |
| `meta.expressCheckout` | No | Provide to enable the express checkout experience. Available only in inclusive tax behavior. |
| `meta.order.billingAddress` | Yes | With the line items, it determines the tax jurisdiction. |
| `billingAddress.country.code` | Yes | The only address field the backend checks. |
| `billingAddress.street`, `city`, `state`, `postalCode` | No | The tax provider drops the **entire** address unless all five are present, and raises no error. Send all of them, for every country. |
| `meta.order.lines[].type` | No | Omit it and Payrails skips the line, then taxes the whole amount as one synthetic line. Use `lineItem`. |
| `meta.order.taxBehavior` | No | `exclusive` \| `inclusive` Provide the tax behavior for the transaction. Default is `exclusive` if not provided. |
| `meta.order.lines[].quantity` | No | Takes an integer. Not sending it doesn't fail the call, but it's needed for accurate line-item pricing on the tax calculation and invoice |
| `meta.order.lines[].unitPrice` | Yes | The exact price for the line. It must be in the product's currency and inside its minimum and maximum price. Payrails needs it to calculate tax and generate the invoice. |
| `meta.order.lines[].product.type` | No | One of `physical`, `digital`, or `service`. Fraud and BNPL connectors read it; the MoR tax calculation does not. |
| `meta.order.lines[].product.id` | Yes | The Product ID of an active product in your catalog. Payrails fills in the line's name, description, tax code, and links from it. |
| `meta.order.lines[].product.version` | Yes | The product version to sell. The call fails without it. |
| `meta.customer.email` | Yes | Without it, the call fails validation and is rejected Payrails needs an email to identify the customer and send the invoice as the seller of record. |

> [!WARNING]
>
> Send the full billing address, including `state`. Payrails needs all of `street`, `city`, `state`, and `postalCode`. If one of them is absent, Payrails discards the whole address before calculating tax and raises no error, and the payment succeeds with the wrong tax.

Don't send `name`, `description`, `taxCode`, or `links` on a line that has a
`product.id`. Payrails takes them from the product, and rejects the call if a value
you send differs from it.

Two details that catch people out:

* Put the house number in `street`. The tax provider reads `street` verbatim as
  the first address line. `doorNumber` only pre-fills the second line of the
  drop-in's billing form.
* The drop-in pre-fills its email field from `billingAddress.email`, while the
  invoice contact comes from `meta.customer.email`. Send both.

## Mount the drop-in

Pass the `client/init` response to the SDK unmodified. Reading the response is
fine; changing it is not, and neither is depending on its internal shape. The
SDK decodes it and renders the tax breakdown, billing form, and terms for you.

```typescript theme={null}
import { Payrails } from "@payrails/web-sdk";

// clientInitResponse is the unmodified body your backend received from client/init
const payrails = await Payrails.init(clientInitResponse);

const dropin = payrails.dropin({
  paymentMethodsConfiguration: {
    cards: {
      showCardHolderName: true,
      alwaysStoreInstrument: true,
    },
  },
});

dropin.mount("#dropin");
```

`Payrails.init()` is asynchronous — it loads the SDK bundle and its styles from
the Payrails CDN, so `await` it. Allow `https://assets.payrails.io` in your
Content-Security-Policy under `script-src`, `style-src`, and `frame-src`, since
v6 renders the card form inside a Payrails-hosted iframe.

These samples target the current major version of the web SDK. On v5 the import
was a default export, the package shipped a `payrails-styles.css` you imported
yourself, and callbacks went in an `events` bag on `dropin()`. All three are
gone — refer to the [v6 migration guide](https://docs.payrails.com/docs/orchestration/checkout-sdks/web/v6-migration/v6-migration-guide.md) if you are
upgrading.

On a recurring plan, prefer `alwaysStoreInstrument: true` over the save-card
checkbox. The mandate is part of the purchase, and the terms block in the
drop-in carries the customer's consent.

### What the drop-in adds on MoR

On an MoR workspace the drop-in renders components that a standard orchestration
checkout does not have:

* A billing address form, once per checkout, pre-filled from the address in your
  `client/init` call.
* A tax breakdown and a terms block, inside each payment method. The tax
  breakdown lists the tax lines, each with an amount and a percentage. It does
  not show a subtotal or a grand total.

Budget vertical space for them — a checkout laid out for a standard drop-in
leaves no room for them. Do not suppress or restyle either block out of view; both
carry disclosure that Payrails depends on as the legal seller. To restyle them
within those limits, use the `morAppearance` option on the payment method.

Pre-filling `billingAddress` is the better path where you already collect the
address: the form renders populated, the customer takes no extra step, and the
tax calculation uses an address you have already validated. Where you do not
collect it, leave the fields out and the customer completes the form in the
drop-in.

## Handle the payment result

Subscribe to payment outcomes on the SDK instance. The same events fire for
every payment method in the session.

```typescript theme={null}
payrails.on("success", (event) => {
  // event.action is 'AUTHORIZE': update the UI and redirect. The event comes from the
  // browser, so grant entitlement (unlock purchase) only after your backend verifies
  // the payment notification
});

payrails.on("failed", (event) => {
  // event.data?.code carries the failure reason
});

payrails.on("pending", () => {
  // the authorization is still processing; no further customer action needed
});
```

## Tax and the amount you charge

Payrails calculates the tax and produces the amount charged to the card. The
amount in your `client/init` call is treated differently depending on the tax behavior provided in the request.

To provide the tax behavior send one of the allowed options in the `meta.order.taxBehavior` field in the client request.

| Tax Behavior | Definition |
| - | - |
| `inclusive` | The amount you provide is treated as the gross amount and tax is collected from this amount. Customer will see the exact amount you provided. |
| `exclusive` | The amount you provide is treated as the net amount and the tax is collected on top of this amount. Customer will see the net amount your provided + Tax calculated on top of it. Default tax behavior if not provided in the request. |

> [!WARNING]
>
> **Tax exclusive**
>
> Payrails adds tax on top of the amount you send. An amount of `19.99` at a tax
> rate of 8.25% charges the customer `21.64`. Check what this does to your published
> prices before your first MoR transaction.

To show a customer the tax-inclusive total before they pay — after they change
their billing address, for example — call
`POST /merchant/workflows/{workflowCode}/executions/{executionId}/priceBreakdown`
with the address, amount, and line items. Reference products in the lines the same way as in `client/init`. It returns `baseAmount`, `taxAmount`,
`totalAmount`, and the tax lines.

Read the charged total from the notification and store it against your order if
your support or accounting tooling needs it. Do not reconstruct it.

If you run your own billing or subscription system alongside MoR, disable its
customer invoice emails. Payrails is the seller of record and needs to be the
only party emailing the customer about a charge. Two parties emailing about one
payment confuses cardholders, and confused cardholders raise chargebacks.

## Merchant-initiated payments (MIT) and subscription payments

For an MIT, such as a subscription renewal, Payrails needs the billing address,
the customer's name, and their email to calculate tax and generate the invoice.
It can happen that customer changed email, last name or moved to another apartment
or even another city since last payment.

Payrails saves the billing address shared by the customer during their first payment
and will use it for future MIT payments. If there are no changes in the customers
address you are not required to pass anything in the billing address other then the
country code.

To change the billing address, name, or email for an MIT, first
[update the instrument](https://docs.payrails.com/reference/updateinstrument.md) with the new data:

```http theme={null}
PATCH /payment/instruments/{instrumentId}
x-idempotency-key: 91874e4d-81e9-4486-aee4-ee1de84da890
Content-Type: application/json

{
  "billingAddress": {
    "name": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com",
    "street": "500 Market Street",
    "city": "San Francisco",
    "state": "CA",
    "postalCode": "94105",
    "country": { "code": "US" }
  }
}
```

For updating of billing information you need to send the full address, as you do in `client/init`.

## Current limitations

* A product has to belong to the workspace you call `client/init` for.
* The drop-in is the supported integration. Elements, secure fields, and the
  native mobile SDKs are not available on MoR.
* Only the United States is supported.
* Payrails refunds the full payment amount. Partial refunds are not supported.

## Next steps

> **[Web SDK quick start](https://docs.payrails.com/docs/orchestration/checkout-sdks/web)**

> **[Authorize a payment](https://docs.payrails.com/docs/orchestration/payment-acceptance/authorize-a-payment.md)**

> **[Receive notifications](https://docs.payrails.com/docs/orchestration/payment-acceptance/receive-notifications.md)**

> **[Authorization flags](https://docs.payrails.com/docs/resources/payments/authorization-flags.md)**


## Related topics

- [Overview](https://docs.payrails.com/docs/overview.md)
- [Workflow Studio Overview](https://docs.payrails.com/docs/orchestration/workflow-studio/overview.md)
- [3D Secure](https://docs.payrails.com/docs/orchestration/payment-acceptance/3d-secure.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.