Before we start thinking about rebilling, we need to roughly know the architecture of subscription. Open questions would be
- Do we need a database?
- What can we get from Shopify subscription endpoint?
- What would be the most basic requirements?
- Customers purchase subscription -> Shopify triggers initial billing, generates orders and new subscription contracts
- Birchbox requests billing attempts -> Shopify bills customers and creates new orders
- Birchbox ships orders -> Shopify creates fulfillments
- subscription history(from Customer)
- subscriptionContracts (SubscriptionContractConnection!) A list of the customer's subscription contracts. https://shopify.dev/docs/admin-api/graphql/reference/customers/customer#fields-2021-04
- shipment history (from Order)
- fulfillments ([Fulfillment!]!) List of shipments for the order. https://shopify.dev/docs/admin-api/graphql/reference/orders/order#fulfillments-2021-04
- order history(from SubscriptionContract)
- orders (OrderConnection!) The list of orders associated with the subscription contract. https://shopify.dev/docs/admin-api/graphql/reference/orders/subscriptioncontract#orders-2021-04
- billing history(from SubscriptionContract)
- billingAttempts (SubscriptionBillingAttemptConnection!) The list of billing attempts associated with the subscription contract. https://shopify.dev/docs/admin-api/graphql/reference/orders/subscriptioncontract#orders-2021-04
These can be achieved with shopify endpoint(no database required)
- 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. https://shopify.dev/tutorials/create-manage-subscription-contracts#create-a-billing-attempt
- 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.
Successful billing attempts create new orders
- 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). https://shopify.dev/tutorials/create-manage-subscription-contracts#create-a-billing-attempt
- cron job to get a list of active subscription contracts with next billing date before cutoff date(assume all plans are billed at the same date)
- webhooks
subscription_billing_attempts/success and subscription_billing_attempts/failure
for subscription status update and billing status update - db optional for billing cycles and billing status
- shopify bulk operation query subscriptionContracts(The output won't be ready immediately)
- get id of bulk operation from response, save id to database, send id to SQS(retry for output/failures)
- SQS consumer retries until bulk operation is completed.
- get url, update url to database, get jsonl object from url, read object and dispatch json objects or contract id(TBD) to SQS
- billing criterias:
- subscriptionContractStatus = ACTIVE
- subscriptionContract nextBillDate is 5th day of current month
- subscriptionContract billingAttempts: get latest one
- status should be READY(it's complete. We should avoid billing customers multiple times)
- lastPaymentStatus could either be
SUCCEEDED
orFAILED
SUCCEEDED
last billing attempt should be in last month(initial monthly billing)FAILED
last billing attempt should be in current month(monthly billing retry)
- SQS consumer gets message and then call subscriptionBillingAttemptCreate mutation(need to handle shopify rate limit)
- Webhook
subscription_billing_attempts/failure, subscription_billing_attempts/success
to alter nextBillingDate
sample response
{
"id": null,
"admin_graphql_api_id": null,
"idempotency_key": "9a453d81-d41d-403e-806f-714dee215ff9",
"order_id": 1,
"admin_graphql_api_order_id": "gid:\/\/shopify\/Order\/1",
"subscription_contract_id": 9998878778,
"admin_graphql_api_subscription_contract_id": "gid:\/\/shopify\/SubscriptionContract\/9998878778",
"ready": true,
"error_message": null,
"error_code": null
}