Validate my Yuno VTEX headless wallet backend following CLAUDE.md.It audits the server code against the deferred preflight contract of the VTEX connector, described in the flow below, and reports what is wrong, with file and line. It only reads code: it changes nothing unless you ask.
When to use this validator
Use it when a server of yours calls the two preflight routes on behalf of a mobile app or a website: a backend service, or a Node, Python or Java client that calls these routes onhttps://<store-host>:
POST /_v/yunopartnerbr.yuno/v4/session/preflightPOST /_v/yunopartnerbr.yuno/v4/session/preflight/payments
The flow being validated
/session/preflight/payments has answered
{ deferred: true }. The payment does not exist in Yuno until Step 8. The outcome comes from VTEX (check 11).
Both routes are public routes of the store: they take no VTEX app key or token. The behaviour below is the one
of VTEX connector 4.2.378.
Checks
Report each one as PASS / FAIL / N/A with the evidence (file:line). Do not assume a check passes because the code “looks right”: quote the line that proves it.1. Session preflight runs before the wallet, with every field
- The body carries
orderFormId,amount,currency,countryandaffiliationName(check 2). orderFormIdis the VTEX order form the order is placed from. At authorization the connector finds the stored session by the order form of the VTEX order, so a different id leaves the stored payment unused.amountis a JSON number in decimal units (845.90), not cents (84590) and not a string. A missing field, or an amount of0, answers400 {"error":"Missing required fields: orderFormId, amount, currency, country"}. A string amount passes that check and fails later with500 {"error":"Failed to create preflight session"}.countryis the alpha-2 (BR) or alpha-3 (BRA) code; the connector normalizes both to alpha-2.- The answer is
200 { checkoutSession, publicApiKey }. - FAIL if any field can be missing, if the amount is a string or in cents, or if preflight can run before shipping, coupons and installments are final.
- A second preflight for the same
orderFormIdreplaces the stored session. When the cart total changes, the backend reruns preflight and the app starts the wallet again with the newcheckoutSession. FAIL if the cart can change while a session is live without rerunning preflight.
2. Both preflight calls name the production affiliation
affiliationName is optional in both bodies, but the backend must send it in both.
- Without it, or with an empty string, the connector takes the first affiliation of the store whose
implementation name contains “Yuno” (any case), in the order VTEX lists them. A VTEX affiliation carries no test or production flag, so a test
affiliation (status “Teste” in the VTEX admin) listed before the production one is the one used. The session
is then opened with the test affiliation’s credentials and fails when they are not valid for production
(for example an Account ID that does not exist), and preflight answers
500 {"error":"Failed to create preflight session"}. - With a name, the connector looks for an affiliation with exactly that name, same spelling and case. A name
the store does not have answers
404 {"error":"Yuno affiliation not found"}on both routes. - The name is per store. The backend must know the name of each store’s production Yuno affiliation, as shown in the VTEX admin under Payments > Affiliations.
- Send the same name in both calls. The payment is bound to the session the first call opened and to the Yuno account of the affiliation the second call resolves, so two names pair a session of one account with another account.
- FAIL if either call omits
affiliationNameor sends'',nullorNone. - FAIL if the name is hard-coded to one value while the backend serves stores whose production affiliation has a different name.
- FAIL if the two calls can send different names for one order.
- Report the value sent in each call, where the backend reads it from, and whether the store has more than one affiliation of the Yuno provider. When the code cannot tell, say so and ask for the store’s affiliation list.
- Symptom: the wallet button fails in some stores and works in others, preflight answers the 500 above, and card payments in the same stores work. The same 500 also appears when the store’s affiliation lookup fails on the VTEX side, whatever name is sent, so a 500 alone does not prove a wrong name.
3. The preflight payment runs only after a non-empty token
- The body is
{ orderFormId, payment, affiliationName, createPaymentInAuth: true }, with the sameorderFormIdandaffiliationNameas the session preflight.paymentcarriespayment_method.type,payment_method.token,amount.value,amount.currencyandcountry. - The call is made only when the app has delivered a non-empty token, never before the wallet sheet closes and
never on a timer. FAIL if the call is reachable with
tokenempty,nullor missing. createPaymentInAuth: trueis in the body. Without it the connector creates the payment in Yuno immediately, before a VTEX order exists. FAIL if it is missing or can befalse.- The answer is
200 {"deferred":true}. FAIL if the VTEX order can be placed before this answer arrives. - Why: the connector accepts a token-less body and answers
{ deferred: true }. The failure only surfaces at authorization, as a denied order withDeferred preflight payment creation failed. - Deferred creation works only for
GOOGLE_PAY,APPLE_PAY,SAMSUNG_PAYandCLICK_TO_PAY. Any otherpayment_method.typeanswers400 {"error":"Deferred payment creation is only supported for GOOGLE_PAY, APPLE_PAY, SAMSUNG_PAY, CLICK_TO_PAY"}.
4. Payment method type matches what VTEX authorizes
payment_method.type in the preflight payment must correspond to the VTEX payment method the order is placed
with (Apple Pay / Google Pay / Samsung Pay / C2P). Check the paymentSystem used in Step 5 and Step 7
against the type the backend sends. A mismatch makes the connector ignore the stored payment.
5. Amount, currency and country are the same in both calls and in the order
- The preflight payment’s
payment.amount.value,payment.amount.currencyandpayment.countrymust be theamount,currencyandcountryof the session preflight. The connector stores a SHA-256 fingerprint ofamount:currency:countrywith the session and recomputes it from the preflight payment. - A mismatch answers
409 {"error":"Preflight session does not match the current payment payload. Please re-run preflight."}. A missing or non-numeric amount, currency or country answers400 {"error":"Missing required fields for preflight validation: amount, currency, country"}. - At authorization the connector recomputes the fingerprint from the VTEX order’s value, currency and
country (the order’s billing country or the country sent in the preflight payment), and checks that the session is less than 5 hours old. When either fails, the order is
denied with
Preflight session expired or does not match the order. Please retry. - FAIL if the two calls read the amount from different sources (cart total in one, order total in the other), or if the amount sent can differ from the total of the VTEX order.
6. One store host per order
Session preflight, preflight payment and the order placement use the same VTEX store host. The connector reads the affiliations of the store it is called on. FAIL if the connector base URL and the checkout base URL can point at different VTEX accounts, for example a single configured host for several stores.7. Split orders are not offered with wallets
A cart fulfilled by more than one seller becomes several VTEX orders, and one wallet token cannot cover them. FAIL if wallets are offered without checking the cart can produce a single order.8. The backend never calls the Payment App routes
- The token goes only to
/session/preflight/payments. FAIL if the backend sends it to/payments/yuno, the Payment App route of the standard checkout. - FAIL if the backend polls
/payments/yuno/status. That route needs the encrypted session of the standard checkout; without it the connector answers200with a timestamp and no payment status. The outcome comes from VTEX (check 11).
9. Errors are handled, logged and retried at most once
- A rerun of the session preflight for the same
orderFormIdreplaces the stored session, so one retry is safe. - Log the store host, the
orderFormId, the route, the HTTP status and the response body of every failed call. FAIL if a failure is swallowed or turned into a generic error with no status. - FAIL if the backend logs the wallet token, the full public API key or any Yuno private key.
- FAIL if the backend retries in a loop, or keeps a failing order alive without rerunning preflight.
10. The public API key goes to the app unchanged and is not kept
- The
publicApiKeyfrom the session preflight answer is the key of the affiliation that call resolved. The app’s SDK must be initialized with exactly that value. - FAIL if the backend replaces it with a key from its own configuration, caches it per store or per app install, or reuses it for another order. Read it from each preflight answer.
- The backend needs no Yuno private key: the connector holds the affiliation’s credentials. FAIL if the backend is configured with a Yuno private secret key for this flow.
11. The outcome is read from VTEX, not from the preflight answers
{ deferred: true } only means the body was stored. The result comes from Step 8 and the order:
After a 2xx,
GET /api/checkout/pub/orders/order-group/{orderGroup} and decide from each order’s status:
payment-approved (or any later state) is success, payment-pending means the payment is still being
processed.
- FAIL if the backend reports success to the app on
{ deferred: true }, or treats a 2xx as approved without reading the order.
Report format
Diagnostics to capture from a failing order
When the checks pass and the wallet still does not complete, capture and send:- The store host and the
orderFormId. - The
affiliationNamesent in each call. - For each call, the UTC timestamp, the HTTP status and the response body. Leave out the token and the keys.
- The
checkoutSessionfrom the session preflight answer. - The VTEX
orderGroup, thegatewayCallbackHTTP status and body, and the order status read afterwards. - The VTEX connector version installed in the store, from the VTEX admin. The connector sets no version header on these routes.