Stripe Subscriptions module API explorer

/api/user/subscriptions/set-subscription-item-quantity (PATCH)

Account information like email addresses is generated with faker-js it is not real user information.

await global.api.user.subscriptions.SetSubscriptionItemQuantity.patch(req)

Returns object

{
  "subscriptionitemid": "si_LwHKsO0Q0Fmfs4",
  "object": "subscriptionitem",
  "stripeObject": {
    "id": "si_LwHKsO0Q0Fmfs4",
    "object": "subscription_item",
    "billing_thresholds": null,
    "created": 1656124274,
    "metadata": {},
    "plan": {
      "id": "price_1LEOm9HHqepMFuCXPKQ0zvdF",
      "object": "plan",
      "active": true,
      "aggregate_usage": null,
      "amount": 1000,
      "amount_decimal": "1000",
      "billing_scheme": "per_unit",
      "created": 1656124269,
      "currency": "usd",
      "interval": "month",
      "interval_count": 1,
      "livemode": false,
      "metadata": {},
      "nickname": null,
      "product": "prod_LwHKpBBLECvSz7",
      "tiers_mode": null,
      "transform_usage": null,
      "trial_period_days": null,
      "usage_type": "licensed"
    },
    "price": {
      "id": "price_1LEOm9HHqepMFuCXPKQ0zvdF",
      "object": "price",
      "active": true,
      "billing_scheme": "per_unit",
      "created": 1656124269,
      "currency": "usd",
      "custom_unit_amount": null,
      "livemode": false,
      "lookup_key": null,
      "metadata": {},
      "nickname": null,
      "product": "prod_LwHKpBBLECvSz7",
      "recurring": {
        "aggregate_usage": null,
        "interval": "month",
        "interval_count": 1,
        "trial_period_days": null,
        "usage_type": "licensed"
      },
      "tax_behavior": "inclusive",
      "tiers_mode": null,
      "transform_quantity": null,
      "type": "recurring",
      "unit_amount": 1000,
      "unit_amount_decimal": "1000"
    },
    "quantity": 2,
    "subscription": "sub_1LEOmDHHqepMFuCX8u8ITAQ9",
    "tax_rates": []
  },
  "accountid": "acct_791e5a6848138589",
  "subscriptionid": "sub_1LEOmDHHqepMFuCX8u8ITAQ9",
  "customerid": "cus_LwHKjWpVRcFaJr",
  "appid": "tests_1656124268",
  "createdAt": "2022-06-25T02:31:16.220Z",
  "updatedAt": "2022-06-25T02:31:19.965Z"
}

Exceptions

These exceptions are thrown (NodeJS) or returned as JSON (HTTP) if you provide incorrect data or do not meet the requirements:

Exception Circumstances
invalid-account ineligible accessing account
invalid-quantity invalid posted quantity
invalid posted quantity is unchanged
invalid posted quantity is negative
invalid posted quantity is zero
invalid-subscriptionitemid missing querystring subscriptionitemid
invalid querystring subscriptionitemid

NodeJS source (view on github)

const dashboard = require('@layeredapps/dashboard')
const stripeCache = require('../../../../stripe-cache.js')
const subscriptions = require('../../../../../index.js')

module.exports = {
  patch: async (req) => {
    if (!req.query || !req.query.subscriptionitemid) {
      throw new Error('invalid-subscriptionitemid')
    }
    const subscriptionItem = await global.api.user.subscriptions.SubscriptionItem.get(req)
    if (!subscriptionItem) {
      throw new Error('invalid-subscriptionitemid')
    }
    if (subscriptionItem.accountid !== req.account.accountid) {
      throw new Error('invalid-account')
    }
    if (!req.body || !req.body.quantity) {
      throw new Error('invalid-quantity')
    }
    try {
      const quantity = parseInt(req.body.quantity, 10)
      if (quantity < 1 || quantity.toString() !== req.body.quantity) {
        throw new Error('invalid-quantity')
      }
      if (subscriptionItem.stripeObject.quantity === quantity) {
        throw new Error('invalid-quantity')
      }
    } catch (error) {
      throw new Error('invalid-quantity')
    }
    const updateInfo = {
      items: [{
        id: req.query.subscriptionitemid,
        quantity: req.body.quantity
      }]
    }
    const subscriptionNow = await stripeCache.execute('subscriptions', 'update', subscriptionItem.subscriptionid, updateInfo, req.stripeKey)
    if (!subscriptionNow) {
      throw new Error('unknown-error')
    }
    await subscriptions.Storage.Subscription.update({
      stripeObject: subscriptionNow
    }, {
      where: {
        subscriptionid: subscriptionItem.subscriptionid,
        appid: req.appid || global.appid
      }
    })
    for (const item of subscriptionNow.items.data) {
      if (item.id !== req.query.subscriptionitemid) {
        continue
      }
      await subscriptions.Storage.SubscriptionItem.update({
        stripeObject: item
      }, {
        where: {
          subscriptionitemid: subscriptionItem.subscriptionitemid,
          appid: req.appid || global.appid
        }
      })
      break
    }
    await dashboard.StorageCache.remove(subscriptionItem.subscriptionid)
    await dashboard.StorageCache.remove(req.query.subscriptionitemid)
    return global.api.user.subscriptions.SubscriptionItem.get(req)
  }
}

Test source (view on github)

/* eslint-env mocha */
const assert = require('assert')
const TestHelper = require('../../../../../test-helper.js')
const TestStripeAccounts = require('../../../../../test-stripe-accounts.js')
const DashboardTestHelper = require('@layeredapps/dashboard/test-helper.js')

describe('/api/user/subscriptions/set-subscription-item-quantity', function () {
  before(TestHelper.disableMetrics)
  after(TestHelper.enableMetrics)
  let cachedResponses
  async function bundledData (retryNumber) {
    if (retryNumber > 0) {
      cachedResponses = {}
    }
    if (cachedResponses && cachedResponses.finished) {
      return
    }
    cachedResponses = {}
    await TestHelper.setupBefore()
    await DashboardTestHelper.setupBeforeEach()
    await TestHelper.setupBeforeEach()
    const administrator = await TestStripeAccounts.createOwnerWithPrice()
    const user = await TestStripeAccounts.createUserWithPaidSubscription(administrator.price)
    const user2 = await TestHelper.createUser()
    // missing and invalid id
    let req = TestHelper.createRequest('/api/user/subscriptions/set-subscription-item-quantity')
    req.account = user.account
    req.session = user.session
    req.body = {
      quantity: '10'
    }
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.missing = error.message
    }
    req = TestHelper.createRequest('/api/user/subscriptions/set-subscription-item-quantity?subscriptionitemid=invalid')
    req.account = user.account
    req.session = user.session
    req.body = {
      quantity: '10'
    }
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.invalid = error.message
    }
    // invalid account
    req = TestHelper.createRequest(`/api/user/subscriptions/set-subscription-item-quantity?subscriptionitemid=${user.subscription.stripeObject.items.data[0].id}`)
    req.account = user2.account
    req.session = user2.session
    req.body = {
      quantity: '1'
    }
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.account = error.message
    }
    // invalid quantity
    req = TestHelper.createRequest(`/api/user/subscriptions/set-subscription-item-quantity?subscriptionitemid=${user.subscription.stripeObject.items.data[0].id}`)
    req.account = user.account
    req.session = user.session
    req.body = {
      quantity: 'letters'
    }
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.invalidQuantity = error.message
    }
    req = TestHelper.createRequest(`/api/user/subscriptions/set-subscription-item-quantity?subscriptionitemid=${user.subscription.stripeObject.items.data[0].id}`)
    req.account = user.account
    req.session = user.session
    req.body = {
      quantity: '1'
    }
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.unchangedQuantity = error.message
    }
    req = TestHelper.createRequest(`/api/user/subscriptions/set-subscription-item-quantity?subscriptionitemid=${user.subscription.stripeObject.items.data[0].id}`)
    req.account = user.account
    req.session = user.session
    req.body = {
      quantity: '-1'
    }
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.negativeQuantity = error.message
    }
    req = TestHelper.createRequest(`/api/user/subscriptions/set-subscription-item-quantity?subscriptionitemid=${user.subscription.stripeObject.items.data[0].id}`)
    req.account = user.account
    req.session = user.session
    req.body = {
      quantity: '0'
    }
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.zeroQuantity = error.message
    }
    // returns
    req = TestHelper.createRequest(`/api/user/subscriptions/set-subscription-item-quantity?subscriptionitemid=${user.subscription.stripeObject.items.data[0].id}`)
    req.account = user.account
    req.session = user.session
    req.body = {
      quantity: '2'
    }
    req.filename = __filename
    req.saveResponse = true
    cachedResponses.returns = await req.patch()
    cachedResponses.finished = true
  }

  describe('exceptions', () => {
    describe('invalid-subscriptionitemid', () => {
      it('missing querystring subscriptionitemid', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.missing
        assert.strictEqual(errorMessage, 'invalid-subscriptionitemid')
      })

      it('invalid querystring subscriptionitemid', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.invalid
        assert.strictEqual(errorMessage, 'invalid-subscriptionitemid')
      })
    })

    describe('invalid-account', () => {
      it('ineligible accessing account', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.account
        assert.strictEqual(errorMessage, 'invalid-account')
      })
    })

    describe('invalid-quantity', () => {
      it('invalid posted quantity', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.invalidQuantity
        assert.strictEqual(errorMessage, 'invalid-quantity')
      })

      it('invalid posted quantity is unchanged', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.unchangedQuantity
        assert.strictEqual(errorMessage, 'invalid-quantity')
      })

      it('invalid posted quantity is negative', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.negativeQuantity
        assert.strictEqual(errorMessage, 'invalid-quantity')
      })

      it('invalid posted quantity is zero', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.zeroQuantity
        assert.strictEqual(errorMessage, 'invalid-quantity')
      })
    })
  })

  describe('returns', () => {
    it('object', async () => {
      const subscriptionItemNow = cachedResponses.returns
      assert.strictEqual(subscriptionItemNow.stripeObject.quantity, 2)
    })
  })
})