- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I have been working on a v2 upgrade in my website software for 3 weeks during the quarantine of the world. I spent a lot of time banging my head against a wall and decided to share my final running code for everyone interested.
I spent a lot of time studying the PayPal docs and I will agree with many of the other cries for help on this support forum that say how horrible the docs are. I didn't expect to become an expert in JSON over the last few weeks and I didn't think I would have to polish my JavaScript skills to get this done.
I'm providing my fully working code with a lot of commentary for everyone. Delete the comments to clean it up before using it.
I HAVE TO POST THIS IN PARTS BECAUSE MY COMMENTARY IS MORE THAN 20,000 CHARACTERS *facepalm*
I'm new to the PayPal developer community, and this is my first post. Give me a vote if this helps you.
<script src="https://www.paypal.com/sdk/js?client-id=[your-client-id-here]&commit=true¤cy=USD"></script>
<script>
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
style: {
//style as you prefer
color: "gold",
shape: "pill",
size: "large",
label: "checkout"
},
// Set up the transaction
createOrder: function(data, actions) {
return actions.order.create(
{
application_context: {
shipping_preference: "SET_PROVIDED_ADDRESS", //Using SET_PROVIDED_ADDRESS will force the shipping address to be grabbed from the purchase_unit below. This setting also prevents the buyer from changing their address. Point of note: This setting also seems to disable the PICKUP and SHIPPING options that are normally available.
landing_page: "LOGIN", //my preference
user_action: "PAY_NOW", //my preference
payment_method: { payer_selected: "PAYPAL", payee_preferred: "IMMEDIATE_PAYMENT_REQUIRED"}
},
payer: {
//I am collecting the payer info on my website and populating it here
//Point of order: this information is not sent back to you
//but it seems like this information will help populate the PayPal screens when the buyer doesn't yet have a PayPal account.
//This invormation is pretty useless if the buyer already has a PayPal account. PayPal does not use the name or address
//information when the buyer logs into their account. It seems like this information is simply discarded for buyers
//with existing PayPal accounts
name: { given_name: "Test", surname:"Buyer"},
address: {
address_line_1: "1 Main St",
address_line_2: "Apt 1",
admin_area_2: "Town name",
admin_area_1: "NJ",
postal_code: "00000",
country_code: "US" //You only get the country_code sent back to you
},
email_address: "[billing_email_address]", //This email will appear on the PayPal login screen because I specified LOGIN in the above setting
payment_method: "PAYPAL",
},
Solved! Go to Solution.
- Labels:
-
REST SDK
Accepted Solutions
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
PART 2 OF MY CODE
purchase_units: [
//Point of note: purchase_units is an array, you can have more than one when you repeat the structure insite the {}
//but using more than one seems useless since PayPal only recognizes the shipping address from the first purchase_unit
//When using more than 1 purchase_unit PayPal will summ the total from all the purchase_units and show that total
//on the PayPal shadowbox popup. Normally the buyer can click that total to see the item list of everything they are buying,
//but when you have more than 1 purchase_unit it does not allow the buyer to expand that item list
//The beauty of using more than 1 purchase_unit is that the buyer and seller will see 2 invoices in their PayPal acounts,
//which is exactly what I wanted to do, but then I ran into shipping address problems that I document below
//Another intereting point of note: When using multiple purchase_units you will find that the order is finalized and captured,
//in other words, you can't authorize the payment then go back later.
{
reference_id: "0", //make sure to always use a ref id, hopefully some day the ability to have multip purcahse_units will actually work
invoice_id: "1234567890123456961", //for your own sanity, pass the invoice number from your website. It will make future tracking easier for you
amount: { currency_code: "USD", value: "278.00", // this is the total of the breakdown lines below
//make sure to specify your currency_code and the price, and make sure the price has 2 digits after the decimal
breakdown: {
item_total: { currency_code: "USD", value: "280.00"},
shipping: { currency_code: "USD", value: "0.00"},
handling: { currency_code: "USD", value: "0.00"},
tax_total: { currency_code: "USD", value: "0.00"},
//enter the discount as a positive number.
discount: { currency_code: "USD", value: "2.00"}
}
},
//write yourself a loop in your code to list all the items
//I have read through all the PayPal doc regarding chargebacks and I feel that you have
//bigger protection when you send the item details to PayPal so they appear within the PayPal account.
//This will also help your own sanity since you can look in your seller account and fully see the
//details of your online sales. Naturally these same details should also be on your website
items: [
{
name: "Item 1", //This appears on the PayPal invoice
//Although the description doesn't really seem to be needed, I found that it also appears on the PayPal invoice.
//Notice I am using this to tell the buyer when they are expected to pick the item up.
//My website allows the customer to choos a pickup or delivery, I tried to follow the docs that say to use
//shipping_type:PICKUP, but no matter how hard I tried I could not get that to work, instead, I'm using
//the description field here to remind the buyer that they have requested to pick this item up.
//I also give them instructions for proving their ID upon pickup.
//This description field also appears on the PayPal payment shadowbox screen as well as on their PayPal invoice
description: "Store Pickup on 12/31/2020. Bring receipt and photo ID for proof of purchase.",
sku: "xyz-2654",
unit_amount: { currency_code: "USD", value: "100.00"},
quantity: "1",
tax: { currency_code: "USD", value: "0.00"},
category: "PHYSICAL_GOODS" //I cannot find any place that this is being recorded, but I'm using it anyway to for CYA purposes
}, //Remember to include this comma in your loop when generating this code on your site.
{
name: "Item 2",
description: "Store Pickup on 12/31/2020. Bring receipt and photo ID for proof of purchase.",
sku: "zdc-3942",
unit_amount: { currency_code: "USD", value: "60.00"},
quantity: "2",
tax: { currency_code: "USD", value: "0.00"},
category: "PHYSICAL_GOODS"
},
{
name: "Item 3",
//In this description I am including the expected shippment date or the expected delivery date, whichever one I have
description: "Expected shipment on 05/10/2020.",
sku: "xyz-2654",
unit_amount: { currency_code: "USD", value: "60.00"},
quantity: "1",
tax: { currency_code: "USD", value: "0.00"},
//I attempted to use DIGITAL_GOODS several times to see if I would get a reply that this item
//is not covered by seller protection, but I never got any such message. Perhaps PayPal is not correctly
//generating those messages
category: "DIGITAL_GOODS"
}
],
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
PART 2 OF MY CODE
purchase_units: [
//Point of note: purchase_units is an array, you can have more than one when you repeat the structure insite the {}
//but using more than one seems useless since PayPal only recognizes the shipping address from the first purchase_unit
//When using more than 1 purchase_unit PayPal will summ the total from all the purchase_units and show that total
//on the PayPal shadowbox popup. Normally the buyer can click that total to see the item list of everything they are buying,
//but when you have more than 1 purchase_unit it does not allow the buyer to expand that item list
//The beauty of using more than 1 purchase_unit is that the buyer and seller will see 2 invoices in their PayPal acounts,
//which is exactly what I wanted to do, but then I ran into shipping address problems that I document below
//Another intereting point of note: When using multiple purchase_units you will find that the order is finalized and captured,
//in other words, you can't authorize the payment then go back later.
{
reference_id: "0", //make sure to always use a ref id, hopefully some day the ability to have multip purcahse_units will actually work
invoice_id: "1234567890123456961", //for your own sanity, pass the invoice number from your website. It will make future tracking easier for you
amount: { currency_code: "USD", value: "278.00", // this is the total of the breakdown lines below
//make sure to specify your currency_code and the price, and make sure the price has 2 digits after the decimal
breakdown: {
item_total: { currency_code: "USD", value: "280.00"},
shipping: { currency_code: "USD", value: "0.00"},
handling: { currency_code: "USD", value: "0.00"},
tax_total: { currency_code: "USD", value: "0.00"},
//enter the discount as a positive number.
discount: { currency_code: "USD", value: "2.00"}
}
},
//write yourself a loop in your code to list all the items
//I have read through all the PayPal doc regarding chargebacks and I feel that you have
//bigger protection when you send the item details to PayPal so they appear within the PayPal account.
//This will also help your own sanity since you can look in your seller account and fully see the
//details of your online sales. Naturally these same details should also be on your website
items: [
{
name: "Item 1", //This appears on the PayPal invoice
//Although the description doesn't really seem to be needed, I found that it also appears on the PayPal invoice.
//Notice I am using this to tell the buyer when they are expected to pick the item up.
//My website allows the customer to choos a pickup or delivery, I tried to follow the docs that say to use
//shipping_type:PICKUP, but no matter how hard I tried I could not get that to work, instead, I'm using
//the description field here to remind the buyer that they have requested to pick this item up.
//I also give them instructions for proving their ID upon pickup.
//This description field also appears on the PayPal payment shadowbox screen as well as on their PayPal invoice
description: "Store Pickup on 12/31/2020. Bring receipt and photo ID for proof of purchase.",
sku: "xyz-2654",
unit_amount: { currency_code: "USD", value: "100.00"},
quantity: "1",
tax: { currency_code: "USD", value: "0.00"},
category: "PHYSICAL_GOODS" //I cannot find any place that this is being recorded, but I'm using it anyway to for CYA purposes
}, //Remember to include this comma in your loop when generating this code on your site.
{
name: "Item 2",
description: "Store Pickup on 12/31/2020. Bring receipt and photo ID for proof of purchase.",
sku: "zdc-3942",
unit_amount: { currency_code: "USD", value: "60.00"},
quantity: "2",
tax: { currency_code: "USD", value: "0.00"},
category: "PHYSICAL_GOODS"
},
{
name: "Item 3",
//In this description I am including the expected shippment date or the expected delivery date, whichever one I have
description: "Expected shipment on 05/10/2020.",
sku: "xyz-2654",
unit_amount: { currency_code: "USD", value: "60.00"},
quantity: "1",
tax: { currency_code: "USD", value: "0.00"},
//I attempted to use DIGITAL_GOODS several times to see if I would get a reply that this item
//is not covered by seller protection, but I never got any such message. Perhaps PayPal is not correctly
//generating those messages
category: "DIGITAL_GOODS"
}
],
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
PART 3 of my Code
//As I said above, when using more than 1 purchase_unit PayPal will create 1 invoice per purchase_unit.
//Sadly, I found that PayPal will ONLY use the shipping address from the FIRST purchase_unit for all of the invoices
//In one of the docs I found a note saying that you should not sell PICKUP and SHIPPING items in the same transaction
//because the PICKUP items do not have seller protection but the SHIPPING items do.
//It's a little ridiculious to ask a buyer to go through multiple checkout transactions when they want to both
//pick something up and also have something shipped to them. PayPal also denies seller protection for DIGITAL_GOODS
//which also seems to mean that you should not sell DIGITAL_GOODS and PHYSICAL_GOODS on the same transaction,
//again, this is another great oppertunity to use multiple purchase_units; but again, I could not get this to work
//The customer is entering their shipping address on my website. I let them enter a different billing and shipping
//address, then I show that shipping address here. Because I am using SET_PROVIDED_ADDRESS above, they are not able
//to change this address on PayPal. This give me more seller protection because now I can control the shipping address.
//When the buyer has the power to change their shipping address on PayPal, you have to grab that new address for your
//records. Since I've already captured their address and calculated shipping on my website I don't want them to change
//the address on PayPal. I also don't want them to be sneaky **bleep** and change the address without me knowing
//which would ultimatly allow them to claim they never got the shippment to their specified address, and win a chargeback claim
shipping:{
address: {
//The address you send to PayPal here will appear on the PayPal invoice. This is the address you must ship to
//in order to have your full seller protection.
//When I was trying multiple purchase_units only the address from the first purchase_unit was grabbed for all the
//invoices. I was trying to have one purchase unit for pickup items, one for shipping items, and one for digital downloads.
//I also wanted to have additional purchase_units for items that were grouped together with different shipping dates.
//But since PayPal does not recognize the different addresses in each purchase_unit the whole concept of those
//multiple invoices with different seller protections went out the window.
//When someone requests to have their entire order picked up at the store, I set this address to the store address
address_line_1: "2 Broadway Blvd",
address_line_2: "Apt 1",
admin_area_2: "Sometown",
admin_area_1: "NJ",
postal_code: "00000",
country_code: "US"
},
//As I said above, the PayPal doesn't seem to care about the payer.name or payer.address invormation that I send above
//however, the name shown below will appear on the invoice as the ship-to person
name: { full_name: "Ref 1 Buyer"}
}
}]
});
},
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
PART 4 of my Code
//It took me 5 days to figure out everything above, then another 5 days to figure out this sectioin below
//The PayPal docs for v2 are so messed up with regard to the onApprove, onCancel, and onError
//I figured the following out after reading the pain and suffering from dozens of other developers
//trying to accomplish this for the last 2 years.
//Point of note: The onError suggestion in the docs seems useless. I have a feeling there is more to it
//than what is documented, but the docs are limited. Why would we, as developers, want to simply show an error
//message to the buyer instead of an actual error code that we can interpret and make look pretty?
//I also found that there seemed to be no way to grab the orderID from within the onError function, which
//make the entire onError function seem pointless because you then have now way to even track the attempted payment.
//Therefore, I am using the onAuthorize to capture both the successfule and unsuccessful payment attempts
onApprove: function(data, actions) {
return actions.order.capture().then(function (details) {
//All of the alerts you see are for testing purposes only. When your website is fully functional, you
//should not rely on these alerts. A better user experience is to grab the results and have your website react accordingly
//This alert is just here to show me were I am in the capture process
alert('Transaction completed by ' + details.payer.name.given_name);
//I create a variable for the URL I'm using to save the transaction results
var EXECUTE_URL = '[paypalsave]?mycart=[mycart]&hash=[hash]&sd=[numseed]';
//Initially I didn't like the idea of fetch, I preferred to either redirect directly like we could in v1 of checkout.js
//but that ability to redirect and capture the JSON result seems to be gone now. IMHO that is stupid and sad.
//Now we have to grab the JSON and save it before redirecting the buyer to the final page of the site.
fetch(EXECUTE_URL, {
method: 'post',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
orderID: data.orderID, //This variable is suggested in the docs
payerID: data.payerID, //This variable is also suggested in the docs
//I also tried to grab data.status and save that, but 'status' is a reserved word and therefore it can't be easily grabbed from the JSON
amtpaid: details.purchase_units[0].amount.value, //I'm grabbing this to speed up the processing steps on my end
invoice_id: details.purchase_units[0].invoice_id, //I'm grabbing this to cross refrence my own shopping cart variable "mycart" to the invoice_id. They should be the same
finalstate: details.purchase_units[0].payments.captures[0].final_capture //This will be set to "true" if the payment was successful. I'm using this in place of my attempt to grab data.status as mentioned above
})
//Here's where things get interesting. I now do a test to make sure the fetch actually worked.
//This is where I test if the network error broke the fetch.
}).then (response => {
//If the fetch response if NOT ok, then I am goint to try doing a redirect to my next screen with URL variables that will pass along the state
//as well as a "fetcherror" variable to tell me the fetch broke
//I thought about skipping the fetch all together and just redirecting to the next page with the URL and the GET, but then I
//realized that more information could be sent in the fetch, and since it's a POST, there's a little more security.
//I'm also planning for future expansion when I can grab more information from the PayPal results and fetch them over to safety
if (!response.ok) {
//I'm building me redirect URL here
var uri = "[paypalreturn]&fetcherror=T&orderid="+data.orderID+"&payerID="+data.payerID+"&finalstate="+details.purchase_units[0].payments.captures[0].final_capture;
//Since the redirect is a GET, the URL needs to be encoded
var res = encodeURI(uri);
//The console message is used in testing. I had the location.href commented out during testing so I could see the console errors in DevTools of my browser
console.log('res',res);
//When redirecting at this point I already know that the payment was successful and teh rest of the script below doesn't matter to me any more
//I'm actually redirecting the buyer to the thank you page right here, but the extra added URL variables will tell my thank you page that there
//was something wrong with the fetch to save the JSON, and it will process the results differently
location.href=res;
}
});
//If the script gets to this point I know that the fetch above was successful. If the fetch wasn't successful I would have already been redirected away from this page
//I'm showing myself messages in the colsole only for testing. In production I comment the colsole lines out or just delete them
console.log('given_name'); console.log(details.payer.name.given_name);
console.log('return to [paypalreturn]');
//This is where I would notmally redirect the buyer to the thank you page
//Under normal conditions I do not have to send any GET variables to the thank you page. The fetch saved JSON will be waiting for me over there
location.href = '[paypalreturn]';
})
//And now we handle the errors. This is how I capture errors and display usable mesages that I could not get from onError as suggested in the docs.
//This catch will recognize when the return actions.order.capture() above fails
.catch(error => {
//The alert is just for my own testing. It is commented out or deleted in production mode
alert('error inside approve ');
//These console messages are also for testing. They are commented out or deleted in production mode
console.log ('error inside approve');
console.log ('orderID',data.orderID);
console.log ('payerID',data.payerID);
//Similar to what I did above, this is just prepwork for my fetch URL
var EXECUTE_URL = '[paypalsave]?mycart=[mycart]&hash=[hash]&sd=[numseed]';
fetch(EXECUTE_URL, {
method: 'post',
headers: {
'content-type': 'application/json'
},
//As I studied the PayPal v2 SDK docs I could not figure out how to grab any usable results from the onError.
//specifically, I needed to orderID so I could track the failed order and help a buyer.
//I saw several other people on this forum request a way to get order details and at least one PayPal Emploee tell them to use the onError function
//I really felt like someone was singing "there's a hole in the bucket, dear Liza, dear Liza" and no one was paying attention
//By including the error capture within the onAuthorize I found that the order variable are still available
//That's exactly why this is here, and you'll notice that I removed the onError from my script
body: JSON.stringify({
orderID: data.orderID,
payerID: data.payerID,
capturestatus:'ERROR' //This is my identifier that there's a problem. I can use the orderID on the next screen to lookup the order and figure out what went wrong
})
//Similar to above, this next part is protection against a potential network error during the fetch.
}).then (response => {
//Same as before, if the fetch result is NOT ok, then we will prepare a GET and redirect to the next page
if (!response.ok) {
//Prepping my next URL, it has the same info as the fetch.
var uri = "[paypalreturn]&fetcherror=T&orderid="+data.orderID+"&payerID="+data.payerID+"&capturestatus=ERROR";
//Once again, since this is a URL I need to encode the URI or spacing and other characters might break it
var res = encodeURI(uri);
//The console message is only for testing. I had the location.href commented out for testing.
//The consol line will be deleted or commented out for production
console.log('res',res);
location.href=res;
}
});
//If the fetch worked correctly, then this is where I expect to be.
//The console line is only for testing. It iw commented out or deleted for production
console.log('return to [paypalreturn]');
//The location.href was commented out for testing. This is where I jumpt the buyer to the next page
//My next page is actually my thank you page, but when I get there I'll find the fetch JSON telling me that there was an error
//Instead of showing the thank you I will use the data.orderID that I was able to save then ping PayPal to find out what the error was
//I built an error table on my website to translate the PayPal errors into something understadable by the buyer.
//I also built an email notification feature into my error routine that tells the seller that there is a purchase error along with all the details from PayPal
location.href = '[paypalreturn]';
});
},
//The onCancel works as expected. Perhaps the only part of this entire v2 SDK that worked without a single problem
onCancel: function(data) {
location.href='[paypalcancel]';
},
}).render('#paypal-button-container');
</script>
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
You're a legend mate. They really need to put out a full Javascript SDK documentation...or else they shouldn't even direct people to the silly buttons guide...
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Very helpful, thanks
Please find this gist (I only copied it into one page)
https://gist.github.com/adardesign/96e5805d6559e594bde46fa555367e69
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Your script @adardesign contains some codes that is repeated from a double post of Matthew. You may want to edit the script.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Thank You, @matthewperosi Matthew! Your posts were very useful. I could not get your script to work for my magazine subscription demo, so I modified and combined your code with some code found else where.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
To give back to the community the many useful codes and explanation that I received, especially the posts of Matthew Perosi, I have created a complete demo using PHP and a modified version of the SDK code, combined with others found else where. The link to the demo is PHP and V2 SDK demo of a magazine subscription framework.
Inside the zip file is a word document explain the system in more details.

Haven't Found your Answer?
It happens. Hit the "Login to Ask the community" button to create a question for the PayPal community.
- How do I handle INSTRUMENT_DECLINED error? in REST APIs
- Change the button from Sandbox to Production in REST APIs
- createOrder not being directed to OnApprove actions.redirect in PayPal Payments Standard
- JavaScript SDK unreliable redirect using "Sofortüberweisung" Klarna payment provider in SDKs
- Need notifications for ORDER ID in my paypal dashborad and to buyer email in PayPal Payments Standard