Creating an order with the PayPal REST API and cURL is working with Bash but not with PHP: 403 Forbidden

Creating an order with the PayPal REST API and cURL is working with Bash.

Here is the Bash script:

$ cat paypal.sh 
# https://developer.paypal.com/api/rest/authentication/

# https://developer.paypal.com/dashboard/applications/sandbox
CLIENT_ID="aaa"
CLIENT_SECRET="aaa"

# authenticate
RESULT=$( \
    curl \
    -s \
    -X POST "https://api-m.sandbox.paypal.com/v1/oauth2/token" \
    -u "${CLIENT_ID}:${CLIENT_SECRET}" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials")
ACCESS_TOKEN=$(echo ${RESULT} | sed 's/.*"access_token":"\([-_a-zA-Z0-9]*\)".*/\1/')
echo "ACCESS_TOKEN=${ACCESS_TOKEN}"

# create order
RESULT=$( \
    curl \
    -s \
    -X POST "https://api-m.sandbox.paypal.com/v2/checkout/orders" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer ${ACCESS_TOKEN}" \
    -H "PayPal-Request-Id: 7b92603e-77ed-4896-8e78-5dea2050476a" \
    -d '{
  "intent": "CAPTURE",
  "purchase_units":
  [
    {
      "reference_id": "d9f80740-38f0-11e8-b467-0ed5f89f718b",
      "amount":
      {
        "currency_code": "EUR",
        "value": "10.00"
      }
    }
  ],
  "payment_source":
  {
    "paypal":
    {
      "experience_context":
      {
        "payment_method_preference": "IMMEDIATE_PAYMENT_REQUIRED",
        "payment_method_selected": "PAYPAL",
        "brand_name": "EXAMPLE INC",
        "locale": "en-US",
        "landing_page": "LOGIN",
        "shipping_preference": "SET_PROVIDED_ADDRESS",
        "user_action": "PAY_NOW",
        "return_url": "https://example.com/returnUrl",
        "cancel_url": "https://example.com/cancelUrl"
      }
    }
  }
}')
ORDER_ID=$(echo ${RESULT}     | sed 's/.*"id":"\([-a-zA-Z0-9_]*\)".*/\1/')
ORDER_STATUS=$(echo ${RESULT} | sed 's/.*"status":"\([A-Z_]*\)".*/\1/')
ORDER_HREF=$(echo ${RESULT}   | sed 's/.*"href":"\([-a-zA-Z0-9_:/.?=]*\)".*/\1/')
echo "ORDER_ID=${ORDER_ID}"
echo "ORDER_STATUS=${ORDER_STATUS}"
echo "ORDER_HREF=${ORDER_HREF}"

Result:

$ ./paypal.sh 
ACCESS_TOKEN=aaa
ORDER_ID=aaa
ORDER_STATUS=PAYER_ACTION_REQUIRED
ORDER_HREF=https://www.sandbox.paypal.com/checkoutnow?token=aaa

Now, let's try with PHP.

Here is the PHP script (I removed error handling because stackoverflow can't accept my question if I let it):

$ cat paypal.php 
<?php
function authenticate()
{
    $ch = false;
    $client_id = "aaa";
    $client_secret = "aaa";
    $http_header = array("Content-Type: application/x-www-form-urlencoded");
    $post_fields = 'grant_type=client_credentials';
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://api.sandbox.paypal.com/v1/oauth2/token");
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_USERPWD, $client_id.":".$client_secret);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $http_header);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
    $result = curl_exec($ch);
    $json = json_decode($result);
    $access_token = $json->access_token;
    curl_close($ch);
    return $access_token;
}
function create_order($access_token)
{
    $ch = false;
    $http_header = array
    (
        "Content-Type: application/x-www-form-urlencoded",
        "Authorization: Bearer $access_token",
        "PayPal-Request-Id: 7b92603e-77ed-4896-8e78-5dea2050476a"
    );
    $post_fields = '{
  "intent": "CAPTURE",
  "purchase_units":
  [
    {
      "reference_id": "d9f80740-38f0-11e8-b467-0ed5f89f718b",
      "amount":
      {
        "currency_code": "EUR",
        "value": "10.00"
      }
    }
  ],
  "payment_source":
  {
    "paypal":
    {
      "experience_context":
      {
        "payment_method_preference": "IMMEDIATE_PAYMENT_REQUIRED",
        "payment_method_selected": "PAYPAL",
        "brand_name": "EXAMPLE INC",
        "locale": "en-US",
        "landing_page": "LOGIN",
        "shipping_preference": "SET_PROVIDED_ADDRESS",
        "user_action": "PAY_NOW",
        "return_url": "https://example.com/returnUrl",
        "cancel_url": "https://example.com/cancelUrl"
      }
    }
  }
}';
    echo "create_order: access_token: $access_token\n";
    echo "create_order: http_header: ";
    print_r($http_header);
    echo "create_order: post_fields: $post_fields\n";
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://api-m.sandbox.paypal.com/v2/checkout/orders");
    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $http_header);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post_fields);
    $result = curl_exec($ch);
    echo "create_order: result: ";
    print_r($result);
    echo "\n";
    curl_close($ch);
}
$access_token = authenticate();
echo "access_token: ".$access_token."\n";
create_order($access_token);
?>

Result:

$ php -f paypal.php 
access_token: aaa
create_order: access_token: aaa
create_order: http_header: Array
(
    [0] => Content-Type: application/x-www-form-urlencoded
    [1] => Authorization: Bearer aaa
    [2] => PayPal-Request-Id: 7b92603e-77ed-4896-8e78-5dea2050476a
)
create_order: post_fields: {
  "intent": "CAPTURE",
  "purchase_units":
  [
    {
      "reference_id": "d9f80740-38f0-11e8-b467-0ed5f89f718b",
      "amount":
      {
        "currency_code": "EUR",
        "value": "10.00"
      }
    }
  ],
  "payment_source":
  {
    "paypal":
    {
      "experience_context":
      {
        "payment_method_preference": "IMMEDIATE_PAYMENT_REQUIRED",
        "payment_method_selected": "PAYPAL",
        "brand_name": "EXAMPLE INC",
        "locale": "en-US",
        "landing_page": "LOGIN",
        "shipping_preference": "SET_PROVIDED_ADDRESS",
        "user_action": "PAY_NOW",
        "return_url": "https://example.com/returnUrl",
        "cancel_url": "https://example.com/cancelUrl"
      }
    }
  }
}
create_order: result: 403 Forbidden

So, as you can see I get "403 Forbidden" instead of the expected result.

Can you explain why please?

Thank you.

Best regards.

Answer

Solution:

The problem was in the PHP script.

I used the wrong Content-Type.

Here is the patch:

diff -ruN 20221113/paypal.php current/paypal.php
--- 20221113/paypal.php 2022-11-13 12:32:58.127153085 +0100
+++ current/paypal.php  2022-11-13 13:07:06.875074932 +0100
@@ -26,7 +26,7 @@
    $ch = false;
    $http_header = array
    (
-       "Content-Type: application/x-www-form-urlencoded",
+       "Content-Type: application/json",
        "Authorization: Bearer $access_token",
        "PayPal-Request-Id: 7b92603e-77ed-4896-8e78-5dea2050476a"
    );

Result:

$ php -f paypal.php 
access_token: aaa
create_order: access_token: aaa
create_order: http_header: Array
(
    [0] => Content-Type: application/json
    [1] => Authorization: Bearer aaa
    [2] => PayPal-Request-Id: 7b92603e-77ed-4896-8e78-5dea2050476a
)
create_order: post_fields: {
  "intent": "CAPTURE",
  "purchase_units":
  [
    {
      "reference_id": "d9f80740-38f0-11e8-b467-0ed5f89f718b",
      "amount":
      {
        "currency_code": "EUR",
        "value": "10.00"
      }
    }
  ],
  "payment_source":
  {
    "paypal":
    {
      "experience_context":
      {
        "payment_method_preference": "IMMEDIATE_PAYMENT_REQUIRED",
        "payment_method_selected": "PAYPAL",
        "brand_name": "EXAMPLE INC",
        "locale": "en-US",
        "landing_page": "LOGIN",
        "shipping_preference": "SET_PROVIDED_ADDRESS",
        "user_action": "PAY_NOW",
        "return_url": "https://example.com/returnUrl",
        "cancel_url": "https://example.com/cancelUrl"
      }
    }
  }
}
create_order: result: {"id":"aaa","status":"PAYER_ACTION_REQUIRED","payment_source":{"paypal":{}},"links":[{"href":"https://api.sandbox.paypal.com/v2/checkout/orders/aaa","rel":"self","method":"GET"},{"href":"https://www.sandbox.paypal.com/checkoutnow?token=aaa","rel":"payer-action","method":"GET"}]}

Source