How to keep pricing policies up to date?

hernancarrizo
Tourist
3 0 4

Hello, we're trying to understand how to use and maintain pricing policies with subscription contracts. We're able to setup selling plans with the corresponding pricing policies for our app. When a subscription is created, we're getting the SubscriptionContract created with the pricing policy "applied" in the corresponding line items.

Now we're trying to understand how we should proceed with the pricing policies after the subscription contract has been created. Our understanding so far is:

  • On subscription creation, Shopify is setting up the line items + the corresponding SubscriptionCyclePriceAdjustment attached to it (if there's a pricing policy applied to the line item). After the subscription creation, is our app the one responsible to update the line item's `currentPrice` property on every successful billing attempt (as discussed in this thread). Is this correct?
  • If at some point, our app needs to update a line item (for example, change a product from the subscription). We have a couple of questions here:
    • Is there a way through the API to get the selling plans applicable for a product variant ID? The seelingPlanGroups query doesn't have a filter per productId or variantId. So, the only way for us to get the candidate selling plans is filtering through the delivery_frequency and then find manually the applicable selling plans. Is this the best way to do it?
    • Once we have the selling plan to be applied to the new line item, is there a way through the API to generate the SubscriptionCyclePriceAdjustment records (to be set as part of the input payload in the subscriptionDraftLineAdd mutation)? We tried setting the selling plan ID, but that doesn't calculate / generate the SubscriptionCyclePriceAdjustment records for the pricing policies associated to the specified selling plan. Do we have to manually recreate these records in our app? (this seems to be already sorted out by Shopify and ideally we would prefer to rely on that implementation that already knows and understands all the logic behind the pricing policies and types of discounts that are supported by Shopify).
Reply 1 (1)

devsmk
Shopify Partner
7 0 3

Hi @hernancarrizo,

 

It's a shame that Shopify hasn't responded to this question of yours and once mine, from almost a year ago. I just finished building out a custom subscription app for my company's store and may have some info to help you with your question. I'm sure by now it's mute, but perhaps it will help future developers that find themselves in the same situation that we have found ourselves.

 

It seems inevitable that MOST developers working on a subscription app would greatly benefit from this answer, but it's nowhere in the docs, not surprisingly, nor explained at all in their official Getting started with subscriptions walkthrough, nor their "division of responsibilities" section. I also double checked in their Create and manage subscription contracts page, also nil. They mention that billing is up to us as app developers to schedule, but they never mention that we are also responsible for computing the price each cycle nor show a breakdown of how to do that. Anyways, don't get me started on the docs. The only thing I'll say about that is I wish their document "developers" were half as detailed as their designers. They look so NICE, but leave you with SOOOO many unanswered questions at every turn. I think if they took a mid level Node.js developer that has no experience with building a Shopify app, had them follow the docs, and took notes at points where the developers became completely lost, they could really improve the docs significantly. An official, base line fully functioning example repo for their Subscription API's would also really, really help. Showing these things like computing the currentPrice each billing cycle using the computedPrice and other small details that are completely left out, would really make a huge difference.

 

Q: Is there a way through the API to get the selling plans applicable for a product variant ID? 

A: So the way it seems to work (at least according to the Create and manage selling plans docs) is everything lives under the Selling Plan Group umbrella. All Selling Plans, products, variants, billing, pricing and delivery policies as well. According to the GraphQL Admin API (version 2022-04) you could go either way.

 

• Meaning search for products or variants in the SellingPlanGroups query. 

Screen Shot 2022-04-15 at 11.29.51 PM.png

 

• Searching for all Selling Plan Groups via a Product or ProductVariant query.

Screen Shot 2022-04-15 at 11.24.02 PM.png

 

Screen Shot 2022-04-15 at 11.25.31 PM.png

 

Q: Once we have the selling plan to be applied to the new line item, is there a way through the API to generate the SubscriptionCyclePriceAdjustment records (to be set as part of the input payload in the subscriptionDraftLineAdd mutation)?

A: Since we as developers are responsible for all billing attempts and the prices used to complete those attempts, that logic needs to occur on the server. To kind of go through a summed up walkthrough, or play-by-play if you will. One thing of note, you would not use the 

subscriptionDraftLineAdd mutation unless you were actually adding a new subscription to the customers contract, which you are not, you are just updating their current subscription line, so you would use the subscriptionDraftLineUpdate mutation instead. Actually is you look at the input object on this mutation you'll notice both the currentPrice and computed price are included, how convenient.

 

1. You need to figure out how to use liquid drops and javascript in your theme file to connect a selling plan Id to a product or variant when it gets added to the cart, using this walkthrough or this light explanation of how to do it with liquid, your choice.

 

2. Once you are able to do that and the customer checks out Shopify automatically creates a new SubscriptionContract for you and the subscription_contract/create webhook is fired, so that is not required on your end unless you are making them programmatically for a specific reason.

 

2. You need to automate the billing of the recurring pricingPolicy using the nextBillingDate field on the contract and through that method, update the currentPrice field with the computedPrice field, while using the afterCycles field to know which cycle to use with which policy. Lost yet? 🙂

Note: Both the afterCycles and computedPrice fields can be found at SubscriptionContract -> lines -> edges -> node -> pricingPolicy -> cycleDiscounts . This object is typed as a SubscriptionCyclePriceAdjustment.

3. So how do you know when a new Subscription is created (purchased through checkout)? The  appropriate Contract API Webhooks are fired and that is when you can store a local copy of your contract in whatever dB you use and/or make any necessary adjustments.

 

To close this out, here is an example repo that I used to model my subscription app after. You can clearly see where the webhooks are triggering the logic that would be needed to update the currentPrice. That specific step is missing in this persons example as this is not 100% functional, but it's close and gives the necessary framework with which to build up and off of to get 100%. Shout out to j-Riv for his invaluable repo that saved me when I had all these questions.

 

Here's an example of how I would include such a method:

(The obfuscated methods are pretty self explanatory using Apollo client for the GraphQL calls, whatever dB methods are required to connect to your store and custom logic messing about with the returned objects to set values appropriately)

 

 

import dotenv from 'dotenv'
import schedule from 'node-schedule'
import DbStore from '../controllers'
import 'isomorphic-fetch'
import { sendSubscriptionRenewalEmail } from './communications'
import { SubscriptionContract } from '~/global'
import {
  createClient,
  createSubscriptionBillingAttempt,
  getSubscriptionContract,
  updateSubscriptionContract,
  updateSubscriptionDraft,
  commitSubscriptionDraft
} from '../handlers'

dotenv.config()

const dbStore = new DbStore()

const every10sec = '*/10 * * * * *' // every 10 seconds for testing
const everymin = '* * * * *' // every min for testing
const everyday3am = '0 0 3 * * *' // every day at 1 am
const everyday6am = '0 0 6 * * *' // every day at 6 am
const everyday10am = '0 0 10 * * *' // every day at 10 am
const everyday12am = '0 0 0 * * *' // every day at 12 am
const everyhour = '0 0 */2 * * *' // every 2 hours

  // scheduler format
  // *    *    *    *    *    *
  // ┬    ┬    ┬    ┬    ┬    ┬
  // │    │    │    │    │    │
  // │    │    │    │    │    └ day of week (0 or 7 is Sun)
  // │    │    │    │    └───── month (1 - 12)
  // │    │    │    └────────── day of month (1 - 31)
  // │    │    └─────────────── hour (0 - 23)
  // │    └──────────────────── minute (0 - 59)
  // └───────────────────────── second (0 - 59, OPTIONAL)

const billingJob = schedule.scheduleJob(everyday6am, async () => {
  console.log(
    `> Running billing job and assigning next job for ${billingJob.nextInvocation()}`
  )
  runBillingAttempts()
})

const syncingJob = schedule.scheduleJob(everyhour, async () => {
  console.log(
    `> Running contract syncing job and assigning next job for 
      ${syncingJob.nextInvocation()}`
  )
  runSubscriptionContractSync()
})

const cleanupJob = schedule.scheduleJob(everyday3am, async () => {
  console.log(
    `> Running cleanup job and assigning next job for ${cleanupJob.nextInvocation()}`
  )
  runCancellation()
})

export const runBillingAttempts = async () => {
  try {
    const ACTIVE_SHOPIFY_SHOPS = await dbStore.loadActiveShops()
    const shops = Object.keys(ACTIVE_SHOPIFY_SHOPS)

    shops.forEach(async (shop: string) => {
      const token = ACTIVE_SHOPIFY_SHOPS[shop].accessToken
      const client = createClient(shop, token)
      // get all active contracts for shop
      const entities = await dbStore.getLocalContractsDueForBilling()
      if (entities) {
        // loop through contracts
        entities.forEach(async (entity) => {
          // create billing attempt
          const contractId = JSON.parse(entity.contract).id
          console.log(
            '@runningBillingAttempt ContractId',
            JSON.parse(entity.contract).id
          )

          const updated = await updateSubscriptionLineCurrentPrice(
            client,
            contractId
          )

          if (updated.success) {
            await createSubscriptionBillingAttempt(client, contractId)
          }
        })
      }
    })
  } catch (err: any) {
    console.log('error', err.message)
  }
}

 

 

 

Cheers!