Stripe Subscriptions module API explorer

/api/administrator/subscriptions/create-coupon (POST)

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

await global.api.administrator.subscriptions.CreateCoupon.post(req)

Returns object

{
  "couponid": "CUSTOM18",
  "object": "coupon",
  "stripeObject": {
    "id": "CUSTOM18",
    "object": "coupon",
    "amount_off": 10,
    "created": 1656122269,
    "currency": "usd",
    "duration": "once",
    "duration_in_months": null,
    "livemode": false,
    "max_redemptions": null,
    "metadata": {},
    "name": "my coupon",
    "percent_off": null,
    "redeem_by": null,
    "times_redeemed": 0,
    "valid": true
  },
  "active": null,
  "appid": "tests_1656122269",
  "createdAt": "2022-06-25T01:57:49.668Z",
  "updatedAt": "2022-06-25T01:57:49.668Z"
}

Receives

API routes may receive parameters from the URL and POST supporting simple and multipart:

Field Value Required Type
amount_off integer, or percent_off configurable as required POST
currency string, if amount_off configurable as required POST
duration string required POST
duration_in_months string configurable as required POST
percent_off integer, or amount_off configurable as required POST
redeem_by date in future configurable as 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
duplicate-couponid invalid posted couponid is already used
invalid-amount_off missing posted amount_off
invalid posted amount_off
invalid-couponid missing posted couponid
invalid posted couponid is not alphanumeric
invalid-currency missing posted currency
invalid posted currency
invalid-duration invalid posted duration
invalid-duration_in_months missing posted duration_in_months
invalid posted duration_in_months
invalid-name missing posted name
invalid-percent_off invalid posted percent_off
invalid-redeem_by invalid posted redeem_by

NodeJS source (view on github)

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

module.exports = {
  post: async (req) => {
    if (!req.body || !req.body.couponid) {
      throw new Error('invalid-couponid')
    }
    if (!req.body.couponid.match(/^[a-zA-Z0-9_]+$/) ||
      global.minimumCouponLength > req.body.couponid.length ||
      global.maximumCouponLength < req.body.couponid.length) {
      throw new Error('invalid-couponid')
    }
    if (!req.body.name) {
      throw new Error('invalid-name')
    }
    if (req.body.amount_off) {
      try {
        req.body.amount_off = parseInt(req.body.amount_off, 10)
        if (!req.body.amount_off) {
          throw new Error('invalid-amount_off')
        }
      } catch (s) {
        throw new Error('invalid-amount_off')
      }
      if (req.body.amount_off < 0) {
        throw new Error('invalid-amount_off')
      }
      if (!req.body.currency || req.body.currency.length !== 3) {
        throw new Error('invalid-currency')
      }
    } else if (req.body.percent_off) {
      try {
        req.body.percent_off = parseInt(req.body.percent_off, 10)
        if (!req.body.percent_off) {
          throw new Error('invalid-percent_off')
        }
      } catch (s) {
        throw new Error('invalid-percent_off')
      }
      if (req.body.percent_off < 0 || req.body.percent_off > 100) {
        throw new Error('invalid-percent_off')
      }
    }
    if (!req.body.amount_off && !req.body.percent_off) {
      throw new Error('invalid-amount_off')
    }
    if (req.body.duration !== 'once' && req.body.duration !== 'repeating' && req.body.duration !== 'forever') {
      throw new Error('invalid-duration')
    }
    if (req.body.duration === 'repeating') {
      if (req.body.duration_in_months) {
        try {
          req.body.duration_in_months = parseInt(req.body.duration_in_months, 10)
          if (!req.body.duration_in_months) {
            throw new Error('invalid-duration_in_months')
          }
        } catch (s) {
          throw new Error('invalid-duration_in_months')
        }
        if (req.body.duration_in_months < 1 || req.body.duration_in_months > 24) {
          throw new Error('invalid-duration_in_months')
        }
      } else {
        throw new Error('invalid-duration_in_months')
      }
    }
    if (req.body.max_redemptions) {
      try {
        req.body.max_redemptions = parseInt(req.body.max_redemptions, 10)
        if (!req.body.max_redemptions) {
          throw new Error('invalid-max_redemptions')
        }
      } catch (s) {
        throw new Error('invalid-max_redemptions')
      }
      if (req.body.max_redemptions < 0) {
        throw new Error('invalid-max_redemptions')
      }
    }
    const couponInfo = {
      id: req.body.couponid,
      duration: req.body.duration || null,
      name: req.body.name
    }
    if (req.body.redeem_by) {
      try {
        const redeemDate = new Date(Date.parse(req.body.redeem_by))
        const now = new Date()
        if (redeemDate.getTime() < now.getTime()) {
          throw new Error('invalid-redeem_by')
        }
        couponInfo.redeem_by = Math.floor(redeemDate.getTime() / 1000)
      } catch (error) {
        throw new Error('invalid-redeem_by')
      }
    }
    if (req.body.amount_off) {
      couponInfo.amount_off = req.body.amount_off
      couponInfo.currency = req.body.currency
    } else {
      couponInfo.percent_off = req.body.percent_off
    }
    if (req.body.duration_in_months) {
      couponInfo.duration_in_months = req.body.duration_in_months
    }
    if (req.body.max_redemptions) {
      couponInfo.max_redemptions = req.body.max_redemptions
    }
    const coupon = await stripeCache.execute('coupons', 'create', couponInfo, req.stripeKey)
    await subscriptions.Storage.Coupon.create({
      appid: req.appid || global.appid,
      couponid: coupon.id,
      stripeObject: coupon
    })
    req.query = req.query || {}
    req.query.couponid = coupon.id
    return global.api.administrator.subscriptions.Coupon.get(req)
  }
}

Test source (view on github)

/* eslint-env mocha */
const assert = require('assert')
const TestHelper = require('../../../../../test-helper.js')

describe('/api/administrator/subscriptions/create-coupon', function () {
  before(TestHelper.disableMetrics)
  after(TestHelper.enableMetrics)
  describe('exceptions', () => {
    describe('invalid-couponid', () => {
      it('missing posted couponid', async () => {
        const administrator = await TestHelper.createOwner()
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: '',
          name: 'my coupon',
          amount_off: '10',
          percent_off: ''
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-couponid')
      })

      it('invalid posted couponid is not alphanumeric', async () => {
        const administrator = await TestHelper.createOwner()
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: '#$%@#$%@#$%',
          name: 'my coupon',
          amount_off: '10',
          percent_off: ''
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-couponid')
      })
    })

    describe('duplicate-couponid', () => {
      it('invalid posted couponid is already used', async () => {
        const administrator = await TestHelper.createOwner()
        await TestHelper.createCoupon(administrator, {
          couponid: 'CUSTOM1',
          name: 'my coupon',
          percent_off: '10',
          duration: 'once'
        })
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: 'CUSTOM1',
          name: 'my coupon',
          percent_off: '10',
          duration: 'once'
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'duplicate-couponid')
      })
    })

    describe('invalid-name', () => {
      it('missing posted name', async () => {
        const administrator = await TestHelper.createOwner()
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: 'CUSTOM18',
          name: '',
          amount_off: '10',
          currency: 'usd',
          duration: 'once'
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-name')
      })
    })

    describe('invalid-amount_off', () => {
      it('missing posted amount_off', async () => {
        const administrator = await TestHelper.createOwner()
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: 'CUSTOM2',
          name: 'my coupon',
          amount_off: '',
          percent_off: ''
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-amount_off')
      })

      it('invalid posted amount_off', async () => {
        const administrator = await TestHelper.createOwner()
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: 'CUSTOM3',
          name: 'my coupon',
          amount_off: 'invalid',
          percent_off: ''
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-amount_off')
      })
    })

    describe('invalid-currency', () => {
      it('missing posted currency', async () => {
        const administrator = await TestHelper.createOwner()
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: 'CUSTOM4',
          name: 'my coupon',
          amount_off: '1',
          currency: ''
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-currency')
      })

      it('invalid posted currency', async () => {
        const administrator = await TestHelper.createOwner()
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: 'CUSTOM5',
          name: 'my coupon',
          amount_off: '1',
          currency: 'invalid'
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-currency')
      })
    })

    describe('invalid-percent_off', () => {
      it('invalid posted percent_off', async () => {
        const administrator = await TestHelper.createOwner()
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: 'CUSTOM6',
          name: 'my coupon',
          percent_off: 'invalid'
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-percent_off')
        req.body = {
          couponid: 'CUSTOM7',
          name: 'my coupon',
          percent_off: '101'
        }
        errorMessage = null
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-percent_off')
      })
    })

    describe('invalid-duration', () => {
      it('invalid posted duration', async () => {
        const administrator = await TestHelper.createOwner()
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: 'CUSTOM8',
          name: 'my coupon',
          amount_off: '10',
          currency: 'usd',
          duration: 'invalid'
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-duration')
      })
    })

    describe('invalid-duration_in_months', () => {
      it('missing posted duration_in_months', async () => {
        const administrator = await TestHelper.createOwner()
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: 'CUSTOM9',
          name: 'my coupon',
          amount_off: '10',
          currency: 'usd',
          duration: 'repeating',
          duration_in_months: ''
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-duration_in_months')
      })

      it('invalid posted duration_in_months', async () => {
        const administrator = await TestHelper.createOwner()
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: 'CUSTOM10',
          name: 'my coupon',
          amount_off: '10',
          currency: 'usd',
          duration: 'repeating',
          duration_in_months: 'invalid'
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-duration_in_months')
      })
    })

    describe('invalid-redeem_by', () => {
      it('invalid posted redeem_by', async () => {
        const administrator = await TestHelper.createOwner()
        const now = new Date()
        const lastYear = new Date(now.getFullYear() - 1, 1, 12, 47, 33)
        const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
        req.account = administrator.account
        req.session = administrator.session
        req.body = {
          couponid: 'CUSTOM11',
          name: 'my coupon',
          amount_off: '10',
          currency: 'usd',
          duration: 'repeating',
          duration_in_months: '1',
          redeem_by: lastYear.toISOString()
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-redeem_by')
      })
    })
  })

  describe('receives', () => {
    it('required posted duration', async () => {
      const administrator = await TestHelper.createOwner()
      const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
      req.account = administrator.account
      req.session = administrator.session
      req.body = {
        couponid: 'CUSTOM12',
        name: 'my coupon',
        amount_off: '10',
        currency: 'usd',
        duration: 'repeating',
        duration_in_months: '8'
      }
      req.filename = __filename
      req.saveResponse = true
      const coupon = await req.post()
      assert.strictEqual(coupon.stripeObject.duration, 'repeating')
    })

    it('optionally-required posted amount_off (integer, or percent_off)', async () => {
      const administrator = await TestHelper.createOwner()
      const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
      req.account = administrator.account
      req.session = administrator.session
      req.body = {
        couponid: 'CUSTOM13',
        name: 'my coupon',
        amount_off: '10',
        currency: 'usd',
        duration: 'once'
      }
      req.filename = __filename
      req.saveResponse = true
      const coupon = await req.post()
      assert.strictEqual(coupon.stripeObject.amount_off, 10)
    })

    it('optionally-required posted currency (string, if amount_off)', async () => {
      const administrator = await TestHelper.createOwner()
      const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
      req.account = administrator.account
      req.session = administrator.session
      req.body = {
        couponid: 'CUSTOM14',
        name: 'my coupon',
        amount_off: '10',
        currency: 'aud',
        duration: 'once'
      }
      req.filename = __filename
      req.saveResponse = true
      const coupon = await req.post()
      assert.strictEqual(coupon.stripeObject.currency, 'aud')
    })

    it('optionally-required posted percent_off (integer, or amount_off)', async () => {
      const administrator = await TestHelper.createOwner()
      const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
      req.account = administrator.account
      req.session = administrator.session
      req.body = {
        couponid: 'CUSTOM15',
        name: 'my coupon',
        percent_off: '10',
        currency: 'usd',
        duration: 'once'
      }
      req.filename = __filename
      req.saveResponse = true
      const coupon = await req.post()
      assert.strictEqual(coupon.stripeObject.percent_off, 10)
    })

    it('optionally-required posted redeem_by (date in future)', async () => {
      const administrator = await TestHelper.createOwner()
      const now = new Date()
      const date = new Date(now.getFullYear() + 1, 1, 1, 12, 47, 33)
      const timestamp = Math.floor(date.getTime() / 1000)
      const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
      req.account = administrator.account
      req.session = administrator.session
      req.body = {
        couponid: 'CUSTOM16',
        name: 'my coupon',
        percent_off: '10',
        currency: 'usd',
        duration: 'once',
        redeem_by: date.toISOString()
      }
      req.filename = __filename
      req.saveResponse = true
      const coupon = await req.post()
      assert.strictEqual(coupon.stripeObject.redeem_by, timestamp)
    })

    it('optionally-required posted duration_in_months', async () => {
      const administrator = await TestHelper.createOwner()
      const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
      req.account = administrator.account
      req.session = administrator.session
      req.body = {
        couponid: 'CUSTOM17',
        name: 'my coupon',
        percent_off: '10',
        currency: 'usd',
        duration: 'repeating',
        duration_in_months: '6'
      }
      req.filename = __filename
      req.saveResponse = true
      const coupon = await req.post()
      assert.strictEqual(coupon.stripeObject.duration_in_months, 6)
    })
  })

  describe('returns', () => {
    it('object', async () => {
      const administrator = await TestHelper.createOwner()
      const req = TestHelper.createRequest('/api/administrator/subscriptions/create-coupon')
      req.account = administrator.account
      req.session = administrator.session
      req.body = {
        couponid: 'CUSTOM18',
        name: 'my coupon',
        amount_off: '10',
        currency: 'usd',
        duration: 'once'
      }
      req.filename = __filename
      req.saveResponse = true
      const coupon = await req.post()
      assert.strictEqual(coupon.object, 'coupon')
    })
  })
})