docs(UPI Issuance): VPA resolution + resolution notification, and restore the UPI Issuance doc set - #450
Merged
Conversation
…rence Client-facing developer documentation for the UPI Issuance onboarding surface, published to the Setu docs site. - Guide pages: overview, quickstart, API envelope, device binding, OTP verification, VPA management, programs, payee blocklist, QA-env testing. - OpenAPI spec (api-references/payments/upi-issuance.json) driving the interactive API reference: human-readable operation summaries, doc-ordered sections, internal endpoints excluded, per-operation error-code enums + examples, and consistent deviceId / mobile / idempotencyKey / cursor / token / id conventions across docs and refs. - Nav registration in endpoints.json, menuItems.json, redirects.json. Spec/code counterparts land under UPIIS-33/34/35. Closes UPIIS-47
- swap 7 local img srcs to docs-assets CDN - add field constraints/regex to user-otp & device-binding tables Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the create-VPA `vpa-account-mismatch` (409) error and a note that re-linking a VPA to a different account requires an explicit deregister first (Setu never silently re-points an active VPA at a different account). Finish documenting the inline field-validation rules (formats, lengths, patterns, enums) across the onboarding, program and payee-blocklist API references, mirrored in the OpenAPI spec.
The API envelope now carries clientId and sig on every request. Document the signing steps (sig = base64(HMAC-SHA256(clientSecret, plaintext body))), add the fields to the request format and the Python reference implementation, and update the quickstart credentials + authentication sections. A missing or invalid signature is rejected 401 invalid-signature. Closes UPIIS-53
Align the UPI Issuance docs and OpenAPI reference with the service's actual
behaviour, so clients don't hit surprises the docs never described.
- alias: add to the user object and the UserView schema (plus the four inline
response examples). It ships in binding-status and otp/verify responses today
but was documented nowhere. The set-alias endpoint stays undocumented for now.
- Defaults: state them for the optional params that omitted them —
otpRequired/defaultDebit/defaultCredit default to false, an omitted programId
creates the VPA without a program, and update-program keeps omitted fields
unchanged.
- otpRequired: the docs read as if Setu enforced the OS pairing. It isn't
validated against os — the flow follows whatever is sent. Reframed as the
value Setu expects the TPAP / Issuing App to send, per current understanding,
with a new "Choosing the otpRequired value" section.
- VPA prefix: corrected from "must start with the subscriber's mobile" to the
user's alias, which defaults to the mobile — the old wording is wrong for any
user with a custom alias.
- Voice: second person -> TPAP / Issuing App across all pages, matching the
convention used elsewhere in these docs.
- Samples: VPA suffix alice -> acme, since the suffix is a program code
("Acme Wallet" / ACME is the program example already used).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the two feeder inputs the playground consumes from this repo, so /payments/upi-issuance resolves there instead of 404ing behind the "Test in API playground" button the docs already render. - products.json: add UPI Issuance under Payments. - json/payments/upi-issuance/: 14 mock payloads, one per operation, named after the operationId per this folder's README. Bodies are the spec's existing examples. Each carries sim.plaintext in idempotencyKey, which exchanges the call outside the crypto envelope; an envelope body cannot be authored statically. requestBindingToken and verifyOTP add a scenario directive (sim.bind-ok, sim.otp-ok) for the outcomes that depend on an async event the playground cannot fire. Directives are honoured on sandbox deployments only. No credentials are committed: the sandbox plaintext path needs none. - upi-issuance.json: operationIds drop the "onboarding#" prefix. '#' is the URL fragment delimiter, so a payload file named after the old form truncates at the '#' and 404s when fetched. Also align three vpa request-body descriptions with the prose pages, which already say the prefix must start with the user's alias rather than their mobile number. set-alias stays undocumented, so it has no entry here. Closes UPIIS-57
- New VPA resolution guide page (content + api-playground resolveVPA.json) and links from overview + api-reference; order numbers rebalanced. - resolveVPA operation + ResolveVPARequestBody / VpaResolveResponse schemas in the OpenAPI reference. - QA-testing: document the X-Sim-Resolution header for pinning resolved-payee values (person/merchant, verified, failure) on the QA env; honoured on QA only.
Move the new resolution endpoint off /onboarding/ to /api/v1/vpa/resolve (VPA/payments operation, not onboarding) across the OpenAPI reference, the guide, and the QA-testing X-Sim-Resolution examples. Existing /onboarding/ endpoints are unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
44e7782 added vpa-resolution.mdx and renumbered the surrounding pages' frontmatter, but never regenerated menuItems.json. The app derives both routes and sidebar from that file, so the page 404'd. Syncs menuItems.json to the MDX frontmatter: VPA resolution at 5 (after Payee blocklist), Testing on QA env 5 -> 6, API reference 6 -> 7. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"money" -> "payment", and drop the envelope / /api/v1 / active-user / idempotencyKey preamble: the route line below already states all of it, and idempotencyKey is carried by the OpenAPI spec and api-envelope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
resolveVPA was tagged "VPA management", so it rendered as one item inside that section rather than mirroring the guides, where VPA resolution is a page of its own. Adds a "VPA resolution" tag and retags the operation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two descriptions on the VPA resolve operation named the NPCI protocol call (ReqValAdd) directly. Replaced with a plain statement that resolution happens via NPCI. Error codes and NPCI itself stay. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- status: spell out what RESOLVED / PENDING / FAILED each mean rather than the terse "(details present)" / "(error present)" shorthand - entityType: PERSON is a non-merchant VPA, ENTITY a merchant VPA - "Present on RESOLVED" -> "Present in the API response in case of successful resolution" - "Present when verified" -> "Present when the payee VPA is a whitelisted / verified VPA" - "Present for a resolved merchant" -> "...merchant VPA" - verifiedUrl is a brand url, not a callback url Applied to both the OpenAPI spec and the guide page so they match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The merchant sample omitted franchiseName and verifiedLogo, and the person sample omitted verified — all three are documented in the field tables directly above. The spec's 200 example was also self-contradictory: entityType PERSON carrying verifiedName "Brand-X" and a brand logo. Replaced with a complete, consistent verified-merchant response covering every field the schema defines apart from the failure-only pair. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Checked against the implementation in setu-upi-issuance: - name is NOT guaranteed masked. On our own handle it is the decrypted account name on record (resolve.go returns derefOr(v.AccountName); the integration test asserts "Test Holder"). On a foreign handle it is whatever the payee PSP put in the RespValAdd maskName attribute, which by convention carries the real name — our own outbound holderName() returns it in the clear, masking it only in logs. - verified is omitted when false: the handler only sets it via `if res.Verified`. So it is present only for a verified payee, not on every successful resolution. Reverts the "verified": false line added to the person sample in 4a79b3b. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The implementation emits lowercase status values. vparesolve.State defines pending / resolved / failed, and the comment there calls the spelling the wire contract: "The values are LOWERCASE, matching every other status the switch puts on the wire (app_user and vpa both carry active / deregistered), so a client never has to remember which surface shouts and which does not." The integration test asserts res["status"] == "resolved". Docs said RESOLVED / PENDING / FAILED throughout. Lowercased in the status enum, its description and example, the response examples, the errorCode / errorMessage notes, the guide's status table and samples, and the qa-testing reference to the polling status. Only these three status values changed. The uppercase enums (PERSON, ENTITY, SAVINGS, CURRENT, SMALL, LARGE, ONLINE, OFFLINE, and the ownership values) are uppercase in the implementation and are untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The directive now spells the outcome field `status`, carrying the same
resolved / failed vocabulary as the resolve response (simresolve.go:
"An earlier revision spelled this field `outcome` with success /
failure, which meant a client pinning a failure wrote one word and read
back another"). The legacy `outcome` spelling is still accepted but is
never emitted, so document the current name.
- qa-testing: `outcome` (success|failure) -> `status` (resolved|failed),
and the forced-failure example now sends {"status":"failed",...}
- spec X-Sim-Resolution: same, plus `verified` is an object
({name,url,logo}) whose presence flags the payee as verified — the
description and both examples had it as a boolean
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- errorCode: "Network error code" -> "VPA resolution failure error code" - add a line break above the merchant-block paragraph Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
50e3fa5 dropped it along with the rest of the intro preamble, which left this the only feature page not mentioning it — payee-blocklist, vpa-management, device-binding and user-otp all do. Restored in the per-operation position those pages use (after the request field table) rather than back in the intro. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Fill in the actual X-Sim-Resolution defaults from the upstream mock (buildRespValAddInner) instead of saying "a default": they differ by entityType — MOCK****PAYEE/SAVINGS for a PERSON, BRAND****X/CURRENT plus the Brand-X merchant block for an ENTITY, ICIC0000052 either way. - Document the no-header behaviour: the outcome is driven by the payee VPA (fail/invalid -> failed, merchant/brand -> ENTITY, else PERSON), not by a fixed default response. - Convert the two plain fenced blocks to CodeBlockWithCopy — this was the only UPI Issuance page not using the component. - Add the two vpa/resolve steps to the full QA env run table. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the webhook section to the VPA resolution guide: the key material the TPAP supplies, the X-Setu-Event event, the sealed envelope, the receiver's validation order, and Setu's retry behaviour. Verified against setu-upi-issuance: - event name / header notify/ports: EventVPAResolve, EventHeader - body is the resolve response field for field, terminal status only - envelope ct/sk/iv/api/clientId/sig, api = the event name; RSA-OAEP (SHA-1) wrap of a 32-byte key, AES-256-CBC, sig = HMAC-SHA256 over the plaintext envelope/outbound.go: Seal - 3 attempts, 500ms doubling backoff, 5s per attempt, retry on 5xx/408/ 429 only configs/config.json + notify.retryableStatus Also fixes two examples that showed entityType PERSON alongside verified:true with Brand-X branding — a person carrying merchant brand verification. The webhook sample is now a plain PERSON matching the poll's person sample, and the spec's schema-level example is synced to the path-level one so the two no longer disagree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Drop second-person voice from the notification section and the QA resolution notes. Every other UPI Issuance page is written in third person around "the TPAP / Issuing App"; these two were the only ones using you/your (41 and 4 occurrences). - Markdown is not processed inside <Callout>, so **mirror image** and **decrypted plaintext** rendered literally. Switched to <b>/<i>, the pattern the other callouts already use. Same bug fixed in api-envelope, where `clientSecret` in a callout was showing its backticks. - "so there is nothing to announce" -> "so a notification is not needed" - Sample target url now tpap.example.com, matching the API spec's own example, instead of your-app.example.com Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Line breaks before the mirror-image callout, the per-environment note, the idempotency note and the all-attempts-failed note. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ssuing App "receiver" was the last actor name in the notification section that did not match the house term used everywhere else in these docs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Checklist to merge a PR 🚀To merge this pull request, please take time to complete the checklist. What action did you perform?Review the corresponding checklist items for the action you performed and mark them done. Edit an existing content (MDX) pageChecklist
Edit an existing API reference pageChecklist
Add a new content (MDX) pageChecklist
Add a new API reference pageChecklist
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds VPA resolution (UPIIS-69) and the
vpa.resolveresolution notification(UPIIS-72) to the UPI Issuance docs, and restores the rest of the UPI Issuance
doc set onto the current
staging.Tickets: UPIIS-47, UPIIS-53, UPIIS-57, UPIIS-69, UPIIS-72
Why it is this large
origin/stagingwas force-pushed and the rewritten history no longer containsany UPI Issuance content —
api-references/payments/upi-issuance.jsonand thewhole
content/payments/upi-issuance/tree are gone, along with merged PRs #445and #447. Oddly,
menuItems.jsonon staging still carries the UPI Issuancesidebar entries, so the site currently links to pages that do not exist.
This branch is cut from the current
origin/stagingand replays the UPI Issuancecommits on top, so it reads as one large addition rather than an incremental diff.
Only
menuItems.jsonis modified; everything else is new.If the force-push was unintentional, that is worth resolving separately — the
pre-rewrite history is preserved locally on
staging-pre-force-push.New in this MR
vpa-resolution.mdx) — the client-polled resolve call,resolved/pending/failedoutcomes, full response and merchant fieldtables, four worked samples, and the HTTP error table.
vpa.resolvewebhook: the key material theTPAP / Issuing App supplies, the sealed envelope, the validation order, and
Setu's retry behaviour.
X-Sim-Resolutionheader, its defaults, and theno-header behaviour.
resolveVPAoperation, its ownVPA resolutiontag, and theAPI playground entry.
Corrections made against the implementation
Checked against
setu-upi-issuancerather than written from the ticket, whichturned up several places where the docs disagreed with the code:
RESOLVED/PENDING/FAILEDresolved/pending/failed—vparesolve.Statecalls the lowercase spelling the wire contractnameverifiedif res.VerifiedX-Sim-Resolutionoutcome(success/failure),verifiedbooleanstatus(resolved/failed),verifiedobject — the directive was renamed soentityType: PERSONalongsideverified: truewith Brand-X brandingfranchiseName/verifiedLogoRetry and crypto claims were verified too: 3 attempts, 500 ms → 1 s backoff, 5 s
per attempt, retry on 5xx/408/429 only, RSA-OAEP-SHA1 + AES-256-CBC + HMAC-SHA256
over plaintext.
Also removed the NPCI protocol operation names (
ReqValAdd) from the spec.NPCI itself and NPCI error codes are kept.
Verified
Rendered locally against
docs-mdxwithLOCAL_CONTENT_DIR. Every UPI Issuancepage returns 200 and the API reference renders
VPA resolutionas its ownsection.
vpa-resolutionwas 404ing before this branch — the page existed buthad never been added to
menuItems.json.Reviewer notes
menuItems.jsonwas edited by hand. The README marks it generated("DO NOT TOUCH"). The edit matches the generator's shape and serves correctly,
but a regeneration pass through the Docter Preview extension before merge would
be safer.
"min 2048-bit" public key and "min 32 bytes of entropy" signing key. Only
MaxLengthis validated. They read as hard requirements — fine if that ispolicy, worth softening to "recommended" if not.