skip to Main Content

I am currently trying the Orders API of PayPal using Postman, but cannot capture any payment.

For now, I could get the access token, set it to a collection variable, then created orders using (note the access token is set in the Authorization tab):

POST https://api-m.sandbox.paypal.com/v2/checkout/orders

Body:
{
  "intent": "CAPTURE",
  "purchase_units": [
    {
      "amount": {
        "currency_code": "USD",
        "value": "10.00"
      }
    }
  ]
}

The request was successful with response body:

{
    "id": "<random-id>",
    "status": "CREATED",
    "links": [
        {
            "href": "https://api.sandbox.paypal.com/v2/checkout/orders/<random-id>",
            "rel": "self",
            "method": "GET"
        },
        {
            "href": "https://www.sandbox.paypal.com/checkoutnow?token=<random-id>",
            "rel": "approve",
            "method": "GET"
        },
        {
            "href": "https://api.sandbox.paypal.com/v2/checkout/orders/<random-id>",
            "rel": "update",
            "method": "PATCH"
        },
        {
            "href": "https://api.sandbox.paypal.com/v2/checkout/orders/<random-id>/capture",
            "rel": "capture",
            "method": "POST"
        }
    ]
}

Then I proceeded to rel:approve‘s link using a browser https://www.sandbox.paypal.com/checkoutnow?token=<random-id> and signed in with my sandbox account. It shows me the usual payment window but when I pressed the "Continue" button, it tried to redirect to the return page but instead, refreshed the page itself.

When I tryed to check the order using rel:self‘s link: GET https://api.sandbox.paypal.com/v2/checkout/orders/<random-id>. It correctly showed the sandbox account’s shipping details (name and address), but the status remained CREATED (not APPROVED or COMPLETED):

{
    "id": "<random-id>",
    "intent": "CAPTURE",
    "status": "CREATED",
    "purchase_units": [
        {
            "reference_id": "default",
            "amount": {
                "currency_code": "USD",
                "value": "10.00"
            },
            "payee": {
                "email_address": "<payee-email>",
                "merchant_id": "<payee-id>"
            },
            "shipping": {
                "name": {
                    "full_name": "<payer-name>"
                },
                "address": {
                    "address_line_1": "<payer-address-1>",
                    "address_line_2": "<payer-address-2>",
                    "admin_area_2": "<payer-address-3>",
                    "admin_area_1": "<payer-address-4>",
                    "postal_code": "<payer-address-5>",
                    "country_code": "<payer-address-6>"
                }
            }
        }
    ],
    "create_time": "<time-of-post-request>",
    "links": [
        {
            "href": "https://api.sandbox.paypal.com/v2/checkout/orders/<random-id>",
            "rel": "self",
            "method": "GET"
        },
        {
            "href": "https://www.sandbox.paypal.com/checkoutnow?token=<random-id>",
            "rel": "approve",
            "method": "GET"
        },
        {
            "href": "https://api.sandbox.paypal.com/v2/checkout/orders/<random-id>",
            "rel": "update",
            "method": "PATCH"
        },
        {
            "href": "https://api.sandbox.paypal.com/v2/checkout/orders/<random-id>/capture",
            "rel": "capture",
            "method": "POST"
        }
    ]
}

When I tried to capture the payment using rel:caputure‘s link: POST https://api.sandbox.paypal.com/v2/checkout/orders/<random-id>/capture with header Content Type: application/json and empty body, it said "payer has not approved the Order for payment", despite I getting the shipping details from the GET request before:

{
    "name": "UNPROCESSABLE_ENTITY",
    "details": [
        {
            "issue": "ORDER_NOT_APPROVED",
            "description": "Payer has not yet approved the Order for payment. Please redirect the payer to the 'rel':'approve' url returned as part of the HATEOAS links within the Create Order call or provide a valid payment_source in the request."
        }
    ],
    "message": "The requested action could not be performed, semantically incorrect, or failed business validation.",
    "debug_id": "6a10ea489ffce",
    "links": [
        {
            "href": "https://developer.paypal.com/docs/api/orders/v2/#error-ORDER_NOT_APPROVED",
            "rel": "information_link",
            "method": "GET"
        }
    ]
}

I have three questions:

  1. Was I using the Orders API correctly? Did I miss some HTTP requests and/or some crucial steps?
  2. I had the return URL set for my sandbox application, why did the payment page not redirect me but instead refreshed itself? Did I miss some setup beforehand?
  3. Why did I fail to capture the payment like above?

P.S. After some digging I think I might be missing the authorize payment step but I have no idea how to do it. (Client-side request? Server-side request?)

2

Answers


  1. I proceeded to rel:approve‘s link .. when I pressed the "Continue" button, it tried to redirect to the return page but instead, refreshed the page itself.

    You did not specify a return_url , so there is nowhere to return to. Refreshing is all that can be done.

    What you should do is not redirect to an approval URL, and integrate with no redirects. For this make two routes on your server, one for ‘Create Order’ and one for ‘Capture Order’, documented here. These routes should return only JSON data (no HTML or text). The latter one should (on success) store the payment details in your database before it does the return (particularly purchase_units[0].payments.captures[0].id, the PayPal transaction ID)

    Pair those two routes with the following approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server

    Login or Signup to reply.
  2. I was also having trouble with this issue, I solved it by expanding the request body, much like @preston-phx said, with the return URL, and it looked something like this:

    {
        "intent": "CAPTURE",
        "payer": {
          "email_address": requestBody.payer_email
        },
        "purchase_units": [{
          "amount": {
            "currency_code": "USD",
            "value": requestBody.amount
          },
          "payee": {
            "email_address": requestBody.payee_email
          },
          "payment_instruction": {
            "disbursement_mode": "INSTANT",  // can be INSTANT or DELAYED
            "platform_fees": [
              {
                "amount": {
                  "currency_code": "USD",
                  "value": calculateFeesFromAmount(requestBody.amount)
                }
              }
            ]
          }
        }],
        "redirect_urls": {
          "return_url": "https://example.com/paypalpay/order/approved",
          "cancel_url": "https://example.com/paypalpay/order/cancelled"
        },
        "application_context": {
          "brand_name": "Header for payment page",
          "locale": "en-US",
          "landing_page": "BILLING", // can be NO_PREFERENCE, LOGIN, BILLING
          "shipping_preference": "NO_SHIPPING" // because I didn't want shipping info on the page,
          "user_action": "PAY_NOW",  // Button name, can be PAY_NOW or CONTINUE
          "return_url": "https://example.com/paypalpay/order/approved",
          "cancel_url": "https://example.com/paypalpay/order/cancelled"
        }
      }
    

    This also helped me customise the payment page to an extent. I hope Paypal folks include these in the docs at the correct places, most of devs have to dig through a lot of documentation to create an extensive, usable request body.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search