Skip to content

Add COP fields needed for Thunes standard-bank payouts#468

Merged
mohamedwane merged 2 commits into
mainfrom
mohamed/grid-cop-add-required-fields
May 13, 2026
Merged

Add COP fields needed for Thunes standard-bank payouts#468
mohamedwane merged 2 commits into
mainfrom
mohamed/grid-cop-add-required-fields

Conversation

@mohamedwane
Copy link
Copy Markdown
Contributor

@mohamedwane mohamedwane commented May 13, 2026

Summary

Thunes' Colombia bank corridor requires four fields beyond what the current `CopAccountInfo` / `CopBeneficiary` schemas expose. Without these, standard-bank COP payouts fail at the Thunes payer-match step:

Field Purpose Per Thunes audit
`bankName` (account) Disambiguate which COP bank Standard for bank rail
`bankAccountType` (account) Part of credit-party identifier (`account_type + bank_account_number`) for std banks Required for 47 of 51 COP payers
`documentType` (beneficiary) Maps to Thunes `id_type` (CC / CE / NIT / PP) Required for KYC match on most std banks
`documentNumber` (beneficiary) Maps to Thunes `id_number` Required for KYC match on most std banks

All four added as optional so existing consumers aren't broken. The runtime decides which subset is required based on the chosen `paymentRails`:

  • `BANK_TRANSFER` → `bankName + accountNumber + bankAccountType + documentType + documentNumber`
  • `MOBILE_MONEY` (Nequi / Daviplata) → `phoneNumber`

That rail-conditional enforcement lands in sparkcore alongside the receiver-field updates.

Why optional, not required

We've been bitten by the union-validation pattern before (PKR's `iban required` regression, SLV's bank-vs-mobile rails). The grid-api schema describes the union of all possible fields; the runtime enforces which subset is needed for the selected rail. This keeps the schema permissive and the validation in one place.

Test plan

  • `npm run lint:openapi` passes
  • Bundled `openapi.yaml` regenerated
  • Sparkcore wiring PR (follow-up): runtime field requirements per rail, response builder, `CURRENCY_PAYEE_FIELD_MAPPINGS` update, union-shape extraction for COP
  • Live test: Bancolombia std bank payout (47-payer baseline) once both PRs land

Out of scope (followup)

  • Davivienda-specific fields (`address` + `city` + `country_iso_code` + `msisdn` on receiver). Already representable via existing optional beneficiary fields; the runtime will pass them when present.
  • Bre-B optional `account_type`: Bre-B-routed banks don't require it. Acceptable to over-ask in v1.
  • Per-payer field variance within COP: deferred to a future refactor of `ThunesFieldsProvider` that's payer-aware rather than corridor-aware.

🤖 Generated with Claude Code

Thunes' Colombia bank corridor requires four fields beyond what the
current CopAccountInfo and CopBeneficiary schemas expose:

- bankName: needed at the account level for bank rail
- bankAccountType: account_type CPI for std banks (CHECKING/SAVINGS)
- documentType / documentNumber: id_type / id_number that nearly every
  Colombian payer needs to KYC-match the beneficiary

Add these as optional properties so existing COP_ACCOUNT consumers
aren't broken. Required-field enforcement moves to the sparkcore
runtime where it can be rail-conditional (BANK_TRANSFER wants
bankName + accountNumber + bankAccountType; MOBILE_MONEY only wants
phoneNumber).

This is the foundation PR — sparkcore wiring (receiver fields,
account.py response shape, CURRENCY_PAYEE_FIELD_MAPPINGS update,
union-shape extraction) follows once Stainless regenerates the SDK.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
grid-flow-builder Ready Ready Preview, Comment May 13, 2026 9:47pm

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

✱ Stainless preview builds for grid

This PR will update the grid SDKs with the following commit messages.

kotlin

feat(api): add document fields to CopBeneficiary, bank fields to Cop account types

openapi

feat(api): add bank and document fields to COP account models

python

feat(api): add bank_account_type, bank_name, document_number/type to COP models

typescript

feat(api): add document and bank fields to Colombian Peso external accounts
⚠️ grid-python studio · code

Your SDK build had at least one "error" diagnostic.
generate ❗build ✅lint ✅test ✅

pip install https://pkg.stainless.com/s/grid-python/13e0f5a3dd7950d7bf3e97f0f440990a1d06d576/grid-0.0.1-py3-none-any.whl
⚠️ grid-kotlin studio · code

Your SDK build had at least one "error" diagnostic.
generate ❗build ✅lint ✅test ✅

⚠️ grid-openapi studio · code

Your SDK build had at least one "error" diagnostic.
generate ❗

⚠️ grid-typescript studio · code

Your SDK build had a failure in the build CI job, which is a regression from the base state.
generate ❗build ❗lint ❗test ❗


This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-05-13 22:09:50 UTC

@mohamedwane mohamedwane marked this pull request as ready for review May 13, 2026 21:36
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 13, 2026

Greptile Summary

This PR extends the COP (Colombian Peso) payment schema with four new optional fields needed for Thunes standard-bank payouts: bankName and bankAccountType on CopAccountInfoBase, and documentType and documentNumber on CopBeneficiary. All fields are added as optional so existing consumers remain unbroken, with rail-conditional enforcement deferred to the companion sparkcore PR.

  • CopAccountInfoBase gains bankName (string, 1–255) and bankAccountType (enum: CHECKING/SAVINGS), with the rail description and example updated accordingly.
  • CopBeneficiary gains documentType and documentNumber as plain, unconstrained strings — unlike the account fields, no enum or length validation is applied, so invalid values (e.g. unsupported id_type) will only surface at Thunes rather than at the API boundary.
  • Both bundled openapi.yaml files are correctly regenerated via make build.

Confidence Score: 4/5

Safe to merge as a schema-only change; all new fields are optional and existing consumers are unaffected.

The account-side fields (bankName, bankAccountType) are well-specified with enums and length guards. The beneficiary-side fields (documentType, documentNumber) are both unconstrained plain strings — documentType in particular maps to Thunes id_type with a known finite set of accepted values (CC/CE/NIT/PP), and the absence of an enum means a caller sending an unsupported value will only discover the error at Thunes, not at schema validation time. The example object also lost its MOBILE_MONEY shape, which may confuse consumers.

openapi/components/schemas/common/CopBeneficiary.yaml — documentType and documentNumber have no validation constraints.

Important Files Changed

Filename Overview
openapi/components/schemas/common/CopAccountInfoBase.yaml Adds bankName (string, 1-255) and bankAccountType (enum: CHECKING/SAVINGS) as optional BANK_TRANSFER fields; updates rail description and example (example now only covers BANK_TRANSFER, MOBILE_MONEY shape is no longer shown)
openapi/components/schemas/common/CopBeneficiary.yaml Adds documentType and documentNumber as optional fields; both lack enum/length constraints that would push validation to the API boundary rather than deferring failures to Thunes
openapi.yaml Generated bundle reflecting CopAccountInfoBase and CopBeneficiary source changes; no direct edits needed here per CLAUDE.md
mintlify/openapi.yaml Mintlify copy of the generated bundle; identical changes to root openapi.yaml, correct per build process

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[COP Payout Request] --> B{paymentRails}
    B -->|BANK_TRANSFER| C[CopAccountInfoBase]
    B -->|MOBILE_MONEY| D[CopAccountInfoBase]
    C --> C1[accountType: COP_ACCOUNT]
    C --> C2[bankName ✨ new]
    C --> C3[accountNumber]
    C --> C4[bankAccountType ✨ new]
    D --> D1[accountType: COP_ACCOUNT]
    D --> D2[phoneNumber]
    A --> E[CopBeneficiary]
    E --> E1[beneficiaryType / fullName]
    E --> E2[documentType ✨ new]
    E --> E3[documentNumber ✨ new]
    E --> E4[existing fields...]
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
openapi/components/schemas/common/CopBeneficiary.yaml:31-38
**`documentType` is unconstrained string; `documentNumber` lacks length guards**

The PR description explicitly names the accepted values for `documentType` (CC, CE, NIT, PP) and maps them to Thunes `id_type`. Without an `enum` constraint, any arbitrary string passes schema validation and the error will only surface at the Thunes payer-match step — after the payout has been accepted. Every other typed field in this PR adds stronger validation (`bankAccountType` gets an enum; `bankName` and `accountNumber` get `minLength`/`maxLength`). `documentNumber` similarly lacks `minLength: 1`, so an empty string is schema-valid even though Thunes would reject it.

If the set of valid `documentType` values is expected to grow over time (beyond CC/CE/NIT/PP), a comment explaining that is worth adding; otherwise an `enum` keeps the contract explicit and moves validation to the API boundary.

### Issue 2 of 2
openapi/components/schemas/common/CopAccountInfoBase.yaml:38-42
The example was updated to show only BANK_TRANSFER fields and `phoneNumber` was removed entirely. Because the schema is intentionally a union of both rails (per the PR description), the example now gives no indication of how a MOBILE_MONEY payload looks. Consider splitting into named `examples` so consumers can see both rail shapes.

```suggestion
examples:
  bankTransfer:
    summary: BANK_TRANSFER rail
    value:
      accountType: COP_ACCOUNT
      bankName: Bancolombia
      accountNumber: '1234567890'
      bankAccountType: CHECKING
  mobileMoney:
    summary: MOBILE_MONEY rail (Nequi / Daviplata)
    value:
      accountType: COP_ACCOUNT
      phoneNumber: '+573001234567'
```

Reviews (1): Last reviewed commit: "Add COP fields needed for Thunes standar..." | Re-trigger Greptile

Comment on lines +31 to +38
documentType:
type: string
description: Identity document type — required by most Colombian banks
(e.g. CC for Cédula de Ciudadanía, CE for Cédula de Extranjería, NIT,
PP for passport)
documentNumber:
type: string
description: Identity document number — required by most Colombian banks
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 documentType is unconstrained string; documentNumber lacks length guards

The PR description explicitly names the accepted values for documentType (CC, CE, NIT, PP) and maps them to Thunes id_type. Without an enum constraint, any arbitrary string passes schema validation and the error will only surface at the Thunes payer-match step — after the payout has been accepted. Every other typed field in this PR adds stronger validation (bankAccountType gets an enum; bankName and accountNumber get minLength/maxLength). documentNumber similarly lacks minLength: 1, so an empty string is schema-valid even though Thunes would reject it.

If the set of valid documentType values is expected to grow over time (beyond CC/CE/NIT/PP), a comment explaining that is worth adding; otherwise an enum keeps the contract explicit and moves validation to the API boundary.

Prompt To Fix With AI
This is a comment left during a code review.
Path: openapi/components/schemas/common/CopBeneficiary.yaml
Line: 31-38

Comment:
**`documentType` is unconstrained string; `documentNumber` lacks length guards**

The PR description explicitly names the accepted values for `documentType` (CC, CE, NIT, PP) and maps them to Thunes `id_type`. Without an `enum` constraint, any arbitrary string passes schema validation and the error will only surface at the Thunes payer-match step — after the payout has been accepted. Every other typed field in this PR adds stronger validation (`bankAccountType` gets an enum; `bankName` and `accountNumber` get `minLength`/`maxLength`). `documentNumber` similarly lacks `minLength: 1`, so an empty string is schema-valid even though Thunes would reject it.

If the set of valid `documentType` values is expected to grow over time (beyond CC/CE/NIT/PP), a comment explaining that is worth adding; otherwise an `enum` keeps the contract explicit and moves validation to the API boundary.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

Comment on lines 38 to +42
example:
accountType: COP_ACCOUNT
bankName: Bancolombia
accountNumber: '1234567890'
phoneNumber: '+1234567890'
bankAccountType: CHECKING
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The example was updated to show only BANK_TRANSFER fields and phoneNumber was removed entirely. Because the schema is intentionally a union of both rails (per the PR description), the example now gives no indication of how a MOBILE_MONEY payload looks. Consider splitting into named examples so consumers can see both rail shapes.

Suggested change
example:
accountType: COP_ACCOUNT
bankName: Bancolombia
accountNumber: '1234567890'
phoneNumber: '+1234567890'
bankAccountType: CHECKING
examples:
bankTransfer:
summary: BANK_TRANSFER rail
value:
accountType: COP_ACCOUNT
bankName: Bancolombia
accountNumber: '1234567890'
bankAccountType: CHECKING
mobileMoney:
summary: MOBILE_MONEY rail (Nequi / Daviplata)
value:
accountType: COP_ACCOUNT
phoneNumber: '+573001234567'
Prompt To Fix With AI
This is a comment left during a code review.
Path: openapi/components/schemas/common/CopAccountInfoBase.yaml
Line: 38-42

Comment:
The example was updated to show only BANK_TRANSFER fields and `phoneNumber` was removed entirely. Because the schema is intentionally a union of both rails (per the PR description), the example now gives no indication of how a MOBILE_MONEY payload looks. Consider splitting into named `examples` so consumers can see both rail shapes.

```suggestion
examples:
  bankTransfer:
    summary: BANK_TRANSFER rail
    value:
      accountType: COP_ACCOUNT
      bankName: Bancolombia
      accountNumber: '1234567890'
      bankAccountType: CHECKING
  mobileMoney:
    summary: MOBILE_MONEY rail (Nequi / Daviplata)
    value:
      accountType: COP_ACCOUNT
      phoneNumber: '+573001234567'
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code

@mohamedwane
Copy link
Copy Markdown
Contributor Author

@greptile-apps re: the two pieces of feedback:

documentType enum + documentNumber length guards — addressed in 28486db. Added enum [CC, CE, TI, NIT, PP] (Colombian id document types) and minLength: 1 / maxLength: 50 on documentNumber. Skipped PEP since it's an immigration-status doc rather than a bank-recognized id type; can add later if Thunes asks for it.

Split examples for both rails — going to skip. Existing multi-rail schemas in this repo (GhsAccountInfoBase.yaml, BdtAccountInfoBase.yaml) all use a single mixed example rather than named examples. Matching that convention keeps the schemas consistent; if we want to switch the convention repo-wide, that's worth a separate PR.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 13, 2026

Both look good:

  1. documentType + documentNumber guards (28486db) — The enum [CC, CE, TI, NIT, PP] and minLength: 1 / maxLength: 50 address the feedback cleanly. Excluding PEP is the right call — it's an immigration status, not a bank-recognized ID type, and you can always extend the enum later if Thunes signals a need.

  2. Single example convention — Fair point. If GhsAccountInfoBase.yaml and BdtAccountInfoBase.yaml both use the single mixed example pattern, matching that is the right call. A repo-wide convention change belongs in its own PR.

Both items resolved. LGTM on this PR pending the sparkcore follow-up.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

@mohamedwane
Copy link
Copy Markdown
Contributor Author

@greptile anything else?

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 13, 2026

Looking at the diff more carefully, I owe a correction on my first comment — documentType already has an enum [CC, CE, TI, NIT, PP] and documentNumber already has minLength: 1 / maxLength: 50 in the committed code. That comment was wrong. Sorry for the noise.

The only remaining open point is the example comment (BANK_TRANSFER fields only, no MOBILE_MONEY shape shown).

One minor thing I didn't flag before: bankName is a free-text string (minLength: 1, maxLength: 255). If Thunes requires the bank name to exactly match their payer list (which is common), a typo here would cause a silent rejection at the payer-match step. Worth a comment in the description noting that the value must match the Thunes payer name — or if there's a lookup table on the sparkcore side that normalizes it, a note referencing that would help future readers.

That's it overall — the schema changes are clean and well-constrained.

@mohamedwane mohamedwane merged commit 2033ecc into main May 13, 2026
7 checks passed
@mohamedwane mohamedwane deleted the mohamed/grid-cop-add-required-fields branch May 13, 2026 22:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants