Skip to main content

Subscription Contracts

Subscription contracts represent the ongoing agreement between a customer and a merchant for recurring purchases. Each contract contains all the details needed to fulfill recurring orders, including product lines, billing schedules, delivery information, and payment methods.

What is a Subscription Contract?

A subscription contract is created when a customer purchases a product with a selling plan. It tracks the entire subscription lifecycle from creation through cancellation, including:
  • Product line items and quantities
  • Billing and delivery schedules
  • Customer information and payment method
  • Pricing and discount policies
  • Current contract status
Contracts are created automatically by Shopify when a customer completes a purchase with a subscription product.

Contract Lifecycle

Subscription contracts move through different states during their lifetime:

Status Types

export const SubscriptionContractStatus = {
  Cancelled: 'CANCELLED',
  Paused: 'PAUSED',
  Failed: 'FAILED',
  Expired: 'EXPIRED',
  Active: 'ACTIVE',
  Stale: 'STALE',
} as const;

export type SubscriptionContractStatusType =
  (typeof SubscriptionContractStatus)[keyof typeof SubscriptionContractStatus];
  • ACTIVE: Contract is currently active and will generate billing attempts
  • PAUSED: Customer has paused their subscription temporarily
  • CANCELLED: Contract has been cancelled and will no longer generate orders
  • FAILED: Payment failures have caused the contract to fail
  • EXPIRED: Contract reached its natural end date
  • STALE: Contract hasn’t been billed in an unusually long time

Contract Data Structure

The app uses TypeScript interfaces to represent subscription contract data:
export interface SubscriptionContractDetails {
  id: string;
  status: SubscriptionContractStatusType;
  lines: SubscriptionContractDetailsLine[];
  billingPolicy: RecurringPolicy;
  deliveryPolicy: RecurringPolicy;
  nextBillingDate?: string;
  originOrder?: Order | null;
  priceBreakdownEstimate?: PriceBreakdown | null;
  customerPaymentMethod?: CustomerPaymentMethod | null;
  customer?: Customer;
  deliveryMethod?: SubscriptionDeliveryMethod | null;
  billingAttempts: {
    id: string;
  }[];
  lastPaymentStatus?: 'SUCCEEDED' | 'FAILED' | null;
  lastBillingAttemptErrorType?: string | null;
}

Line Items

Each contract contains one or more line items representing the products being subscribed to:
export interface SubscriptionContractDetailsLine {
  id: string;
  title: string;
  variantTitle?: string | null;
  quantity: number;
  productId?: string | null;
  variantId?: string | null;
  currentPrice: Money;
  lineDiscountedPrice: Money;
  variantImage?: {
    altText?: string | null;
    url?: string | null;
  } | null;
  pricingPolicy?: ContractDetailsPricingPolicy | null;
  currentOneTimePurchasePrice?: number;
}

Querying Contract Details

The app uses GraphQL to fetch subscription contract information:
query SubscriptionContractDetails($id: ID!) {
  subscriptionContract(id: $id) {
    id
    status
    currencyCode
    lines(first: 50) {
      edges {
        node {
          id
          title
          variantTitle
          quantity
          currentPrice {
            currencyCode
            amount
          }
          pricingPolicy {
            basePrice {
              currencyCode
              amount
            }
            cycleDiscounts {
              adjustmentType
              adjustmentValue {
                ... on MoneyV2 {
                  amount
                  currencyCode
                }
                ... on SellingPlanPricingPolicyPercentageValue {
                  percentage
                }
              }
            }
          }
        }
      }
    }
    billingPolicy {
      interval
      intervalCount
    }
    deliveryPolicy {
      interval
      intervalCount
    }
    nextBillingDate
    customerPaymentMethod(showRevoked: true) {
      id
      revokedAt
      instrument {
        __typename
        ... on CustomerCreditCard {
          brand
          lastDigits
          expiryYear
          expiryMonth
          expiresSoon
        }
      }
    }
  }
}
The showRevoked: true parameter ensures you can see if a payment method has been revoked, which is important for handling payment failures.

Managing Contracts in the App

The reference app provides several ways to manage subscription contracts:

Viewing Contract Details

Contract details are accessible through the admin interface at /app/contracts/$id, which displays:
  • Current status and next billing date
  • Customer information
  • Line items with pricing
  • Billing and delivery schedules
  • Payment method status
  • Billing attempt history

Contract Operations

Customers can pause their subscription temporarily. The contract status changes to PAUSED and no billing attempts are made until the contract is resumed.

Payment Methods

Contracts track the customer’s payment method for automatic billing:
export interface CustomerPaymentMethod {
  id: string;
  instrument?: PaymentInstrument;
  revokedAt?: string | null;
}

export type PaymentInstrument =
  | CustomerCreditCard
  | CustomerShopPayAgreement
  | CustomerPaypalBillingAgreement
  | null;

export interface CustomerCreditCard {
  brand: string;
  lastDigits: string;
  maskedNumber: string;
  expiryYear: number;
  expiryMonth: number;
  expiresSoon: boolean;
  source?: string | null;
}
Always check if revokedAt is set or if expiresSoon is true to proactively handle potential payment issues before billing attempts fail.

Delivery Methods

Contracts support multiple delivery methods:
export interface SubscriptionDeliveryMethod {
  name: string;
  isLocalPickup: boolean;
}

export interface ShippingDelivery extends SubscriptionDeliveryMethod {
  shippingOption: {title?: string | null};
  address: CustomerAddress;
}

export interface LocalPickup extends SubscriptionDeliveryMethod {
  pickupOption: {title?: string | null};
}

Best Practices

  1. Monitor Contract Status: Regularly check contract status to identify issues like failed payments or expired payment methods
  2. Handle Terminal States: Contracts in CANCELLED or EXPIRED status are in terminal states and should be handled appropriately
  3. Track Billing Attempts: Monitor the billingAttempts array to understand payment history and identify problematic contracts
  4. Validate Payment Methods: Check for expiring cards using the expiresSoon flag to proactively reach out to customers

Build docs developers (and LLMs) love