KYC update-required fallback

State
UPDATE_REQUIRED → prefilled correction form
Status
Live
Endpoint
POST /psp/alfred/kyc/fallback (multipart)
This is LIVE, not planned. Instead of a dead end, when Alfred asks for corrections the widget shows a prefilled correction form so the user fixes the flagged data and re-uploads documents themselves — without leaving the widget or contacting support. Every step works on dev/UAT today — no manual curl to Alfred needed (the __dev/* endpoints are exchange-gated to VAKOTEST / *UAT).
Why "update-required" shows the full form, not just the broken field. Alfred's updateInfo webhook carries only { referenceId, eventType, status }status: UPDATE_REQUIRED says "fix something", never which field. So we compute the form from GET /kycRequirements?country=XXX (canonical required set, incl. document uploads + per-country fields like cuit), prefill from GET /customers/{id} + our user_kyc_data, and — because there is no per-field rejection signal — present the full prefilled set and always re-collect documents. The submissionId needed to re-submit is fetched live via GET /customers/kyc/{customerId}; we never store it.

Sequence ④ — the KYC fallback

End-to-end: drive UPDATE_REQUIRED → render the prefilled form → re-submit → unblock.

④ Update-required flow — the KYC fallback LIVE
QA
Widget
vakotrade BE
Alfred
  1. 1
    QA
    Force a KYC UPDATE_REQUIRED webhook with the live submissionId (helper below). Verified today: Alfred accepts the push (201).
  2. 2
    BE
    Fans back → BE writes alfred_kyc_status: UPDATE_REQUIRED. LIVE
  3. 3
    Widget
    GET /eligibility{ eligible: false, reason: "KYC_UPDATE_REQUIRED", fallback: { fields[], documents[] } }. LIVE
  4. 4
    BE
    Descriptor built from GET /kycRequirements ∖ values held (GET /customers/{id} + user_kyc_data); all documents flagged for re-upload. LIVE
  5. 5
    Widget
    Renders the prefilled form; user corrects + re-uploads → POST /psp/alfred/kyc/fallback. LIVE
  6. 6
    BE
    PUT /customers/kyc { customerId, kycUpdateSubmission: { submissionId, …corrected } } + re-upload docs + updateKycStatusByCirclealfred_kyc_status: PENDING. LIVE
  7. 7
    QA
    Force a KYC COMPLETED webhook to simulate Alfred re-approval.
  8. BE
    Result: alfred_kyc_status: APPROVEDeligibility passes → payment unblocked. No optimistic unblock — we wait for COMPLETED.

Scenario E — KYC update-required fallback recipe

Full recipe to drive a user into Alfred's UPDATE_REQUIRED state, render the prefilled correction form, re-submit, and unblock.

  1. Seed + approve a customer for the country under test: POST /psp/alfred/__dev/seed-kyc-and-provision?country=AR (or MX / BR / CO), then __dev/emit-kyc-status { status: "COMPLETED" } to land APPROVED.
  2. Force the update-required state in one call: POST /psp/alfred/__dev/emit-kyc-status { status: "UPDATE_REQUIRED" }. BE resolves your customerId + live submissionId, pushes the synthetic webhook, and writes alfred_kyc_status: UPDATE_REQUIRED.
  3. Open the widget (or call GET /psp/alfred/eligibility). Expect { eligible: false, reason: "KYC_UPDATE_REQUIRED", fallback: { customerId, country, fields[], documents[] } } — the prefilled correction form renders, every FILE_UPLOAD flagged for re-upload.
  4. Correct a field and re-upload the documents → POST /psp/alfred/kyc/fallback (multipart). Status returns to PENDING.
  5. Unblock: POST /psp/alfred/__dev/emit-kyc-status { status: "COMPLETED" } → eligibility passes → payment opens.
# 2. force update-required — server resolves customerId + submissionId
POST /psp/alfred/__dev/emit-kyc-status
{ "status": "UPDATE_REQUIRED" }
# → { customerId, submissionId, status: "UPDATE_REQUIRED", alfred: { message: "success" } }

# 4. re-submit corrected KYC — multipart/form-data (AuthGuard)
POST /psp/alfred/kyc/fallback
# corrections = JSON keyed by Alfred field name; files keyed by document name
corrections={"firstName":"Maria","lastName":"Gomez","address":"Av. Corrientes 1234","city":"Buenos Aires","cuit":"20304050607"}
Selfie=@selfie.jpg
"National ID Front"=@id-front.jpg
"National ID Back"=@id-back.jpg
# → { status: "submitted", submissionId }   then alfred_kyc_status: PENDING

Per-country corrections field sets

The required set comes live from GET /kycRequirements?country=<ALPHA-3> (ARG / MEX / BRA / COL) and is echoed in fallback.fields[].

Base text fields (all countries): firstName, lastName, dateOfBirth, country, state, city, zipCode, address, dni, phoneNumber, pep, email. Documents always re-collected: Selfie, National ID Front, National ID Back.
CountryExtra fields beyond base setNotes
AR (ARG)cuitArgentine tax/ID number; required in corrections.
CO (COL)typeDocumentColColombian document type. PSE bank_id is a payments concern, not a corrections field.
BR (BRA)dni carries the CPF.
MX (MEX)Base set only.
Per-region coverage. Drive the update flow once per country group — the required-field set differs. Confirm the rebuilt form renders the right country-specific fields and that all FILE_UPLOAD documents (Selfie, National ID Front/Back) re-appear for re-upload.

Fallback form preview — what the user actually sees

On UPDATE_REQUIRED the widget renders the full required set for the user's country (Alfred's /kycRequirements) — not a missing-only subset. Every text/date/boolean field is pre-filled from the KYC we already hold, so the user never re-types data; they just fix the one field Alfred rejected and submit. Documents must be re-uploaded — Alfred exposes no per-document status, and our stored copy expires after 7 days, so we can't auto-resend. Tick the fields you want to be missing: the panel below emits the exact seed-kyc-and-provision?missing=… curl that creates that state, so QA can drive any incomplete-KYC scenario end-to-end — then see the form, the GET /eligibility descriptor, and the POST /psp/alfred/kyc/fallback body.

Tick a field = we don't hold it → renders blank, user must enter. Untick = prefilled from KYC. Documents always re-upload; untick one to drop it from this country's required set.

Fields
Documents (required)

        
        

        
        

        
Preview only — nothing is sent. Type → control mapping the live widget renders: STRING → text, DATE → date picker, BOOLEAN → checkbox, FILE_UPLOAD → file upload. Fields tagged prefilled arrive populated; pep is blank for SumSub tenants (we don't hold it). The submit body carries corrections (a JSON string of the field values — pre-filled ones included, edited or not) + one multipart part per re-uploaded document. No submissionId is sent — Alfred resolves the active submission from the session's customerId.

Inspect / reset the state directly

The status lives in user_kyc_data under kyc-property alfred_kyc_status.

# inspect the current Alfred KYC status for a user
SELECT kyc_value FROM user_kyc_data
WHERE user_id='<id>' AND kyc_property='alfred_kyc_status';
Move a user between states without touching Alfred by hand. Re-run __dev/emit-kyc-status with any status to move a user between UPDATE_REQUIRED / PENDING / APPROVED / REJECTED. Fire it from the Dev playground.
Run it. Open the Dev playground → use the KYC status runner, pick UPDATE_REQUIRED, and Fire. Then verify the widget renders the prefilled correction form, re-submit, and push COMPLETED to unblock.
Drive UPDATE_REQUIRED
emit-kyc-status (UPDATE_REQUIRED / COMPLETED) · seed-kyc-and-provision.