[Ready] Implement and unify KYC/B processes via API. - #1318
Conversation
✅ Deploy Preview for vortex-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
# Conflicts: # docs/proposal-headless-profiles-and-pricing-plans.md
# Conflicts: # apps/api/src/api/controllers/brla.controller.test.ts # apps/api/src/api/controllers/brla.controller.ts # docs/security-spec/05-integrations/brla.md # packages/shared/src/services/brla/brlaApiService.test.ts # packages/shared/src/services/brla/brlaApiService.ts # packages/shared/src/services/brla/mappings.ts # packages/shared/src/services/brla/schemas.test.ts # packages/shared/src/services/brla/schemas.ts # packages/shared/src/services/brla/types.ts
# Conflicts: # docs/api/openapi/vortex.openapi.d.ts # docs/api/openapi/vortex.openapi.json # docs/api/pages/10-sandbox.md # docs/api/scripts/check-openapi.ts # docs/security-spec/05-integrations/alfredpay.md # docs/security-spec/05-integrations/brla.md
Bring the managed-profile idempotency ledger (runFinancialOperation, the 064 profile-scope migration) and the associated infrastructure into the streamlined KYC/KYB branch. createSubaccount now claims an exactly-once financial operation keyed on the tax-reference hash, keeping HEAD's Avenia attempt-state simplification while removing the overwrite-on-retry hazard. Resolve conflicts in favor of HEAD's Avenia simplification (no submission state machine) plus the merged idempotency block. Regenerate the wire-contract snapshot for the now-required record-attempt quoteId, and fix check-openapi to resolve the record-attempt request-body $ref before asserting its required fields. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Avenia omits resultMessage and retryable until an attempt settles, so a PENDING poll parsed by aveniaAttemptSchema raised a ZodError that surfaced to the client as a 502 and could permanently block hosted KYB resume. Make both fields optional in the schema and the KycAttempt / AveniaVerificationAttempt types; every consumer already reads them optional-safely. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
recordInitialKycAttempt only null-checked quoteId and never used it, so any authenticated caller could plant a Consulted provider_customers marker on another profile's CPF/CNPJ — a started row that then blocks the rightful owner's createSubaccount. Assert the caller owns the referenced quote and that it is a Brazil corridor (BRL on either leg) before persisting the marker. Update the F-064 security-spec entry to record quoteId as an authorization input. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds API-driven Avenia KYB and public onboarding discovery, while also bundling managed-profile pricing and ramp-recovery changes.
Changes:
- Adds Avenia document, UBO, submission, reconciliation, and status APIs.
- Publishes provider-specific onboarding requirements and OpenAPI validation.
- Changes managed-profile pricing and BRL ramp recovery behavior.
Reviewed changes
Copilot reviewed 50 out of 52 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
packages/shared/src/services/brla/types.ts |
Adds Avenia KYB types. |
packages/shared/src/services/brla/schemas.ts |
Validates Avenia responses. |
packages/shared/src/services/brla/schemas.test.ts |
Tests response schemas. |
packages/shared/src/services/brla/mappings.ts |
Adds provider endpoint mappings. |
packages/shared/src/services/brla/brlaApiService.ts |
Implements KYB provider calls. |
packages/shared/src/services/brla/brlaApiService.test.ts |
Tests provider mappings. |
packages/shared/src/endpoints/onboarding-requirements.endpoints.ts |
Defines discovery metadata. |
packages/shared/src/endpoints/onboarding-requirements.endpoints.test.ts |
Tests discovery flows. |
packages/shared/src/endpoints/index.ts |
Exports discovery contracts. |
packages/shared/src/endpoints/brla.endpoints.ts |
Makes KYC result optional. |
packages/shared/src/endpoints/alfredpay.endpoints.ts |
Adds status type selector. |
docs/security-spec/07-operations/api-surface.md |
Documents public discovery. |
docs/security-spec/05-integrations/brla.md |
Updates Avenia invariants. |
docs/security-spec/05-integrations/alfredpay.md |
Documents typed status lookup. |
docs/security-spec/03-ramp-engine/quote-lifecycle.md |
Documents manager pricing. |
docs/security-spec/03-ramp-engine/profile-partner-pricing.md |
Defines pricing precedence. |
docs/README.md |
Indexes the proposal. |
docs/proposal-api-driven-kyc-kyb.md |
Proposes API-driven verification. |
docs/api/wire-contract.snapshot.md |
Updates generated contracts. |
docs/api/scripts/check-openapi.ts |
Checks discovery/OpenAPI consistency. |
docs/api/pages/10-sandbox.md |
Improves wallet guidance. |
docs/api/pages/06-quotes-and-pricing.md |
Documents inherited pricing. |
docs/api/pages/03-authentication-and-partner-keys.md |
Documents managed pricing precedence. |
docs/api/apidog/page-manifest.json |
Publishes new API pages. |
docs/adr-0003-managed-headless-profiles.md |
Updates pricing decision. |
apps/api/src/tests/managed-profile-quote-ramp-lifecycle.integration.test.ts |
Tests inherited pricing. |
apps/api/src/tests/http-surface.invariants.test.ts |
Tests public discovery. |
apps/api/src/tests/contracts/avenia.contract.test.ts |
Notes missing live coverage. |
apps/api/src/api/workers/unhandled-payment.worker.ts |
Retains recoverable ramps. |
apps/api/src/api/workers/unhandled-payment.worker.test.ts |
Tests repeated recovery. |
apps/api/src/api/services/ramp/ramp.service.update-additional-data.test.ts |
Tests state-field restrictions. |
apps/api/src/api/services/ramp/ramp.service.ts |
Allowlists client state updates. |
apps/api/src/api/services/quote/index.ts |
Carries manager context. |
apps/api/src/api/services/quote/core/types.ts |
Extends pricing context types. |
apps/api/src/api/services/quote/core/quote-context.ts |
Accepts manager context. |
apps/api/src/api/services/quote/core/partner-resolution.ts |
Adds manager pricing fallback. |
apps/api/src/api/services/quote/core/partner-resolution.test.ts |
Tests pricing precedence. |
apps/api/src/api/services/avenia/avenia-kyb.service.ts |
Adds KYB ownership helpers. |
apps/api/src/api/services/avenia/avenia-customer.service.ts |
Reformats case updates. |
apps/api/src/api/routes/v1/onboarding.route.ts |
Exposes discovery route. |
apps/api/src/api/routes/v1/onboarding-requirements.route.test.ts |
Tests unauthenticated access. |
apps/api/src/api/routes/v1/brla.route.ts |
Registers KYB routes. |
apps/api/src/api/middlewares/validators.ts |
Validates KYB payloads. |
apps/api/src/api/middlewares/validators.test.ts |
Tests KYB validation. |
apps/api/src/api/controllers/quote.controller.ts |
Passes manager pricing context. |
apps/api/src/api/controllers/onboarding.controller.ts |
Serves discovery metadata. |
apps/api/src/api/controllers/onboarding-requirements.controller.test.ts |
Tests discovery responses. |
apps/api/src/api/controllers/brla.controller.ts |
Implements API KYB orchestration. |
apps/api/src/api/controllers/brla.controller.test.ts |
Tests KYB and ownership behavior. |
apps/api/src/api/controllers/alfredpay.controller.ts |
Supports typed status lookup. |
Suppressed comments (2)
apps/api/src/api/controllers/brla.controller.ts:897
- This reconciliation blindly rewrites the case from a provider-list snapshot. If a concurrent status poll observes the same attempt as completed and persists approval/rejection after this list call but before this update, reconciliation can overwrite that terminal state back to pending/in-review and clear lifecycle fields. Lock/CAS the current nonterminal case and enforce monotonic external-status transitions, as the polling path already does.
await kycCase.update(
{
approvedAt: null,
failureReasons: [],
providerCaseId: attempt.id,
apps/api/src/api/controllers/brla.controller.ts:976
- UBO creation is not retry-safe. If Avenia creates the UBO but the response is lost (the shared client has a 30-second timeout), retrying this endpoint issues another POST with no idempotency key, durable operation claim, or reconciliation path, potentially creating duplicate beneficial-owner records and leaving the client without the original ID. Add an idempotent operation identifier or persist/reconcile the provider result before allowing retries.
const response = await brlaApiService.createUbo(req.body, subAccountId);
res.status(httpStatus.CREATED).json(response);
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
✅ Deploy Preview for vrtx-dashboard canceled.
|
✅ Deploy Preview for vortexfi ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 82 out of 84 changed files in this pull request and generated 2 comments.
Suppressed comments (4)
packages/shared/src/services/brla/brlaApiService.ts:451
- API KYB submission has the same PII exposure as standard KYC (company identity, tax number, address, document IDs and financial profile), but this call does not enable sensitive-body mode. The generic client logs the payload and includes provider response text in thrown errors. Pass
sensitiveBody: trueand cover both request logging and echoed provider errors in the client tests.
packages/shared/src/endpoints/alfredpay.endpoints.ts:35 - Moving
typeto the status request does not require removing it from this existing exported request contract. Typed clients that still construct the prior redirect-link query now fail excess-property checks, contrary to the compatibility guarantee. Retain the property as deprecated/ignored so the runtime behavior can remain unchanged without a source break.
apps/api/src/api/controllers/brla.controller.ts:1118 - A timeout or lost response from this POST leaves no durable submission claim. On retry, if Avenia has accepted the attempt but its list endpoint has not exposed it yet, preflight sees zero active attempts and sends the KYB payload again. Persist a prepared/submitted/ambiguous claim before the provider call, and allow retries to reconcile that claim without repeating the POST, as the individual KYC flow does.
let response: KycLevel1Response;
try {
response = await brlaApiService.submitKybLevel1(req.body, subAccountId);
} catch (error) {
if (!(error instanceof BrlaApiError) || error.status !== httpStatus.CONFLICT) {
throw error;
packages/shared/src/endpoints/brla.endpoints.ts:47
- The PR states that existing request contracts remain available, but removing
quoteIdandsessionIdfrom this exported shared wire type is source-breaking for typed clients that still send the legacy query shape, even though the server can ignore those fields. Keep them as deprecated optional properties while making onlytaxIdrequired.
| public async createUbo(payload: AveniaUboPayload, subAccountId: string): Promise<AveniaUboResponse> { | ||
| const query = `subAccountId=${encodeURIComponent(subAccountId)}`; | ||
| return aveniaUboResponseSchema.parse(await this.sendRequest(Endpoint.Ubos, "POST", query, payload)); |
| const now = new Date(); | ||
| await sequelize.transaction(async transaction => { | ||
| await record.update( | ||
| { | ||
| lastFailureReasons: [], |
API-driven, provider-specific KYC/KYB onboarding
Adds provider-aware onboarding discovery and expands API-driven KYC/KYB support while preserving the existing provider-specific endpoint families.
Changes
Managed-profile pricing
Managed children inherit the controlling manager’s active partner-pricing assignment when the child has no active assignment.
Pricing precedence is:
This applies to delegated manager requests and requests authenticated directly as the managed child. It can change quote fees and pricing attribution for existing managed children that do not have their own assignment.
Compatibility
Testing