All Tutorials

Create and manage fulfillments for prepaid subscriptions

All Tutorials

Create and manage fulfillments for prepaid subscriptions

Create and manage fulfillments for prepaid subscriptions

You can create prepaid subscriptions with scheduled fulfillments. When a customer purchases a pre-paid subscription from a merchant, a subscriptionContract and an order are created. The order contains multiple fulfillment orders which are scheduled to be fulfilled at a later time. The dates are determined based on the delivery anchors, interval and intervalCount set on the sellingPlanRecurringDeliveryPolicy. This allows for payment and goods sold to be kept in the same order, supporting accurate reporting in Shopify.

This tutorial illustrates the calls you can make to manage fulfillments on subscriptions. It provides information about deferred fulfillment orders, manual changing of fulfillments, cutoffs, renewals, scheduling, skipping, and refunds.

Requirements

Scopes

Apps require the following permissions to reschedule or open fulfillment orders. These permissions enable apps to manage scheduled cycles for an order that was already billed:

  • read_merchant_managed_fulfillment_orders: Read scheduled fulfillment orders assigned to a merchant-managed location.
  • write_merchant_managed_fulfillment_orders: Reschedule and open fulfillment orders assigned to a merchant-managed location.
  • read_third_party_fulfillment_orders: Read scheduled fulfillment orders assigned to a third party service.
  • write_third_party_fulfillment_orders: Reschedule and open fulfillment orders assigned to a third party service.

Core concepts

Line items for checkouts and orders

A subscription appears as one line item on the checkout. However, the line item quantity on the corresponding order represents the interval count of the subscription billing policy. For example, if a customer buys a three month subscription of coffee brans, then the checkout line item quantiy is 1 and order line item quantity is 3.

Fulfillment order attributes

The fulfillAt attribute of a fulfillment order represents when the merchant should start fulfilling the items in the fulfillment order. The attribute also contributes to the grouping of line items for fulfillment orders with SCHEDULED status, along with location, fulfillmentService, deliveryMethodType and deliveryProfile.

For fulfillment orders assigned to a third-party fulfillment service, the fulfillAt attribute represents when the merchant should request fulfillment from the fulfillment service.

The SCHEDULED state of fulfillmentOrderStatus represents the state of fulfillment orders created with a future fulfillAt date. The status is also represented at the order's displayFulfillmentStatus, for orders where all associated fulfillment orders are in a SCHEDULED state.

Fulfillment orders

When a buyer purchases a prepaid subscription from a merchant, a subscription contract and an order are created. One or more fulfillment orders are also created and are associated with the order. This enables support for prepaid subscription scenarios, where a buyer wants to pay up front for a product that's delivered using billing and delivery intervals.

Fulfillment orders workflow

API object Description
Subscription contract A subscription contract is the agreement between a customer and a merchant for recurring purchases over a set or undefined period of time. When a buyer purchases a prepaid subscription from a merchant, a subscription contract and an order are created.
Order The order contains all the items that will be sent out for the duration of the prepaid subscription. The order has multiple associated fulfillment orders that are scheduled to be fulfilled at a later time, based on the delivery anchor, interval, and interval count set on the sellingPlanRecurringDeliveryPolicy . This allows for payment and goods sold to be kept in the same order, supporting accurate reporting in Shopify.
Line items Represents a single line in a shopping cart. After a customer completes checkout for a prepaid subscription, the order contains the line items to be sent for the duration of the prepaid subscription.
Fulfillment orders Units of work (for example, line items) that are grouped together for fulfillment based on the following:

What you'll build

The examples in this tutorial revolve around the scenario of creating a recurring three-month prepaid subscription of coffee bags. In this scenario, three separate fulfillment orders are created for the order. The test shop assumed in this scenario has the following setup:

The tutorial covers the following:

Prepaid subscription lifecycle

A successful prepaid subscription flow looks like the following:

Prepaid subscription lifecycle

  1. The customer begins checkout. The checkout line item is a three-month prepaid subscription of coffee bags and the quantity is one.
  2. The customer completes the checkout. Both a subscription contract and an order are created. The order line item is coffee bags and the quantity is three.
  3. Three fulfillment orders in a SCHEDULED state are created associated with the order. The fulfillment orders have the following fulfillAt dates:
    • Fulfillment order 1: January 15
    • Fulfillment order 2: February 15
    • Fulfillment order 3: March 15

When the fulfillAt date for each fulfillment order is reached, inventory is committed and the fulfillment order is transitioned to OPEN state, marking the items in that fulfillment order as fulfillable.

Prepaid subscriptions status

On January 15, the first fulfillmentOrder transitions to an OPEN state, indicating that fulfillment should begin for the item. Only the items meant to be fulfilled on January 15 are ready for fulfillment, whereas the remaining items are shown as scheduled:

Prepaid subscriptions Shopify admin

Manually open a fulfillment order

Merchants can opt to Fulfill early for scheduled fulfillments in Shopify admin. For scheduled fulfillment orders, this changes the fulfillment order's status to OPEN ahead of the fulfillAt date. You can also use the API to set a fulfillment order's status to OPEN, as demonstrated in the following example:

Request

POST /admin/api/unstable/graphql.json

mutation {
fulfillmentOrderOpen(
  id: "gid://shopify/FulfillmentOrder/1899849515064"
)
{
   fulfillmentOrder {
      id
    status
   }
   userErrors {
    field
    message
   }
  }
}

View response

JSON response:

{
  "data": {
    "fulfillmentOrderOpen": {
      "fulfillmentOrder": {
        "id": "gid://shopify/FulfillmentOrder/1899849515064",
        "status": "OPEN"
      },
      "userErrors": []
    }
  },
  ...
}

Prepaid subscriptions Shopify admin

Set the cutoff date for a selling plan

Selling plans can be set up with a cutoff on the sellingPlanRecurringDeliveryPolicy. The cutoff indicates how many days in advance of the delivery anchor the order would need to be placed in order to qualify for the upcoming delivery cycle.

In the following example, cutoff is set to 5. Orders would need to be placed five days in advance of the cutoff date (on the 10th at 23:59:59:999) to be included in the fulfillment order. The SellingPlanRecurringDeliveryPolicyPreAnchorBehavior field is set to ASAP. The field determines when items are fulfilled. For example, if the customer completed the checkout on January 8, then the settings would function as follows:

  • NEXT - The fulfillAt date would be January 15, February 15, and March 15.
  • ASAP - The fulfillAt date would be January 8, February 15, and March 15.

Cutoff diagram

Request

POST /admin/api/unstable/graphql.json

mutation ($input: SellingPlanGroupInput!, $resources: SellingPlanGroupResourceInput) {
  sellingPlanGroupCreate(input: $input, resources: $resources) {
    userErrors {
      field
      message
    }    
  }
}

Input:

The input below includes the deliveryPolicy cutoff set to "5" with preAnchorBehavior set to ASAP:

{
  "input": {
      "name": "Prepaid",
      "merchantCode": "prepaid",
      "options": ["prepaid options"],
      "sellingPlansToCreate": [
        {
          "name": "Prepaid 3 months - delivery every months - 20% off",
          "options": ["3 months - delivery every month"],
          "billingPolicy": { "recurring": { "interval": "MONTH", "intervalCount": 3 } },
          "deliveryPolicy": { "recurring": { "interval": "MONTH", "intervalCount": 1, "cutoff": 5, "preAnchorBehavior": "ASAP"  } },
          "pricingPolicies": [
            {
              "fixed": {
                "adjustmentType": "PERCENTAGE",
                "adjustmentValue": { "percentage": 20 }
              }
            }
          ]
        }
      ]
  },
  ...
  }
}

Renew the subscription

When a pre-paid subscription contract is up for renewal, apps need to trigger a billing attempt using the subscriptionBillingAttemptCreate mutation. The customer is then billed and a new order is generated for the new billing cycles.

In the example scenario, after the last fulfillment order is fulfilled on March 15 then a new order needs to be created for the following three months (containing three scheduled fulfillment orders for the 15 of April, May and June).

Reschedule a fulfillment order

The following example reschedules a fulfillment by specifying a new value for fulfillAt along with the ID of the fulfillment order.

Example request:

POST /admin/api/unstable/graphql.json

mutation {
fulfillmentOrderReschedule(
  id: "gid://shopify/FulfillmentOrder/1899851251768",
  fulfillAt: "2021-01-01"
)
{
   fulfillmentOrder {
      fulfillAt
   }
   userErrors {
    field
    message
   }
  }
}

View response

JSON response:

{
  "data": {
    "fulfillmentOrderReschedule": {
      "fulfillmentOrder": {
        "fulfillAt": "2021-01-01T00:00:00Z"
      },
      "userErrors": []
    }
  },
  ...
}

Skip fulfillment orders

Customers can decide to skip a scheduled fulfillment of a product. For example, in the case of the three-month coffee subscription, the customer might decide to skip a fulfillment since they already have enough coffee that month. To support this use case, you need to make a call to reschedule the fulfillment order, and another call to change the renewal date by one cycle.

Reschedule the fulfillment order

The following mutation reschedules the fulfillment order which is scheduled for Feb 15 to April 15.

Example request:

POST /admin/api/unstable/graphql.json

mutation {
fulfillmentOrderReschedule(
  id: "gid://shopify/FulfillmentOrder/1899851251768",
  fulfillAt: "“2020-04-15”"
)
{
   fulfillmentOrder {
      fulfillAt
   }
   userErrors {
    field
    message
   }
  }
}

View response

JSON response:

{
  "data": {
    "FulfillmentOrderReschedule": {
      "fulfillmentOrder": {
        "fulfillAt": "2020-04-15T00:00:00Z"
      },
      "userErrors": []
    }
  },
  ...
}

Change the renewal date

The following mutation changes the the renewal date by one cycle. The renewal triggers on May 15, since the fulfillment order from Feb 15 was moved to April 15.

Example request:

POST /admin/api/unstable/graphql.json

mutation {
subscriptionContractSetNextBillingDate(
  contractId: "gid://shopify/SubscriptionContract/1671224",
  date: "2020-05-15"
)
{
   contract {
      nextBillingDate
   }
   userErrors {
    field
    message
   }
  }
}

View response

JSON response:

{
  "data": {
    "subscriptionContractSetNextBillingDate": {
      "contract": {
        "nextBillingDate": "2020-05-15T00:00:00Z"
      },
      "userErrors": []
    }
  },
  ...
}

Handle refunds

Customers can request refunds for one or more scheduled fulfillments. Also, refunds are supported in the Shopify admin. You can support a variety of refund scenarios using the refundCreate mutation.

How refunds work

Refunds are made starting on the last cycle and then working backwards in reverse chronological order.

Refunds

Fulfillment orders with SCHEDULED status are prioritized over those with OPEN status. Also, the fulfillment orders are selected for refund using the value of fulfillAt in descending order.

About refunds and cancelling subscription contracts

Refunding fulfillment orders doesn't automatically cancel the renewal associated with the subscription contract. To cancel the renewal, you need to update the subscription contract.

Example refund calls

The following examples can be used to implement some common refunding scenarios.

Partial refund

The following request cancels the remaining subscription cycles after the first cycle is already completed.

Example request:

POST /admin/api/unstable/graphql.json

mutation {
refundCreate(
  input: {
    orderId: "gid://shopify/Order/2067697074232",
    refundLineItems: {
      lineItemId: "gid://shopify/LineItem/4780880232504",
      quantity: 2,
      restockType: CANCEL,
      locationId: "gid://shopify/Location/9562054"
    }
  }
)
{
   refund {
      id
   }
   userErrors {
    field
    message
   }
  }
}

View response

JSON response:

{
  "data": {
    "refundCreate": {
      "refund": {
        "id": "gid://shopify/Refund/65502773304"
      },
      "userErrors": []
    }
  },
  ...
}

Refund last cycle

The following request cancels the last scheduled fulfillment.

Example request:

POST /admin/api/unstable/graphql.json

mutation {
refundCreate(
  input: {
    orderId: "gid://shopify/Order/2067697074232",
    refundLineItems: {
      lineItemId: "gid://shopify/LineItem/4780880232504",
      quantity: 1,
      restockType: CANCEL,
      locationId: "gid://shopify/Location/9562054"
    }
  }
)
{
   refund {
      id
   }
   userErrors {
    field
    message
   }
  }
}

View response

JSON response:

{
  "data": {
    "refundCreate": {
      "refund": {
        "id": "gid://shopify/Refund/659856"
      },
      "userErrors": []
    }
  },
  ...
}

Refund a specific amount of cycles

The following request cancels the last four months of a six-month subscription.

Example request:

POST /admin/api/unstable/graphql.json

mutation {
refundCreate(
  input: {
    orderId: "gid://shopify/Order/2067697074232",
    refundLineItems: {
      lineItemId: "gid://shopify/LineItem/4780880232504",
      quantity: 4,
      restockType: CANCEL,
      locationId: "gid://shopify/Location/9562054"
    }
  }
)
{
   refund {
      id
   }
   userErrors {
    field
    message
   }
  }
}

View response

JSON response:

{
  "data": {
    "refundCreate": {
      "refund": {
        "id": "gid://shopify/Refund/659856"
      },
      "userErrors": []
    }
  },
  ...
}

Webhooks

You can subscribe to the refunds/create webhook in case the merchant does a refund on a prepaid subscription order through the Shopify admin or another app. In these scenarios, you can use the webhook as a trigger to communicate with the merchant.

Important notes

Legacy mobile clients

For mobile clients (version 8.75.0 or older), scheduled fulfillment orders are hidden from the mobile UI. Once a scheduled fulfillment order’s fulfillAt date expires and it transitions to an OPEN state, it becomes visible in the UI.

Legacy POS clients

POS Classic (Android & iOS) does not show the fulfillment statuses of scheduled orders.

For the all-new POS (iOS only), as of v6.19 and earlier, fulfillment status is not exposed for scheduled orders. Products that are sold as subscription-only are not displayed in the product catalogue on POS. Also, if an app adds a subscription to the cart, then an error is raised.

Next steps