Repository navigation
Commit 0bd5a28
authored
feat(customers): accept typed agreement consents for seven documents (#990)
## Summary
- Replaces the single `endUserTermsConsent` field with `agreementConsents`, a list carrying one entry per agreement, so each document's acceptance is recorded and auditable separately.
- Adds an `AgreementType` enum with the seven supported documents, and changes `GET /customers/end-user-terms` to return a catalog of all seven as `{ data: [{ type, version, url }] }`.
- Spec + docs only. Backend, migration, and generated-client work follow in separate PRs.
## The seven agreement types
| Document | `AgreementType` |
| --- | --- |
| Lightspark Terms of Service (End User Terms) | `LIGHTSPARK_END_USER_TERMS` |
| Lightspark E-SIGN consent | `LIGHTSPARK_E_SIGN_CONSENT` |
| Lightspark Privacy Policy | `LIGHTSPARK_PRIVACY_POLICY` |
| Lead E-SIGN consent | `LEAD_E_SIGN_CONSENT` |
| Lead Accountholder Agreement | `LEAD_ACCOUNTHOLDER_AGREEMENT` |
| Lead Privacy Policy | `LEAD_PRIVACY_POLICY` |
| Lead Cardholder Agreement | `LEAD_CARDHOLDER_AGREEMENT` |
Each consent entry carries `type` plus the four existing evidence fields (`acceptedAt`, `ipAddress`, `termsVersion`, `acceptanceMethod`), all required. `termsVersion` is the `version` the catalog returns for that type — versions are scoped per type, so a version valid for one agreement is not valid for another.
## Design decisions worth a reviewer's attention
**The endpoint path is unchanged.** `GET /customers/end-user-terms` keeps its path and gains a list response (`operationId` `getEndUserTerms` → `listAgreementDocuments`). Renaming the path would be a second breaking change for no functional gain; happy to rename if you'd prefer to take both at once.
**The catalog is not a requirements list.** Seven supported types does not mean every customer must accept all seven. Which documents are required, for which products and customers, is a legal decision this PR deliberately does not encode.
**`info.version` is intentionally not bumped.** It tracks the published dated version — a live routing prefix, also hardcoded across the docs and the generated client — not each breaking merge. It has not moved since that rule landed in #679, across 36+ `breaking-change` PRs. The `breaking-change` label and API-reviewer approval are the real gate; flagging it here so that decision is explicit rather than implied.
**Clients pinned to the dated version are not affected.** The server change lands this shape on the `/grid/rc` release-candidate surface only, leaving `/grid/2025-10-13` serving the existing contract. Cutting a new dated version is a separate, coordinated release.
**`retrieve_end_user_terms` in `.stainless/stainless.yml` is left alone.** It's a published SDK method name and its path is unchanged, so renaming it is an independent SDK-breaking decision. The three model mappings that pointed at now-deleted schemas are updated.
## Migration for existing consumers
- Acceptance already recorded under the old field maps only to `LIGHTSPARK_END_USER_TERMS`. Acceptance of one agreement never implies another, so the other six must be collected from each customer.
- On update, supplying consents records additional acceptances; evidence already on file for other types is untouched. Omitting the field changes nothing.
- `Customer.agreementConsents` is read-only and returns the most recent acceptance per accepted type. It is omitted entirely until the first acceptance, matching how the old `endUserTermsConsent` behaved.
## Changes: 24 files
- `openapi/components/schemas/customers/Agreement*.yaml` — 6 new schemas (type, acceptance method, consent request/response, document, list response)
- `openapi/components/schemas/customers/EndUserTerms*.yaml` — 3 superseded schemas removed (`EndUserTerms`, `EndUserTermsAcceptanceMethod`, `EndUserTermsConsentRequest`); `EndUserTermsConsent` is kept and marked `deprecated: true`
- `openapi/components/schemas/customers/Customer{,CreateRequest,UpdateRequest}.yaml` — field swapped to the list. On `Customer` the field is **optional and omitted when the customer has no acceptances**, rather than always present as `[]` (per review) — matching the deprecated `endUserTermsConsent`, which already documented omit-when-absent
- `openapi/paths/customers/customers_agreements.yaml` (new) — `GET /customers/agreements`, the catalog of all seven documents
- `openapi/paths/customers/customers_end-user-terms.yaml` — kept and marked `deprecated: true`, still returning its original single-document body so existing callers keep working
- `openapi/webhooks/customer-update.yaml` — 2 examples updated to match `Customer`
- `openapi/components/schemas/errors/Error400.yaml` — `END_USER_TERMS_VERSION_NOT_FOUND` now fires for any of the seven agreements, so its description no longer names the End User Terms specifically. Widened the wording rather than adding an enum value, which would be a wire change for clients to distinguish cases with the same cause and the same remedy
- `.stainless/stainless.yml` — model mappings repointed
- `mintlify/` — disclosures rewritten, snippet + changelog entry, regenerated bundles
## Rebase note
Rebased onto `main` (6 commits, through #934 "bind one funding source per card"). Two conflicts, both resolved keeping each side:
- `Customer.yaml` — main still carried only `endUserTermsConsent`; this branch adds the deprecation marker plus `agreementConsents`. Took this branch's version, which contains both.
- `mintlify/changelog.mdx` — both sides added a new top entry. Kept both, main's card-refunds entry above this one.
Verified after the rebase that main's card work survived intact (e.g. `CardTransaction.originalTransactionId` is present in the rebuilt bundle) alongside the seven-value enum and the deprecated field.
## Test plan
This repo has no test suite; it is spec + docs. Verified:
- `make build` bundles cleanly; both generated bundles regenerated and committed.
- `make lint` exits 0 with **2 warnings / 14 ignored — byte-identical to the `main` baseline**, confirmed by re-running lint on a stashed tree. No new lint problems.
- Parsed the bundle and asserted: the enum holds exactly 7 unique values; all 5 consent fields are required; all three customer schemas expose `agreementConsents` and no longer expose `endUserTermsConsent`; the 200 response resolves to `data[]` of `AgreementDocument` with `{type, version, url}` all required; no `EndUserTerms*` schema remains; `info.version` unchanged.
- An automated review pass caught the stale `.stainless` model mappings, which are fixed here. Its remaining finding is the version-bump question addressed above.
## Public
Consent is now recorded per agreement type, and the agreements endpoint lists every supported document with its current version and URL.
Original PR: #9541 parent 633ee21 commit 0bd5a28
24 files changed
Lines changed: 609 additions & 124 deletions
File tree
- .stainless
- mintlify
- payouts-and-b2b/onboarding
- snippets
- openapi
- components/schemas
- customers
- errors
- paths/customers
- webhooks
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
| 116 | + | |
| 117 | + | |
116 | 118 | | |
117 | | - | |
118 | | - | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
119 | 123 | | |
120 | 124 | | |
121 | 125 | | |
| |||
142 | 146 | | |
143 | 147 | | |
144 | 148 | | |
| 149 | + | |
145 | 150 | | |
146 | 151 | | |
147 | 152 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
51 | 76 | | |
52 | 77 | | |
53 | 78 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
0 commit comments