Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions src/api/platforms/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,20 @@ export default class PlatformFiles {
}

/**
* Upload identity documentation required for full due diligence (standalone file upload).
* Upload identity documentation required for full due diligence (standalone file upload,
* POST /files on the Files host, multipart). The returned id is what a document's front and
* back fields take in onboardSubEntity.
*
* @param {string} purpose The purpose of the file upload.
* This Files host endpoint is not described in the API reference (its POST /files is the
* disputes upload on the API host), so the purposes below are the onboarding purposes the
* reference lists for the sub-entity upload (uploadAFile, PlatformsFileUpload):
* additional_document, articles_of_association, bank_verification,
* certified_authorised_signatory, company_ownership, company_verification,
* financial_verification, identity_verification, proof_of_legality,
* proof_of_principal_address, shareholder_structure, tax_verification,
* proof_of_residential_address, proof_of_registration.
*
* @param {string} purpose The purpose of the file upload, one of the values above.
* @param {Object} path The local path of the file to upload, and its type.
* @return {Promise<Object>} A promise to the Platforms response.
*/
Expand All @@ -47,11 +58,19 @@ export default class PlatformFiles {
}

/**
* Generate a file upload link for a sub-entity (full sub-entity onboarding).
* Generate a file upload link for a sub-entity (POST /entities/{entityId}/files). The body is
* { purpose } only; the response carries the file id, maximum_size_in_bytes,
* document_types_for_purpose and _links.upload, the link the file content is then sent to.
*
* Onboarding purposes (PlatformsFileUpload): additional_document, articles_of_association,
* bank_verification, certified_authorised_signatory, company_ownership, company_verification,
* financial_verification, identity_verification, proof_of_legality, proof_of_principal_address,
* shareholder_structure, tax_verification, proof_of_residential_address, proof_of_registration.
*
* @param {string} entityId The ID of the sub-entity.
* @param {Object} body The body; body.purpose is the purpose of the file upload.
* @returns {Promise<Object>}
* @param {Object} body The body; body.purpose is the purpose of the file upload, one of the
* values above.
* @returns {Promise<Object>} A promise to the file id and upload link.
*/
async uploadAFile(entityId, body) {
try {
Expand All @@ -69,11 +88,14 @@ export default class PlatformFiles {
}

/**
* Retrieve information about a previously uploaded file for a sub-entity.
* Retrieve information about a previously uploaded file for a sub-entity
* (GET /entities/{entityId}/files/{fileId}).
*
* @param {string} entityId The ID of the sub-entity.
* @param {string} fileId The ID of the file (prefix file_).
* @returns {Promise<Object>}
* @returns {Promise<Object>} A promise to the file's id, status, status_reasons, size,
* mime_type, uploaded_on, purpose and _links (download, the link to fetch the content, and
* self).
*/
async retrieveAFile(entityId, fileId) {
try {
Expand Down
17 changes: 14 additions & 3 deletions src/api/platforms/payment-instruments.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,23 @@ export default class PaymentInstruments {
}

/**
* Update a payment instrument's details.
* Update a payment instrument's details
* (PATCH /accounts/entities/{entityId}/payment-instruments/{id}).
*
* The API reads the ETag only from the If-Match HTTP header: without it the update fails with
* 428 Precondition Required, and with a stale one with 412 Precondition Failed. Pass the ETag
* from the ETag header of the GET in body.headers['if-match']; every key of body.headers is
* sent as an HTTP header.
* - body.label: [Optional] string, 1 to 50 characters.
* - body.default: [Optional] boolean. Deprecated by the API: for scheduled payouts the first
* payment instrument created for a currency is used; to change it, update the payout
* schedule.
* - body.headers: { 'if-match': etag }, [Required] by the API.
*
* @param {string} entityId Sub-entity id.
* @param {string} id Payment instrument's id.
* @param {Object} body Platforms request body.
* @return {Promise<Object>} A promise to the Platforms response.
* @param {Object} body The update, as described above.
* @return {Promise<Object>} A promise to the Platforms response: the instrument id and _links.
*/
async updatePaymentInstrumentDetails(entityId, id, body) {
try {
Expand Down
6 changes: 6 additions & 0 deletions src/api/platforms/platforms.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,33 @@ export default class Platforms {
}

// ——— Files (backwards compatibility) ———
/** Upload a file for onboarding (POST /files, Files host). See PlatformFiles.uploadFile. */
async uploadFile(purpose, path) {
return this.files.uploadFile(purpose, path);
}

/** Generate a sub-entity file upload link. See PlatformFiles.uploadAFile. */
async uploadAFile(entityId, body) {
return this.files.uploadAFile(entityId, body);
}

/** Retrieve a sub-entity file. See PlatformFiles.retrieveAFile. */
async retrieveAFile(entityId, fileId) {
return this.files.retrieveAFile(entityId, fileId);
}

// ——— Sub-entity (backwards compatibility) ———
/** Onboard a sub-entity, including both documents objects. See Subentity.onboardSubEntity. */
async onboardSubEntity(body, schemaVersion) {
return this.subentity.onboardSubEntity(body, schemaVersion);
}

/** Retrieve a sub-entity and its full details. See Subentity.getSubEntityDetails. */
async getSubEntityDetails(id, schemaVersion) {
return this.subentity.getSubEntityDetails(id, schemaVersion);
}

/** Update a sub-entity. See Subentity.updateSubEntityDetails. */
async updateSubEntityDetails(id, body, schemaVersion) {
return this.subentity.updateSubEntityDetails(id, body, schemaVersion);
}
Expand Down
117 changes: 110 additions & 7 deletions src/api/platforms/subentity.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,107 @@ export default class Subentity {
}

/**
* Onboard a sub-entity so they can start receiving payments.
* Onboard a sub-entity so they can start receiving payments (POST /accounts/entities).
*
* @param {Object} body Platforms request body.
* The body is sent as given. Which fields are required depends on the onboarding variant
* (region, company or sole trader, Full or Lite, schema version). The main fields of a v3.0
* request:
* - body.reference: [Required] string, 1 to 50 characters.
* - body.contact_details: [Required on every variant except EEA Company Full (3.0)]
* { phone: { country_code, number }, email_addresses: { primary }, invitee: { email } }.
* On v3.0 phone.country_code is the ISO 3166-1 alpha-2 country where the number is
* registered (for example "FR"), not the dialling code; v2.0 takes number only. The US ISV
* Seller variants take email_addresses: { primary, pci_compliance_contact } and no invitee;
* there both are [Required], email addresses, and pci_compliance_contact is the person
* responsible for PCI compliance at the sub-entity. invitee.email is [Required] in the
* hosted onboarding invite body ({ reference, is_draft, contact_details: { invitee } }) and
* [Optional] in the Full and Lite onboarding variants.
* - body.profile: [Required] { urls, mccs, default_holding_currency, holding_currencies }.
* - body.company: { legal_name, trading_name, business_registration_number, business_type,
* date_of_incorporation, principal_address, registered_address, representatives }, plus
* regulatory_licence_number on EEA Company Full (3.0) and additional_trading_names on the
* US ISV Seller variants. A v3.0 sole trader's company has no legal_name,
* business_registration_number or registered_address; it takes trading_name, business_type,
* date_of_incorporation, principal_address and representatives (plus
* is_registered_company and additional_trading_names on US ISV Seller Sole Trader).
* - body.company.representatives[]: a person of interest { id, individual, roles,
* company_position, ownership_percentage, documents }, or, on EEA and GB Company Full
* (3.0), a controlling company { id, company: { legal_name, trading_name,
* registered_address }, ownership_percentage }. On the US ISV Seller variants a
* representative has no id: { individual, roles, company_position, ownership_percentage,
* documents } (no company_position on the sole trader). Sole traders have exactly one
* representative, with roles ["ubo"].
* - body.processing_details: [Required on v3.0] { settlement_country, target_countries,
* annual_processing_volume, average_transaction_value, highest_transaction_value,
* currency }; amounts in minor units without decimals. The US ISV Seller variants take
* { annual_processing_volume, average_transaction_value, average_order_fulfillment_time,
* currency, target_countries, payments } instead.
* - body.agreed_terms and body.seller_category: [Required] on the US ISV Seller variants
* only.
* - body.individual: v2.0 sole traders only. On v3.0 a sole trader is onboarded as
* body.company with one representative.
*
* **Documents go in two different places, and the two objects behave differently.**
*
* - body.company.representatives[].documents: the representative's own documents. On the
* Company Full and Sole Trader Full (3.0) variants of EEA, GB and US the API validates this
* object **strictly**: it accepts only the keys below and rejects any other. The US ISV
* Seller variants do not reject unknown keys, and on v2.0 it takes identity_verification
* only.
* - identity_verification: type passport, national_identity_card, driving_license,
* citizen_card, residence_permit or electoral_id. Required on Sole Trader Full (3.0);
* optional on the company variants and the US ISV Seller variants.
* - certified_authorised_signatory: type power_of_attorney. Optional, on Company Full
* (3.0) and US ISV Seller Company (3.0).
* - proof_of_residential_address: type proof_of_address. EEA Sole Trader Full (3.0) only,
* required there.
* - proof_of_registration: type extract_from_trade_register or other. EEA Sole Trader Full
* (3.0) only, required there.
* Each is { type, front } (identity_verification also takes back). front and back are file
* IDs, ^file_[a-z2-7]{26}$.
*
* - body.documents: the top-level documents. Unlike the strict representative object, the
* API ignores keys it does not recognise here rather than rejecting them: the request
* succeeds and a misplaced document (for example a representative document) is dropped
* silently. Keys by variant ([Required] marked, all
* others optional):
* - EEA Company Full (3.0): company_verification [Required], articles_of_association
* [Required], shareholder_structure [Required], bank_verification [Required],
* proof_of_legality, proof_of_principal_address, additional_document1/2/3.
* - GB Company Full (3.0): articles_of_association [Required], shareholder_structure
* [Required], company_verification, bank_verification, proof_of_legality,
* proof_of_principal_address, additional_document1/2/3.
* - US Company Full (3.0): tax_verification, company_verification,
* articles_of_association, bank_verification, shareholder_structure,
* proof_of_legality, proof_of_principal_address, additional_document1/2/3.
* - EEA, GB and US Sole Trader Full (3.0): bank_verification [Required],
* additional_document1/2/3.
* - US ISV Seller Company (3.0): tax_verification, company_verification,
* articles_of_association, shareholder_structure, proof_of_legality,
* proof_of_principal_address, financial_statements. US ISV Seller Sole Trader (3.0):
* the same without shareholder_structure.
* - EEA Company Full and Lite (2.0): company_verification ([Required] on Full),
* bank_verification, financial_verification. GB Company Full and Lite (2.0):
* company_verification ([Required] on Full). US Company Full and Lite (2.0):
* company_verification, tax_verification.
* - The six v2.0 sole trader variants: identity_verification [Required].
* Type values: company_verification incorporation_document (also articles_of_association
* on US Company Full and Lite (2.0)), articles_of_association memorandum_of_association or
* articles_of_association, bank_verification bank_statement, shareholder_structure
* certified_shareholder_structure, proof_of_legality proof_of_legality,
* proof_of_principal_address proof_of_address, tax_verification ein_letter,
* financial_verification financial_statement, financial_statements financial_statements,
* identity_verification as on the representative. Each is { type, front };
* additional_document1/2/3 take { front } only, no type. body.documents itself is
* [Required] on the Company Full and Sole Trader Full (3.0) variants of EEA, GB and US,
* and on EEA Company Full and EEA Sole Trader Full (2.0).
*
* Upload each file first (see PlatformFiles.uploadFile) and use the returned ID as front.
*
* @param {Object} body Platforms request body, as described above.
* @param {string} [schemaVersion='3.0'] Schema version to use (1.0, 2.0, or 3.0).
* @return {Promise<Object>} A promise to the Platforms response.
* @return {Promise<Object>} A promise to the Platforms response: on v3.0 the sub-entity id,
* reference, requirements_due and _links; v2.0 also returns capabilities.
*/
async onboardSubEntity(body, schemaVersion) {
try {
Expand All @@ -41,7 +137,10 @@ export default class Subentity {
}

/**
* Use this endpoint to retrieve a sub-entity and its full details.
* Retrieve a sub-entity and its full details (GET /accounts/entities/{id}).
* The response carries the same shape as the onboarding body of the entity's variant,
* including company.representatives[].documents and the top-level documents, and on v3.0
* processing_details.
*
* @param {string} id Sub-entity id.
* @param {string} [schemaVersion='3.0'] Schema version to use (1.0, 2.0, or 3.0).
Expand All @@ -62,12 +161,16 @@ export default class Subentity {
}

/**
* Update sub-entity details (contact, profile, company, identification).
* Update sub-entity details (PUT /accounts/entities/{id}). The body takes the same shape as
* onboardSubEntity, including both documents objects and their different validation (see
* onboardSubEntity). Set body.is_draft to true to keep the sub-entity in draft and skip due
* diligence checks.
*
* @param {string} id Sub-entity id.
* @param {Object} body Platforms request body.
* @param {Object} body Platforms request body, as described in onboardSubEntity.
* @param {string} [schemaVersion='3.0'] Schema version to use (1.0, 2.0, or 3.0).
* @return {Promise<Object>} A promise to the Platforms response.
* @return {Promise<Object>} A promise to the Platforms response, the same shape as the
* onboardSubEntity response.
*/
async updateSubEntityDetails(id, body, schemaVersion) {
try {
Expand Down
14 changes: 7 additions & 7 deletions test/platforms/entity-requirements-unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,32 @@ describe('Platforms — entity-requirements endpoints', () => {

it('getEntityRequirements GETs /accounts/entities/{id}/requirements', async () => {
nock('https://123456789.api.sandbox.checkout.com')
.get('/accounts/entities/ent_1/requirements')
.get('/accounts/entities/ent_se7b7fgom3ulhnjv3k3q43zzbb/requirements')
.reply(200, { data: [] });

const cko = new Checkout(SK, { subdomain: '123456789' });
const res = await cko.platforms.getEntityRequirements('ent_1');
const res = await cko.platforms.getEntityRequirements('ent_se7b7fgom3ulhnjv3k3q43zzbb');
expect(res.data).to.deep.equal([]);
});

it('getEntityRequirementDetails GETs /accounts/entities/{id}/requirements/{reqId}', async () => {
nock('https://123456789.api.sandbox.checkout.com')
.get('/accounts/entities/ent_1/requirements/req_1')
.get('/accounts/entities/ent_se7b7fgom3ulhnjv3k3q43zzbb/requirements/req_1')
.reply(200, { id: 'req_1', priority: 'urgent' });

const cko = new Checkout(SK, { subdomain: '123456789' });
const res = await cko.platforms.getEntityRequirementDetails('ent_1', 'req_1');
const res = await cko.platforms.getEntityRequirementDetails('ent_se7b7fgom3ulhnjv3k3q43zzbb', 'req_1');
expect(res.id).to.equal('req_1');
});

it('updateEntityRequirement PUTs /accounts/entities/{id}/requirements/{reqId}', async () => {
nock('https://123456789.api.sandbox.checkout.com')
.put('/accounts/entities/ent_1/requirements/req_1')
.put('/accounts/entities/ent_se7b7fgom3ulhnjv3k3q43zzbb/requirements/req_1')
.reply(200, { status: 'pending_review' });

const cko = new Checkout(SK, { subdomain: '123456789' });
const res = await cko.platforms.updateEntityRequirement('ent_1', 'req_1', {
value: { file_id: 'file_abc' },
const res = await cko.platforms.updateEntityRequirement('ent_se7b7fgom3ulhnjv3k3q43zzbb', 'req_1', {
value: { file_id: 'file_zp3actu55nhhmpwnaf5qbonqav' },
});
expect(res.status).to.equal('pending_review');
});
Expand Down
Loading
Loading