Create, approve,
then capture.
In that order.
The PayPal Orders API v2 is a three-step flow. HATEOAS links in every response tell you exactly which URL to call next — no hardcoding required. The one mistake most integrations make: fulfilling on APPROVED instead of COMPLETED.
3
steps
HATEOAS
link navigation
0
accounts needed
{
"status": "CREATED",
"links": [
{ "rel": "approve", "method": "GET" },
{ "rel": "capture", "method": "POST" },
{ "rel": "self", "method": "GET" }
]
}
// always follow the link — never hardcode the URL
PayPal's Orders API v2 replaced the older Payments API and Express Checkout. Payments now go through three explicit steps: create an order, have the customer approve it on PayPal, then capture the funds via a second API call. Many integrations skip the capture step or fulfill the order on APPROVED status — which only means the customer clicked approve, not that money moved.
The API uses HATEOAS links in every response. Rather than building endpoint URLs yourself, you follow the links returned in each response: the approve link to send the customer to PayPal, the capture link to finalize the transaction, and the self link to check order status. Hardcoded PayPal URLs are wrong by design.
Getting a PayPal business account verified takes time, and the sandbox behaves differently from production in ways that matter. This mock gives you the full Orders API v2 flow — create, HATEOAS navigation, customer approval, capture, and PAYMENT.CAPTURE.COMPLETED webhook — so you can build and verify the integration before any PayPal account exists.
The create → approve → capture flow
Every PayPal Orders API v2 integration follows the same three steps. First you create an order with intent=CAPTURE — this reserves the authorization. PayPal responds with an order ID and a set of HATEOAS links.
You redirect the customer to the approve link. After they confirm on the PayPal page, they come back to your return_url. The order is now APPROVED.
Then you POST to the capture link. When that returns COMPLETED, funds have transferred.
IMPORTANT
Never fulfill on APPROVED. The customer has authorized, not paid. Only the PAYMENT.CAPTURE.COMPLETED webhook confirms that money has actually moved. Fulfilling on the return_url or on APPROVED status will cost you inventory.
Create Order
POST /api/base/paypal/v2/checkout/ordersReturns order ID + HATEOAS links
Customer Approves
Redirect → approve linkOrder status moves to APPROVED
Capture Payment
POST → capture linkCOMPLETED = safe to fulfill
Order status scenarios
Your integration needs to handle all four. Two are critical paths.
COMPLETEDCapture succeeded. Funds are on the way. This is the only status where it's safe to trigger fulfillment.
criticalAPPROVEDCustomer clicked Approve on the PayPal page. The authorization is in place but no money has moved yet. You must still call capture.
criticalVOIDEDThe authorization was cancelled — either you explicitly voided it, or it expired without a capture. Create a fresh order to retry.
PAYER_ACTION_REQUIREDExtra customer verification is needed (e.g., 3D Secure). Follow the payer-action HATEOAS link to send them to the authentication page.
Integration code
curl -X POST https://mockgateway.dev/api/base/paypal/v2/checkout/orders \
-H "Authorization: Bearer YOUR_MOCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"intent": "CAPTURE",
"purchase_units": [{
"reference_id": "ORD-PP-4455",
"amount": {
"currency_code": "USD",
"value": "99.00"
},
"description": "Enterprise plan — annual"
}],
"application_context": {
"return_url": "https://yourapp.com/checkout/return",
"cancel_url": "https://yourapp.com/checkout/cancel"
}
}'Request parameters
Sent to POST /api/base/paypal/v2/checkout/orders
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | number | required | The total amount for the order e.g. 100.00 |
currency_code | string | required | Three-letter ISO 4217 currency code e.g. USD |
description | string | optional | Purchase description e.g. Payment for services |
reference_id | string | optional | API caller-provided external ID for reference e.g. my-order-123 |
invoice_id | string | optional | Your invoice or order reference e.g. INV-2024-001 |
custom_id | string | optional | Custom data for your own tracking e.g. CUST-12345 |
Response fields
| Field | Type | Description |
|---|---|---|
id | string | PayPal-generated order ID |
status | enum | Status of the PayPal order |
payer_id | string | PayPal payer ID |
payer_email | Email of the PayPal payer | |
create_time | datetime | Order creation timestamp |
capture_id | string | PayPal capture transaction ID |
Questions about PayPal Orders API v2
Other gateway templates
Looking for a different provider?