Stripe QA Pack #1: PaymentIntents, Refunds, Idempotency, Test Mode
Index — Test Case Summary
Legend: P1 = highest risk / core reliability, P2 = secondary risk / edge cases
Key finding: All test cases (TC-00 → TC-14) passed, covering PaymentIntent lifecycle (success/declines/SCA), manual capture, refund integrity, and idempotency retry safety.
0) Evidence Conventions
Request evidence
TC-XX-RY-auth.png= Auth tabTC-XX-RY-body.png= Body tab (x-www-form-urlencoded)TC-XX-RY-header.png= Headers tab (used for idempotency)TC-XX-RY-script.png= Tests/Pre-request script (used when extractingpi_id)
Response evidence:
TC-XX-RESY-body.png...-part1.png / ...-part2.pngwhen response doesn’t fit...-first-attempt.png / ...-second-attempt.pngwhen same request is re-sent
Note: In the evidence, the secret key will be fully redacted and all other IDs will be partially redacted (leaving the last 5 characters, to allow comparisons)
1) Postman Mapping Conventions
Each request includes:
- Request ID: R1 / R2 / …
- Method + endpoint
- Key inputs (the meaningful parameters)
- Variable dependencies (what must be captured/stored for later requests in the same TC)
2) Baseline Setup
- Auth: Stripe test secret key (Bearer) configured in Postman Auth tab for each request.
- Body type:
x-www-form-urlencoded
Environment variables used across the suite:
{{baseUrl}} = https://api.stripe.com{{amount}} = 2000
{{currency}} = nzd{{pm_success}} = pm_card_visa
{{pm_decline}} = pm_card_visa_chargeDeclined
{{pm_insufficient}} = pm_card_visa_chargeDeclinedInsufficientFunds
{{pm_auth_required}} = pm_card_authenticationRequired{{pi_id}}, {{refund_id}} and {{latest_charge_id}} are automatically saved via a script whenever they are generated by a request
3) Test Cases
TC-00 — API auth + reachability: create baseline PaymentIntent
Priority: P1
Purpose: Verify that the test secret key is accepted and Stripe returns a valid PaymentIntent (with id and status), proving the environment and Postman setup are working before running any higher-risk scenarios.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body:
○amount={{amount}}
○currency={{currency}}
○automatic_payment_methods[enabled]=true
Steps
- Send R1 (create PaymentIntent)
Expected
- R1 responds with HTTP 200
- R1 response contains a PaymentIntent object with:
○idpresent
○ status present (commonlyrequires_payment_methodorrequires_confirmation)
Result
PASS
Evidence
TC-00-ENV.pngTC-00-R1-auth.pngTC-00-R1-body.pngTC-00-RES1-body-part1.png, TC-00-RES1-body-part2.png
TC-00-ENV.png
TC-00-R1-auth.png
TC-00-R1-body.png
TC-00-RES1-body-part1.png
TC-00-RES1-body-part2.png
TC-01 — PaymentIntent Happy Path (Create + Confirm → Succeeded)
Priority: P1
Purpose: Validate that creating and confirming a PaymentIntent completes without errors and returns "status": "succeeded", establishing the baseline successful payment flow.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body:
○amount={{amount}}
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
- R2:
POST {{baseUrl}}/v1/payment_intents/{{pi_id}}/confirm - Body:
○payment_method={{pm_success}}
Steps
- Send R1 (create PaymentIntent)
- Confirm pi_id is stored via Postman script
- Send R2 (confirm PaymentIntent with successful PaymentMethod)
Expected
- Confirm returns PaymentIntent with
"status": "succeeded"
Result
PASS
Click to view evidence
Evidence
TC-01-R1-auth.pngTC-01-R1-body.pngTC-01-R1-script.pngTC-01-R2-auth.pngTC-01-R2-body.pngTC-01-RES1-body.pngTC-01-RES2-body-part1.pngTC-01-RES2-body-part2.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-02 — PaymentIntent decline: generic card failure → requires_payment_method
Priority: P1
Purpose: Confirm that using a generic declined payment produces an HTTP 4xx error and leaves/returns the PaymentIntent in requires_payment_method, so integrators can reliably detect failed payments and prompt for a new method.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body:
○amount={{amount}}
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
- R2:
POST {{baseUrl}}/v1/payment_intents/{{pi_id}}/confirm - Body:
○payment_method={{pm_decline}}
Steps
- Send R1 (create PaymentIntent)
- Send R2 (confirm using declined PaymentMethod)
Expected
- Payment fails in a controlled way:
○ R2 responds with HTTP 400 or equivalent and"status": "requires_payment_method"
Result
PASS
Click to view evidence
Evidence
TC-02-R1-auth.pngTC-02-R1-body.pngTC-02-R2-auth.pngTC-02-R2-body.pngTC-02-RES1-body.pngTC-02-RES2-body.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-01-R1-auth.png
TC-03 — PaymentIntent decline: insufficient funds → requires_payment_method
Priority: P2
Purpose: Ensure that an insufficient-funds request triggers the expected decline error and requires_payment_method state, proving that this failure mode is handled consistently while following the same recovery path as other declines.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body:
○amount={{amount}}
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
- R2:
POST {{baseUrl}}/v1/payment_intents/{{pi_id}}/confirm - Body:
○payment_method={{pm_insufficient}}
Steps
- Send R1 (create PaymentIntent)
- Send R2 (confirm using insufficient funds PaymentMethod)
Expected
- Payment fails in a controlled way:
○ R2 responds with HTTP 400 or equivalent and an error message stating that there are insufficient funds
Result
PASS
Click to view evidence
Evidence
TC-03-R1-auth.pngTC-03-R1-body.pngTC-03-R2-auth.pngTC-03-R2-body.pngTC-03-RES1-body.pngTC-03-RES2-body.png
TC-01-R1-auth.png
TC-04 — PaymentIntent auth-required branch: requires_action + next_action
Priority: P2
Purpose: Verify that using an authentication-required request drives the PaymentIntent into "status": "requires_action" and returns a usable next_action payload, so frontends can correctly trigger 3DS or similar auth flows.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body:
○amount={{amount}}
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
- R2:
POST {{baseUrl}}/v1/payment_intents/{{pi_id}}/confirm - Body:
○payment_method={{pm_auth_required}}
Steps
- Send R1 (create PaymentIntent)
- Send R2 (confirm using auth-required PaymentMethod)
Expected
- R2 responds with
"status": "requires_action" - R2 response includes
next_actionpayload - Note: Postman cannot complete challenge; test is about correct state + next_action presence.
Result
PASS
Click to view evidence
Evidence
TC-04-R1-auth.pngTC-04-R1-body.pngTC-04-R2-auth.pngTC-04-R2-body.pngTC-04-RES1-body.pngTC-04-RES2-body-part1.pngTC-04-RES2-body-part2.png
TC-01-R1-auth.png
TC-05 — Input validation: invalid PaymentIntent amount rejected at create
Priority: P1
Purpose: Validate that creating a PaymentIntent with 0 or negative amount fails with a 4xx error and does not produce a usable PaymentIntent, proving that amount rules are enforced at the API boundary.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body (contains an invalid amount case #1 (amount = 0):
○amount= 0
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
- R2:
POST {{baseUrl}}/v1/payment_intents - Body (contains an invalid amount case #2 (amount = -100):
○amount= -100
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
Steps
- Send R1 with
amount=0 - Send R2 with
amount=-100
Expected
- Both R1 and R2 respond with HTTP 400 or equivalent
- Both R1 and R2 respond with message stating that amount must be a positive integer
Result
PASS
Click to view evidence
Evidence
TC-05-R1-auth.pngTC-05-R1-body.pngTC-05-R2-auth.pngTC-05-R2-body.pngTC-05-RES1-body.pngTC-05-RES2-body.png
TC-01-R1-auth.png
TC-06 — Input validation: invalid currency code rejected at create
Priority: P2
Purpose: Confirm that creating a PaymentIntent with an unsupported or malformed currency code returns a 4xx error and does not create a valid PaymentIntent, proving that currency validation is enforced server-side.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body:
○amount= notacurrency
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
Steps
- Send R1 with invalid currency.
Expected
- R1 responds with HTTP 400 or equivalent
- R1 responds with an error message stating the given currency is invalid
Result
PASS
Click to view evidence
Evidence
TC-06-R1-auth.pngTC-06-R1-body.pngTC-06-RES1-body.png
TC-01-R1-auth.png
TC-07 — Manual capture flow: authorize → requires_capture → capture → succeeded
Priority: P1
Purpose: Validate that a PaymentIntent created with "capture_method": "manual" moves to requires_capture after confirmation and to succeeded after capture, proving the end-to-end manual capture lifecycle works correctly.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body:
○capture_method=manual
○amount= {{amount}}
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
- R2:
POST {{baseUrl}}/v1/payment_intents/{{pi_id}}/confirm - Body:
○payment_method={{pm_success}}
- R3:
POST {{baseUrl}}/v1/payment_intents/{{pi_id}}/capture
Steps
- Send R1 (create manual-capture PaymentIntent)
- Send R2 (confirm)
- Send R3 (capture)
Expected
- R2 responds with
"status": "requires_capture" - R3 responds with
"status": "succeeded"
Result
PASS
Click to view evidence
Evidence
TC-07-R1-auth.pngTC-07-R1-body.pngTC-07-R2-auth.pngTC-07-R2-body.pngTC-07-R3-auth.pngTC-07-R3-body.pngTC-07-RES1-body.pngTC-07-RES2-body.pngTC-07-RES3-body.png
TC-01-R1-auth.png
TC-08 — Manual capture guardrail: capture disallowed when not in requires_capture
Priority: P1
Purpose: Ensure that calling the capture endpoint on a PaymentIntent that is not in requires_capture returns an error and does not change money state, proving that invalid state transitions are rejected.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body (by not including
capture_method=manualin the body, the capture method will be set to automatic):
○amount= {{amount}}
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
- R2:
POST {{baseUrl}}/v1/payment_intents/{{pi_id}}/confirm - Body:
○payment_method={{pm_success}}
- R3:
POST {{baseUrl}}/v1/payment_intents/{{pi_id}}/capture○Attempt capture on non-capturable PaymentIntent
Steps
- Create PaymentIntent (R1)
- Confirm PaymentIntent has succeeded (R2)
- Attempt capture (R3)
Expected
- R3 responds with HTTP 400 or equivalent
- R3 responds with an error message stating that PaymentIntent could not be captured
Result
PASS
Click to view evidence
Evidence
TC-08-R1-auth.pngTC-08-R1-body.pngTC-08-R2-auth.pngTC-08-R2-body.pngTC-08-R3-auth.pngTC-08-R3-body.pngTC-08-RES1-body.pngTC-08-RES2-body.pngTC-08-RES3-body.png
TC-01-R1-auth.png
TC-09 — Full refund: refund entire captured amount via payment_intent
Priority: P1
Purpose: Verify that issuing a full refund against a captured PaymentIntent succeeds, returns a Refund object, and leaves the original payment fully refunded.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body (by not including
capture_method=manualin the body, the capture method will be set to automatic):
○amount= {{amount}}
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
- R2:
POST {{baseUrl}}/v1/payment_intents/{{pi_id}}/confirm - Body:
○payment_method={{pm_success}}
- R3:
POST {{baseUrl}}/v1/refunds - Body:
○payment_intent={{pi_id}}
Steps
- Create PaymentIntent (R1)
- Confirm PaymentIntent has succeeded (R2)
- Create full refund referencing payment_intent (R3)
Expected
- R3 responds with
"status": "succeeded", indicating refund created successfully
Result
PASS
Click to view evidence
Evidence
TC-09-R1-auth.pngTC-09-R1-body.pngTC-09-R2-auth.pngTC-09-R2-body.pngTC-09-R3-auth.pngTC-09-R3-body.pngTC-09-RES1-body.pngTC-09-RES2-body.pngTC-09-RES3-body.png
TC-01-R1-auth.png
TC-10 — Partial refund: remaining refundable balance tracked correctly
Priority: P1
Purpose: Validate that performing a partial refund for less than the captured amount succeeds, ensuring incremental refunds are tracked accurately.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body (by not including
capture_method=manualin the body, the capture method will be set to automatic):
○amount= {{amount}}
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
- R2:
POST {{baseUrl}}/v1/payment_intents/{{pi_id}}/confirm - Body:
○payment_method={{pm_success}}
- R3:
POST {{baseUrl}}/v1/refunds - Body:
○payment_intent={{pi_id}}
○amount=500
Steps
- Create PaymentIntent (R1)
- Confirm PaymentIntent has succeeded (R2)
- Create partial refund for 500 (R3)
Expected
- R3 responds with
"status": "succeeded", indicating refund created successfully - R3 responds with
"amount": 500
Result
PASS
Click to view evidence
Evidence
TC-10-R1-auth.pngTC-10-R1-body.pngTC-10-R2-auth.pngTC-10-R2-body.pngTC-10-R3-auth.pngTC-10-R3-body.pngTC-10-RES1-body.pngTC-10-RES2-body.pngTC-10-RES3-body.png
TC-01-R1-auth.png
TC-11 — Over-refund protection: reject second refund exceeding remaining amount
Priority: P1
Purpose: Confirm that attempting a second refund which would exceed the remaining unrefunded amount fails with a 4xx error and does not change balances, proving that Stripe prevents over-refunds beyond what was originally paid.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body (by not including
capture_method=manualin the body, the capture method will be set to automatic):
○amount= {{amount}}
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
- R2:
POST {{baseUrl}}/v1/payment_intents/{{pi_id}}/confirm - Body:
○payment_method={{pm_success}}
- R3:
POST {{baseUrl}}/v1/refunds - Body:
○payment_intent={{pi_id}}
○amount=500 - R4:
POST {{baseUrl}}/v1/refunds - Body:
○payment_intent={{pi_id}}
○amount={{amount}}(full original again = 2000)
Steps
- Create PaymentIntent (R1)
- Confirm PaymentIntent has succeeded (R2)
- Create partial refund for 500 (R3)
- Attempt second refund that exceeds remaining refundable amount (2000) (R4)
Expected
- R4 responds with HTTP 400 or equivalent
- R4 responds with an error message stating refund amount is greater than unrefunded amount
Result
PASS
Click to view evidence
Evidence
TC-11-R1-auth.pngTC-11-R1-body.pngTC-11-R2-auth.pngTC-11-R2-body.pngTC-11-R3-auth.pngTC-11-R3-body.pngTC-11-R4-auth.pngTC-11-R4-body.pngTC-11-RES1-body.pngTC-11-RES2-body.pngTC-11-RES3-body.pngTC-11-RES4-body.png
TC-01-R1-auth.png
TC-11 — Refund input validation: zero / negative refund amounts rejected
Priority: P2
Purpose: Ensure that creating a refund with 0 or negative amount returns a 4xx error and no valid Refund object, proving that refund amount validation is enforced at the API boundary.
Postman Mapping
- R1:
POST {{baseUrl}}/v1/payment_intents - Body (by not including
capture_method=manualin the body, the capture method will be set to automatic):
○amount= {{amount}}
○currency={{currency}}
○automatic_payment_methods[enabled]=true
- Script store
○pi_id
- R2:
POST {{baseUrl}}/v1/payment_intents/{{pi_id}}/confirm - Body:
○payment_method={{pm_success}}
- R3:
POST {{baseUrl}}/v1/refunds - Body:
○payment_intent={{pi_id}}
○amount=0 - R4:
POST {{baseUrl}}/v1/refunds - Body:
○payment_intent={{pi_id}}
○amount=-50
Steps
- Create PaymentIntent (R1)
- Confirm PaymentIntent has succeeded (R2)
- Create partial refund for 500 (R3)
- Attempt second refund that exceeds remaining refundable amount (2000) (R4)
Expected
- R4 responds with HTTP 400 or equivalent
- R4 responds with an error message stating refund amount is greater than unrefunded amount
Result
PASS
Click to view evidence
Evidence
TC-11-R1-auth.pngTC-11-R1-body.pngTC-11-R2-auth.pngTC-11-R2-body.pngTC-11-R3-auth.pngTC-11-R3-body.pngTC-11-R4-auth.pngTC-11-R4-body.pngTC-11-RES1-body.pngTC-11-RES2-body.pngTC-11-RES3-body.pngTC-11-RES4-body.png
TC-01-R1-auth.png