/api/user/connect/update-payment-information (PATCH)
await global.api.user.connect.UpdatePaymentInformation.patch(req) Located in Stripe Connect module API
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 stripeid | |
invalid-stripeid | missing querystring stripeid |
invalid-account | ineligible accessing account |
Receives
API routes may receive parameters from the URL and POST supporting simple and multipart:
Field | Value | Required | Type |
---|---|---|---|
string | required | URL |
NodeJS source (edit on github)
If you see a problem with the source submit a pull request on Github.
const connect = require('../../../../../index.js')
const stripeCache = require('../../../../stripe-cache.js')
module.exports = {
patch: async (req) => {
if (!req.query || !req.query.stripeid) {
throw new Error('invalid-stripeid')
}
const stripeAccount = await global.api.user.connect.StripeAccount.get(req)
if (stripeAccount.metadata.accountid !== req.account.accountid) {
throw new Error('invalid-stripe-account')
}
if (!req.body || !req.body.currency || !req.body.currency.length) {
throw new Error('invalid-currency')
}
if (!req.body.country || !req.body.country.length) {
throw new Error('invalid-country')
}
if (!connect.countrySpecIndex[req.body.country]) {
throw new Error('invalid-country')
}
if (!req.body.account_holder_name || !req.body.account_holder_name.length) {
throw new Error('invalid-account_holder_name')
}
if (!req.body.account_holder_type || !req.body.account_holder_type.length) {
throw new Error('invalid-account_holder_type')
}
if (req.body.account_holder_type !== 'individual' &&
req.body.account_holder_type !== 'company') {
throw new Error('invalid-account_holder_type')
}
let requiredFields
switch (req.body.country) {
case 'AU':
requiredFields = ['account_number', 'bsb_number']
break
case 'GB':
if (req.body.currency === 'gbp') {
requiredFields = ['account_number', 'sort_code']
} else {
requiredFields = ['iban']
}
break
case 'CA':
requiredFields = ['account_number', 'institution_number', 'transit_number']
break
case 'MY':
case 'US':
case 'NZ':
requiredFields = ['account_number', 'routing_number']
break
case 'HK':
requiredFields = ['account_number', 'clearing_code', 'branch_code']
break
case 'JP':
case 'SG':
case 'BR':
requiredFields = ['account_number', 'bank_code', 'branch_code']
break
default:
requiredFields = ['iban']
break
}
for (const field of requiredFields) {
if (!req.body[field]) {
throw new Error(`invalid-${field}`)
}
if (field === 'iban') {
const countryPart = req.body[field].substring(0, 2).toUpperCase()
if (!connect.countryCurrencyIndex[countryPart]) {
throw new Error('invalid-iban')
}
const numericPart = req.body[field].substring(2)
const integers = '0123456789'
for (let i = 0, len = numericPart.length; i < len; i++) {
if (integers.indexOf(numericPart.charAt(i)) === -1) {
throw new Error('invalid-iban')
}
}
continue
}
if (process.env.NODE_ENV === 'testing' && req.body[field] === 'TESTMYKL') {
// do nothing
} else {
const int = parseInt(req.body[field], 10)
if (!int && int !== 0) {
throw new Error(`invalid-${field}`)
}
if (int.toString() !== req.body[field]) {
if (req.body[field].startsWith('0')) {
let zeroes = ''
for (let i = 0, len = req.body[field].length; i < len; i++) {
if (req.body[field].charAt(i) !== '0') {
break
}
zeroes += '0'
}
if (int > 0) {
zeroes += int.toString()
}
if (zeroes !== req.body[field]) {
throw new Error(`invalid-${field}`)
}
} else {
throw new Error(`invalid-${field}`)
}
}
}
}
const currencies = connect.countryCurrencyIndex[stripeAccount.country]
let foundCurrency = false
for (const currency of currencies) {
foundCurrency = currency.currency === req.body.currency
if (foundCurrency) {
break
}
}
if (!foundCurrency) {
throw new Error('invalid-currency')
}
const stripeData = {
external_account: {
object: 'bank_account',
currency: req.body.currency,
country: req.body.country,
account_holder_name: req.body.account_holder_name,
account_holder_type: req.body.account_holder_type
}
}
switch (req.body.country) {
case 'AU':
stripeData.external_account.account_number = req.body.account_number
stripeData.external_account.routing_number = req.body.bsb_number
break
case 'GB':
if (req.body.currency === 'gbp') {
stripeData.external_account.account_number = req.body.account_number
stripeData.external_account.routing_number = req.body.sort_code
} else {
stripeData.external_account.account_number = req.body.iban
}
break
case 'CA':
stripeData.external_account.account_number = req.body.account_number
stripeData.external_account.routing_number = req.body.transit_number + '-' + req.body.institution_number
break
case 'HK':
stripeData.external_account.account_number = req.body.account_number
stripeData.external_account.routing_number = req.body.clearing_code + '-' + req.body.branch_code
break
case 'JP':
case 'BR':
stripeData.external_account.account_number = req.body.account_number
stripeData.external_account.routing_number = req.body.bank_code + '' + req.body.branch_code
break
case 'SG':
stripeData.external_account.account_number = req.body.account_number
stripeData.external_account.routing_number = req.body.bank_code + '-' + req.body.branch_code
break
case 'NZ':
case 'US':
case 'MY':
stripeData.external_account.account_number = req.body.account_number
stripeData.external_account.routing_number = req.body.routing_number
break
default:
stripeData.external_account.account_number = req.body.iban
break
}
try {
const stripeAccountNow = await stripeCache.execute('accounts', 'update', req.query.stripeid, stripeData, req.stripeKey)
const bankAccount = stripeAccountNow.external_accounts.data[0]
await connect.StorageList.add(`${req.appid}/stripeAccount/bankAccounts/${req.query.stripeid}`, bankAccount.id)
await connect.Storage.write(`${req.appid}/map/bankAccount/stripeid/${bankAccount.id}`, req.query.stripeid)
await stripeCache.delete(req.query.stripeid)
return stripeAccountNow
} catch (error) {
if (error.message && error.message.startsWith('invalid-external_account_')) {
throw new Error(error.message.replace('external_account_', ''))
}
throw error
}
}
}
Test source (edit on github)
Tests perform real HTTP requests against a running Dashboard server.
/* eslint-env mocha */
const assert = require('assert')
const connect = require('../../../../../index.js')
const TestHelper = require('../../../../../test-helper.js')
const TestStripeAccounts = require('../../../../../test-stripe-accounts.js')
const DashboardTestHelper = require('@userdashboard/dashboard/test-helper.js')
describe('/api/user/connect/update-payment-information', function () {
this.timeout(30 * 60 * 1000)
// TODO: invalid values marked as 'false' are skipped until they can be verified
const invalidValues = {
account_holder_name: false,
account_holder_type: 'invalid',
routing_number: '111111111',
account_number: '111111111',
bank_code: false,
branch_code: false,
clearing_code: false,
bsb_number: false,
institution_number: false,
currency: 'invalid',
country: 'invalid',
iban: 'invalid',
transit_number: false,
sort_code: false
}
const errorMessages = {}
const invalidMessages = {}
const submitResponses = {}
const submitIdentities = {}
before(async () => {
await DashboardTestHelper.setupBeforeEach()
await TestHelper.setupBeforeEach()
const testedMissingFields = []
for (const country of connect.countrySpecs) {
let payload
if (TestStripeAccounts.paymentData[country.id].length) {
payload = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData[country.id])[0]
} else {
payload = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData[country.id])
}
if (payload === false) {
continue
}
const user = await TestHelper.createUser()
await TestHelper.createStripeAccount(user, {
country: country.id,
type: 'company'
})
errorMessages[country.id] = {}
invalidMessages[country.id] = {}
submitIdentities[country.id] = user.profile
for (const field in payload) {
if (testedMissingFields.indexOf(field) > -1) {
continue
}
testedMissingFields.push(field)
const req = TestHelper.createRequest(`/api/user/connect/update-payment-information?stripeid=${user.stripeAccount.id}`)
req.account = user.account
req.session = user.session
if (TestStripeAccounts.paymentData[country.id].length) {
req.body = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData[country.id][0], user.profile)
} else {
req.body = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData[country.id], user.profile)
}
delete (req.body[field])
try {
await req.patch()
} catch (error) {
errorMessages[country.id][field] = error.message
}
req.body[field] = invalidValues[field]
try {
await req.patch()
} catch (error) {
invalidMessages[country.id][field] = error.message
}
}
const req = TestHelper.createRequest(`/api/user/connect/update-payment-information?stripeid=${user.stripeAccount.id}`)
req.account = user.account
req.session = user.session
if (TestStripeAccounts.paymentData[country.id].length) {
req.body = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData[country.id][0], user.profile)
} else {
req.body = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData[country.id], user.profile)
}
submitResponses[country.id] = await req.patch()
}
})
describe('exceptions', () => {
describe('invalid-stripeid', () => {
it('missing querystring stripeid', async () => {
const user = await TestHelper.createUser()
const req = TestHelper.createRequest('/api/user/connect/update-payment-information')
req.account = user.account
req.session = user.session
req.body = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData.US)
let errorMessage
try {
await req.patch(req)
} catch (error) {
errorMessage = error.message
}
assert.strictEqual(errorMessage, 'invalid-stripeid')
})
it('invalid querystring stripeid', async () => {
const user = await TestHelper.createUser()
const req = TestHelper.createRequest('/api/user/connect/update-payment-information?stripeid=invalid')
req.account = user.account
req.session = user.session
req.body = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData.US)
let errorMessage
try {
await req.patch(req)
} catch (error) {
errorMessage = error.message
}
assert.strictEqual(errorMessage, 'invalid-stripeid')
})
})
describe('invalid-account', () => {
it('ineligible accessing account', async () => {
const user = await TestHelper.createUser()
await TestHelper.createStripeAccount(user, {
country: 'US',
type: 'company'
})
const user2 = await TestHelper.createUser()
const req = TestHelper.createRequest(`/api/user/connect/update-payment-information?stripeid=${user.stripeAccount.id}`)
req.account = user2.account
req.session = user2.session
req.body = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData[user.stripeAccount.country])
let errorMessage
try {
await req.patch(req)
} catch (error) {
errorMessage = error.message
}
assert.strictEqual(errorMessage, 'invalid-account')
})
})
const testedMissingFields = []
for (const country of connect.countrySpecs) {
let payload
if (TestStripeAccounts.paymentData[country.id].length) {
payload = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData[country.id])[0]
} else {
payload = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData[country.id])
}
if (payload === false) {
continue
}
for (const field in payload) {
if (testedMissingFields.indexOf(field) > -1) {
continue
}
testedMissingFields.push(field)
describe(`invalid-${field}`, () => {
it(`missing posted ${field}`, async () => {
const errorMessage = errorMessages[country.id][field]
assert.strictEqual(errorMessage, `invalid-${field}`)
})
if (invalidValues[field] !== undefined && invalidValues[field] !== false) {
it(`invalid posted ${field}`, async () => {
const errorMessage = invalidMessages[country.id][field]
assert.strictEqual(errorMessage, `invalid-${field}`)
})
}
})
}
}
})
describe('receives', () => {
const testedRequiredFields = []
for (const country of connect.countrySpecs) {
let payload
if (TestStripeAccounts.paymentData[country.id].length) {
payload = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData[country.id])[0]
} else {
payload = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData[country.id])
}
if (payload === false) {
continue
}
for (const field in payload) {
if (testedRequiredFields.indexOf(field) > -1) {
continue
}
testedRequiredFields.push(field)
it(`optionally-required posted ${field}`, async () => {
const stripeAccountNow = submitResponses[country.id]
if (field === 'iban' || field === 'account_number') {
assert.strictEqual(stripeAccountNow.external_accounts.data[0].last4, payload[field].substring(payload[field].length - 4))
} else if (field === 'bsb_number' ||
field === 'institution_number' ||
field === 'sort_code' ||
field === 'bank_code' ||
field === 'branch_code' ||
field === 'clearing_code' ||
field === 'transit_number') {
const routing = stripeAccountNow.external_accounts.data[0].routing_number.split(' ').join('').split('-').join('')
assert.strictEqual(true, routing.indexOf(payload[field]) > -1)
} else if (field === 'account_holder_name') {
assert.strictEqual(stripeAccountNow.external_accounts.data[0][field], submitIdentities[country.id].firstName + ' ' + submitIdentities[country.id].lastName)
} else {
assert.strictEqual(stripeAccountNow.external_accounts.data[0][field], payload[field])
}
})
}
}
})
describe('returns', () => {
it('object', async () => {
const user = await TestHelper.createUser()
await TestHelper.createStripeAccount(user, {
country: 'US',
type: 'individual'
})
const req = TestHelper.createRequest(`/api/user/connect/update-payment-information?stripeid=${user.stripeAccount.id}`)
req.account = user.account
req.session = user.session
req.body = TestStripeAccounts.createPostData(TestStripeAccounts.paymentData.US, user.profile)
req.filename = __filename
req.saveResponse = true
const stripeAccountNow = await req.patch()
assert.strictEqual(stripeAccountNow.object, 'account')
assert.strictEqual(stripeAccountNow.external_accounts.data.length, 1)
})
})
})