# Net payment terms for B2B buyers

> A Shopify payment customization Function that sets Net 30 (or 7–90 day) payment terms and an optional deposit for B2B company buyers. Shopify Plus.

Page: https://lintelagency.com/resources/shopify-functions/b2b-net-payment-terms

## In short

This payment customization Function gives B2B company buyers net payment terms, Net 30 by default, with per-company overrides and an optional deposit. Consumers aren’t affected. It uses the paymentTermsSet operation of the cart.payment-methods.transform.run target, Payment Customization API version 2026-10. Shopify only allows paymentTermsSet on stores with the Plus plan.

## Facts

- **Function API:** Payment Customization
- **Target:** cart.payment-methods.transform.run
- **Plan:** Shopify Plus (paymentTermsSet)
- **App scope:** write_payment_customizations
- **API version:** 2026-10
- **Last checked:** 2 November 2026

## How to use it

1. Create an app with the Shopify CLI, then add the extension: shopify app generate extension --template payment_customization, with JavaScript.
2. Replace the generated shopify.extension.toml and the two files in src/ with the files below.
3. Run the fixtures with shopify app function run --input tests/company-default-terms.input.json.
4. Deploy with shopify app deploy and create the customization with paymentCustomizationCreate on the Plus store.
5. Set the $app:function-configuration metafield, for example {"dueInDays": 30, "companyDueInDays": {"gid://shopify/Company/1234567890": 60}, "depositPercentage": 20}.

## Code

### shopify.extension.toml

```toml
api_version = "2026-10"

[[extensions]]
name = "Net payment terms for B2B buyers"
handle = "b2b-net-payment-terms"
type = "function"

  [[extensions.targeting]]
  target = "cart.payment-methods.transform.run"
  input_query = "src/cart_payment_methods_transform_run.graphql"
  export = "cart-payment-methods-transform-run"

  [extensions.build]
  command = ""
  path = "dist/function.wasm"
```

### src/cart_payment_methods_transform_run.graphql

```graphql
query CartPaymentMethodsTransformRunInput {
  cart {
    buyerIdentity {
      purchasingCompany {
        company {
          id
        }
      }
    }
  }
  paymentCustomization {
    metafield(namespace: "$app", key: "function-configuration") {
      jsonValue
    }
  }
}
```

### src/cart_payment_methods_transform_run.js

```js
// @ts-check

/**
 * @typedef {import("../generated/api").CartPaymentMethodsTransformRunInput} CartPaymentMethodsTransformRunInput
 * @typedef {import("../generated/api").CartPaymentMethodsTransformRunResult} CartPaymentMethodsTransformRunResult
 */

/** Net terms Shopify accepts for dueInDays in API version 2026-10. */
const SUPPORTED_DUE_IN_DAYS = [7, 15, 30, 45, 60, 90];

/** Settings stored as JSON in the customization's metafield. */
const DEFAULT_CONFIG = {
  // Terms for every company buyer without an override below.
  dueInDays: 30,
  // Company ID → days, e.g. { "gid://shopify/Company/1234567890": 60 }.
  companyDueInDays: /** @type {Record<string, number>} */ ({}),
  // Optional deposit (1–99 % of the order total) paid at checkout; null for none.
  depositPercentage: /** @type {number | null} */ (null),
};

/** @type {CartPaymentMethodsTransformRunResult} */
const NO_CHANGES = { operations: [] };

/**
 * @param {CartPaymentMethodsTransformRunInput} input
 * @returns {CartPaymentMethodsTransformRunResult}
 */
export function cartPaymentMethodsTransformRun(input) {
  const company = input.cart.buyerIdentity?.purchasingCompany?.company;
  // Consumers (no purchasing company) keep paying at checkout.
  if (!company) return NO_CHANGES;

  const config = { ...DEFAULT_CONFIG, ...input.paymentCustomization?.metafield?.jsonValue };
  const dueInDays = (config.companyDueInDays ?? {})[company.id] ?? config.dueInDays;
  // An unsupported value would make Shopify reject the operation; leave the
  // company's own terms in place instead.
  if (!SUPPORTED_DUE_IN_DAYS.includes(dueInDays)) return NO_CHANGES;

  // Shopify accepts a deposit of 1–99 %; anything else is left out rather than rejected.
  const percentage = config.depositPercentage;
  const deposit = Number.isFinite(percentage) && percentage >= 1 && percentage <= 99 ? { percentage } : null;
  return {
    operations: [{ paymentTermsSet: { paymentTerms: { net: { dueInDays, deposit } } } }],
  };
}
```

## Test input and expected output

### tests/company-default-terms.input.json

```json
{
  "cart": {
    "buyerIdentity": { "purchasingCompany": { "company": { "id": "gid://shopify/Company/1234567890" } } }
  },
  "paymentCustomization": { "metafield": null }
}
```

### tests/company-default-terms.output.json

```json
{
  "operations": [
    { "paymentTermsSet": { "paymentTerms": { "net": { "dueInDays": 30, "deposit": null } } } }
  ]
}
```

### tests/company-override-with-deposit.input.json

```json
{
  "cart": {
    "buyerIdentity": { "purchasingCompany": { "company": { "id": "gid://shopify/Company/1234567890" } } }
  },
  "paymentCustomization": {
    "metafield": {
      "jsonValue": {
        "companyDueInDays": { "gid://shopify/Company/1234567890": 60 },
        "depositPercentage": 20
      }
    }
  }
}
```

### tests/company-override-with-deposit.output.json

```json
{
  "operations": [
    { "paymentTermsSet": { "paymentTerms": { "net": { "dueInDays": 60, "deposit": { "percentage": 20 } } } } }
  ]
}
```

### tests/consumer-checkout.input.json

```json
{
  "cart": { "buyerIdentity": { "purchasingCompany": null } },
  "paymentCustomization": { "metafield": null }
}
```

### tests/consumer-checkout.output.json

```json
{ "operations": [] }
```

## Limits and edge cases

- Net terms accept only 7, 15, 30, 45, 60 or 90 days in this API version; any other value is ignored and the company keeps its own terms.
- A deposit must be between 1 and 99 percent of the order total.
- Payment terms can’t be set when the cart has subscription items, and aren’t supported in storefront accelerated checkouts.
- Shopify also offers event-based terms (for example due on fulfillment) and fixed due dates through the same operation; this example only sets net terms.

## Questions

### How do I offer Net 30 to wholesale customers on Shopify?

Use a payment customization Function that returns paymentTermsSet with net terms for company buyers, as this example does. It can also give some companies longer terms or ask for a deposit.

### Does this work without Shopify Plus?

No. Shopify’s reference says paymentTermsSet is only available on stores with the Plus plan.

### Can I require a deposit on large B2B orders?

Yes. Set depositPercentage and the buyer pays that share at checkout and the rest when the terms are due. To make it depend on order size, add cart.cost.totalAmount to the input query.

### Can I remove payment terms for some buyers?

Yes. Returning paymentTermsSet with paymentTerms set to null removes them for that checkout, so the buyer pays upfront.

## Shopify documentation

- [Payment Customization Function API](https://shopify.dev/docs/api/functions/latest/payment-customization)

## Related

- [Activate a delivery, payment or validation Function](https://lintelagency.com/resources/graphql/register-function-customization)
- [B2B on Shopify: Options by Plan and Feature](https://lintelagency.com/journal/b2b-on-shopify-options)
- [Which Shopify Functions capabilities need Shopify Plus](https://lintelagency.com/journal/shopify-functions-plus-requirements)
- [Do you need Shopify Plus? The features that actually require it](https://lintelagency.com/journal/do-i-need-shopify-plus)

License: MIT. Free to use in your own store and in client work.
