Skip to content

fix(payu): implement real verify_payment API contract#29

Merged
samithreddychinni merged 6 commits into
mainfrom
fix/payu-status-verify-real-api
Jul 3, 2026
Merged

fix(payu): implement real verify_payment API contract#29
samithreddychinni merged 6 commits into
mainfrom
fix/payu-status-verify-real-api

Conversation

@Keerthivasan-Venkitajalam

Copy link
Copy Markdown
Member

closes #21

what changed

the old payu client wasn't actually calling the verify api. it was sending a GET with a Bearer token, which PayU doesn't support, so verification could never work correctly.

switched it to the actual verify_payment flow:

  • uses a POST request with application/x-www-form-urlencoded

  • sends key, command, var1 (txnid) and hash

  • hash is generated as sha512(merchantKey|verify_payment|txnid|salt) where salt comes from WEBHOOK_SECRET

  • parses the actual transaction_details[txnid] response instead of expecting a flat payload

  • normalizes PayU statuses:

    • success, captured, completedsuccess
    • failed, failurefailed
    • everything else → pending
  • NewClient now accepts the merchant salt and main.go wires it from cfg.WebhookSecret

updated the mock gateway as well so it reads var1 from the POST form body and returns the same nested response shape as PayU, keeping the testkit aligned with production.

tests

rewrote client_test.go from scratch (28 tests, all passing).

covers:

  • correct POST method and Content-Type
  • form fields and hash generation
  • status normalization (success, captured, completed, failed, failure, pending, not_found, unknown values)
  • amount conversion (decimal rupees → paise, integer paise passthrough)
  • malformed JSON
  • HTTP 4xx/5xx
  • network errors and timeouts
  • raw response preservation across all paths

also ran the existing integration and stabilizer suites against a real database — everything passes.

@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
paystable Ready Ready Preview, Comment Jul 2, 2026 3:35pm

@samithreddychinni samithreddychinni left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is the right direction, but please fix these before merge.

  1. client.go reads the amount from amount. The PayU verify payment response example uses amt and transaction_amount, not amount. With the current code a real successful payment can parse as amount 0, then Paystable will mark it as a mismatch. Please parse amt first, then transaction_amount. Keeping amount as a fallback is fine for the mock gateway. Add a test with the PayU shaped response from the docs.

  2. CI lint is failing because captureRequest in client_test.go is unused. Please remove it or use it.

Small cleanup: the code comment links to transaction-status-check-api-2, but the response shape used here is from the Verify Payment API docs. Please link the right PayU page so the next person can check the contract.

@samithreddychinni

Copy link
Copy Markdown
Collaborator

One more thing while fixing the amount field: please do not ignore parseAmount errors.

If PayU returns a bad amount value, Status should return an error with the raw response. Returning amount 0 can turn bad gateway data into a false mismatch.

@Keerthivasan-Venkitajalam

Copy link
Copy Markdown
Member Author

This is the right direction, but please fix these before merge.

  1. client.go reads the amount from amount. The PayU verify payment response example uses amt and transaction_amount, not amount. With the current code a real successful payment can parse as amount 0, then Paystable will mark it as a mismatch. Please parse amt first, then transaction_amount. Keeping amount as a fallback is fine for the mock gateway. Add a test with the PayU shaped response from the docs.

  2. CI lint is failing because captureRequest in client_test.go is unused. Please remove it or use it.

Small cleanup: the code comment links to transaction-status-check-api-2, but the response shape used here is from the Verify Payment API docs. Please link the right PayU page so the next person can check the contract.

good catches. i'll fix all of them.

small request though... if you're using ai to polish review comments, keep them a bit shorter. the actionable bits are gr8, but the essays make it harder to spot the actual changes.

…error, fix doc link, drop unused captureRequest

@samithreddychinni samithreddychinni left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

few blockers before this can merge

biggest issue is the parser only handles the nested transaction_details[txnid] shape, nothing else. if payu ever sends a flat payload it'll just silently return not_found instead of failing loud. should throw a schema error with the raw response attached instead, or have a flat fallback

also the not_found case is mapped wrong. real payu response for that is transaction_details[txnid].status = "Not Found", but rn it's getting normalized to pending w amount 0

minor thing :- doc link in the description 404s (docs.payu.in/reference/verify-payment), should be verify_payment_api.md instead

form=2 is required by payu, currently it's just assumed to already be in the env url, nothing actually checks for it

and amount parsing :- amt/transaction_amount come back as rupee decimals not paise, treating them as integer paise is gonna cause issues

to be clear, switching to post + hash + nested parsing is the right call vs the old get/bearer thing, no issue with that part. the problem is specifically making nested-only the one acceptable shape and letting anything unexpected fall through as not_found instead of erroring

…ound to not_found, enforce form=2, fix doc link
@Keerthivasan-Venkitajalam

Copy link
Copy Markdown
Member Author

few blockers before this can merge

biggest issue is the parser only handles the nested transaction_details[txnid] shape, nothing else. if payu ever sends a flat payload it'll just silently return not_found instead of failing loud. should throw a schema error with the raw response attached instead, or have a flat fallback

also the not_found case is mapped wrong. real payu response for that is transaction_details[txnid].status = "Not Found", but rn it's getting normalized to pending w amount 0

minor thing :- doc link in the description 404s (docs.payu.in/reference/verify-payment), should be verify_payment_api.md instead

form=2 is required by payu, currently it's just assumed to already be in the env url, nothing actually checks for it

and amount parsing :- amt/transaction_amount come back as rupee decimals not paise, treating them as integer paise is gonna cause issues

to be clear, switching to post + hash + nested parsing is the right call vs the old get/bearer thing, no issue with that part. the problem is specifically making nested-only the one acceptable shape and letting anything unexpected fall through as not_found instead of erroring

yep, went through all 5 points and pushed fixes.

  • schema violation vs not_found is fixed. if transaction_details itself is missing now we error out and attach the raw payload instead of silently treating it as not found. empty object / missing txn key still maps to not_found since that's what the mock gateway actually returns.
  • "Not Found" status is fixed too. it was falling through the default branch and getting normalized to pending. now it explicitly maps to not_found and skips amount parsing entirely.
  • updated the doc reference, points to verify_payment_api now.
  • added ensureFormParam in NewClient so form=2 gets injected automatically if it's not already present. won't override existing values.
  • rechecked amount parsing. real payu decimal values (499.00) are already parsed correctly and converted to paise (49900). the integer path only exists for the mock gateway which already emits paise. added a test against the documented payu response shape as well to lock that behaviour in.

so overall i agree with the original concern around unexpected payloads silently becoming not_found, that's fixed now. post + hash + nested parsing stays the same, just tightened up the error handling and response normalization around it.

@samithreddychinni samithreddychinni merged commit a949e78 into main Jul 3, 2026
8 checks passed
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.

[P0] Implement PayU status verification against the real API

2 participants