PayPal Orders API - cannot specify customer phone number - paypal

PayPal rejects my request to create an order (400 bad Request) if I include the customer's phone number.
I am using the V2 Orders API, using C and CURL.
Here is an example of the JSON data I POST to
https://api.sandbox.paypal.com/v2/checkout/orders
{
intent:"CAPTURE",
payer:
{
name:
{
given_name:"BOB",
surname:"SMITH"
},
email_address:"bob#domain.com",
phone:
{
country_code:"011",
national_number:"4162876593"
},
address:
{
address_line_1:"4180 YONGE STREET",
admin_area_2:"TORONTO",
admin_area_1:"ON",
postal_code:"M1S 2A9",
country_code:"CA"
}
},
purchase_units:
[
{
amount:
{
currency_code:"CAD",
value:"90.00"
}
}
],
application_context:
{
brand_name:"Benefit Gala",
landing_page:"LOGIN",
shipping_preference:"NO_SHIPPING",
user_action:"PAY_NOW",
payment_method:
{
payee_preferred:"IMMEDIATE_PAYMENT_REQUIRED"
},
return_url:"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=Y",
cancel_url:"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=C"
}
}
I have tried a large variety of ways of specifying the phone number.
phone:{ country_code:"01", national_number:"14162876593" },
phone:{ country_code:"01", phone_number: { national_number:"14162876593" } },
and many others.
If I omit the phone number entirely, my request is accepted and the order is created and it can be subsequently captured.
If I include any variant of a phone number object, I get a 400 Bad Request returned.
Somewhere in the PayPal documentation it mentions that in order for the phone number to "be available" it is necessary to turn on Contact Number Required in the merchant account preferences. I have tried all three choices (On, Off, Optional) without effect.
The PayPal documentation has very few detailed examples and most of what I find with Google is for a language library like Java or PHP, which doesn't help me.
Is anyone passing a payer phone number when creating an order ? A sample of your JSON please !

The payer's definition (from the api doc) states that:
The phone.phone_number supports only national_number
So I don't think it'll accept the the country code.
Also the phone attribute in the payer's definition is of type phone_with_type :
So its content should follow this structure:
{
"phone_type" : "HOME",
"phone_number" : {
"national_number" : "4162876593"
}
}
I'm not familiar with paypal's api, but it seems to me (unless I'm mistaken) that your example doesn't match what's described the api's documentation.
Here is an example from paypal's documentation that I think is similar to what you want to do :
<script>paypal.Buttons({
enableStandardCardFields: true,
createOrder: function(data, actions) {
return actions.order.create({
intent: 'CAPTURE',
payer: {
name: {
given_name: "PayPal",
surname: "Customer"
},
address: {
address_line_1: '123 ABC Street',
address_line_2: 'Apt 2',
admin_area_2: 'San Jose',
admin_area_1: 'CA',
postal_code: '95121',
country_code: 'US'
},
email_address: "customer#domain.com",
phone: {
phone_type: "MOBILE",
phone_number: {
national_number: "14082508100"
}
}
},
purchase_units: [
{
amount: {
value: '15.00',
currency_code: 'USD'
},
shipping: {
address: {
address_line_1: '2211 N First Street',
address_line_2: 'Building 17',
admin_area_2: 'San Jose',
admin_area_1: 'CA',
postal_code: '95131',
country_code: 'US'
}
},
}
]
});
}
}).render("body");
</script>
Update : Test program and results from Paypal's Sandbox
I added a test program at the end of the answer (and its output from my testing).
The program sends 4 requests using 4 json payloads:
the 1st one is the same as the example from the question.
the 2nd one is the same as the 1st without the phone attribute
the 3rd one is the same as the 1st but all the attribute names are quoted
the 4th one is the same as the 3rd one but the phone attribute structure follows the definition from the api doc.
In my testing (in paypal's sandbox), the first 3 jsons result in a response with a 400 Bad Request status code. Only the 4th one works and result in a response with a 201 Created status code.
Test Program
Before using the program the string ACCESS_TOKEN_HERE should be replaced by a valid access token (how to generate it).
To build the program with gcc : gcc -lcurl -o test-paypal test-paypal.c
#include <stdio.h>
#include <curl/curl.h>
void create_order(CURL* curl, char* payload, struct curl_slist *auth_header){
CURLcode res;
curl_easy_setopt(curl, CURLOPT_URL, "https://api-m.sandbox.paypal.com/v2/checkout/orders");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, auth_header);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
res = curl_easy_perform(curl);
long http_code = 0;
curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code);
if (http_code >= 200 && http_code < 300 && res != CURLE_ABORTED_BY_CALLBACK)
{
printf("\n******************************** SUCCESS *********************************");
printf("\n******************************** SUCCESS *********************************");
printf("\n******************************** SUCCESS *********************************\n");
}
else
{
printf("\n******************************** ERROR *********************************");
printf("\n******************************** ERROR *********************************");
printf("\n******************************** ERROR *********************************\n");
}
}
int main(char** args){
char* request1 = "{\n"
" intent:\"CAPTURE\",\n"
" payer:{\n"
" name:{\n"
" given_name:\"BOB\",\n"
" surname:\"SMITH\"\n"
" },\n"
" email_address:\"bob#domain.com\",\n"
" phone:{\n"
" country_code:\"011\",\n"
" national_number:\"4162876593\"\n"
" },\n"
" address:{\n"
" address_line_1:\"4180 YONGE STREET\",\n"
" admin_area_2:\"TORONTO\",\n"
" admin_area_1:\"ON\",\n"
" postal_code:\"M1S 2A9\",\n"
" country_code:\"CA\"\n"
" }\n"
" },\n"
" purchase_units:[\n"
" {\n"
" amount:{\n"
" currency_code:\"CAD\",\n"
" value:\"90.00\"\n"
" }\n"
" }\n"
" ],\n"
" application_context:{\n"
" brand_name:\"Benefit Gala\",\n"
" landing_page:\"LOGIN\",\n"
" shipping_preference:\"NO_SHIPPING\",\n"
" user_action:\"PAY_NOW\",\n"
" payment_method:{\n"
" payee_preferred:\"IMMEDIATE_PAYMENT_REQUIRED\"\n"
" },\n"
" return_url:\"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=Y\",\n"
" cancel_url:\"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=C\"\n"
" }\n"
"}";
char* request2 = "{\n"
" intent:\"CAPTURE\",\n"
" payer:{\n"
" name:{\n"
" given_name:\"BOB\",\n"
" surname:\"SMITH\"\n"
" },\n"
" email_address:\"bob#domain.com\",\n"
" address:{\n"
" address_line_1:\"4180 YONGE STREET\",\n"
" admin_area_2:\"TORONTO\",\n"
" admin_area_1:\"ON\",\n"
" postal_code:\"M1S 2A9\",\n"
" country_code:\"CA\"\n"
" }\n"
" },\n"
" purchase_units:[\n"
" {\n"
" amount:{\n"
" currency_code:\"CAD\",\n"
" value:\"90.00\"\n"
" }\n"
" }\n"
" ],\n"
" application_context:{\n"
" brand_name:\"Benefit Gala\",\n"
" landing_page:\"LOGIN\",\n"
" shipping_preference:\"NO_SHIPPING\",\n"
" user_action:\"PAY_NOW\",\n"
" payment_method:{\n"
" payee_preferred:\"IMMEDIATE_PAYMENT_REQUIRED\"\n"
" },\n"
" return_url:\"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=Y\",\n"
" cancel_url:\"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=C\"\n"
" }\n"
"}";
char* request3 = "{\n"
" \"intent\":\"CAPTURE\",\n"
" \"payer\":{\n"
" \"name\":{\n"
" \"given_name\":\"BOB\",\n"
" \"surname\":\"SMITH\"\n"
" },\n"
" \"email_address\":\"bob#domain.com\",\n"
" \"phone\":{\n"
" \"country_code\":\"011\",\n"
" \"national_number\":\"4162876593\"\n"
" },\n"
" \"address\":{\n"
" \"address_line_1\":\"4180 YONGE STREET\",\n"
" \"admin_area_2\":\"TORONTO\",\n"
" \"admin_area_1\":\"ON\",\n"
" \"postal_code\":\"M1S 2A9\",\n"
" \"country_code\":\"CA\"\n"
" }\n"
" },\n"
" \"purchase_units\":[\n"
" {\n"
" \"amount\":{\n"
" \"currency_code\":\"CAD\",\n"
" \"value\":\"90.00\"\n"
" }\n"
" }\n"
" ],\n"
" \"application_context\":{\n"
" \"brand_name\":\"Benefit Gala\",\n"
" \"landing_page\":\"LOGIN\",\n"
" \"shipping_preference\":\"NO_SHIPPING\",\n"
" \"user_action\":\"PAY_NOW\",\n"
" \"payment_method\":{\n"
" \"payee_preferred\":\"IMMEDIATE_PAYMENT_REQUIRED\"\n"
" },\n"
" \"return_url\":\"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=Y\",\n"
" \"cancel_url\":\"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=C\"\n"
" }\n"
"}";
char *request4 = "{\n"
" \"intent\":\"CAPTURE\",\n"
" \"payer\":{\n"
" \"name\":{\n"
" \"given_name\":\"BOB\",\n"
" \"surname\":\"SMITH\"\n"
" },\n"
" \"email_address\":\"bob#domain.com\",\n"
" \"phone\":{\n"
" \"phone_type\":\"HOME\",\n"
" \"phone_number\":{\n"
" \"national_number\":\"4162876593\"\n"
" }\n"
" },\n"
" \"address\":{\n"
" \"address_line_1\":\"4180 YONGE STREET\",\n"
" \"admin_area_2\":\"TORONTO\",\n"
" \"admin_area_1\":\"ON\",\n"
" \"postal_code\":\"M1S 2A9\",\n"
" \"country_code\":\"CA\"\n"
" }\n"
" },\n"
" \"purchase_units\":[\n"
" {\n"
" \"amount\":{\n"
" \"currency_code\":\"CAD\",\n"
" \"value\":\"90.00\"\n"
" }\n"
" }\n"
" ],\n"
" \"application_context\":{\n"
" \"brand_name\":\"Benefit Gala\",\n"
" \"landing_page\":\"LOGIN\",\n"
" \"shipping_preference\":\"NO_SHIPPING\",\n"
" \"user_action\":\"PAY_NOW\",\n"
" \"payment_method\":{\n"
" \"payee_preferred\":\"IMMEDIATE_PAYMENT_REQUIRED\"\n"
" },\n"
" \"return_url\":\"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=Y\",\n"
" \"cancel_url\":\"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=C\"\n"
" }\n"
"}";
struct curl_slist *headers=NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "Authorization: Bearer ACCESS_TOKEN_HERE");
CURL *curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
printf("\n*************************************************************");
printf("\n****************** REQUEST 1 ********************************");
printf("\n*************************************************************\n");
printf("%s\n", request1);
create_order(curl, request1, headers);
printf("\n*************************************************************");
printf("\n****************** REQUEST 2 ********************************");
printf("\n*************************************************************\n");
printf("%s\n", request2);
create_order(curl, request2, headers);
printf("\n*************************************************************");
printf("\n****************** REQUEST 3 ********************************");
printf("\n*************************************************************\n");
printf("%s\n", request3);
create_order(curl, request3, headers);
printf("\n*************************************************************");
printf("\n****************** REQUEST 4 ********************************");
printf("\n*************************************************************\n");
printf("%s\n", request4);
create_order(curl, request4, headers);
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return 0;
}
Output of the test program
*************************************************************
****************** REQUEST 1 ********************************
*************************************************************
{
intent:"CAPTURE",
payer:{
name:{
given_name:"BOB",
surname:"SMITH"
},
email_address:"bob#domain.com",
phone:{
country_code:"011",
national_number:"4162876593"
},
address:{
address_line_1:"4180 YONGE STREET",
admin_area_2:"TORONTO",
admin_area_1:"ON",
postal_code:"M1S 2A9",
country_code:"CA"
}
},
purchase_units:[
{
amount:{
currency_code:"CAD",
value:"90.00"
}
}
],
application_context:{
brand_name:"Benefit Gala",
landing_page:"LOGIN",
shipping_preference:"NO_SHIPPING",
user_action:"PAY_NOW",
payment_method:{
payee_preferred:"IMMEDIATE_PAYMENT_REQUIRED"
},
return_url:"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=Y",
cancel_url:"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=C"
}
}
* Trying 151.101.65.35:443...
* Connected to api-m.sandbox.paypal.com (151.101.65.35) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
* CApath: none
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: businessCategory=Private Organization; jurisdictionC=US; jurisdictionST=Delaware; serialNumber=3014267; C=US; ST=California; L=San Jose; O=PayPal, Inc.; CN=www.sandbox.paypal.com
* start date: Jun 2 00:00:00 2021 GMT
* expire date: Mar 24 23:59:59 2022 GMT
* subjectAltName: host "api-m.sandbox.paypal.com" matched cert's "api-m.sandbox.paypal.com"
* issuer: C=US; O=DigiCert Inc; OU=www.digicert.com; CN=DigiCert SHA2 Extended Validation Server CA
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0xa65310)
> POST /v2/checkout/orders HTTP/2
Host: api-m.sandbox.paypal.com
accept: */*
content-type: application/json
authorization: Bearer REMOVED_ACCESS_TOKEN
content-length: 982
* We are completely uploaded and fine
< HTTP/2 400
< content-type: application/json
< server: nginx/1.14.0 (Ubuntu)
< cache-control: max-age=0, no-cache, no-store, must-revalidate
< paypal-debug-id: f724c871e93df
< strict-transport-security: max-age=31536000; includeSubDomains
< accept-ranges: bytes
< via: 1.1 varnish, 1.1 varnish
< edge-control: max-age=0
< date: Wed, 14 Jul 2021 21:43:56 GMT
< x-served-by: cache-lhr6623-LHR, cache-cdg20728-CDG
< x-cache: MISS, MISS
< x-cache-hits: 0, 0
< x-timer: S1626299036.618323,VS0,VE589
< content-length: 356
<
* Connection #0 to host api-m.sandbox.paypal.com left intact
{"name":"INVALID_REQUEST","message":"Request is not well-formed, syntactically incorrect, or violates schema.","debug_id":"f724c871e93df","details":[{"location":"body","issue":"MALFORMED_REQUEST_JSON"}],"links":[{"href":"https://developer.paypal.com/docs/api/orders/v2/#error-MALFORMED_REQUEST_JSON","rel":"information_link","encType":"application/json"}]}
******************************** ERROR *********************************
******************************** ERROR *********************************
******************************** ERROR *********************************
*************************************************************
****************** REQUEST 2 ********************************
*************************************************************
{
intent:"CAPTURE",
payer:{
name:{
given_name:"BOB",
surname:"SMITH"
},
email_address:"bob#domain.com",
address:{
address_line_1:"4180 YONGE STREET",
admin_area_2:"TORONTO",
admin_area_1:"ON",
postal_code:"M1S 2A9",
country_code:"CA"
}
},
purchase_units:[
{
amount:{
currency_code:"CAD",
value:"90.00"
}
}
],
application_context:{
brand_name:"Benefit Gala",
landing_page:"LOGIN",
shipping_preference:"NO_SHIPPING",
user_action:"PAY_NOW",
payment_method:{
payee_preferred:"IMMEDIATE_PAYMENT_REQUIRED"
},
return_url:"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=Y",
cancel_url:"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=C"
}
}
* Found bundle for host api-m.sandbox.paypal.com: 0xa62db0 [can multiplex]
* Re-using existing connection! (#0) with host api-m.sandbox.paypal.com
* Connected to api-m.sandbox.paypal.com (151.101.65.35) port 443 (#0)
* Using Stream ID: 3 (easy handle 0xa65310)
> POST /v2/checkout/orders HTTP/2
Host: api-m.sandbox.paypal.com
accept: */*
content-type: application/json
authorization: Bearer REMOVED_ACCESS_TOKEN
content-length: 892
* We are completely uploaded and fine
< HTTP/2 400
< content-type: application/json
< server: nginx/1.14.0 (Ubuntu)
< cache-control: max-age=0, no-cache, no-store, must-revalidate
< paypal-debug-id: c7ac50f5fe921
< strict-transport-security: max-age=31536000; includeSubDomains
< accept-ranges: bytes
< via: 1.1 varnish, 1.1 varnish
< edge-control: max-age=0
< date: Wed, 14 Jul 2021 21:43:56 GMT
< x-served-by: cache-lhr7357-LHR, cache-cdg20728-CDG
< x-cache: MISS, MISS
< x-cache-hits: 0, 0
< x-timer: S1626299036.212274,VS0,VE470
< content-length: 356
<
* Connection #0 to host api-m.sandbox.paypal.com left intact
{"name":"INVALID_REQUEST","message":"Request is not well-formed, syntactically incorrect, or violates schema.","debug_id":"c7ac50f5fe921","details":[{"location":"body","issue":"MALFORMED_REQUEST_JSON"}],"links":[{"href":"https://developer.paypal.com/docs/api/orders/v2/#error-MALFORMED_REQUEST_JSON","rel":"information_link","encType":"application/json"}]}
******************************** ERROR *********************************
******************************** ERROR *********************************
******************************** ERROR *********************************
*************************************************************
****************** REQUEST 3 ********************************
*************************************************************
{
"intent":"CAPTURE",
"payer":{
"name":{
"given_name":"BOB",
"surname":"SMITH"
},
"email_address":"bob#domain.com",
"phone":{
"country_code":"011",
"national_number":"4162876593"
},
"address":{
"address_line_1":"4180 YONGE STREET",
"admin_area_2":"TORONTO",
"admin_area_1":"ON",
"postal_code":"M1S 2A9",
"country_code":"CA"
}
},
"purchase_units":[
{
"amount":{
"currency_code":"CAD",
"value":"90.00"
}
}
],
"application_context":{
"brand_name":"Benefit Gala",
"landing_page":"LOGIN",
"shipping_preference":"NO_SHIPPING",
"user_action":"PAY_NOW",
"payment_method":{
"payee_preferred":"IMMEDIATE_PAYMENT_REQUIRED"
},
"return_url":"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=Y",
"cancel_url":"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=C"
}
}
* Found bundle for host api-m.sandbox.paypal.com: 0xa62db0 [can multiplex]
* Re-using existing connection! (#0) with host api-m.sandbox.paypal.com
* Connected to api-m.sandbox.paypal.com (151.101.65.35) port 443 (#0)
* Using Stream ID: 5 (easy handle 0xa65310)
> POST /v2/checkout/orders HTTP/2
Host: api-m.sandbox.paypal.com
accept: */*
content-type: application/json
authorization: Bearer REMOVED_ACCESS_TOKEN
content-length: 1038
* We are completely uploaded and fine
< HTTP/2 400
< content-type: application/json
< server: nginx/1.14.0 (Ubuntu)
< cache-control: max-age=0, no-cache, no-store, must-revalidate
< paypal-debug-id: b52221afefcf6
< strict-transport-security: max-age=31536000; includeSubDomains
< accept-ranges: bytes
< via: 1.1 varnish, 1.1 varnish
< edge-control: max-age=0
< date: Wed, 14 Jul 2021 21:43:57 GMT
< x-served-by: cache-lhr7340-LHR, cache-cdg20728-CDG
< x-cache: MISS, MISS
< x-cache-hits: 0, 0
< x-timer: S1626299037.687787,VS0,VE596
< content-length: 468
<
* Connection #0 to host api-m.sandbox.paypal.com left intact
{"name":"INVALID_REQUEST","message":"Request is not well-formed, syntactically incorrect, or violates schema.","debug_id":"b52221afefcf6","details":[{"field":"/payer/phone/phone_number","value":"","location":"body","issue":"MISSING_REQUIRED_PARAMETER","description":"A required field / parameter is missing."}],"links":[{"href":"https://developer.paypal.com/docs/api/orders/v2/#error-MISSING_REQUIRED_PARAMETER","rel":"information_link","encType":"application/json"}]}
******************************** ERROR *********************************
******************************** ERROR *********************************
******************************** ERROR *********************************
*************************************************************
****************** REQUEST 4 ********************************
*************************************************************
{
"intent":"CAPTURE",
"payer":{
"name":{
"given_name":"BOB",
"surname":"SMITH"
},
"email_address":"bob#domain.com",
"phone":{
"phone_type":"HOME",
"phone_number":{
"national_number":"4162876593"
}
},
"address":{
"address_line_1":"4180 YONGE STREET",
"admin_area_2":"TORONTO",
"admin_area_1":"ON",
"postal_code":"M1S 2A9",
"country_code":"CA"
}
},
"purchase_units":[
{
"amount":{
"currency_code":"CAD",
"value":"90.00"
}
}
],
"application_context":{
"brand_name":"Benefit Gala",
"landing_page":"LOGIN",
"shipping_preference":"NO_SHIPPING",
"user_action":"PAY_NOW",
"payment_method":{
"payee_preferred":"IMMEDIATE_PAYMENT_REQUIRED"
},
"return_url":"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=Y",
"cancel_url":"https://Gala.domain.com/cgi-bin/paypal?FZ=4&MF=4&K=SMITH&MR=M&PP=C"
}
}
* Found bundle for host api-m.sandbox.paypal.com: 0xa62db0 [can multiplex]
* Re-using existing connection! (#0) with host api-m.sandbox.paypal.com
* Connected to api-m.sandbox.paypal.com (151.101.65.35) port 443 (#0)
* Using Stream ID: 7 (easy handle 0xa65310)
> POST /v2/checkout/orders HTTP/2
Host: api-m.sandbox.paypal.com
accept: */*
content-type: application/json
authorization: Bearer REMOVED_ACCESS_TOKEN
content-length: 1077
* We are completely uploaded and fine
< HTTP/2 201
< content-type: application/json
< server: nginx/1.14.0 (Ubuntu)
< cache-control: max-age=0, no-cache, no-store, must-revalidate
< paypal-debug-id: 532a3181c034b
< strict-transport-security: max-age=31536000; includeSubDomains
< accept-ranges: bytes
< via: 1.1 varnish, 1.1 varnish
< edge-control: max-age=0
< date: Wed, 14 Jul 2021 21:43:58 GMT
< x-served-by: cache-lhr7357-LHR, cache-cdg20728-CDG
< x-cache: MISS, MISS
< x-cache-hits: 0, 0
< x-timer: S1626299037.296772,VS0,VE954
< content-length: 501
<
* Connection #0 to host api-m.sandbox.paypal.com left intact
{"id":"82447239S1973611B","status":"CREATED","links":[{"href":"https://api.sandbox.paypal.com/v2/checkout/orders/82447239S1973611B","rel":"self","method":"GET"},{"href":"https://www.sandbox.paypal.com/checkoutnow?token=82447239S1973611B","rel":"approve","method":"GET"},{"href":"https://api.sandbox.paypal.com/v2/checkout/orders/82447239S1973611B","rel":"update","method":"PATCH"},{"href":"https://api.sandbox.paypal.com/v2/checkout/orders/82447239S1973611B/capture","rel":"capture","method":"POST"}]}
******************************** SUCCESS *********************************
******************************** SUCCESS *********************************
******************************** SUCCESS *********************************

Mohammed, thanks very much for this !!
The unexpected answer is that the quotes around the field names are required. I have seen many examples where they were not included, and in fact it worked fine without those quotes when the phone number was not specified.
The "phone_type" object is ignored, with HOME specified, the number is still displayed by PayPal as MOBILE, but I can live with that.
Thanks again !!

Related

HTTP 401 Basic Authentication error accessing Magento 2 Rest API

I am attempting to use the Rest API in Magento 2. I have a piece of PHP that uses cURL to first get an admin token for my Magento user, then use the token to return a piece of Magento data (in this example a list of product types). The first part returns a token with no problems, but the second part comes back with an HTTP 401 Basic Authentication error.
My code is:
<?php
// Get handle for token retrieval
$userData = array("username" => "user", "password" => "password!");
$ch = curl_init("https://my.magento/rest/V1/integration/admin/token/");
// Set options
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($userData));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/json", "Content-Length: " . strlen(json_encode($userData))));
curl_setopt($ch, CURLOPT_VERBOSE, true);
$verbose = fopen('/tmp/curl.log', 'w+');
curl_setopt($ch, CURLOPT_STDERR, $verbose);
// Get token
$token = curl_exec($ch);
echo "Token returned: " . $token . "<BR><BR>";
// Display log
rewind($verbose);
$verboseLog = stream_get_contents($verbose);
echo "Verbose information 1:\n<pre>", htmlspecialchars($verboseLog), "</pre>\n";
echo "About to get product<BR>";
// Get handle for product types
$ch = curl_init("https://my.magento/rest/V1/products/types/");
// Set options
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: application/json", "Authorization: Bearer " . json_decode($token)));
curl_setopt($ch, CURLOPT_VERBOSE, true);
$verbose = fopen('/tmp/curl.log', 'w+');
curl_setopt($ch, CURLOPT_STDERR, $verbose);
// Get types
$result = curl_exec($ch);
echo "Result: " . $result . "<BR>";
// Display log
rewind($verbose);
$verboseLog = stream_get_contents($verbose);
echo "<BR>Verbose information 2:\n<pre>", htmlspecialchars($verboseLog), "</pre>\n";
?>
And the browser output is:
Tokenreturned: "t8iskt68xlo5frf9hhtc1lk8wmqzbzx8"
Verbose information 1:
* About to connect() to my.magento port 443 (#2)
* Trying 104.25.128.20...
* Connected to mymagento (nn.nn.nn.nn) port 443 (#2)
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
* SSL connection using TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
* Server certificate:
* subject: CN=ssl379212.cloudflaressl.com,OU=PositiveSSL Multi-Domain,OU=Domain Control Validated
* start date: Oct 26 00:00:00 2018 GMT
* expire date: May 04 23:59:59 2019 GMT
* common name: ssl379212.cloudflaressl.com
* issuer: CN=COMODO ECC Domain Validation Secure Server CA 2,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB
> POST /rest/V1/integration/admin/token/ HTTP/1.1
Host: sand2.firetoys.co.uk
Accept: */*
Content-Type: application/json
Content-Length: 48
* upload completely sent off: 48 out of 48 bytes
< HTTP/1.1 200 OK
< Date: Wed, 31 Oct 2018 12:50:01 GMT
< Content-Type: application/json; charset=utf-8
< Content-Length: 34
< Connection: keep-alive
< Set-Cookie: __cfduid=d69af7d1f0a1205231a8867c1f45875621540990201; expires=Thu, 31-Oct-19 12:50:01 GMT; path=/; domain=.my.magento; HttpOnly
< X-Frame-Options: SAMEORIGIN
< X-UA-Compatible: IE=edge
< Pragma: no-cache
< Expires: -1
< Cache-Control: no-store, no-cache, must-revalidate, max-age=0
< Accept-Ranges: bytes
< Set-Cookie: PHPSESSID=9p378rsfito8gfocnrufucssh6; expires=Wed, 31-Oct-2018 13:50:01 GMT; Max-Age=3600; path=/; domain=sand2.firetoys.co.uk; secure; HttpOnly
< Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< Server: cloudflare
< CF-RAY: 47263eb629ea0ce9-LHR
<
* Connection #2 to host my.magento left intact
About to get product
Result:
Verbose information 2:
* About to connect() to my.magento port 443 (#3)
* Trying nn.nn.nn.nn...
* Connected to my.magento (nn.nn.nn.nn) port 443 (#3)
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
* SSL connection using TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
* Server certificate:
* subject: CN=ssl379212.cloudflaressl.com,OU=PositiveSSL Multi-Domain,OU=Domain Control Validated
* start date: Oct 26 00:00:00 2018 GMT
* expire date: May 04 23:59:59 2019 GMT
* common name: ssl379212.cloudflaressl.com
* issuer: CN=COMODO ECC Domain Validation Secure Server CA 2,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB
> GET /rest/V1/products/types/ HTTP/1.1
Host: sand2.firetoys.co.uk
Accept: */*
Content-Type: application/json
Authorization: Bearer t8iskt68xlo5frf9hhtc1lk8wmqzbzx8
< HTTP/1.1 401 Unauthorized
< Date: Wed, 31 Oct 2018 12:50:01 GMT
< Content-Length: 0
< Connection: keep-alive
< Set-Cookie: __cfduid=d38c9e4bc3019d9ac55c7f68f5c5ca1161540990201; expires=Thu, 31-Oct-19 12:50:01 GMT; path=/; domain=.my.magento; HttpOnly
< X-Varnish: 7995397
< WWW-Authenticate: Basic
< Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< Server: cloudflare
< CF-RAY: 47263eb70f5b3512-LHR
<
* Connection #3 to host my.magento left intact
When I try just browsing directly to https://my.magento/rest/V1/products/types/ I get a Magento error back saying I am not authorised for the Products resource, which I would expect as I am sending no token or login credentials, but at least it is getting through to Magento.
Any ideas??
I should add that the server is set for Basic authentication, and if I replace the Bearer auth with the necessary Basic auth in the header for the GET, it returns the Magento message about not having access to the resource, which is fair enough. So I guess there are two questions:
How can I get past the basic authentication and still include the bearer authentication in my GET request, given that you can't put two authentications into the header?
Why does the initial POST to get the token work without any basic auth??
"How can I get past the basic authentication and still include the bearer authentication in my GET request, given that you can't put two authentications into the header?"
Disable auth for /index.php/rest location (in webserver)
"Why does the initial POST to get the token work without any basic auth??"
If the POST location is protected then you should get a 401 response.
Did you put the username and password on url on post request? http://user:pass#my.magento/rest/V1/
By the way, putting user:pass#my.magento into URL, will be translated into Authorization: User .
But you also set Authorization: Bearer t8iskt68xlo5frf9hhtc1lk8wmqzbzx8 which will overwrite the http authentification Authorization.

Curl doesn't redirect properly for some facebook profile IDs

I'm trying to hack together a bit of code (c++) that returns a current facebook display name for a given user ID. The code works perfectly fine for most IDs but for some IDs curl returns a 404 even though the same URL opens fine in a browser. Here's the bit of code related to curl.
int main(int argc, char const *argv[]) {
CURL* curl;
char userAgent[] = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US;
rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13";
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
string fbID = argv[1];
string searchTerm = "https://www.facebook.com/profile.php?id=" + fbID;
cout << "searching for: " << searchTerm << endl;
if(!curl)
return 0;
else{
curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent);
curl_easy_setopt(curl, CURLOPT_URL, searchTerm.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeCallback);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); //tell curl to output
its progress
}
//scan the retrieved data for the name...
}
size_t writeCallback(char* buf, size_t size, size_t nmemb, void* up)
{ //i copied this function
for (int c = 0; c<size*nmemb; c++)
{
data.push_back(buf[c]);
}
return size*nmemb;
}
I don't exactly know where to look because it works for most ids but for example "1094145063" returns a 404 even though https://www.facebook.com/profile.php?id=1094145063
opens fine in a browser. (1331601579 for example works fine with my code)
I can't spot the difference between the sites.
It does however make a redirect to https://www.facebook.com/"username" which works fine once CURLOPT_FOLLOWLOCATION is set, but only on some sites. Here's the message curl outputs.
* Hostname was NOT found in DNS cache
* Trying 31.13.84.36...
* Connected to www.facebook.com (31.13.84.36) port 443 (#0)
* found 174 certificates in /etc/ssl/certs/ca-certificates.crt
* server certificate verification OK
* common name: *.facebook.com (matched)
* server certificate expiration date OK
* server certificate activation date OK
* certificate public key: EC
* certificate version: #3
* subject: C=US,ST=California,L=Menlo Park,O=Facebook\, Inc.,CN=*.facebook.com
* start date: Fri, 09 Dec 2016 00:00:00 GMT
* expire date: Thu, 25 Jan 2018 12:00:00 GMT
* issuer: C=US,O=DigiCert Inc,OU=www.digicert.com,CN=DigiCert SHA2 High Assurance Server CA
* compression: NULL
* cipher: AES-128-GCM
* MAC: AEAD
> GET /profile.php?id=1094145063 HTTP/1.1
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13
Host: www.facebook.com
Accept: */*
HTTP/1.1 404 Not Found
X-XSS-Protection: 0
public-key-pins-report-only: max-age=500; pin-sha256="WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18="; pin-sha256="r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E="; pin-sha256="q4PO2G2cbkZhZ82+JgmRUyGMoAeozA+BSXVXQWB8XWQ="; report-uri="http://reports.fb.com/hpkp/"
Pragma: no-cache
content-security-policy: default-src * data: blob:;script-src *.facebook.com *.fbcdn.net *.facebook.net *.google-analytics.com *.virtualearth.net *.google.com 127.0.0.1:* *.spotilocal.com:* 'unsafe-inline' 'unsafe-eval' fbstatic-a.akamaihd.net fbcdn-static-b-a.akamaihd.net *.atlassolutions.com blob: data: 'self';style-src data: blob: 'unsafe-inline' *;connect-src *.facebook.com *.fbcdn.net *.facebook.net *.spotilocal.com:* *.akamaihd.net wss://*.facebook.com:* https://fb.scanandcleanlocal.com:* *.atlassolutions.com attachment.fbsbx.com ws://localhost:* blob: *.cdninstagram.com 'self';
Cache-Control: private, no-cache, no-store, must-revalidate
Strict-Transport-Security: max-age=15552000; preload
X-Content-Type-Options: nosniff
Expires: Sat, 01 Jan 2000 00:00:00 GMT
Vary: Accept-Encoding
Content-Type: text/html; charset=UTF-8
X-FB-Debug: pA9SSTdWWx2a66QeP7Je/4ik/2a+/ZL/m/nckHKf+KoEZLloClzu+qMDzyE/B8M1PRDl4SdS19C9vIIl7f43mA==
Date: Tue, 06 Jun 2017 18:25:15 GMT
Transfer-Encoding: chunked
Connection: keep-alive
* Connection #0 to host www.facebook.com left intact
Any help would be appreciated.
(my first SO post so please don't be too harsh if i did something dumb)

Unexpected character in payload

My Citrus test sends a (travel)request to some REST API. The response is handled as follows:
http()
.client("http://localhost:18082/cases")
.send()
.post()
.accept("application/json; charset=UTF-8")
.contentType("application/json")
//.payload(new ClassPathResource("templates/travelrequest.json"));
.payload(
"{ "+
"\"definition\": \"travelrequest.xml\", "+
"\"name\": \"travelrequest\" "+
"} "
);
Although response code 500 is received, this is what I expect. In Wireshark I captured the following package:
Host: localhost:18082
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.3 (Java/1.8.0_121)
Accept-Encoding: gzip,deflate
{ "definition": "travelrequest.xml", "name": "travelrequest" } HTTP/1.1 500 Internal Server Error
Server: spray-can/1.3.3
Date: Thu, 13 Apr 2017 15:33:37 GMT
When I move the payload to a template, the receive part of my test now looks like this:
http()
.client("http://localhost:18082/cases")
.send()
.post()
.accept("application/json; charset=UTF-8")
.contentType("application/json")
.payload(new ClassPathResource("templates/travelrequest.json"));
//.payload(
// "{ "+
// "\"definition\": \"travelrequest.xml\", "+
// "\"name\": \"travelrequest\" "+
// "} "
//);
The template resource contains this text:
{
"definition": "travelrequest.xml",
"name": "travelrequest"
}
When I run this test, I receive a different response code: 400. In Wireshark I captured the following package:
Host: localhost:18082
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.3 (Java/1.8.0_121)
Accept-Encoding: gzip,deflate
?{
"definition": "travelrequest.xml",
"name": "travelrequest"
}HTTP/1.1 400 Bad Request
Server: spray-can/1.3.3
Date: Thu, 13 Apr 2017 15:36:15 GMT
Do notice that the request starts with an unexpected questionmark. This questionmark is not visible in the Citrus output:
17:36:13,629 DEBUG client.HttpClient| Sending HTTP message to: 'http://localhost:18082/cases'
17:36:13,629 DEBUG client.HttpClient| Message to send:
{
"definition": "travelrequest.xml",
"name": "travelrequest"
}
17:36:13,630 DEBUG ingClientInterceptor| Sending Http request message
Do notice the space directly before the opening bracket.
Is this some special character? Why is it added to the payload? Is there a logical explanation?
Cheers,
Ed
Seems to be an encoding issue. Citrus by default uses UTF-8 encoding when reading the file content. Maybe the file uses some other encoding and the first character is the result of this difference.
Please check the file encoding. You can tell Citrus to use some other encoding by setting the System property
citrus.file.encoding=UTF-8
You can also add this property to the citrus-application.properties file as described here: http://www.citrusframework.org/reference/html/configuration.html#citrus-application-properties

REST API returning an AUTHORIZATION_ERROR

Following some of the examples provided in the Invoicing REST API https://developer.paypal.com/docs/api/#invoicing I am always receiving an AUTHORIZATION_ERROR.
Here is an example:
First retrieving my token:
curl https://api.sandbox.paypal.com/v1/oauth2/token \
> -H "Accept: application/json" \
> -H "Accept-Language: en_US" \
> -u "AbNzCxxxxs6iVF0:EBhQWxxxxxxrgefvhb" \
> -d "grant_type=client_credentials"
{"scope":"openid https://uri.paypal.com/services/invoicing https://api.paypal.com/v1/payments/.* https://api.paypal.com/v1/vault/credit-card/.* https://api.paypal.com/v1/vault/credit-card","access_token":"F9Ig.4FXq1DQICPrMaUUb0-K--3dWBHvqRck636df4A","token_type":"Bearer","app_id":"APP-80W284485P519543T","expires_in":28800}%
Then using that token to create an invoice:
curl -v -X 'POST' 'https://api.sandbox.paypal.com/v1/invoicing/invoices' \
> -H 'Content-Type: application/json' \
> -H 'Authorization: Bearer F9Ig.4FXq1DQICPrMaUUb0-K--3dWBHvqRck636df4A' \
> -d '{
quote> "merchant_info": {
quote> "email": "rdg#rapiddg.com",
quote> "first_name": "Mike",
quote> "last_name": "Bopp",
quote> "business_name": "RDG",
quote> "phone": {
quote> "country_code": "001",
quote> "national_number": "5032141716"
quote> },
quote> "address": {
quote> "line1": "1234 Main St.",
quote> "city": "Portland",
quote> "state": "OR",
quote> "postal_code": "97217",
quote> "country_code": "US"
quote> }
quote> }
quote> }'
* Adding handle: conn: 0x7ff110804000
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7ff110804000) send_pipe: 1, recv_pipe: 0
* About to connect() to api.sandbox.paypal.com port 443 (#0)
* Trying 23.52.155.39...
* Connected to api.sandbox.paypal.com (23.52.155.39) port 443 (#0)
* TLS 1.2 connection using TLS_RSA_WITH_RC4_128_SHA
* Server certificate: api.sandbox.paypal.com
* Server certificate: VeriSign Class 3 Secure Server CA - G3
* Server certificate: VeriSign Class 3 Public Primary Certification Authority - G5
* Server certificate: Class 3 Public Primary Certification Authority - G2
> POST /v1/invoicing/invoices HTTP/1.1
> User-Agent: curl/7.30.0
> Host: api.sandbox.paypal.com
> Accept: */*
> Content-Type: application/json
> Authorization: Bearer F9Ig.4FXq1DQICPrMaUUb0-K--3dWBHvqRck636df4A
> Content-Length: 387
>
* upload completely sent off: 387 out of 387 bytes
< HTTP/1.1 401 Unauthorized
* Server Apache-Coyote/1.1 is not blacklisted
< Server: Apache-Coyote/1.1
< PROXY_SERVER_INFO: host=slcsbjava3.slc.paypal.com;threadId=220
< Paypal-Debug-Id: 0329fb0c0e560
< WWW-Authenticate: OAuth
< Content-Type: application/json
< DC: origin2-api.sandbox.paypal.com
< Date: Fri, 02 May 2014 15:31:53 GMT
< Connection: close
< Set-Cookie: DC=origin2-api.sandbox.paypal.com; secure
<
* Closing connection 0
{"name":"AUTHORIZATION_ERROR","message":"Authorization error occurred.","debug_id":"0329fb0c0e560"}%
I can successfully send a payment request as is done here: https://developer.paypal.com/docs/integration/direct/make-your-first-call/
But for some reason the Invoice does NOT work.
I have verified that in my sandbox app I have enabled invoicing
Any help appreciated, thanks in advance.
Please change the email address(rdg#rapiddg.com) to the one that linked to the app you created before.
Please go ahead to check the email address in your REST APP.

Zend Rest Api: putAction on POST method

I got a weird issue. I hope someone could help me.
I am new to Zend and I'm writing a RESTfull API.
When I run the curl command as POST method, it calls the putAction() function.
For example, I am run a curl command:
curl -X POST http://localhost/ws/user/post -v
Here is the response:
* About to connect() to localhost port 80 (#0)
* Trying 127.0.0.1...
* connected
* Connected to localhost (127.0.0.1) port 80 (#0)
> POST /ws/user/post HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5
> Host: localhost
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Mon, 22 Oct 2012 13:37:56 GMT
< Server: Apache/2.2.14 (Ubuntu)
< X-Powered-By: PHP/5.3.2-1ubuntu4.18
< Vary: Accept-Encoding
< Content-Length: 53
< Content-Type: text/html
<
* Connection #0 to host localhost left intact
"From putAction() updating the requested article"
Method = POST
* Closing connection #0
Here is the code:
[...]
public function postAction() {
echo 'Method = ' . $_SERVER['REQUEST_METHOD'];
if (!$id = $this->_getParam('id', false)) {
$this->sendResponse("Bad request. Id is missing", 400);
}
$this->sendResponse("From postAction() creating the requested article", 201);
}
public function putAction() {
echo 'Method = ' . $_SERVER['REQUEST_METHOD'];
$this->sendResponse("From putAction() updating the requested article");
}
[...]
Any ideas ?
EDIT:
I realized that I put this code in my bootstrap:
public function _initRestRoute() {
$front = Zend_Controller_Front::getInstance();
$router = $front->getRouter();
$restRoute = new Zend_Rest_Route($front, array(), array(
'ws',
));
$router->addRoute('rest', $restRoute);
}
When I comment it, it works.
Any explanations ? Thanks!
You shouldn't be having http://localhost/ws/user/post in your request while using the Zend_Rest_Route. This route interprets /user/post as parameters and changes action from post to put. Try http://localhost/ws instead.
See Zend/Rest/Route.php line 208.