# Billing Contains function used to bill merchants for your app. This object is returned on authenticated Admin requests. ## billing Provides utilities that apps can use to request billing for the app using the Admin API. ### BillingContext ### require value: `(options: RequireBillingOptions) => Promise` - RequireBillingOptions: export interface RequireBillingOptions extends Omit { /** * The plans to check for. Must be one of the values defined in the `billing` config option. */ plans: (keyof Config['billing'])[]; /** * How to handle the request if the shop doesn't have an active payment for any plan. */ onFailure: (error: any) => Promise; } - BillingCheckResponseObject: export interface BillingCheckResponseObject { /** * Whether the user has an active payment method. */ hasActivePayment: boolean; /** * The one-time purchases the shop has. */ oneTimePurchases: OneTimePurchase[]; /** * The active subscriptions the shop has. */ appSubscriptions: AppSubscription[]; } Checks if the shop has an active payment for any plan defined in the `billing` config option. ### check value: `(options: CheckBillingOptions) => Promise` - BillingCheckResponseObject: export interface BillingCheckResponseObject { /** * Whether the user has an active payment method. */ hasActivePayment: boolean; /** * The one-time purchases the shop has. */ oneTimePurchases: OneTimePurchase[]; /** * The active subscriptions the shop has. */ appSubscriptions: AppSubscription[]; } - CheckBillingOptions: export interface CheckBillingOptions extends Omit { /** * The plans to check for. Must be one of the values defined in the `billing` config option. */ plans: (keyof Config['billing'])[]; } Checks if the shop has an active payment for any plan defined in the `billing` config option. ### request value: `(options: RequestBillingOptions) => Promise` - RequestBillingOptions: export interface RequestBillingOptions extends Omit { /** * The plan to request. Must be one of the values defined in the `billing` config option. */ plan: keyof Config['billing']; /** * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged. */ isTest?: boolean; /** * The URL to return to after the merchant approves the payment. */ returnUrl?: string; } Requests payment for the plan. ### cancel value: `(options: CancelBillingOptions) => Promise` - AppSubscription: export interface AppSubscription { /** * The ID of the app subscription. */ id: string; /** * The name of the purchased plan. */ name: string; /** * Whether this is a test subscription. */ test: boolean; /* * The line items for this plan. This will become mandatory in v10. */ lineItems?: ActiveSubscriptionLineItem[]; } - CancelBillingOptions: export interface CancelBillingOptions { /** * The ID of the subscription to cancel. */ subscriptionId: string; /** * Whether to prorate the cancellation. * * {@link https://shopify.dev/docs/apps/billing/subscriptions/cancel-recurring-charges} */ prorate?: boolean; /* * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged. */ isTest?: boolean; } Cancels an ongoing subscription, given its ID. ### RequireBillingOptions ### plans value: `(keyof Config["billing"])[]` The plans to check for. Must be one of the values defined in the `billing` config option. ### onFailure value: `(error: any) => Promise` How to handle the request if the shop doesn't have an active payment for any plan. ### isTest value: `boolean` Whether to consider test purchases. ### BillingCheckResponseObject ### hasActivePayment value: `boolean` Whether the user has an active payment method. ### oneTimePurchases value: `OneTimePurchase[]` - OneTimePurchase: export interface OneTimePurchase { /** * The ID of the one-time purchase. */ id: string; /** * The name of the purchased plan. */ name: string; /** * Whether this is a test purchase. */ test: boolean; /** * The status of the one-time purchase. */ status: string; } The one-time purchases the shop has. ### appSubscriptions value: `AppSubscription[]` - AppSubscription: export interface AppSubscription { /** * The ID of the app subscription. */ id: string; /** * The name of the purchased plan. */ name: string; /** * Whether this is a test subscription. */ test: boolean; /* * The line items for this plan. This will become mandatory in v10. */ lineItems?: ActiveSubscriptionLineItem[]; } The active subscriptions the shop has. ### OneTimePurchase ### id value: `string` The ID of the one-time purchase. ### name value: `string` The name of the purchased plan. ### test value: `boolean` Whether this is a test purchase. ### status value: `string` The status of the one-time purchase. ### AppSubscription ### id value: `string` The ID of the app subscription. ### name value: `string` The name of the purchased plan. ### test value: `boolean` Whether this is a test subscription. ### lineItems value: `ActiveSubscriptionLineItem[]` - ActiveSubscriptionLineItem: export interface ActiveSubscriptionLineItem { /* * The ID of the line item. */ id: string; /* * The details of the plan. */ plan: AppPlan; } ### ActiveSubscriptionLineItem ### id value: `string` ### plan value: `AppPlan` - AppPlan: export interface AppPlan { /* * The pricing details of the plan. */ pricingDetails: RecurringAppPlan | UsageAppPlan; } ### AppPlan ### pricingDetails value: `RecurringAppPlan | UsageAppPlan` - AppPlan: export interface AppPlan { /* * The pricing details of the plan. */ pricingDetails: RecurringAppPlan | UsageAppPlan; } - RecurringAppPlan: export interface RecurringAppPlan { /* * The interval for this plan is charged on. */ interval: BillingInterval.Every30Days | BillingInterval.Annual; /* * The price of the plan. */ price: Money; /* * The discount applied to the plan. */ discount: AppPlanDiscount; } - UsageAppPlan: export interface UsageAppPlan { /* * The total usage records for interval. */ balanceUsed: Money; /* * The capped amount prevents the merchant from being charged for any usage over that amount during a billing period. */ cappedAmount: Money; /* * The terms and conditions for app usage pricing. */ terms: string; } ### RecurringAppPlan ### interval value: `BillingInterval.Every30Days | BillingInterval.Annual` - BillingInterval: export enum BillingInterval { OneTime = 'ONE_TIME', Every30Days = 'EVERY_30_DAYS', Annual = 'ANNUAL', Usage = 'USAGE', } ### price value: `Money` - Money: interface Money { amount: number; currencyCode: string; } ### discount value: `AppPlanDiscount` - AppPlan: export interface AppPlan { /* * The pricing details of the plan. */ pricingDetails: RecurringAppPlan | UsageAppPlan; } - AppPlanDiscount: export interface AppPlanDiscount { /* * The total number of intervals the discount applies to. */ durationLimitInIntervals: number; /* * The remaining number of intervals the discount applies to. */ remainingDurationInIntervals: number; /* * The price after the discount is applied. */ priceAfterDiscount: Money; /* * The value of the discount applied every billing interval. */ value: AppPlanDiscountAmount; } ### BillingInterval ### OneTime value: `ONE_TIME` ### Every30Days value: `EVERY_30_DAYS` ### Annual value: `ANNUAL` ### Usage value: `USAGE` ### Money ### amount value: `number` ### currencyCode value: `string` ### AppPlanDiscount ### durationLimitInIntervals value: `number` ### remainingDurationInIntervals value: `number` ### priceAfterDiscount value: `Money` - Money: interface Money { amount: number; currencyCode: string; } ### value value: `AppPlanDiscountAmount` - AppPlan: export interface AppPlan { /* * The pricing details of the plan. */ pricingDetails: RecurringAppPlan | UsageAppPlan; } - AppPlanDiscount: export interface AppPlanDiscount { /* * The total number of intervals the discount applies to. */ durationLimitInIntervals: number; /* * The remaining number of intervals the discount applies to. */ remainingDurationInIntervals: number; /* * The price after the discount is applied. */ priceAfterDiscount: Money; /* * The value of the discount applied every billing interval. */ value: AppPlanDiscountAmount; } - AppPlanDiscountAmount: BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage ### BillingConfigSubscriptionPlanDiscountAmount ### amount value: `number` The amount to discount. Cannot be set if `percentage` is set. ### percentage value: `never` The percentage to discount. Cannot be set if `amount` is set. ### BillingConfigSubscriptionPlanDiscountPercentage ### amount value: `never` The amount to discount. Cannot be set if `percentage` is set. ### percentage value: `number` The percentage to discount. Cannot be set if `amount` is set. ### UsageAppPlan ### balanceUsed value: `Money` - Money: interface Money { amount: number; currencyCode: string; } ### cappedAmount value: `Money` - Money: interface Money { amount: number; currencyCode: string; } ### terms value: `string` ### CheckBillingOptions ### plans value: `(keyof Config["billing"])[]` The plans to check for. Must be one of the values defined in the `billing` config option. ### isTest value: `boolean` Whether to consider test purchases. ### RequestBillingOptions ### plan value: `keyof Config["billing"]` The plan to request. Must be one of the values defined in the `billing` config option. ### isTest value: `boolean` Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged. ### returnUrl value: `string` The URL to return to after the merchant approves the payment. ### CancelBillingOptions ### subscriptionId value: `string` The ID of the subscription to cancel. ### prorate value: `boolean` Whether to prorate the cancellation. ### isTest value: `boolean` ## Related - [Admin context](https://shopify.dev/docs/api/shopify-app-remix/authenticate/admin) ## Examples Contains function used to bill merchants for your app. This object is returned on authenticated Admin requests. ### require Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.```typescript import { LoaderFunctionArgs } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); await billing.require({ plans: [MONTHLY_PLAN], isTest: true, onFailure: async () => billing.request({ plan: MONTHLY_PLAN }), }); // App logic }; ``` ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.```typescript import { LoaderFunctionArgs, redirect } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); const billingCheck = await billing.require({ plans: [MONTHLY_PLAN, ANNUAL_PLAN], isTest: true, onFailure: () => redirect('/select-plan'), }); const subscription = billingCheck.appSubscriptions[0]; console.log(`Shop is on ${subscription.name} (id ${subscription.id})`); // App logic }; ``` ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` Call `billing.request` with the `v3_lineItemBilling` future flag enabled```typescript import { LoaderFunctionArgs } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); await billing.require({ plans: [MONTHLY_PLAN], isTest: true, onFailure: async () => billing.request({ plan: MONTHLY_PLAN }), }); // App logic }; ``` ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { lineItems: [ { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, { amount: 1, currencyCode: 'USD', interval: BillingInterval.Usage. terms: '1 dollar per 1000 emails', }, ], }, } future: {v3_lineItemBilling: true} }); export default shopify; export const authenticate = shopify.authenticate; ``` ### check Use billing.check if you want to determine which plans are in use. Unlike `require`, `check` does notthrow an error if no active billing plans are present.```typescript import { LoaderFunctionArgs } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); const { hasActivePayment, appSubscriptions } = await billing.check({ plans: [MONTHLY_PLAN], isTest: false, }); console.log(hasActivePayment) console.log(appSubscriptions) }; ``` ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` ### request Change where the merchant is returned to after approving the purchase using the `returnUrl` option.```typescript import { LoaderFunctionArgs } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); await billing.require({ plans: [MONTHLY_PLAN], onFailure: async () => billing.request({ plan: MONTHLY_PLAN, isTest: true, returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page', }), }); // App logic }; ``` ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` ### cancel Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.```typescript import { LoaderFunctionArgs } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); const billingCheck = await billing.require({ plans: [MONTHLY_PLAN], onFailure: async () => billing.request({ plan: MONTHLY_PLAN }), }); const subscription = billingCheck.appSubscriptions[0]; const cancelledSubscription = await billing.cancel({ subscriptionId: subscription.id, isTest: true, prorate: true, }); // App logic }; ``` ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` ## Examples Contains function used to bill merchants for your app. This object is returned on authenticated Admin requests. ### require Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.```typescript import { LoaderFunctionArgs } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); await billing.require({ plans: [MONTHLY_PLAN], isTest: true, onFailure: async () => billing.request({ plan: MONTHLY_PLAN }), }); // App logic }; ``` ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.```typescript import { LoaderFunctionArgs, redirect } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); const billingCheck = await billing.require({ plans: [MONTHLY_PLAN, ANNUAL_PLAN], isTest: true, onFailure: () => redirect('/select-plan'), }); const subscription = billingCheck.appSubscriptions[0]; console.log(`Shop is on ${subscription.name} (id ${subscription.id})`); // App logic }; ``` ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` Call `billing.request` with the `v3_lineItemBilling` future flag enabled```typescript import { LoaderFunctionArgs } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); await billing.require({ plans: [MONTHLY_PLAN], isTest: true, onFailure: async () => billing.request({ plan: MONTHLY_PLAN }), }); // App logic }; ``` ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { lineItems: [ { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, { amount: 1, currencyCode: 'USD', interval: BillingInterval.Usage. terms: '1 dollar per 1000 emails', }, ], }, } future: {v3_lineItemBilling: true} }); export default shopify; export const authenticate = shopify.authenticate; ``` ### check Use billing.check if you want to determine which plans are in use. Unlike `require`, `check` does notthrow an error if no active billing plans are present.```typescript import { LoaderFunctionArgs } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); const { hasActivePayment, appSubscriptions } = await billing.check({ plans: [MONTHLY_PLAN], isTest: false, }); console.log(hasActivePayment) console.log(appSubscriptions) }; ``` ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` ### request Change where the merchant is returned to after approving the purchase using the `returnUrl` option.```typescript import { LoaderFunctionArgs } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); await billing.require({ plans: [MONTHLY_PLAN], onFailure: async () => billing.request({ plan: MONTHLY_PLAN, isTest: true, returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page', }), }); // App logic }; ``` ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ``` ### cancel Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.```typescript import { LoaderFunctionArgs } from "@remix-run/node"; import { authenticate, MONTHLY_PLAN } from "../shopify.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { const { billing } = await authenticate.admin(request); const billingCheck = await billing.require({ plans: [MONTHLY_PLAN], onFailure: async () => billing.request({ plan: MONTHLY_PLAN }), }); const subscription = billingCheck.appSubscriptions[0]; const cancelledSubscription = await billing.cancel({ subscriptionId: subscription.id, isTest: true, prorate: true, }); // App logic }; ``` ```typescript import { shopifyApp, BillingInterval } from "@shopify/shopify-app-remix/server"; export const MONTHLY_PLAN = 'Monthly subscription'; export const ANNUAL_PLAN = 'Annual subscription'; const shopify = shopifyApp({ // ...etc billing: { [MONTHLY_PLAN]: { amount: 5, currencyCode: 'USD', interval: BillingInterval.Every30Days, }, [ANNUAL_PLAN]: { amount: 50, currencyCode: 'USD', interval: BillingInterval.Annual, }, } }); export default shopify; export const authenticate = shopify.authenticate; ```