--- title: Set payment terms description: Set payment terms on a checkout based on the buyer identity and cart total. source_url: html: 'https://shopify.dev/docs/apps/build/checkout/payments/payment-terms' md: 'https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md' --- ExpandOn this page * [Understanding payment terms](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#understanding-payment-terms) * [What you'll learn](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#what-youll-learn) * [Requirements](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#requirements) * [Step 1: Configure the function](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#step-1-configure-the-function) * [Step 2: Populate the payment customization configuration metafield](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#step-2-populate-the-payment-customization-configuration-metafield) * [Step 3: Test the payment customization](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#step-3-test-the-payment-customization) * [Considerations for direct-to-consumer checkouts with payment terms](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#considerations-for-direct-to-consumer-checkouts-with-payment-terms) * [Next steps](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#next-steps) # Set payment terms Payment terms enable buyers to pay for their orders at a later date instead of immediately at checkout. This tutorial shows you how to use the `paymentTermsSet` operation in the [Payment Customization API](https://shopify.dev/docs/api/functions/reference/payment-customization) to dynamically set [payment terms](https://help.shopify.com/manual/fulfillment/managing-orders/payments/deferred-payments) based on business rules. *** ## Understanding payment terms Payment terms allow merchants to offer flexible payment options to their customers: * **Fixed payment terms**: Set a specific due date for payment (for example, payment due by January 31st). * **Net payment terms**: Set the payment to be due a certain number of days after the order is placed (for example, "net 30" means that payment is due 30 days after the order is placed). * **Event payment terms**: Set the payment as due when a specific event occurs: * `FULFILLMENT_CREATED`: Due when individual items are fulfilled. * `ORDER_FULFILLED`: Due when the entire order is fulfilled. * `INVOICE_SENT`: Due when the invoice is sent. * **Deposits**: Require a percentage upfront while deferring the remainder (for example, 25% deposit due now, remaining 75% within 30 days). *** ## What you'll learn In this tutorial, you'll learn how to do the following tasks: * Set different payment terms on a checkout based on the buyer identity and the total value of the cart. * Set net, fixed or event payment terms with an optional deposit for a buyer. ![Screenshot that shows a b2b checkout with fixed payment terms](https://shopify.dev/assets/assets/images/apps/checkout/b2b-checkout-with-fixed-payment-terms-and-deposit-CN46g4nJ.png) *** ## Requirements * You've completed the previous tutorials in this [series](https://shopify.dev/docs/apps/build/checkout/payments#tutorial-series). *** ## Step 1: Configure the function In this example, payment terms are set based on the cart total and whether it's a B2B order. You'll implement the following behaviour: * Payment terms won't be modified for any order where the cart total is less than $500. * If the cart total is $500 or more: * B2B orders will have fixed terms with a 50% deposit. * Direct-to-consumer (D2C) orders won't have their payment terms modified. 1. Navigate to your function in `extensions/payment-customization`: ```terminal cd extensions/payment-customization ``` 2. Replace the code in the `src/run.graphql` file with the following code. The input query performs the following actions: * Retrieves a metafield from the [`paymentCustomization`](https://shopify.dev/docs/api/functions/reference/payment-customization#Input.fields.paymentCustomization) object, which is the function owner. This metafield is used to store the configuration for the function. * Retrieves the [`cart`](https://shopify.dev/docs/api/functions/reference/payment-customization#Input.fields.cart) object, which includes a `cost` object with the total value of the cart, and `buyerIdentity` that can be used to determine if the buyer is B2B. The query differs slightly in Rust and JavaScript due to code generation requirements. ## run.graphql ## src/run.graphql ```graphql query Input { paymentCustomization { metafield(namespace: "$app", key: "payment-customization-function-configuration") { jsonValue } } cart { cost { totalAmount { amount } } buyerIdentity{ purchasingCompany{ company { id } } } } } ``` ```graphql query RunInput { paymentCustomization { metafield(namespace: "$app", key: "payment-customization-function-configuration") { jsonValue } } cart { cost { totalAmount { amount } } buyerIdentity{ purchasingCompany{ company { id } } } } } ``` ##### Rust input query ``` query Input { paymentCustomization { metafield(namespace: "$app", key: "payment-customization-function-configuration") { jsonValue } } cart { cost { totalAmount { amount } } buyerIdentity{ purchasingCompany{ company { id } } } } } ``` ##### JavaScript input query ``` query RunInput { paymentCustomization { metafield(namespace: "$app", key: "payment-customization-function-configuration") { jsonValue } } cart { cost { totalAmount { amount } } buyerIdentity{ purchasingCompany{ company { id } } } } } ``` 3. If you're using JavaScript, then run the following command to regenerate types based on your input query: ## Terminal ```terminal shopify app function typegen ``` 4. Replace the contents of`src/run.rs` or `src/run.js` file with the following code: ## File ## src/run.rs ```rust use crate::schema; use shopify_function::prelude::*; use shopify_function::Result; #[derive(Deserialize, Default, PartialEq)] #[shopify_function(rename_all = "camelCase")] pub struct Configuration { min_for_payment_terms: String, due_at_for_fixed_terms: String, } #[shopify_function] fn run(input: schema::run::Input) -> Result { let no_changes = schema::FunctionRunResult { operations: vec![] }; let configuration: &Configuration= match input.payment_customization().metafield() { Some(metafield) => metafield.json_value(), None => return Ok(no_changes), }; if configuration.min_for_payment_terms.is_empty() || configuration.due_at_for_fixed_terms.is_empty() { return Ok(no_changes); } let min_for_payment_terms = match configuration.min_for_payment_terms.parse::() { Ok(val) => val, Err(_) => 0.0, }; let cart_total = input.cart().cost().total_amount().amount().to_string().parse::().unwrap_or(0.0); if cart_total < min_for_payment_terms { return Ok(no_changes); } let mut is_b2b = false; if let Some(buyer_identity) = &input.cart().buyer_identity() { if let Some(_) = &buyer_identity.purchasing_company() { // Check if purchasing company exists is_b2b = true; } } if !is_b2b { return Ok(no_changes); } let operations = vec![schema::Operation::PaymentTermsSet( schema::PaymentTermsSetOperation { payment_terms: Some(schema::PaymentTerms::Fixed( schema::FixedPaymentTerms { deposit: Some(schema::Deposit { percentage: 50.0, }), due_at: configuration.due_at_for_fixed_terms.clone(), } )), } )]; Ok(schema::FunctionRunResult { operations }) } ``` ```javascript // @ts-check /** * @typedef {import("../generated/api").RunInput} RunInput * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult */ /** * @type {FunctionRunResult} */ const NO_CHANGES = { operations: [], }; /** * Configuration schema for payment terms function * @typedef {Object} Configuration * @property {number} minForPaymentTerms - Minimum cart total required to apply payment terms * @property {Date} dueAtForFixedTerms - Due date for fixed payment terms */ /** * Parses configuration from metafield or returns null if invalid * @param {RunInput} input - Function input * @returns {Configuration|null} Parsed configuration or null if invalid */ function getConfiguration(input) { const config = input?.paymentCustomization?.metafield?.jsonValue; if (!config) { console.log("No configuration metafield found"); return null; } if (!config.minForPaymentTerms || !config.dueAtForFixedTerms) { console.log("Missing required configuration values"); return null; } return { minForPaymentTerms: parseFloat(config.minForPaymentTerms), dueAtForFixedTerms: new Date(config.dueAtForFixedTerms), }; } /** * Checks if the buyer is a B2B customer * @param {RunInput} input - Function input * @returns {boolean} True if the buyer is a B2B customer */ function isB2BCustomer(input) { return Boolean(input.cart.buyerIdentity?.purchasingCompany); } /** * Sets payment terms based on cart total and buyer identity * @param {RunInput} input - Function input * @returns {FunctionRunResult} Function result with operations to apply */ export function run(input) { const configuration = getConfiguration(input); if (!configuration) { return NO_CHANGES; } const cartTotal = parseFloat(input.cart.cost.totalAmount.amount); if (cartTotal < configuration.minForPaymentTerms) { console.error(`Cart total ${cartTotal} is below minimum ${configuration.minForPaymentTerms}`); return NO_CHANGES; } if (!isB2BCustomer(input)) { console.log('Not a B2B customer, no changes to payment terms'); return NO_CHANGES; } const dueAtForFixedTerms = configuration.dueAtForFixedTerms; console.log(`Setting fixed terms for B2B customer, due at: ${dueAtForFixedTerms}`); return { operations: [ { paymentTermsSet: { paymentTerms: { fixed: { deposit: { percentage: 50 }, dueAt: dueAtForFixedTerms } } } } ] }; } ``` ##### Rust ``` use crate::schema; use shopify_function::prelude::*; use shopify_function::Result; #[derive(Deserialize, Default, PartialEq)] #[shopify_function(rename_all = "camelCase")] pub struct Configuration { min_for_payment_terms: String, due_at_for_fixed_terms: String, } #[shopify_function] fn run(input: schema::run::Input) -> Result { let no_changes = schema::FunctionRunResult { operations: vec![] }; let configuration: &Configuration= match input.payment_customization().metafield() { Some(metafield) => metafield.json_value(), None => return Ok(no_changes), }; if configuration.min_for_payment_terms.is_empty() || configuration.due_at_for_fixed_terms.is_empty() { return Ok(no_changes); } let min_for_payment_terms = match configuration.min_for_payment_terms.parse::() { Ok(val) => val, Err(_) => 0.0, }; let cart_total = input.cart().cost().total_amount().amount().to_string().parse::().unwrap_or(0.0); if cart_total < min_for_payment_terms { return Ok(no_changes); } let mut is_b2b = false; if let Some(buyer_identity) = &input.cart().buyer_identity() { if let Some(_) = &buyer_identity.purchasing_company() { // Check if purchasing company exists is_b2b = true; } } if !is_b2b { return Ok(no_changes); } let operations = vec![schema::Operation::PaymentTermsSet( schema::PaymentTermsSetOperation { payment_terms: Some(schema::PaymentTerms::Fixed( schema::FixedPaymentTerms { deposit: Some(schema::Deposit { percentage: 50.0, }), due_at: configuration.due_at_for_fixed_terms.clone(), } )), } )]; Ok(schema::FunctionRunResult { operations }) } ``` ##### JavaScript ``` // @ts-check /** * @typedef {import("../generated/api").RunInput} RunInput * @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult */ /** * @type {FunctionRunResult} */ const NO_CHANGES = { operations: [], }; /** * Configuration schema for payment terms function * @typedef {Object} Configuration * @property {number} minForPaymentTerms - Minimum cart total required to apply payment terms * @property {Date} dueAtForFixedTerms - Due date for fixed payment terms */ /** * Parses configuration from metafield or returns null if invalid * @param {RunInput} input - Function input * @returns {Configuration|null} Parsed configuration or null if invalid */ function getConfiguration(input) { const config = input?.paymentCustomization?.metafield?.jsonValue; if (!config) { console.log("No configuration metafield found"); return null; } if (!config.minForPaymentTerms || !config.dueAtForFixedTerms) { console.log("Missing required configuration values"); return null; } return { minForPaymentTerms: parseFloat(config.minForPaymentTerms), dueAtForFixedTerms: new Date(config.dueAtForFixedTerms), }; } /** * Checks if the buyer is a B2B customer * @param {RunInput} input - Function input * @returns {boolean} True if the buyer is a B2B customer */ function isB2BCustomer(input) { return Boolean(input.cart.buyerIdentity?.purchasingCompany); } /** * Sets payment terms based on cart total and buyer identity * @param {RunInput} input - Function input * @returns {FunctionRunResult} Function result with operations to apply */ export function run(input) { const configuration = getConfiguration(input); if (!configuration) { return NO_CHANGES; } const cartTotal = parseFloat(input.cart.cost.totalAmount.amount); if (cartTotal < configuration.minForPaymentTerms) { console.error(`Cart total ${cartTotal} is below minimum ${configuration.minForPaymentTerms}`); return NO_CHANGES; } if (!isB2BCustomer(input)) { console.log('Not a B2B customer, no changes to payment terms'); return NO_CHANGES; } const dueAtForFixedTerms = configuration.dueAtForFixedTerms; console.log(`Setting fixed terms for B2B customer, due at: ${dueAtForFixedTerms}`); return { operations: [ { paymentTermsSet: { paymentTerms: { fixed: { deposit: { percentage: 50 }, dueAt: dueAtForFixedTerms } } } } ] }; } ``` *** ## Step 2: Populate the payment customization configuration metafield To populate the configuration metafield, first use the [`paymentCustomizations`](https://shopify.dev/docs/api/admin-graphql/current/queries/paymentCustomizations) query to confirm the payment customization ID, and then use the [`metafieldsSet`](https://shopify.dev/docs/api/admin-graphql/current/mutations/metafieldsSet) mutation to populate the same metafield that you specified in the input query. 1. Open the local GraphiQL interface bundled in the Shopify CLI. Run `shopify app dev` to start your development server, then press `g` to open GraphiQL. 2. In the GraphiQL interface, in the **API Version** field, select the **2025-07** version or later. 3. Execute the following query, and make note of the `id` value of the payment customization that you created in the [previous tutorial](https://shopify.dev/docs/apps/build/checkout/payments/create-payments-function). For more information about global IDs, refer to [Global IDs in Shopify APIs](https://shopify.dev/docs/api/usage/gids). ```graphql query { paymentCustomizations(first: 100) { edges { node { id title } } } } ``` 4. Execute the following mutation, replacing `YOUR_CUSTOMIZATION_ID_HERE` with the full global ID of your payment customization. The value of the metafield specifies that the minimum cart total to set payment terms is $500, and the due date for fixed terms is November 15, 2025 EST. ```graphql mutation { metafieldsSet(metafields: [ { ownerId: "YOUR_CUSTOMIZATION_ID_HERE" namespace: "$app" key: "payment-customization-function-configuration" value: "{ \"minForPaymentTerms\":500,\"dueAtForFixedTerms\":\"Nov 15 2025 00:00:00 GMT-0500\"}" type: "json" } ]) { metafields { id } userErrors { message } } } ``` You should receive a GraphQL response that includes the ID of the created metafield. If the response includes any messages under `userErrors`, then review the errors, check that your mutation and `ownerId` are correct, and try the request again. *** ## Step 3: Test the payment customization Test how payment terms are set based on the buyer identity and cart total. ### Test with a B2B checkout with a cart total under $500 Open your development store and log in as a B2B customer. Build a cart with a total (including shipping and tax) under $500. The function doesn't set any payment terms. The checkout uses the existing payment terms of the company location. In this case, the company location has net 45 terms. ![Screenshot that shows a B2B checkout with net payment terms](https://shopify.dev/assets/assets/images/apps/checkout/b2b-checkout-with-net-payment-terms-Bu-Cs4zZ.png) ### Test with a B2B checkout with a cart total over $500 Open your development store and log in as a B2B customer. Build a cart with a total (including shipping and tax) over $500. The function sets fixed payment terms with a 50% deposit. The checkout uses the fixed payment terms. ![Screenshot that shows a B2B checkout with fixed payment terms with a deposit](https://shopify.dev/assets/assets/images/apps/checkout/b2b-checkout-with-fixed-payment-terms-and-deposit-CN46g4nJ.png) ### Test with a direct-to-consumer checkout Open your development store and log in as a direct-to-consumer customer. Build a cart with a total (including shipping and tax) over $500. The function doesn't set any payment terms, so it doesn't modify the checkout. ![Screenshot that shows a direct-to-consumer checkout with no payment terms](https://shopify.dev/assets/assets/images/apps/checkout/d2c-checkout-with-no-payment-terms-B7-PZNIc.png) *** ## Considerations for direct-to-consumer checkouts with payment terms If you choose to implement payment terms for direct-to-consumer buyers, there are several important considerations: * Direct-to-consumer checkouts with payment terms don't support any pay now options. Buyers will only see the option to defer their payment. Additional deferred payment options (vaulted credit cards) are available for B2B. ![Screenshot that shows a direct-to-consumer checkout with net payment terms](https://shopify.dev/assets/assets/images/apps/checkout/d2c-checkout-with-net-payment-terms-VR5PR3xA.png) * If you set fulfillment event payment terms on a checkout and the **Automatically when fulfilling** payment capture setting is enabled, the payment terms are captured according to the payment term set by the function: * `FULFILLMENT_CREATED`: Payment is captured per fulfillment. * `ORDER_FULFILLED`: Payment is captured when the entire order is fulfilled. ![Screenshot that shows a direct-to-consumer checkout with due on fulfillment event payment terms](https://shopify.dev/assets/assets/images/apps/checkout/d2c-checkout-with-due-on-fulfillment-event-payment-terms-6D8tkTIS.png) * Checkouts with [deferred purchase options](https://shopify.dev/docs/apps/build/purchase-options/deferred) (such as pre-orders and try before you buy) follow specific rules for determining due dates and deposits when a payment customization function is used. For more information, see [Orders with multiple due dates and deposits](https://shopify.dev/docs/apps/build/purchase-options/deferred#orders-with-multiple-due-dates-and-deposits). *** ## Next steps * Build a [payment customization user interface](https://shopify.dev/docs/apps/build/checkout/payments/build-ui) with App Bridge. * Learn how to use [variables](https://shopify.dev/docs/apps/build/functions/input-queries/use-variables-input-queries) in your input query. * Explore more [payment customization options](https://shopify.dev/docs/api/functions/reference/payment-customization) in the API reference. *** * [Understanding payment terms](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#understanding-payment-terms) * [What you'll learn](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#what-youll-learn) * [Requirements](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#requirements) * [Step 1: Configure the function](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#step-1-configure-the-function) * [Step 2: Populate the payment customization configuration metafield](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#step-2-populate-the-payment-customization-configuration-metafield) * [Step 3: Test the payment customization](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#step-3-test-the-payment-customization) * [Considerations for direct-to-consumer checkouts with payment terms](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#considerations-for-direct-to-consumer-checkouts-with-payment-terms) * [Next steps](https://shopify.dev/docs/apps/build/checkout/payments/payment-terms.md#next-steps)