Billing
Contains function used to bill merchants for your app.
This object is returned on authenticated Admin requests.
Anchor to billingbilling
Provides utilities that apps can use to request billing for the app using the Admin API.
- Anchor to cancelcancelcancel(options: CancelBillingOptions) => Promise<AppSubscription>(options: CancelBillingOptions) => Promise<AppSubscription>requiredrequired
Cancels an ongoing subscription, given its ID.
- Anchor to checkcheckcheck(options: CheckBillingOptions<Config>) => Promise<BillingCheckResponseObject>(options: CheckBillingOptions<Config>) => Promise<BillingCheckResponseObject>requiredrequired
Checks if the shop has an active payment for any plan defined in the
billingconfig option.- Anchor to requestrequestrequest(options: RequestBillingOptions<Config>) => Promise<never>(options: RequestBillingOptions<Config>) => Promise<never>requiredrequired
Requests payment for the plan.
- Anchor to requirerequirerequire(options: RequireBillingOptions<Config>) => Promise<BillingCheckResponseObject>(options: RequireBillingOptions<Config>) => Promise<BillingCheckResponseObject>requiredrequired
Checks if the shop has an active payment for any plan defined in the
billingconfig option.
CancelBillingOptions
- isTest
boolean - prorate
Whether to prorate the cancellation.
boolean - subscriptionId
The ID of the subscription to cancel.
string
CheckBillingOptions
- isTest
Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.
boolean - plans
The plans to check for. Must be one of the values defined in the `billing` config option.
(keyof Config["billing"])[]
RequestBillingOptions
- amount
Amount to charge for this plan.
number - currencyCode
Currency code for this plan.
string - interval
Interval for this plan. Must be set to `OneTime`.
BillingInterval.OneTime - isTest
Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.
boolean - lineItems
The line items for this plan.
({ interval?: BillingInterval.Every30Days | BillingInterval.Annual; discount?: { durationLimitInIntervals?: number; value?: { amount?: number; percentage?: never; } | { amount?: never; percentage?: number; }; }; amount?: number; currencyCode?: string; } | { interval?: BillingInterval.Usage; amount?: number; terms?: string; currencyCode?: string; })[] - plan
The plan to request. Must be one of the values defined in the `billing` config option.
keyof Config["billing"] - replacementBehavior
The replacement behavior to use for this plan.
BillingReplacementBehavior - returnUrl
The URL to return to after the merchant approves the payment.
string - trialDays
How many trial days to give before charging for this plan.
number
RequireBillingOptions
- isTest
Whether to include charges that were created on test mode. Test shops and demo shops cannot be charged.
boolean - onFailure
How to handle the request if the shop doesn't have an active payment for any plan.
(error: any) => Promise<Response> - plans
The plans to check for. Must be one of the values defined in the `billing` config option.
(keyof Config["billing"])[]
Examples
Cancelling a subscription
Description
Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.
/app/routes/cancel-subscription.ts
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 };shopify.server.ts
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;Check what billing plans a merchant is subscribed to
Description
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.
/app/routes/**\/*.ts
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) };shopify.server.ts
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;Using a custom return URL
Description
Change where the merchant is returned to after approving the purchase using the `returnUrl` option.
/app/routes/**\/*.ts
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 };shopify.server.ts
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;Requesting billing right away
Description
Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.
/app/routes/**\/*.ts
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 };shopify.server.ts
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;Redirect to a plan selection page
Description
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.
/app/routes/**\/*.ts
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 };shopify.server.ts
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;Requesting billing with line items
Description
Call `billing.request` with the `v3_lineItemBilling` future flag enabled
/app/routes/**\/*.ts
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 };shopify.server.ts
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;