/api/user/subscriptions/create-usage-record (POST)
Account information like email addresses is generated with faker-js it is not real user information.
await global.api.user.subscriptions.CreateUsageRecord.post(req)Returns object
{
"usagerecordid": "mbur_1LEOdvHHqepMFuCXX4mnTjeQ",
"object": "usagerecord",
"stripeObject": {
"id": "mbur_1LEOdvHHqepMFuCXX4mnTjeQ",
"object": "usage_record",
"livemode": false,
"quantity": 400,
"subscription_item": "si_LwHCaawYvGMBtB",
"timestamp": 1656123758
},
"customerid": "cus_LwHCFzn9o1EEsR",
"accountid": "acct_bca7369ef4ad8873",
"subscriptionid": "sub_1LEOdfHHqepMFuCXUSGSS2rj",
"subscriptionitemid": "si_LwHCaawYvGMBtB",
"appid": "tests_1656123739",
"createdAt": "2022-06-25T02:22:39.311Z",
"updatedAt": "2022-06-25T02:22:39.311Z"
}
Receives
API routes may receive parameters from the URL and POST supporting simple and multipart:
Field | Value | Required | Type |
---|---|---|---|
action | string | required | POST |
quantity | string | required | POST |
subscriptionitemid | string | required | POST |
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-action | missing posted action |
invalid posted action | |
invalid-quantity | invalid posted quantity is not integer |
invalid posted quantity is negative | |
invalid-subscription | invalid querystring subscription is not "metered" |
invalid-subscriptionid | missing querystring subscriptionid |
invalid querystring subscriptionid | |
invalid-subscriptionitemid | missing posted subscriptionitemid |
invalid posted subscriptionitemid |
NodeJS source (view on github)
const stripeCache = require('../../../../stripe-cache.js')
const subscriptions = require('../../../../../index.js')
module.exports = {
post: async (req) => {
if (!req.query || !req.query.subscriptionid) {
throw new Error('invalid-subscriptionid')
}
const subscription = await global.api.user.subscriptions.Subscription.get(req)
if (!subscription) {
throw new Error('invalid-subscriptionid')
}
if (!req.body) {
throw new Error('invalid-quantity')
}
try {
const quantity = parseInt(req.body.quantity, 10)
if (req.body.quantity !== quantity.toString()) {
throw new Error('invalid-quantity')
}
} catch (s) {
throw new Error('invalid-quantity')
}
if (req.body.quantity < 0) {
throw new Error('invalid-quantity')
}
if (!req.body.action || (req.body.action !== 'increment' && req.body.action !== 'set')) {
throw new Error('invalid-action')
}
if (!req.body.subscriptionitemid || !req.body.subscriptionitemid.length) {
throw new Error('invalid-subscriptionitemid')
}
let found = false
for (const item of subscription.stripeObject.items.data) {
found = item.id === req.body.subscriptionitemid
if (found) {
break
}
}
if (!found) {
throw new Error('invalid-subscriptionitemid')
}
const usageInfo = {
action: req.body.action,
quantity: req.body.quantity
}
if (Math.floor(new Date().getTime() / 1000) >= subscription.stripeObject.current_period_start) {
usageInfo.timestamp = Math.floor(new Date().getTime() / 1000)
} else {
usageInfo.timestamp = subscription.stripeObject.current_period_start
}
let usageRecord
try {
usageRecord = await stripeCache.execute('subscriptionItems', 'createUsageRecord', req.body.subscriptionitemid, usageInfo, req.stripeKey)
} catch (error) {
if (error.message === 'invalid-subscriptionitemid' || error.message === 'invalid-subscription') {
throw new Error('invalid-subscription')
}
}
if (!usageRecord) {
throw new Error('invalid-usagerecord')
}
await subscriptions.Storage.UsageRecord.create({
appid: req.appid || global.appid,
usagerecordid: usageRecord.id,
stripeObject: usageRecord,
customerid: subscription.stripeObject.customer,
accountid: req.account.accountid,
subscriptionid: req.query.subscriptionid,
subscriptionitemid: req.body.subscriptionitemid
})
req.query.usagerecordid = usageRecord.id
return global.api.user.subscriptions.UsageRecord.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/create-usage-record', 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({
active: 'true',
unit_amount: 3000,
currency: 'usd',
tax_behavior: 'inclusive',
recurring_interval: 'month',
recurring_interval_count: '1',
recurring_usage_type: 'metered',
recurring_aggregate_usage: 'sum'
})
const user = await TestStripeAccounts.createUserWithPaidSubscription(administrator.price)
// invalid / missing subscriptionid
const req = TestHelper.createRequest('/api/user/subscriptions/create-usage-record')
req.account = user.account
req.session = user.session
req.body = {
quantity: 'abcde',
action: 'set',
subscriptionitemid: 'fake'
}
try {
await req.post()
} catch (error) {
cachedResponses.missing = error.message
}
const req2 = TestHelper.createRequest('/api/user/subscriptions/create-usage-record?subscriptionid=invalid')
req2.account = user.account
req2.session = user.session
req2.body = {
quantity: 'abcde',
action: 'set',
subscriptionitemid: 'fake'
}
try {
await req2.post()
} catch (error) {
cachedResponses.invalid = error.message
}
// invalid subscription
const price2 = await TestHelper.createPrice(administrator, {
productid: administrator.product.productid,
unit_amount: 3000,
currency: 'usd',
recurring_interval: 'month',
recurring_interval_count: '1',
recurring_usage_type: 'licensed',
recurring_aggregate_usage: 'sum',
tax_behavior: 'inclusive',
active: 'true'
})
const user2 = await TestStripeAccounts.createUserWithPaidSubscription(price2)
const req3 = TestHelper.createRequest(`/api/user/subscriptions/create-usage-record?subscriptionid=${user2.subscription.subscriptionid}`)
req3.account = user2.account
req3.session = user2.session
req3.body = {
quantity: '10',
action: 'set',
subscriptionitemid: user2.subscription.stripeObject.items.data[0].id
}
try {
await req3.post()
} catch (error) {
cachedResponses.invalidSubscription = error.message
}
// invalid account
const req4 = TestHelper.createRequest(`/api/user/subscriptions/create-usage-record?subscriptionid=${user.subscription.subscriptionid}`)
req4.account = user2.account
req4.session = user2.session
try {
await req4.post()
} catch (error) {
cachedResponses.invalidAccount = error.message
}
// invalid quantity
const req5 = TestHelper.createRequest(`/api/user/subscriptions/create-usage-record?subscriptionid=${user.subscription.subscriptionid}`)
req5.account = user.account
req5.session = user.session
req5.body = {
quantity: 'abcde',
action: 'set',
subscriptionitemid: user.subscription.stripeObject.items.data[0].id
}
try {
await req5.post()
} catch (error) {
cachedResponses.invalidQuantity = error.message
}
const req6 = TestHelper.createRequest(`/api/user/subscriptions/create-usage-record?subscriptionid=${user.subscription.subscriptionid}`)
req6.account = user.account
req6.session = user.session
req6.body = {
quantity: '-20',
action: 'set',
subscriptionitemid: user.subscription.stripeObject.items.data[0].id
}
try {
await req6.post()
} catch (error) {
cachedResponses.negativeQuantity = error.message
}
// invalid action
const req7 = TestHelper.createRequest(`/api/user/subscriptions/create-usage-record?subscriptionid=${user.subscription.subscriptionid}`)
req7.account = user.account
req7.session = user.session
req7.body = {
quantity: '30',
action: '',
subscriptionitemid: user.subscription.stripeObject.items.data[0].id
}
try {
await req7.post()
} catch (error) {
cachedResponses.missingAction = error.message
}
req7.body = {
quantity: '40',
action: 'invalid',
subscriptionitemid: user.subscription.stripeObject.items.data[0].id
}
try {
await req7.post()
} catch (error) {
cachedResponses.invalidAction = error.message
}
// missing and invalid subscriptionitemid
req7.body = {
quantity: '50',
action: 'set',
subscriptionitemid: ''
}
try {
await req7.post()
} catch (error) {
cachedResponses.missingItem = error.message
}
req7.body = {
quantity: '60',
action: 'set',
subscriptionitemid: 'invalid'
}
try {
await req7.post()
} catch (error) {
cachedResponses.invalidItem = error.message
}
// quantity
const req8 = TestHelper.createRequest(`/api/user/subscriptions/create-usage-record?subscriptionid=${user.subscription.subscriptionid}`)
req8.account = user.account
req8.session = user.session
req8.body = {
quantity: '70',
action: 'set',
subscriptionitemid: user.subscription.stripeObject.items.data[0].id
}
cachedResponses.quantity = await req8.post()
await TestHelper.wait(1000)
// action
req8.body = {
quantity: '200',
action: 'set',
subscriptionitemid: user.subscription.stripeObject.items.data[0].id
}
cachedResponses.action = await req8.post()
await TestHelper.wait(1000)
// item
req8.body = {
quantity: '300',
action: 'set',
subscriptionitemid: user.subscription.stripeObject.items.data[0].id
}
cachedResponses.item = await req8.post()
await TestHelper.wait(1000)
// response
const req9 = TestHelper.createRequest(`/api/user/subscriptions/create-usage-record?subscriptionid=${user.subscription.subscriptionid}`)
req9.account = user.account
req9.session = user.session
req9.body = {
quantity: '400',
action: 'set',
subscriptionitemid: user.subscription.stripeObject.items.data[0].id
}
req9.filename = __filename
req9.saveResponse = true
await TestHelper.wait(1000)
cachedResponses.returns = await req9.post()
cachedResponses.finished = true
}
describe('exceptions', () => {
describe('invalid-subscriptionid', () => {
it('missing querystring subscriptionid', async function () {
await bundledData(this.test.currentRetry())
const errorMessage = cachedResponses.missing
assert.strictEqual(errorMessage, 'invalid-subscriptionid')
})
it('invalid querystring subscriptionid', async function () {
await bundledData(this.test.currentRetry())
const errorMessage = cachedResponses.invalid
assert.strictEqual(errorMessage, 'invalid-subscriptionid')
})
})
describe('invalid-subscription', () => {
it('invalid querystring subscription is not "metered"', async function () {
await bundledData(this.test.currentRetry())
const errorMessage = cachedResponses.invalidSubscription
assert.strictEqual(errorMessage, 'invalid-subscription')
})
})
describe('invalid-account', () => {
it('ineligible accessing account', async function () {
await bundledData(this.test.currentRetry())
const errorMessage = cachedResponses.invalidAccount
assert.strictEqual(errorMessage, 'invalid-account')
})
})
describe('invalid-quantity', () => {
it('invalid posted quantity is not integer', async function () {
await bundledData(this.test.currentRetry())
const errorMessage = cachedResponses.invalidQuantity
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')
})
})
describe('invalid-action', () => {
it('missing posted action', async function () {
await bundledData(this.test.currentRetry())
const errorMessage = cachedResponses.missingAction
assert.strictEqual(errorMessage, 'invalid-action')
})
it('invalid posted action', async function () {
await bundledData(this.test.currentRetry())
const errorMessage = cachedResponses.invalidAction
assert.strictEqual(errorMessage, 'invalid-action')
})
})
describe('invalid-subscriptionitemid', () => {
it('missing posted subscriptionitemid', async function () {
await bundledData(this.test.currentRetry())
const errorMessage = cachedResponses.missingItem
assert.strictEqual(errorMessage, 'invalid-subscriptionitemid')
})
it('invalid posted subscriptionitemid', async function () {
await bundledData(this.test.currentRetry())
const errorMessage = cachedResponses.invalidItem
assert.strictEqual(errorMessage, 'invalid-subscriptionitemid')
})
})
})
describe('receives', () => {
it('required posted quantity', async () => {
const usageRecord = cachedResponses.quantity
assert.strictEqual(usageRecord.stripeObject.quantity, 70)
})
it('required posted action', async () => {
const usageRecord = cachedResponses.action
assert.strictEqual(usageRecord.stripeObject.quantity, 200)
// TODO: actions can be verified setting/incrementing
// when the usage record summaries are available
})
it('required posted subscriptionitemid', async () => {
const usageRecord = cachedResponses.item
assert.notStrictEqual(usageRecord.subscriptionitemid, null)
assert.notStrictEqual(usageRecord.subscriptionitemid, undefined)
})
})
describe('returns', () => {
it('object', async () => {
const usageRecord = cachedResponses.returns
assert.strictEqual(usageRecord.object, 'usagerecord')
})
})
})