kyc: support individual and business entity types#354
Conversation
| /** | ||
| * The entity types which this KYC provider can | ||
| * verify. If not specified, the provider is | ||
| * assumed to verify `individual` entities only, | ||
| * preserving the classic KYC behavior. | ||
| * | ||
| * - `individual` is the classic KYC redirect flow | ||
| * (the provider returns a `webURL` hosted journey). | ||
| * - `business` is a Know Your Business (KYB) flow, | ||
| * performed synchronously from supplied business | ||
| * details with no `webURL`. | ||
| * | ||
| * A provider that supports both lists both values. | ||
| */ | ||
| entityTypes?: ('individual' | 'business')[]; |
There was a problem hiding this comment.
This should probably be an object so we can enforce keys are not added multiple times
There was a problem hiding this comment.
Done in e9809ca. entityTypes is now a presence map { individual?: true; business?: true } so each type can be declared at most once. The resolver reads it via ('object') and matches on Object.keys(...), and the server publishes it by folding the author-facing array into the map.
| * entity type (a provider with no declared `entityTypes` is | ||
| * treated as `individual`-only). | ||
| */ | ||
| entityType?: 'individual' | 'business'; |
There was a problem hiding this comment.
This should probably have a type associated with it as it is re used
There was a problem hiding this comment.
Done in e9809ca. Extracted a named KYCEntityType ('individual' | 'business') exported from resolver and reused across the metadata field, this search criteria, common.ts, and server.ts.
| /* | ||
| * The hosted-journey webURL only applies to individual | ||
| * verifications. A business (KYB) verification is performed | ||
| * synchronously from the supplied business details, so there | ||
| * is no user-facing redirect and no webURL is required. | ||
| */ |
There was a problem hiding this comment.
This does not have to be the case, the anchor should allow hosted journeys for businesses aswell
There was a problem hiding this comment.
Agreed, and fixed. Business is now a hosted redirect flow just like individual: the provider returns a webURL to its own hosted experience and the entity type only selects which form to present. The earlier synchronous/no-webURL branch is gone, and webURL is required for both.
| const businessResponse = await fetch(createURL, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, | ||
| body: JSON.stringify({ | ||
| request: { | ||
| countryCodes: ['US'], | ||
| account: requesterAccount.publicKeyString.get(), | ||
| signed: signed, | ||
| entityType: 'business', | ||
| business: { | ||
| names: ['Acme, Inc.'], | ||
| addresses: [{ streetAddress1: '1 Main St', city: 'Springfield', state: 'IL', postalCode: '62701' }], | ||
| persons: [{ firstName: 'John', lastName: 'Doe' }] | ||
| } | ||
| } | ||
| }) | ||
| }); |
There was a problem hiding this comment.
This should be in the client test.
There was a problem hiding this comment.
Moved in e9809ca. The business createVerification now runs through the client in client.test.ts (resolves a business-capable provider, calls startVerification, asserts a webURL). The server test keeps just the metadata-publish and resolver-lookup coverage.
| * | ||
| * Present for `individual` verifications (the hosted journey). Absent | ||
| * for `business` verifications, which are performed synchronously from | ||
| * the supplied business details with no user-facing redirect. | ||
| */ | ||
| webURL: string; | ||
| webURL?: string; |
There was a problem hiding this comment.
- This comment is wrong, the anchor should allow webURL for both. If anything, we probably want to enforce the web url flow for everything as every provider we use gives us a web flow. If there is not a web flow, there is a manual flow. We should try to avoid sending documents through this service as then we need to maintain what each provider might want to request.
There was a problem hiding this comment.
This is exactly the direction we took. The business-details block and its document/identifier fields are removed from the request, webURL is required for both flows, and the provider hosts and owns collection for both. The package only carries which entity type to start, so we never have to model what each provider wants to request.
Let a kyc provider declare which entity types it can verify (individual, business, or both) and let a verification request declare which type it is for. Both are redirect flows: the provider returns a webURL to a hosted experience and the client polls for the certificate. The entity type tells the provider which hosted experience to present. The provider hosts and owns the collection experience for both entity types (see the demo-kyc-provider app pattern: a hosted form served alongside the anchor API). So the package does not carry entity-specific input details in the request -- it only carries which kind of experience to start. This keeps the surface minimal and leaves KYB detail collection to the anchor's hosted form rather than the SDK. Changes: - common.ts: add entityType to the request (defaults to individual). webURL stays required (both flows redirect). KYCEntityType is derived from the metadata type. - server.ts: add entityTypes to the kyc config (default ['individual']) and publish it in the service metadata. - client.ts: pass entityType through to the resolver lookup. - resolver.ts: add entityTypes to the kyc service metadata, add an optional entityType filter to the kyc search criteria, and filter providers by it in lookupKYCServices (a provider with no declared entityTypes is treated as individual-only). - server.test.ts: business entity test covering metadata advertisement, entityType-filtered resolution, and a business createVerification that returns a hosted webURL. The cert schema needs no change for KYB: the existing ISO20022 attribute set (EntityType.organization, OrganizationIdentification bic/lei/other, the generic Document container) already represents business identifiers and documents. Back-compat: entityType defaults to individual and entityTypes defaults to ['individual'], so existing providers and callers are unaffected. tsc clean, make do-lint clean. (Pre-existing common.test.ts error round-trip flake fails identically on clean main -- not introduced here.)
b43eeb2 to
8be46e8
Compare
- entityTypes metadata is now a presence map ({ individual?: true,
business?: true }) so a type cannot be declared twice, per review
- extract a named KYCEntityType type in resolver and reuse it across
the metadata, search criteria, common, and server modules
- move the business createVerification assertion into the client test
where the rest of the client flow lives; server test keeps the
metadata-publish and resolver-lookup coverage
|
Thanks for the review. Pushed e9809ca addressing all five comments:
tsc and lint clean. The only |
|
Superseded by #355, opened from a branch on this repo (KeetaNetwork/anchor) instead of my fork so CI runs with repository secrets. Same commits, all the review comments here are already addressed there. Closing this and the fork. |
Built in collaboration with @schenkty as part of project Gildor.
What this does
Lets a
kycprovider declare which entity types it can verify (individual,business, or both) in its service metadata, and lets a verification request declare which type it is for. This folds business verification (KYB) into the existing kyc service rather than standing up a separate service.How it works
Both individual and business are redirect flows: the provider returns a
webURLto a hosted experience and the client polls for the certificate. TheentityTypetells the provider which hosted experience to present.The provider hosts and owns the collection experience for both entity types (the demo-kyc-provider pattern: a hosted form served alongside the anchor API). So the request does not carry entity-specific input details, only which kind of experience to start. KYB detail collection lives in the anchor's hosted form, not in the SDK.
This is intentionally a minimal surface. An earlier draft carried a business-details block in the request and made
webURLoptional for a synchronous business path; that was dropped once we settled on the anchor hosting the KYB form (so business redirects too, andwebURLstays required for both).Changes
entityTypeto the request (defaults toindividual).webURLstays required.KYCEntityTypeis derived from the metadata type for one source of truth.entityTypesto the kyc config (defaults to['individual']) and publish it in the service metadata.entityTypethrough to the resolver lookup.entityTypesto the kyc service metadata; add an optionalentityTypefilter to the kyc search criteria; filter providers by it inlookupKYCServices. A provider with no declaredentityTypesis treated asindividual-only.The cert schema needs no KYB changes
The existing KYC certificate attribute set (generated from
oids.json) is already generic and entity-agnostic, so KYB needs nothing added:EntityTypealready has anorganizationarm (SEQUENCE OF GenericOrganizationIdentification) alongsideperson.OrganizationIdentificationalready carriesbic,lei, and a genericother({ id, schemeName, issuer }).Documentis a single generic container (number, front/back/selfie references, dates, issuing authority) that every document type already reuses.Business identifiers (EIN, registration number, LEI, DUNS) map to
entityType.organization[]via ISO20022 scheme names, and KYB documents map to the genericDocument. No per-document cert fields are required.Back-compat
entityTypedefaults toindividualandentityTypesdefaults to['individual'], so existing providers and callers are unaffected.Verification
npx tsc --noEmit: 0 errorsmake do-lint: cleanmake test: the only failures are the pre-existingcommon.test.tserror round-trip flake, which fails identically on a cleanmaincheckout (2 failed / 549 passed there too). The changed files (server, client, resolver tests) all pass.