All Tutorials

Create and manage subscription contracts

All Tutorials

Create and manage subscription contracts

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 subcription 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

View subscription contracts object descriptions
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 SubscriptionContracts. 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

query {
  subscriptionContracts(first: 10) {
    edges {
      node {
        id
        createdAt
        status
        nextBillingDate
        customer {
          firstName
          lastName
        }
        billingPolicy {
          interval
          intervalCount
        }
      }
    }
  }
}

View response

JSON response:

{
 "data": {
   "subscriptionContracts": {
     "edges": [
       {
         "node": {
           "id": "gid://shopify/SubscriptionContract/1",
           "createdAt": "2020-06-16T19:01:46Z",
           "status": "ACTIVE",
           "nextBillingDate": "2020-06-01T00:00:00Z",
           "customer": {
             "firstName": "John",
             "lastName": "Smith"
           },
           "billingPolicy": {
             "interval": "MONTH",
             "intervalCount": 3
           }
         }
       },
       {
         "node": {
           "id": "gid://shopify/SubscriptionContract/2",
           "createdAt": "2020-06-16T19:04:15Z",
           "status": "ACTIVE",
           "nextBillingDate": "2020-06-01T00:00:00Z",
           "customer": {
             "firstName": "Jane",
             "lastName": "Smith"
           },
           "billingPolicy": {
             "interval": "MONTH",
             "intervalCount": 3
           }
         }
       }
     ]
   }
 },
 "extensions": {
   "cost": {
     "requestedQueryCost": 32,
     "actualQueryCost": 8
   }
 }
}

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

mutation {
  subscriptionDraftUpdate(
    draftId: "gid://shopify/SubscriptionDraft/22"
    input: { deliveryPolicy: { interval: MONTH, intervalCount: 2 } }
  ) {
    draft {
      id
    }
    userErrors {
      field
      message
    }
  }
}

View response

JSON response:

{
  "data": {
    "subscriptionDraftUpdate": {
      "draft": {
        "id": "gid://shopify/SubscriptionDraft/22"
      },
      "userErrors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 10,
      "actualQueryCost": 10,
      "throttleStatus": {
        "maximumAvailable": 1000.0,
        "currentlyAvailable": 990,
        "restoreRate": 50.0
      }
    }
  }
}

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

mutation {
  subscriptionDraftLineAdd(
    draftId: "gid://shopify/SubscriptionDraft/7"
    input: {
      productVariantId: "gid://shopify/ProductVariant/2"
      quantity: 20
      currentPrice: 25.00
    }
  ) {
    lineAdded {
      id
      quantity
      productId
      variantId
      variantImage {
        id
      }
      title
      variantTitle
      currentPrice {
        amount
        currencyCode
      }
      requiresShipping
      sku
      taxable
    }
    draft {
      id
    }
    userErrors {
      field
      message
      code
    }
  }
}

View response

JSON response:

{
  "data": {
    "subscriptionDraftLineAdd": {
      "lineAdded": {
        "id": "gid://shopify/SubscriptionLine/818b344f-1e7f-4b0e-9fc2-2b749d4b5494",
        "quantity": 20,
        "productId": "gid://shopify/Product/1",
        "variantId": "gid://shopify/ProductVariant/2",
        "variantImage": {
          "id": "gid://shopify/ImageSource/1474738389014"
        },
        "title": "Aerodynamic Wool Coat",
        "variantTitle": "Rustic Plastic Computer",
        "currentPrice": {
          "amount": "25.0",
          "currencyCode": "USD"
        },
        "requiresShipping": true,
        "sku": "",
        "taxable": true
      },
      "draft": {
        "id": "gid://shopify/SubscriptionDraft/7"
      },
      "userErrors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 11,
      "actualQueryCost": 11,
      "throttleStatus": {
        "maximumAvailable": 1000.0,
        "currentlyAvailable": 989,
        "restoreRate": 50.0
      }
    }
  }
}

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

mutation {
  subscriptionDraftLineUpdate(
    draftId: "gid://shopify/SubscriptionDraft/229541"
    lineId: "gid://shopify/SubscriptionLine/9c952e0f-43df-4881-84a1-f720003c43a3"
    input: { quantity: 10 }
  ) {
    lineUpdated {
      id
      quantity
      productId
      variantId
      variantImage {
        id
      }
      title
      variantTitle
      requiresShipping
      sku
      taxable
    }
    draft {
      id
    }
    userErrors {
      field
      message
      code
    }
  }
}

View response

JSON response:

{
  "data": {
    "subscriptionDraftLineUpdate": {
      "lineUpdated": {
        "id": "gid://shopify/SubscriptionLine/9c952e0f-43df-4881-84a1-f720003c43a3",
        "quantity": 10,
        "productId": "gid://shopify/Product/5515510218917",
        "variantId": "gid://shopify/ProductVariant/35599671197861",
        "variantImage": {
          "id": "gid://shopify/ProductImage/18372397138085"
        },
        "title": "Shoes",
        "variantTitle": null,
        "requiresShipping": true,
        "sku": null,
        "taxable": true
      },
      "draft": {
        "id": "gid://shopify/SubscriptionDraft/229541"
      },
      "userErrors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 11,
      "actualQueryCost": 11,
      "throttleStatus": {
        "maximumAvailable": 5000.0,
        "currentlyAvailable": 4989,
        "restoreRate": 250.0
      }
    }
  }
}

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

mutation {
  subscriptionDraftLineRemove(
    draftId: "gid://shopify/SubscriptionDraft/7"
    lineId: "gid://shopify/SubscriptionLine/cb0141b4-93fc-4dde-b6dc-53933b596b10"
  ) {
    lineRemoved {
      id
    }
    draft {
      id
    }
    userErrors {
      field
      message
      code
    }
  }
}

View response

JSON response:

{
 "data": {
   "subscriptionDraftLineRemove": {
     "lineRemoved": {
       "id": "gid://shopify/SubscriptionLine/cb0141b4-93fc-4dde-b6dc-53933b596b10"
     },
     "draft": {
       "id": "gid://shopify/SubscriptionDraft/7"
     },
     "userErrors": []
   }
 },
 "extensions": {
   "cost": {
     "requestedQueryCost": 10,
     "actualQueryCost": 10
   }
 }
}

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

mutation {
  subscriptionDraftCommit(draftId: "gid://shopify/SubscriptionDraft/22") {
    contract {
      id
    }
    userErrors {
      field
      message
    }
  }
}

View response

JSON response:

{
  "data": {
    "subscriptionDraftCommit": {
      "contract": {
        "id": "gid://shopify/SubscriptionContract/22"
      },
      "userErrors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 10,
      "actualQueryCost": 10,
      "throttleStatus": {
        "maximumAvailable": 1000.0,
        "currentlyAvailable": 990,
        "restoreRate": 50.0
      }
    }
  }
}

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

The View subscription button is hardcoded and isn't supported by app extensions. In order for the merchant to be directed to the relevant subscription contract, the app must implement a specific endpoint.

The hardcoded link has the following format:

{app_application_url}/subscriptions?shop={shop_domain}&id={subscription_contract_id}

Once implemented, the endpoint redirects to the subscription contract page within the app for the subscription defined by subscription_contract_id.

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

mutation {
  subscriptionContractUpdate(
    contractId: "gid://shopify/SubscriptionContract/22"
  ) {
    draft {
      id
    }
    userErrors {
      field
      message
    }
  }
}

View response

JSON response:

{
  "data": {
    "subscriptionContractUpdate": {
      "draft": {
        "id": "gid://shopify/SubscriptionContract/22"
      },
      "userErrors": []
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 10,
      "actualQueryCost": 10,
      "throttleStatus": {
        "maximumAvailable": 1000.0,
        "currentlyAvailable": 990,
        "restoreRate": 50.0
      }
    }
  }
}

Create a billing attempt

To schedule and automate the billing of subscriptions, apps must 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.

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

mutation {
  subscriptionBillingAttemptCreate(
    subscriptionContractId: "gid://shopify/SubscriptionContract/1"
    subscriptionBillingAttemptInput: { idempotencyKey: "abc123" }
  ) {
    subscriptionBillingAttempt {
      id
      errorMessage
      nextActionUrl
      order {
        id
      }
      ready
    }
  }
}

View response

JSON response:

{
  "data": {
    "subscriptionBillingAttemptCreate": {
      "subscriptionBillingAttempt": {
        "id": "gid://shopify/SubscriptionBillingAttempt/32933",
        "errorMessage": null,
        "nextActionUrl": null,
        "order": null,
        "ready": false
      }
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 11,
      "actualQueryCost": 11,
      "throttleStatus": {
        "maximumAvailable": 5000.0,
        "currentlyAvailable": 4989,
        "restoreRate": 250.0
      }
    }
  }
}

Since 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

  query {
    subscriptionBillingAttempt(
      id: "gid://shopify/SubscriptionBillingAttempt/524310"
    ) {
      id
      errorMessage
      nextActionUrl
      order {
        id
      }
      ready
    }
  }

View response

JSON response:

{
    "data": {
        "subscriptionBillingAttempt": {
            "id": "gid://shopify/SubscriptionBillingAttempt/32933",
            "errorMessage": null,
            "nextActionUrl": null,
            "order": {
                "id": "gid://shopify/Order/2014567596054"
            },
            "ready": true
        }
    },
    "extensions": {
        "cost": {
            "requestedQueryCost": 2,
            "actualQueryCost": 2,
            "throttleStatus": {
                "maximumAvailable": 1000.0,
                "currentlyAvailable": 998,
                "restoreRate": 50.0
            }
        }
    }
}

Your app can subscribe to the following subscription-related webhooks as of the 2021-01 API version. To learn how to set up and consume webhooks, refer to our webhooks tutorial.

customer_payment_methods/create
Description Payload Required scope
Emitted when a payment method is created. { customer_id, token, instrument_type, payment_instrument, } read_customer_payment_methods
customer_payment_methods/update
Description Payload Required scope
Emitted when a payment method is updated. { customer_id, token, instrument_type, payment_instrument, } read_customer_payment_methods
customer_payment_methods/revoke
Description Payload Required scope
Emitted when the payment method is revoked. Not applicable read_customer_payment_methods
subscription_contracts/create
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
subscription_contracts/update
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
subscription_billing_attempts/success
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
subscription_billing_attempts/failure
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


Next steps