Stripe Subscriptions module API explorer

/api/user/subscriptions/create-payment-method (POST)

await global.api.user.subscriptions.CreatePaymentMethod.post(req)

Returns object

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 querystring customerid
invalid-customerid missing querystring customerid
invalid-account ineligible accessing account

Receives

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

Field Value Required Type
cvc string configurable as required POST
exp_month string configurable as required POST
exp_year string configurable as required POST
name string configurable as required POST
number string configurable as required POST
token string configurable as required POST

NodeJS source (edit on github)

If you see a problem with the source submit a pull request on Github.

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

module.exports = {
  post: async (req) => {
    if (!req.query || !req.query.customerid) {
      throw new Error('invalid-customerid')
    }
    const customer = await global.api.user.subscriptions.Customer.get(req)
    if (!customer) {
      throw new Error('invalid-customer')
    }
    if (!global.stripeJS) {
      if (!req.body || !req.body.name || !req.body.name.length) {
        throw new Error('invalid-name')
      }
      if (!req.body.number || !req.body.number.length) {
        throw new Error('invalid-number')
      }
      if (!req.body.cvc || req.body.cvc.length !== 3) {
        throw new Error('invalid-cvc')
      }
      try {
        const intValue = parseInt(req.body.cvc, 10)
        if (intValue.toString() !== req.body.cvc) {
          throw new Error('invalid-cvc')
        }
      } catch (error) {
        throw new Error('invalid-cvc')
      }
      if (!req.body.exp_month || !req.body.exp_month.length) {
        throw new Error('invalid-exp_month')
      }
      try {
        const intValue = parseInt(req.body.exp_month, 10)
        if (intValue.toString() !== req.body.exp_month) {
          throw new Error('invalid-exp_month')
        }
        if (intValue < 1 || intValue > 12) {
          throw new Error('invalid-exp_month')
        }
      } catch (error) {
        throw new Error('invalid-exp_month')
      }
      if (!req.body.exp_year || !req.body.exp_year.length) {
        throw new Error('invalid-exp_year')
      }
      try {
        const intValue = parseInt(req.body.exp_year, 10)
        if (intValue.toString() !== req.body.exp_year) {
          throw new Error('invalid-exp_year')
        }
        const now = parseInt(new Date().getFullYear().toString().substring(2), 10)
        if (intValue < now || intValue > now + 10) {
          throw new Error('invalid-exp_year')
        }
      } catch (error) {
        throw new Error('invalid-exp_year')
      }
    } else if (global.stripeJS === 2 || global.stripeJS === 3) {
      if (!req.body || !req.body.token || !req.body.token.length) {
        throw new Error('invalid-token')
      }
    }
    const cardInfo = {}
    if (!global.stripeJS) {
      cardInfo.number = req.body.number
      cardInfo.cvc = req.body.cvc
      cardInfo.exp_month = req.body.exp_month
      cardInfo.exp_year = req.body.exp_year
    } else if (global.stripeJS === 2 || global.stripeJS === 3) {
      cardInfo.token = req.body.token
    }
    const billingInfo = {
      name: req.body.name,
      address: {}
    }
    for (const field of ['line1', 'line2', 'city', 'state', 'zip', 'country']) {
      if (req.body[field] && req.body[field].length) {
        billingInfo.address[field] = req.body[`address_${field}`]
      }
    }
    const paymentMethodInfo = {
      type: 'card',
      card: cardInfo,
      metadata: {
        appid: req.appid,
        accountid: req.account.accountid
      }
    }
    if (global.stripeJS === false) {
      paymentMethodInfo.billing_details = billingInfo
    }
    let paymentMethod = await stripeCache.execute('paymentMethods', 'create', paymentMethodInfo, req.stripeKey)
    paymentMethod = await stripeCache.execute('paymentMethods', 'attach', paymentMethod.id, {
      customer: req.query.customerid
    }, req.stripeKey)
    const setupIntent = await stripeCache.execute('setupIntents', 'create', {
      customer: req.query.customerid,
      payment_method: paymentMethod.id,
      usage: 'off_session'
    }, req.stripeKey)
    await stripeCache.update(setupIntent)
    if (req.body.default === 'true') {
      const customerNow = await stripeCache.execute('customers', 'update', req.query.customerid, {
        invoice_settings: {
          default_payment_method: paymentMethod.id
        }
      }, req.stripeKey)
      await stripeCache.update(customerNow)
    }
    await stripeCache.update(paymentMethod)
    await subscriptions.StorageList.addMany({
      [`${req.appid}/paymentMethods`]: paymentMethod.id,
      [`${req.appid}/customer/paymentMethods/${req.query.customerid}`]: paymentMethod.id,
      [`${req.appid}/account/paymentMethods/${req.account.accountid}`]: paymentMethod.id,
      [`${req.appid}/setupIntents`]: setupIntent.id,
      [`${req.appid}/customer/setupIntents/${req.query.customerid}`]: setupIntent.id,
      [`${req.appid}/account/setupIntents/${req.account.accountid}`]: setupIntent.id
    })
    return paymentMethod
  }
}

Test source (edit on github)

Tests perform real HTTP requests against a running Dashboard server.

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

describe('/api/user/subscriptions/create-payment-method', () => {
  describe('exceptions', () => {
    describe('invalid-customerid', () => {
      it('missing querystring customerid', async () => {
        global.stripeJS = false
        const user = await TestHelper.createUser()
        const req = TestHelper.createRequest('/api/user/subscriptions/create-payment-method')
        req.account = user.account
        req.session = user.session
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-customerid')
      })

      it('invalid querystring customerid', async () => {
        global.stripeJS = false
        const user = await TestHelper.createUser()
        const req = TestHelper.createRequest('/api/user/subscriptions/create-payment-method?customerid=invalid')
        req.account = user.account
        req.session = user.session
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-customerid')
      })
    })

    describe('invalid-account', () => {
      it('ineligible accessing account', async () => {
        const user = await TestHelper.createUser()
        await TestHelper.createCustomer(user, {
          email: user.profile.contactEmail,
          description: user.profile.firstName
        })
        const user2 = await TestHelper.createUser()
        const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.id}`)
        req.account = user2.account
        req.session = user2.session
        req.body = {
          name: `${user2.profile.firstName} ${user2.profile.lastName}`,
          cvc: '111',
          number: '4111111111111111',
          exp_month: '1',
          exp_year: (new Date().getFullYear() + 1).toString().substring(2),
          address_line1: 'A street address',
          address_city: 'City',
          address_state: 'NY',
          address_zip: '90120',
          address_country: 'US',
          default: 'true'
        }
        let errorMessage
        try {
          await req.post()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-account')
      })
    })
  })

  describe('receives', () => {
    it('optionally-required posted name', async () => {
      global.stripeJS = false
      const user = await TestHelper.createUser()
      await TestHelper.createCustomer(user, {
        email: user.profile.contactEmail,
        description: user.profile.firstName
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.id}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        email: user.profile.contactEmail,
        description: 'description',
        name: '',
        cvc: '111',
        number: '4111111111111111',
        exp_month: '1',
        exp_year: (new Date().getFullYear() + 1).toString().substring(2)
      }
      let errorMessage
      try {
        await req.post()
      } catch (error) {
        errorMessage = error.message
      }
      assert.strictEqual(errorMessage, 'invalid-name')
    })

    it('optionally-required posted cvc', async () => {
      global.stripeJS = false
      const user = await TestHelper.createUser()
      await TestHelper.createCustomer(user, {
        email: user.profile.contactEmail,
        description: user.profile.firstName
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.id}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        email: user.profile.contactEmail,
        description: 'description',
        name: `${user.profile.firstName} ${user.profile.lastName}`,
        cvc: '0',
        number: '4111111111111111',
        exp_month: '1',
        exp_year: (new Date().getFullYear() + 1).toString().substring(2)
      }
      let errorMessage
      try {
        await req.post()
      } catch (error) {
        errorMessage = error.message
      }
      assert.strictEqual(errorMessage, 'invalid-cvc')
    })

    it('optionally-required posted number', async () => {
      global.stripeJS = false
      const user = await TestHelper.createUser()
      await TestHelper.createCustomer(user, {
        email: user.profile.contactEmail,
        description: user.profile.firstName
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.id}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        email: user.profile.contactEmail,
        description: 'description',
        name: `${user.profile.firstName} ${user.profile.lastName}`,
        cvc: '123',
        number: '',
        exp_month: '1',
        exp_year: (new Date().getFullYear() + 1).toString().substring(2)
      }
      let errorMessage
      try {
        await req.post()
      } catch (error) {
        errorMessage = error.message
      }
      assert.strictEqual(errorMessage, 'invalid-number')
    })

    it('optionally-required posted exp_month', async () => {
      global.stripeJS = false
      const user = await TestHelper.createUser()
      await TestHelper.createCustomer(user, {
        email: user.profile.contactEmail,
        description: user.profile.firstName
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.id}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        email: user.profile.contactEmail,
        description: 'description',
        name: `${user.profile.firstName} ${user.profile.lastName}`,
        cvc: '123',
        number: '4111111111111111',
        exp_month: '',
        exp_year: (new Date().getFullYear() + 1).toString().substring(2)
      }
      let errorMessage
      try {
        await req.post()
      } catch (error) {
        errorMessage = error.message
      }
      assert.strictEqual(errorMessage, 'invalid-exp_month')
    })

    it('optionally-required posted exp_year', async () => {
      global.stripeJS = false
      const user = await TestHelper.createUser()
      await TestHelper.createCustomer(user, {
        email: user.profile.contactEmail,
        description: user.profile.firstName
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.id}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        email: user.profile.contactEmail,
        description: 'description',
        name: `${user.profile.firstName} ${user.profile.lastName}`,
        cvc: '123',
        number: '4111111111111111',
        exp_month: '1',
        exp_year: ''
      }
      let errorMessage
      try {
        await req.post()
      } catch (error) {
        errorMessage = error.message
      }
      assert.strictEqual(errorMessage, 'invalid-exp_year')
    })

    it('optionally-required posted token', async () => {
      global.stripeJS = 2
      const user = await TestHelper.createUser()
      await TestHelper.createCustomer(user, {
        email: user.profile.contactEmail,
        description: user.profile.firstName
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.id}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        email: user.profile.contactEmail,
        description: 'description',
        name: `${user.profile.firstName} ${user.profile.lastName}`,
        token: ''
      }
      let errorMessage
      try {
        await req.post()
      } catch (error) {
        errorMessage = error.message
      }
      assert.strictEqual(errorMessage, 'invalid-token')
    })
  })

  describe('returns', () => {
    it('object', async () => {
      global.stripeJS = false
      const user = await TestHelper.createUser()
      await TestHelper.createCustomer(user, {
        email: user.profile.contactEmail,
        description: user.profile.firstName
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.id}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        name: `${user.profile.firstName} ${user.profile.lastName}`,
        cvc: '111',
        number: '4111111111111111',
        exp_month: '1',
        exp_year: (new Date().getFullYear() + 1).toString().substring(2),
        address_line1: 'A street address',
        address_city: 'City',
        address_state: 'NY',
        address_zip: '90120',
        address_country: 'US',
        default: 'true'
      }
      req.filename = __filename
      req.saveResponse = true
      const paymentMethod = await req.post()
      assert.strictEqual(paymentMethod.object, 'payment_method')
    })
  })

  describe('configuration', () => {
    it('environment STRIPE_JS', async () => {
      global.stripeJS = 2
      const user = await TestHelper.createUser()
      await TestHelper.createCustomer(user, {
        email: user.profile.contactEmail,
        description: user.profile.firstName
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.id}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        name: `${user.profile.firstName} ${user.profile.lastName}`,
        cvc: '111',
        number: '4111111111111111',
        exp_month: '1',
        exp_year: (new Date().getFullYear() + 1).toString().substring(2),
        address_line1: 'A street address',
        address_city: 'City',
        address_state: 'NY',
        address_zip: '90120',
        address_country: 'US',
        default: 'true'
      }
      let errorMessage
      try {
        await req.post()
      } catch (error) {
        errorMessage = error.message
      }
      assert.strictEqual(errorMessage, 'invalid-token')
    })
  })
})