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

# Payment Customization Functions

> Hide, rename, or reorder payment methods at checkout

# Payment Customization Functions

A `payment_customization` function modifies the merchant's payment-method
list before it renders at checkout. Use it to hide a method conditionally
(e.g. block COD for high-value orders), rename one for clarity, or pin a
preferred method to the top of the radio list.

Runs at **both** cart verification (preview) and order placement (enforcement)
so a stale frontend can't bypass a hide op — the backend rejects the
order if a hidden method is submitted.

## How It Works

```mermaid theme={null}
sequenceDiagram
    participant Checkout
    participant Backend
    participant YourFunction

    Checkout->>Backend: Verify cart
    Backend->>YourFunction: cart + paymentMethods + shippingAddress
    YourFunction->>Backend: { operations: [...] }
    Backend->>Checkout: Filtered + renamed + reordered methods
    Note over Backend: Order placement also runs this and rejects hidden selections
```

## Function Manifest

```json theme={null}
{
  "handle": "my-payment-app",
  "name": "Payment Customizer",
  "version": "1.0.0",
  "functions": {
    "payment_customization": {
      "handle": "payment-rules",
      "name": "Payment Rules",
      "config": {
        "hideCODAbove": 500
      }
    }
  }
}
```

## Input Schema

```typescript theme={null}
interface PaymentCustomizationInput {
  cart: {
    items: CartItem[];
    totalPrice: number;     // Subtotal in display currency
    itemCount: number;
    currency: string;
  };
  paymentMethods: PaymentMethod[];
  shippingAddress: {
    firstName: string;
    lastName: string;
    address1: string;
    address2: string;
    city: string;
    province: string;
    country: string;
    zip: string;
    phone: string;
  };
}

interface PaymentMethod {
  id: string;             // paymentProviderId (UUID)
  name: string;           // Provider key (e.g. "stripe", "razorpay", "cod")
  type: string;           // Same as name in current backend
}

interface CartItem {
  id: string;             // lineItemId
  variantId: string;
  productId: string;
  title: string;
  quantity: number;
  price: number;          // Effective unit price (display currency)
  originalPrice: number;
}
```

<Note>
  `price` and `totalPrice` are in **display currency units**, not cents.
  `totalPrice: 599.99` means ₹599.99 / \$599.99.

  The input is minimal — no `tags`, `productType`, or `vendor` is passed.
  `customer` is passed on the **order-placement** dispatch (addClientOrder)
  but **not** on the cart-verification dispatch, so don't rely on it for
  preview-time rules. If your rules need richer data, look the customer up
  via your own backend using the customer email from the verify-cart
  payload (which your app receives via the App Bridge).
</Note>

## Output Schema

```typescript theme={null}
interface PaymentCustomizationOutput {
  operations: PaymentOperation[];
}

type PaymentOperation =
  | { hide:    { paymentMethod: string } }    // Match by provider key
  | { hide:    { paymentMethodId: string } }  // Match by paymentProviderId
  | { rename:  { paymentMethod: string; name: string } }
  | { rename:  { paymentMethodId: string; name: string } }
  | { move:    { paymentMethod: string; position: number } }
  | { move:    { paymentMethodId: string; position: number } };
```

Either `paymentMethod` (the provider key like `"cod"`) or `paymentMethodId`
(the UUID) works for all ops. The backend matches against both. On a
`move` op, `index` is accepted as an alias for `position` (either field
works).

<Note>
  `reorder` is accepted as an alias for `move` — both produce the same
  result. New code should use `move`.
</Note>

## Operations

### `hide`

Removes the method from the radio list (preview) **and** rejects the
order if the customer submits it.

```js theme={null}
{ operations: [{ hide: { paymentMethod: 'cod' } }] }
```

The order-place error message is:
`"Payment method 'cod' is not available for this order"`

### `rename`

Overrides the displayed name. The underlying provider key is unchanged —
charging behaviour is identical, only the radio label is different.

```js theme={null}
{ operations: [{
  rename: { paymentMethod: 'stripe', name: 'Credit Card (incl. 3-D Secure)' }
}] }
```

On the frontend the renamed plan carries `__renamed: true` so the
checkout payment list displays `methodName` instead of the provider's
built-in label.

### `move`

Sets the index of the method in the radio list. Lower positions render
first.

```js theme={null}
{ operations: [{ move: { paymentMethod: 'stripe', position: 0 } }] }
```

## Examples

### Hide COD for high-value orders

```js theme={null}
function customizePayments(input, config) {
  if (input.cart.totalPrice <= (config.hideCODAbove || 500)) {
    return { operations: [] };
  }
  return {
    operations: [{ hide: { paymentMethod: 'cod' } }]
  };
}
```

### Region-based methods

```js theme={null}
function customizePayments(input) {
  const country = input.shippingAddress?.country;
  const ops = [];

  if (country !== 'IN') ops.push({ hide: { paymentMethod: 'razorpay' } });
  if (!['NL'].includes(country)) ops.push({ hide: { paymentMethod: 'ideal' } });
  return { operations: ops };
}
```

### Rename for clarity, pin preferred

```js theme={null}
function customizePayments() {
  return {
    operations: [
      { move:    { paymentMethod: 'stripe', position: 0 } },
      { rename:  { paymentMethod: 'stripe', name: 'Credit / debit card' } },
      { rename:  { paymentMethod: 'cod',    name: 'Cash on delivery (₹49 handling)' } }
    ]
  };
}
```

## How it renders in the UI

```
Payment Method
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

○ Credit / debit card                ← renamed + reordered
○ Cash on delivery (₹49 handling)    ← renamed
                                     ← bank_transfer hidden
```

The checkout reads:

* `paymentMethods` in the verified-cart response — filtered list (after `hide`)
* Each plan carries `__renamed: true` + `methodName` when a rename op
  fired.

## Best Practices

<AccordionGroup>
  <Accordion title="Never hide every method">
    The backend doesn't auto-recover — if all methods are hidden, the
    customer literally can't pay. Always leave at least one method
    available for any reachable cart state.
  </Accordion>

  <Accordion title="Match by provider key, not UUID">
    Provider UUIDs differ per store. `paymentMethod: 'cod'` is portable
    across installs; `paymentMethodId: '<uuid>'` is not.
  </Accordion>

  <Accordion title="Test against the real checkout">
    Drive `/checkout` with Puppeteer and screenshot the payment radio.
    JSON-only tests don't catch the `__renamed` flag wiring; only the
    rendered label does.
  </Accordion>

  <Accordion title="Keep customizations idempotent">
    Functions re-run on every cart change. Always derive ops from the
    current cart + config, not from cached state.
  </Accordion>
</AccordionGroup>

## See Also

* [Delivery Customization Functions](/functions/delivery-customization) —
  same operations applied to shipping zones.
* [Order Validation Functions](/functions/order-validation) — block the
  order outright instead of just hiding a method.
