Stripe Subscriptions module API explorer

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

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

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

Returns object

{
  "paymentmethodid": "pm_1LEObsHHqepMFuCXyvhecH4d",
  "object": "paymentmethod",
  "accountid": "acct_f101ed737dc761d2",
  "customerid": "cus_LwHA1TuiIt5S6t",
  "stripeObject": {
    "id": "pm_1LEObsHHqepMFuCXyvhecH4d",
    "object": "payment_method",
    "billing_details": {
      "address": {
        "city": "City",
        "country": "US",
        "line1": "A street address",
        "line2": null,
        "postal_code": "90120",
        "state": "NY"
      },
      "email": null,
      "name": "Harriet Homenick",
      "phone": null
    },
    "card": {
      "brand": "visa",
      "checks": {
        "address_line1_check": "pass",
        "address_postal_code_check": "pass",
        "cvc_check": "pass"
      },
      "country": "US",
      "exp_month": 1,
      "exp_year": 2023,
      "fingerprint": "IRcdqfBUCskmPkNV",
      "funding": "credit",
      "generated_from": null,
      "last4": "1111",
      "networks": {
        "available": [
          "visa"
        ],
        "preferred": null
      },
      "three_d_secure_usage": {
        "supported": true
      },
      "wallet": null
    },
    "created": 1656123632,
    "customer": "cus_LwHA1TuiIt5S6t",
    "livemode": false,
    "metadata": {},
    "type": "card"
  },
  "appid": "tests_1656123632",
  "createdAt": "2022-06-25T02:20:33.754Z",
  "updatedAt": "2022-06-25T02:20:34.374Z"
}

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

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

NodeJS source (view 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', 'postal_code', 'country']) {
      if (req.body[field] && req.body[field].length) {
        billingInfo.address[field] = req.body[field]
      }
    }
    const paymentMethodInfo = {
      type: 'card',
      card: cardInfo
    }
    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)
    await subscriptions.Storage.PaymentMethod.create({
      appid: req.appid || global.appid,
      customerid: req.query.customerid,
      paymentmethodid: paymentMethod.id,
      accountid: req.account.accountid,
      stripeObject: paymentMethod
    })
    await stripeCache.execute('setupIntents', 'create', {
      customer: req.query.customerid,
      payment_method: paymentMethod.id,
      usage: 'off_session'
    }, req.stripeKey)
    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 subscriptions.Storage.Customer.update({
        stripeObject: customerNow
      }, {
        where: {
          customerid: req.query.customerid,
          appid: req.appid || global.appid
        }
      })
    }
    req.query.paymentmethodid = paymentMethod.id
    return global.api.user.subscriptions.PaymentMethod.get(req)
  }
}

Test source (view on github)

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

describe('/api/user/subscriptions/create-payment-method', function () {
  before(TestHelper.disableMetrics)
  after(TestHelper.enableMetrics)
  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
        })
        const user2 = await TestHelper.createUser()
        const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.customerid}`)
        req.account = user2.account
        req.session = user2.session
        req.body = {
          name: user2.profile.fullName,
          cvc: '111',
          number: '4111111111111111',
          exp_month: '1',
          exp_year: (new Date().getFullYear() + 1).toString().substring(2),
          line1: 'A street address',
          city: 'City',
          state: 'NY',
          postal_code: '90120',
          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
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.customerid}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        email: user.profile.contactEmail,
        description: 'Chase Sapphire',
        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
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.customerid}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        email: user.profile.contactEmail,
        description: 'Chase Sapphire',
        name: user.profile.fullName,
        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
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.customerid}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        email: user.profile.contactEmail,
        description: 'Chase Sapphire',
        name: user.profile.fullName,
        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
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.customerid}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        email: user.profile.contactEmail,
        description: 'Chase Sapphire',
        name: user.profile.fullName,
        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
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.customerid}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        email: user.profile.contactEmail,
        description: 'Chase Sapphire',
        name: user.profile.fullName,
        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
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.customerid}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        email: user.profile.contactEmail,
        description: 'Chase Sapphire',
        name: user.profile.fullName,
        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
      })
      const req = TestHelper.createRequest(`/api/user/subscriptions/create-payment-method?customerid=${user.customer.customerid}`)
      req.account = user.account
      req.session = user.session
      req.body = {
        name: user.profile.fullName,
        cvc: '111',
        number: '4111111111111111',
        exp_month: '1',
        exp_year: (new Date().getFullYear() + 1).toString().substring(2),
        line1: 'A street address',
        city: 'City',
        state: 'NY',
        postal_code: '90120',
        country: 'US',
        default: 'true'
      }
      req.filename = __filename
      req.saveResponse = true
      const paymentMethod = await req.post()
      assert.strictEqual(paymentMethod.object, 'paymentmethod')
    })
  })

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