Stripe Connect module API explorer

/api/user/connect/set-stripe-account-submitted (PATCH)

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

await global.api.user.connect.SetStripeAccountSubmitted.patch(req)

Returns object (individual),object (company)

{
  "stripeid": "acct_1LEC0tRnXb6DmWDd",
  "object": "stripeAccount",
  "accountid": "acct_a8f01ce377932f33",
  "tokenUpdate": null,
  "stripeObject": {
    "id": "acct_1LEC0tRnXb6DmWDd",
    "object": "account",
    "business_profile": {
      "mcc": "7535",
      "name": null,
      "product_description": null,
      "support_address": null,
      "support_email": null,
      "support_phone": null,
      "support_url": null,
      "url": "https://www.an-example-website.com"
    },
    "business_type": "individual",
    "capabilities": {
      "card_payments": "pending",
      "transfers": "pending"
    },
    "charges_enabled": false,
    "company": {
      "address": {
        "city": "Berlin",
        "country": "DE",
        "line1": "123 Park Lane",
        "line2": null,
        "postal_code": "01067",
        "state": null
      },
      "directors_provided": true,
      "executives_provided": true,
      "name": null,
      "owners_provided": true,
      "phone": "+14567890123",
      "tax_id_provided": false,
      "verification": {
        "document": {
          "back": null,
          "details": null,
          "details_code": null,
          "front": null
        }
      }
    },
    "country": "DE",
    "created": 1656075212,
    "default_currency": "eur",
    "details_submitted": true,
    "email": null,
    "external_accounts": {
      "object": "list",
      "data": [
        {
          "id": "ba_1LEC0wRnXb6DmWDdI9UqWQok",
          "object": "bank_account",
          "account": "acct_1LEC0tRnXb6DmWDd",
          "account_holder_name": "Gerardo Roob",
          "account_holder_type": "individual",
          "account_type": null,
          "available_payout_methods": [
            "standard"
          ],
          "bank_name": "STRIPE TEST BANK",
          "country": "DE",
          "currency": "eur",
          "default_for_currency": true,
          "fingerprint": "8JEgQB776Ig8OZcq",
          "last4": "3000",
          "metadata": {},
          "routing_number": "110000000",
          "status": "new"
        }
      ],
      "has_more": false,
      "total_count": 1,
      "url": "/v1/accounts/acct_1LEC0tRnXb6DmWDd/external_accounts"
    },
    "future_requirements": {
      "alternatives": [],
      "current_deadline": null,
      "currently_due": [],
      "disabled_reason": null,
      "errors": [],
      "eventually_due": [],
      "past_due": [],
      "pending_verification": []
    },
    "individual": {
      "id": "person_1LEC10RnXb6DmWDdN3Neicsn",
      "object": "person",
      "account": "acct_1LEC0tRnXb6DmWDd",
      "address": {
        "city": "Berlin",
        "country": "DE",
        "line1": "123 Park Lane",
        "line2": null,
        "postal_code": "01067",
        "state": null
      },
      "created": 1656075218,
      "dob": {
        "day": 1,
        "month": 1,
        "year": 1970
      },
      "email": "Gerardo.Roob8@hotmail.com",
      "first_name": "Gerardo",
      "future_requirements": {
        "alternatives": [],
        "currently_due": [],
        "errors": [],
        "eventually_due": [],
        "past_due": [],
        "pending_verification": []
      },
      "last_name": "Roob",
      "metadata": {},
      "phone": "+14567890123",
      "relationship": {
        "director": false,
        "executive": false,
        "owner": false,
        "percent_ownership": null,
        "representative": true,
        "title": null
      },
      "requirements": {
        "alternatives": [],
        "currently_due": [],
        "errors": [],
        "eventually_due": [],
        "past_due": [],
        "pending_verification": [
          "verification.additional_document",
          "verification.document"
        ]
      },
      "verification": {
        "additional_document": {
          "back": null,
          "details": null,
          "details_code": null,
          "front": null
        },
        "details": null,
        "details_code": null,
        "document": {
          "back": null,
          "details": null,
          "details_code": null,
          "front": null
        },
        "status": "pending"
      }
    },
    "metadata": {},
    "payouts_enabled": false,
    "requirements": {
      "alternatives": [],
      "current_deadline": null,
      "currently_due": [],
      "disabled_reason": "requirements.pending_verification",
      "errors": [],
      "eventually_due": [],
      "past_due": [],
      "pending_verification": [
        "individual.verification.additional_document",
        "individual.verification.document"
      ]
    },
    "settings": {
      "bacs_debit_payments": {},
      "branding": {
        "icon": null,
        "logo": null,
        "primary_color": null,
        "secondary_color": null
      },
      "card_issuing": {
        "tos_acceptance": {
          "date": null,
          "ip": null
        }
      },
      "card_payments": {
        "decline_on": {
          "avs_failure": false,
          "cvc_failure": false
        },
        "statement_descriptor_prefix": null,
        "statement_descriptor_prefix_kana": null,
        "statement_descriptor_prefix_kanji": null
      },
      "dashboard": {
        "display_name": "An-example-website",
        "timezone": "Etc/UTC"
      },
      "payments": {
        "statement_descriptor": "WWW.AN-EXAMPLE-WEBSITE.COM",
        "statement_descriptor_kana": null,
        "statement_descriptor_kanji": null
      },
      "payouts": {
        "debit_negative_balances": false,
        "schedule": {
          "delay_days": 7,
          "interval": "daily"
        },
        "statement_descriptor": null
      },
      "sepa_debit_payments": {}
    },
    "tos_acceptance": {
      "date": 1656075220,
      "ip": "127.0.0.1",
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0"
    },
    "type": "custom"
  },
  "requiresOwners": false,
  "requiresDirectors": false,
  "requiresExecutives": false,
  "submittedAt": "2022-06-24T12:53:42.821Z",
  "appid": "tests_1656075154",
  "createdAt": "2022-06-24T12:53:33.636Z",
  "updatedAt": "2022-06-24T12:53:42.821Z"
}

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-company-owner ineligible company owners not submitted
invalid-director ineligible company directors not submitted
invalid-executive ineligible company executives not submitted
invalid-payment-details ineligible Stripe company account missing payment details
ineligible Stripe individual account missing payment details
invalid-person ineligible company person missing information
invalid-registration ineligible Stripe company account missing information
ineligible Stripe individual account missing information
invalid-stripe-account ineligible Stripe company account is submitted
ineligible Stripe individual account is submitted
invalid-stripeid missing querystring stripeid
invalid querystring stripeid

NodeJS source (view on github)

const stripeCache = require('../../../../stripe-cache.js')
const connect = require('../../../../../index.js')
const dashboard = require('@layeredapps/dashboard')

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.submittedAt ||
      stripeAccount.accountid !== req.account.accountid) {
      throw new Error('invalid-stripe-account')
    }
    if (!stripeAccount.stripeObject.external_accounts.data.length) {
      throw new Error('invalid-payment-details')
    }
    if (stripeAccount.stripeObject.business_type === 'company') {
      if (stripeAccount.requiresOwners && !stripeAccount.stripeObject.company.owners_provided) {
        throw new Error('invalid-company-owner')
      }
      if (stripeAccount.requiresDirectors && !stripeAccount.stripeObject.company.directors_provided) {
        throw new Error('invalid-company-director')
      }
      if (stripeAccount.requiresExecutives && !stripeAccount.stripeObject.company.executives_provided) {
        throw new Error('invalid-company-executive')
      }
      req.query.all = true
      const persons = await global.api.user.connect.Persons.get(req)
      if (persons && persons.length) {
        for (const person of persons) {
          if (person.stripeObject.requirements.currently_due.length) {
            throw new Error('invalid-person')
          }
        }
      }
    }
    if (stripeAccount.stripeObject.requirements.currently_due.length) {
      for (const field of stripeAccount.stripeObject.requirements.currently_due) {
        if (field !== 'tos_acceptance.date' &&
            field !== 'tos_acceptance.ip') {
          throw new Error('invalid-registration')
        }
      }
    }
    const accountInfo = {
      tos_acceptance: {
        ip: req.ip,
        user_agent: req.headers['user-agent'] || 'None',
        date: Math.floor(new Date().getTime() / 1000)
      }
    }
    const stripeAccountNow = await stripeCache.execute('accounts', 'update', req.query.stripeid, accountInfo, req.stripeKey)
    await connect.Storage.StripeAccount.update({
      stripeObject: stripeAccountNow,
      submittedAt: new Date()
    }, {
      where: {
        stripeid: req.query.stripeid,
        appid: req.appid || global.appid
      }
    })
    await dashboard.StorageCache.remove(req.query.stripeid)
    return global.api.user.connect.StripeAccount.get(req)
  }
}

Test source (view on github)

/* eslint-env mocha */
const assert = require('assert')
const TestHelper = require('../../../../../test-helper.js')
const DashboardTestHelper = require('@layeredapps/dashboard/test-helper.js')
const TestStripeAccounts = require('../../../../../test-stripe-accounts.js')

describe('/api/user/connect/set-stripe-account-submitted', function () {
  before(TestHelper.disableMetrics)
  after(TestHelper.enableMetrics)
  let cachedResponses
  async function bundledData (retryNumber) {
    if (retryNumber > 0) {
      cachedResponses = {}
    }
    if (cachedResponses && cachedResponses.finished) {
      return
    }
    cachedResponses = {}
    await DashboardTestHelper.setupBeforeEach()
    await TestHelper.setupBeforeEach()
    const user = await TestHelper.createUser()
    // company issues
    await TestHelper.createStripeAccount(user, {
      country: 'DE',
      business_type: 'company'
    })
    const user2 = await TestHelper.createUser()
    let req = TestHelper.createRequest(`/api/user/connect/set-stripe-account-submitted?stripeid=${user.stripeAccount.stripeid}`)
    req.account = user2.account
    req.session = user2.session
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.invalidAccount = error.message
    }
    // 1) missing payment details
    req = TestHelper.createRequest(`/api/user/connect/set-stripe-account-submitted?stripeid=${user.stripeAccount.stripeid}`)
    req.account = user.account
    req.session = user.session
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.companyMissingPaymentDetails = error.message
    }
    const companyBankingData = TestStripeAccounts.createBankingData(user.stripeAccount.stripeObject.business_type, user.profile, user.stripeAccount.stripeObject.country)
    await TestHelper.createExternalAccount(user, companyBankingData)
    await TestStripeAccounts.waitForAccountFieldToLeave(user, 'external_account')
    // 2) missing submitted owners
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.companyMissingOwners = error.message
    }
    await TestHelper.submitCompanyOwners(user)
    // 3) missing submitted directors
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.companyMissingDirectors = error.message
    }
    await TestHelper.submitCompanyDirectors(user)
    // 4) missing submitted executives
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.companyMissingExecutives = error.message
    }
    await TestHelper.submitCompanyExecutives(user)
    // 5) missing person details
    await TestHelper.createPerson(user, {
      relationship_representative: 'true',
      relationship_executive: user.stripeAccount.requiresExecutives ? 'true' : undefined,
      relationship_title: 'SVP Testing',
      relationship_percent_ownership: 0
    })
    req = TestHelper.createRequest(`/api/user/connect/set-stripe-account-submitted?stripeid=${user.stripeAccount.stripeid}`)
    req.account = user.account
    req.session = user.session
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.invalidPerson = error.message
    }
    await TestStripeAccounts.waitForWebhook('person.created', (stripeEvent) => {
      return stripeEvent.data.object.id === user.representative.personid
    })
    await TestStripeAccounts.waitForPersonField(user, 'representative', 'first_name')
    const representativeData = TestStripeAccounts.createPersonData(TestHelper.nextIdentity(), user.stripeAccount.stripeObject.country, user.representative.stripeObject)
    await TestHelper.updatePerson(user, user.representative, representativeData)
    await TestStripeAccounts.waitForPersonField(user, 'representative', 'verification.document')
    const representativeUploadData = TestStripeAccounts.createPersonUploadData(user.representative.stripeObject)
    if (representativeUploadData && Object.keys(representativeUploadData).length) {
      await TestHelper.updatePerson(user, user.representative, {}, representativeUploadData)
      await TestStripeAccounts.waitForPersonFieldToLeave(user, 'representative', 'verification.document')
    }
    // 6) missing registration details
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.companyMissingRegistrationDetails = error.message
    }
    await TestStripeAccounts.waitForAccountField(user, 'company.name')
    const companyAccountData = TestStripeAccounts.createAccountData(user.profile, user.stripeAccount.stripeObject.country, user.stripeAccount.stripeObject)
    await TestHelper.updateStripeAccount(user, companyAccountData)
    // 7) submitted company
    cachedResponses.submittedCompany = await req.patch()
    // 8) company is already submitted
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.companyAlreadySubmitted = error.message
    }
    // individual
    await TestHelper.createStripeAccount(user, {
      country: 'DE',
      business_type: 'individual'
    })
    req = TestHelper.createRequest(`/api/user/connect/set-stripe-account-submitted?stripeid=${user.stripeAccount.stripeid}`)
    req.account = user.account
    req.session = user.session
    // 1) missing banking details
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.individualMissingPaymentDetails = error.message
    }
    const individualBankingData = TestStripeAccounts.createBankingData(user.stripeAccount.stripeObject.business_type, user.profile, user.stripeAccount.stripeObject.country)
    await TestHelper.createExternalAccount(user, individualBankingData)
    await TestStripeAccounts.waitForAccountFieldToLeave(user, 'external_account')
    // 2) missing registration details
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.individualMissingRegistrationDetails = error.message
    }
    const individualAccountData = TestStripeAccounts.createAccountData(user.profile, user.stripeAccount.stripeObject.country, user.stripeAccount.stripeObject)
    await TestHelper.updateStripeAccount(user, individualAccountData)
    // 3) submitted individual
    req.filename = __filename
    req.saveResponse = true
    cachedResponses.submittedIndividual = await req.patch()
    // 4) already submitted
    try {
      await req.patch()
    } catch (error) {
      cachedResponses.individualAlreadySubmitted = error.message
    }
    cachedResponses.finished = true
  }

  describe('exceptions', () => {
    describe('invalid-stripeid', () => {
      it('missing querystring stripeid', async function () {
        await bundledData(this.test.currentRetry())
        const user = await TestHelper.createUser()
        const req = TestHelper.createRequest('/api/user/connect/set-stripe-account-submitted')
        req.account = user.account
        req.session = user.session
        let errorMessage
        try {
          await req.patch()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-stripeid')
      })

      it('invalid querystring stripeid', async function () {
        await bundledData(this.test.currentRetry())
        const user = await TestHelper.createUser()
        const req = TestHelper.createRequest('/api/user/connect/set-stripe-account-submitted?stripeid=invalid')
        req.account = user.account
        req.session = user.session
        let errorMessage
        try {
          await req.patch()
        } catch (error) {
          errorMessage = error.message
        }
        assert.strictEqual(errorMessage, 'invalid-stripeid')
      })
    })

    describe('invalid-stripe-account', () => {
      it('ineligible Stripe company account is submitted', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.companyAlreadySubmitted
        assert.strictEqual(errorMessage, 'invalid-stripe-account')
      })
      it('ineligible Stripe individual account is submitted', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.individualAlreadySubmitted
        assert.strictEqual(errorMessage, 'invalid-stripe-account')
      })
    })

    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-payment-details', () => {
      it('ineligible Stripe company account missing payment details', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.companyMissingPaymentDetails
        assert.strictEqual(errorMessage, 'invalid-payment-details')
      })

      it('ineligible Stripe individual account missing payment details', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.individualMissingPaymentDetails
        assert.strictEqual(errorMessage, 'invalid-payment-details')
      })
    })

    describe('invalid-registration', () => {
      it('ineligible Stripe company account missing information', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.companyMissingRegistrationDetails
        assert.strictEqual(errorMessage, 'invalid-registration')
      })

      it('ineligible Stripe individual account missing information', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.individualMissingRegistrationDetails
        assert.strictEqual(errorMessage, 'invalid-registration')
      })
    })

    describe('invalid-person', () => {
      it('ineligible company person missing information', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.invalidPerson
        assert.strictEqual(errorMessage, 'invalid-person')
      })
    })

    describe('invalid-company-owner', () => {
      it('ineligible company owners not submitted', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.companyMissingOwners
        assert.strictEqual(errorMessage, 'invalid-company-owner')
      })
    })

    describe('invalid-director', () => {
      it('ineligible company directors not submitted', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.companyMissingDirectors
        assert.strictEqual(errorMessage, 'invalid-company-director')
      })
    })

    describe('invalid-executive', () => {
      it('ineligible company executives not submitted', async function () {
        await bundledData(this.test.currentRetry())
        const errorMessage = cachedResponses.companyMissingExecutives
        assert.strictEqual(errorMessage, 'invalid-company-executive')
      })
    })
  })

  describe('returns', () => {
    it('object (individual)', async () => {
      const stripeAccountNow = cachedResponses.submittedIndividual
      assert.notStrictEqual(stripeAccountNow.submittedAt, undefined)
      assert.notStrictEqual(stripeAccountNow.submittedAt, null)
    })

    it('object (company)', async () => {
      const stripeAccountNow = cachedResponses.submittedCompany
      assert.notStrictEqual(stripeAccountNow.submittedAt, undefined)
      assert.notStrictEqual(stripeAccountNow.submittedAt, null)
    })
  })
})