Create and manage subscription contracts

A subscription contract is the agreement between a customer and a merchant over a specific term for recurring purchases over a set or undefined period of time.

This tutorial shows you how to create and manage subscription contracts by illustrating two use cases: "Subscribe and save" subscriptions and "Prepaid" subscriptions.

Requirements

Scope requirements

To use the GraphQL mutations, your app needs to request the following access scopes for a Shopify store:

  • read_own_subscription_contracts: Allows an app to read subscription contract mutations for contracts they own.
  • write_own_subscription_contracts: Allows an app to write subscription contract mutations for contracts they own.

Subscription contracts

A subscription contract is the result of purchasing a selling plan that has recurring policies. After a customer purchases a subscription product or variant at checkout, Shopify generates a subscription contract and shares the contract with your app using a webhook.

Subscription contracts generate new orders for merchants to fulfill at subscription renewal time. They're also the mechanism by which apps will bill customers for their subscriptions.

Subscription contracts objects diagram

API object Description
Subscription contract Represents the agreement for a set of items delivered to a customer, on a specific billing and delivery schedule and at a specific price.

A subscription contract is different from an order. An order is a customer's completed request to purchase one or more products from a shop. An order is created when a customer completes the checkout process, during which time they provide an email address or phone number, billing address, and payment information.

Subscription contracts might not be immediately available when an order is created. It's best to rely on subscription contract webhooks to be notified when contracts are created.
Contract line Represents the price and quantity of items in a subscription contract.
Pricing policy Represents the future intent to change the price after a given number of billing cycles. When the customer completes a checkout, Shopify creates a contract, and the app developer is notified of which policies the customer chose.
Subscription order line Links a subscription contract to its related order line items, and optionally, to the billing attempt that created this order line.

A subscription order line is created when one of the following events occurs:

  • A billing attempt creates an order.
  • A subscription is created from a selling plan purchase.
Subscription draft Represents the intent to change a subscription. If a customer wants to change their subscription, then the app needs to create a subscription draft and commit the change to make the changes active.
Draft change Represents a change made to a subscription draft, such as changing a contract line.
Billing attempt Represents an attempt at executing a billing cycle and charging the customer payment method for a subscription contract.

Query the available contracts on a store

To view the available contracts on a store, you can query SubscriptionContract. In the following example, the response body returns all of the available contracts, including the status of the contract and information about the customer and billing policy.

A subscription contract can have one of the following statuses:

  • ACTIVE: The contract is active and continuing as per its policies.
  • PAUSED: The contract is temporarily paused and is expected to resume in the future.
  • EXPIRED: The contract has ended per the expected circumstances. All billing and delivery cycles of the subscriptions were executed.
  • CANCELED: The contract ended by an unplanned customer action.
  • FAILED: The contract ended because billing failed and no further billing attempts are expected.

Example request:

POST /admin/api/2021-01/graphql.json

View response

JSON response:

Create a new subscription draft

A subscription draft captures the intent to change a subscription contract. Apps can incrementally build subscription contracts.

A subscription draft provides a way to get the projected state of the contract with all of the updates applied. Subscription contracts should always be up-to-date and accurate so that you can report on subscriptions, email subscribers, and build flows based on subscription changes.

Update a subscription draft

You can call the subscriptionDraftUpdate mutation to make changes to a subscription draft. In the following example, the delivery policy is changed to every two months.

Example request

POST /admin/api/2021-01/graphql.json

View response

JSON response:

Add a line to the subscription draft

You can call the subscriptionDraftLineAdd mutation to add a subscription line to the subscription draft. In the following example, a subscription line is added to specify a product variant with its quantity and price.

Example request:

POST /admin/api/2021-01/graphql.json

View response

JSON response:

Update a line on a subscription contract

You can call the subscriptionDraftLineUpdate mutation to update a line on the subscription draft. In this example, quantity is updated to 10.

Example request:

POST /admin/api/2021-01/graphql.json

View response

JSON response:

Remove a line on a subscription draft

You can call the subscriptionDraftLineRemove mutation and supply the lineId to remove a subscription line on the subscription draft.

Example request:

POST /admin/api/2021-01/graphql.json

View response

JSON response:

Commit a subscription draft

When you are satisfied with the state of the draft subscription, you can commit it. When you commit a draft subscription, all of the changes are made active.

Example request

POST /admin/api/2021-01/graphql.json

View response

JSON response:

Viewing subscription contract details

The View subscription button on the customer subscriptions card and the order subscriptions card allows merchants to navigate to the app and view the subscription contract details.

Customers page in the Shopify admin

Customers page screenshot

Order page in the Shopify admin

Order page screenshot

Redirecting to the subscription contract within the app

To redirect merchants to the relevant subscription contract, the app needs to implement a specific endpoint. After implemented, the endpoint redirects to the subscription contract page within the app for the subscription defined by subscription_contract_id.

You can customize the View subscription link by managing the Subscription link app extension in your Partner Dashboard:

Subscription link app extension in Partner Dashboard

You can modify the link target URL, save the changes, or remove the app extension if it's already present:

Subscription link app extension form

If you don't customize the View subscription link, then the link is hardcoded. The hardcoded link has the following format:

{app_application_url}/subscriptions?customer_id={customer_id}&hmac={hmac}&id={subscription_contract_id}&shop={myshopify_domain}

Update a subscription contract

When a customer updates their payment method or makes a change to their subscription, you need to update the appropriate subscription contract.

To update a subscription contract, use the subscriptionContractUpdate mutation and supply the subscription contract ID. The call returns a draft with the existing state of the contract.

Example request:

POST /admin/api/2021-01/graphql.json

View response

JSON response:

Create a billing attempt

To schedule and automate the billing of subscriptions, apps need to create a billing attempt. A subscription is renewed when an app makes a billing attempt.

A billing attempt represents an attempt at executing a billing cycle and charging the customer payment method for a subscription contract. A billing attempt executes a contract in its current state and ensures that the app’s contract data is synced with Shopify before billing.

Statuses

A billing attempt starts in a pending status. After it has been processed, it either transitions to successful or failed, both of which are terminal states:

  • If the billing attempt is successful, then an order is created.
  • If the billing attempt fails, then it means that the transaction has failed.

If an action is pending on the part of the customer in regards to 3D Secure, then a 3D Secure challenge can occur before the billing attempt transitions to a terminal state.

Example call

To create a billing attempt, specify the following inputs in the subscriptionBillingAttemptCreate mutation:

  • subscriptionContractId: The ID of the subscription contract.
  • subscriptionBillingAttemptInput
    • idempotencyKey: A unique key generated by the client to avoid duplicate payments.
    • test: Whether to execute the payment in test mode, if possible. The default is false. When set to true, it will ignore the payment gateway’s mode. When set to false, it will use the payment gateway’s mode.

Billing attempts are processed asynchronously, which means the resulting order will not be available right away. You can fetch the billing attempt and inspect the ready field to find out whether the order has been created (true) or not (false).

Example request:

POST /admin/api/2021-01/graphql.json

View response

JSON response:

Because the order isn't ready right away, you can query the SubScriptionBillingAttempt to get the resulting order information.

Example request:

POST /admin/api/2021-01/graphql.json

View response

JSON response:

About 3D Secure

Shopify handles 3D Secure authentication by emailing the customer when the financial institution requires a challenge. This flow is demonstrated in the diagram below:

You can poll the subscriptionBillingAttempt object until the nextActionUrl field is available to see the URL. It's up to apps to attempt re-billing for failed payment attempts.

Example request:

POST /admin/api/2021-01/graphql.json

View response

JSON response:

Your app can subscribe to the following subscription-related webhooks. To learn how to set up and consume webhooks, refer to Configuring webhooks.

Description Payload Required scope
Emitted when a payment method is created.
  {
    customer_id,
    token,
    instrument_type,
    payment_instrument,
  }
  
read_customer_payment_methods
Description Payload Required scope
Emitted when a payment method is updated.
  {
    customer_id,
    token,
    instrument_type,
    payment_instrument,
  }
  
read_customer_payment_methods
Description Payload Required scope
Emitted when the payment method is revoked. Not applicable read_customer_payment_methods
Description Payload Required scope
Emitted when a contract is first created. This is deferred until after the order is created.
  {
   admin_graphql_api_id,
   id,
   billing_policy,
   delivery_policy,
   admin_graphql_api_customer_id,
   customer_id,
   currency_code,
   status,
   admin_graphql_api_origin_order_id,
   origin_order_id
  }
  
read_own_subscription_contracts
Description Payload Required scope
Emitted when a contract is updated.
    {
     admin_graphql_api_id,
     id,
     billing_policy,
     delivery_policy,
     admin_graphql_api_customer_id,
     customer_id,
     currency_code,
     status,
     admin_graphql_api_origin_order_id,
     origin_order_id
    }
    
read_own_subscription_contracts
Description Payload Required scope
Emitted when billing succeeds.
  {
   id,
   admin_graphql_api_id,
   idempotency_key,
   order_id,
   admin_graphql_api_order_id,
   subscription_contract_id,
   admin_graphql_api_subscription_contract_id,
   ready,
   error_message,
  }
  
read_own_subscription_contracts
Description Payload Required scope
Emitted when billing fails.
  {
   id,
   admin_graphql_api_id,
   idempotency_key,
   order_id,
   admin_graphql_api_order_id,
   subscription_contract_id,
   admin_graphql_api_subscription_contract_id,
   ready,
   error_message,
  }
  
In this case, order_id might be null. The error_message will be populated.
read_own_subscription_contracts
Description Payload Required scope
Emitted when the financial institution challenges the subscription billing attempt charge as per 3D Secure.
  {
   id,
   admin_graphql_api_id,
   idempotency_key,
   order_id,
   admin_graphql_api_order_id,
   subscription_contract_id,
   admin_graphql_api_subscription_contract_id,
   ready,
   error_message,
  }
  
read_own_subscription_contracts

Next steps