I find that there is precious little Paypal help online when things are behaving badly, and I am having some trouble with onShippingChange() and actions.order.patching. When I run the following code with the new Paypal buttons, return actions.order.patch() doesn't seem to work properly.
My code is below:
<script src="https://www.paypal.com/sdk/js?commit=false&client-id={sandbox-merchant-id}&disable-funding=paylater¤cy=USD"></script>
<script>
paypal.Buttons({
createOrder: function(data, actions) {
return actions.order.create({
"purchase_units": [{
"description": "Sporting Goods",
"amount": {
"currency_code": "USD",
"value": 12,
"breakdown": {
"item_total": {
"currency_code": "USD",
"value": 12
},
"shipping": {
"currency_code": "USD",
"value": 0
}
}
},
"items": [{
"name": "Arizona Christmas",
"sku": "sku01",
"quantity": "1",
"unit_amount": {
"currency_code": "USD",
"value": 12
}
}]
}]
});
},
onShippingChange: function(data, actions) {
const baseOrderAmount = '12.00';
if (data.shipping_address.state == "AZ") {
const shippingAmount = data.shipping_address.state === 'AZ' ? '50.00' : '60.00';
return actions.order.patch([{
op: 'replace',
path: '/purchase_units/#reference_id==\'default\'/amount',
value: {
currency_code: 'USD',
value: (parseFloat(baseOrderAmount) + parseFloat(shippingAmount)).toFixed(2),
breakdown: {
item_total: {
currency_code: 'USD',
value: baseOrderAmount
},
shipping: {
currency_code: 'USD',
value: shippingAmount
}
}
}
}]);
}
}
}).render('#paypal-button-container');
</script>
With the above code, when I change from a default California address to an Arizona address (in order to cause a change in shipping costs), the onShippingChange(...) event fires immediately, then the Paypal window shows the "processing" loader, then shows the new total in the upper right corner. The Paypal window itself shows $62 (as expected), but when I expand the total price breakdown, it shows $0 for shipping. Also, console.log(data) shows nothing updated... all the original dollar values. So it appears that the details of the .patch are not being applied completely.
But then there is a strange thing I notice. If I hit F5 to refresh the Paypal window, it refreshes, of course. The total cost and breakdown become correct (shows correct total, shipping, etc), and the console.log(data) shows the correct breakdown, but then the address is back to the original address (a California address; that is the default address for the sandbox personal account).
So... I'm not sure what to make of this. Am I doing something wrong with the code above? Or is Paypal's system just not quite refined enough to use the new Buttons Javascript-only based solution (vs. the server-side solution)? I can't do the server side solution, so I'm hoping to get this to work properly using the plain javascript sdk implementation.
Related
I am extending an application that is currently having stripe as a Payment processor. The architecture is implemented in such a way that I am not storing any of the transaction and customer information in the application database in order to meet the compliances. So all of data is being stored in Stripe as transaction metadata.
Now I need to extend the application to add Paypal Payment Gateways, but after going through the documentation I can't find a metadata option available in the PayPal Transaction API just like its there in Stripe.
I have tried PayPal's create order API with metadata as suggested in v1
https://developer.paypal.com/docs/business/checkout/server-side-api-calls/create-order/
createOrder: function(data, actions) {
return actions.order.create({
metadata:{
"cid":1233,
"dgn":"AH-GHGJTTHHHJ",
"ct":"X-HGD898989","tfn":"xyz params",
"type":"2",
"lo":"lorem ipsum is a dummy text and hope it works"
},
purchase_units: [
{
"description":'xyz P30 Pro mobile',
"amount":{"currency_code":"USD","value":199}
}]
});
},
But on retrieving the transaction by TX_ID i donot get the metadata object back.
So anyone who faced this challenge in the past and can suggest a workaround to achieve this with payPal will be highly appricated.
Apart from a unique (never used before for a completed payment) invoice_id for the transaction, the only custom metadata field in PayPal is custom_id, documented here: https://developer.paypal.com/docs/api/orders/v2/#orders-create-request-body
If you need additional fields, store them locally based on your own order/invoice ID or the resulting PayPal Transaction ID and look them up later as needed. Your most sensitive data with compliance issues might possibly go into custom_id as a serialized JSON string, if it is small enough (127 chars)
Purchase item information can go in an "items" array, which requires the amount have a "breakdown" object. See the example within the docs,
createOrder: function(data, actions) {
return actions.order.create({
"purchase_units": [{
"amount": {
"currency_code": "USD",
"value": "100",
"breakdown": {
"item_total": { /* Required when including the `items` array */
"currency_code": "USD",
"value": "100"
}
}
},
"items": [
{
"name": "First Product Name", /* Shows within upper-right dropdown during payment approval */
"description": "Optional descriptive text..", /* Item details will also be in the completed paypal.com transaction view */
"unit_amount": {
"currency_code": "USD",
"value": "50"
},
"quantity": "2"
},
]
}]
});
},
I have an ecommerce platform that has multiple sellers selling products and I want to use PayPal checkout v2 Javascript to send a payment to multiple merchants.
Using v2 JavaScript API:
https://www.paypal.com/sdk/js?client-id=CLIENT_ID&disable-funding=credit,card
paypal.Buttons({
onInit: function(data, actions) {
},
createOrder: function(data, actions) {
return actions.order.create({
"purchase_units": [
{
"reference_id": "1",
"amount": {
"currency_code": "AUD",
"value": "20.00",
"breakdown": {
"item_total": {
"currency_code": "AUD",
"value": "20.00"
}
}
},
"payee": {
"email_address": "payee1#business.example.com"
}
},
{
"reference_id": "2",
"amount": {
"currency_code": "AUD",
"value": "10.00",
"breakdown": {
"item_total": {
"currency_code": "AUD",
"value": "10.00"
}
}
},
"payee": {
"email_address": "payee2#business.example.com"
}
}
]
});
},
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
alert('Transaction completed by ' + details.payer.name.given_name);
});
},
onError: function (err) {
alert(err.message);
}
}).render('#paypal-button');
I can login as a payer and see total $30, when I click Pay Now it says error "Order can not be captured". If I remove the second purchase unit object (payee 2) the transaction is approved.
I read on PayPal forums and api documentations and see mostly deprecated methods. How do I send a payment to multiple merchants now adays?
There is no mechanism for approving multiple v2/checkout/orders purchase_units with the JS checkout.
The deprecated methods should also not be used
How do I send a payment to multiple merchants now adays?
You don't -- not simultaneously, anyway. Each is its own separate checkout/approval event.
You have to create app as platform in your sandbox or live account
its best user experience to use js integration with multi seller checkout
You have pass merchantids on url and in js section as below
if multiple seller use this
multiple seller
<script
src="https://www.paypal.com/sdk/js?client-id=client_id&intent=capture&disable-funding=credit&merchant-id=*¤cy=USD"
data-partner-attribution-id="bn_code"
data-merchant-id="merchant_id1,merchant_id2"
>
</script>
single seller
<script
src="https://www.paypal.com/sdk/js?client-id=client_id&intent=capture&disable-funding=credit&merchant-id=merchant_id1¤cy=USD"
data-partner-attribution-id="bn_code"
data-merchant-id="merchant_id1"
>
</script>
Then you have to use js sdk integration with div as below
paypal.Buttons({
style : {
color: 'gold',
shape: 'pill',
label: 'pay',
},
createOrder: function (data, actions) {
return actions.order.create(data.json);
},
onApprove: function (data, actions) {
return actions.order.capture().then(function (details) {
//redirect to your server
window.location.replace(base_url+'paypal/create_order/'+details.id)
})
},
onCancel: function (data) {
console.log(data);
},
onError: function (err) {
// For example, redirect to a specific error page
console.log(err);
}
}).render('#paypal-payment-button');
For how to create order please check the documentation.
Everyhing works flawlessly with my PayPal classic implementation in the sandbox. (Marked as deprecated from jan 17 but still supported.) But it simply does not transfer money from the customer to the merchant.
"payment": {
"intent": "sale",
"transactions": [
{
"amount": {
"total": "1.21",
"currency": "EUR",
"details": {
"subtotal": "1.08",
"tax": "0.02",
"handling_fee": "0.11"
}
},
"description": "Description content."
}
]
}
After the customer agrees and executes the payment I receive an authorization:
"onAuthorize": {
"paymentToken": "EC-024873345P059811F",
"orderID": "EC-024873345P059811F",
"payerID": "4M6QNG7UDS626",
"paymentID": "PAY-7W7465253T270972NLO5EG6A",
"intent": "sale",
"returnUrl": "https://www.sandbox.paypal.com/?paymentId=PAY-7W7465253T270972NLO5EG6A&token=EC-024873345P059811F&PayerID=4M6QNG7UDS626"
}
I expect that the money transfer took place but nothing shows. No transactions, no errors, no nothing. As if nothing happened. I also expect that I get a notification if the transaction is not executed due to its deprecation. That is not the case. I am clueless.
I wasn't using the deprecated classic after all but the new Client Side Rest: https://developer.paypal.com/demo/checkout/#/pattern/client
It is close to classic, I only needed to add ...
return actions.payment.execute();
Then the actual transfer takes place. I am a happy coder now.
The Client-side REST integration documentaion describes about creating a express checkout for one or more items.
How can i use the same for creating a subscription or Recurring payment? How should the following be modified?
payment: function(data, actions) {
return actions.payment.create({
transactions: [
{
amount: { total: '1.00', currency: 'USD' }
}
]
});
},
I found a similar Rest api for Node. Not sure how it would be on JS.
First you need to create a billing plan:
billing_plan_attributes = {
"name": PLAN_NAME_HERE,
"description": PLAN_DESCRIPTION,
"merchant_preferences": {
"auto_bill_amount": "yes", # yes if you want auto bill
"cancel_url": "http://www.cancel.com", # redirect uri if user cancels payment
"initial_fail_amount_action": "continue",
"max_fail_attempts": "1",
"return_url": RETURN_URL,
"setup_fee": {
"currency": CURRENCY,
"value": VALUE # how much do you want to charge
}
},
"payment_definitions": [
{
"amount": {
"currency": request.form['currency'],
"value": request.form['amount']
},
"cycles": CYCLES, # how much time this subscription will charge user
"frequency": FREQ, # month, day
"frequency_interval": INTERVAL, # per month or per three month or so on
"name": NAME,
"type": TYPE
}
],
"type": TYPE
}
billing_plan = BillingPlan(billing_plan_attributes)
if billing_plan.create():
print("success")
The attributes used have literal meaning here. Now since you have created a billing plan you need to give users some interface so that they can subscribe with it. Below is a sample code for this:
billing_agreement = BillingAgreement({
"name": "Organization plan name",
"description": "Agreement for " + request.args.get('name', ''),
"start_date": (datetime.now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:%M:%SZ'),
"plan": {
"id": request.args.get('id', '')
},
"payer": {
"payment_method": "paypal"
},
"shipping_address": {
"line1": "StayBr111idge Suites",
"line2": "Cro12ok Street",
"city": "San Jose",
"state": "CA",
"postal_code": "95112",
"country_code": "US"
}
})
if billing_agreement.create():
for link in billing_agreement.links:
if link.rel == "approval_url":
approval_url = link.href
In the last line you get the approval link which can be given to user.
Next you have to setup an endpoint which will be the callback url if user approves the payment.
billing_agreement_response = BillingAgreement.execute(payment_token)
payment_token is sent by paypal to your callback url.
I'm trying to use PayPal REST API instead of PayPal Classic API but it seems that the REST API is lacking two features that the Classic API has:
immediate payment: when the user goes to PayPal page show him a "Pay now" button instead of a "Continue" button and "You’re almost done. You will confirm your payment on ..." phrase.
no shipping address: avoid asking the user to confirm his shipping address while on PayPal page (in Classic API is done with NOSHIPPING=1 parameter, if I remember well)
So my question is: is it possibile do perform an immediate payment without asking for shipping address using REST API? Do I have to go back to Classic API?
I provide here a little more informations about how I'm using the PayPal REST API.
I'm using the PayPal REST Java SDK.
This is a sample request:
{
"intent": "sale",
"payer": {
"payment_method": "paypal"
},
"transactions": [
{
"amount": {
"currency": "USD",
"total": "5",
"details": {
"subtotal": "5"
}
},
"description": "This is the payment transaction description.",
"item_list": {
"items": [
{
"quantity": "1",
"name": "Item 1",
"price": "5",
"currency": "USD"
}
]
}
}
],
"redirect_urls": {
"return_url": "http://XXX/handlePayment.jsp?guid\u003dXXX",
"cancel_url": "http://XXX/cancelPayment.jsp?guid\u003dXXX"
}
}
And its response:
{
"id": "XXX",
"create_time": "2014-06-29T08:52:55Z",
"update_time": "2014-06-29T08:52:55Z",
"state": "created",
"intent": "sale",
"payer": {
"payment_method": "paypal",
"payer_info": {
"shipping_address": {}
}
},
"transactions": [
{
"amount": {
"total": "5.00",
"currency": "USD",
"details": {
"subtotal": "5.00"
}
},
"description": "This is the payment transaction description.",
"item_list": {
"items": [
{
"name": "Item 1",
"price": "5.00",
"currency": "USD",
"quantity": "1"
}
]
}
}
],
"links": [
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/XXX",
"rel": "self",
"method": "GET"
},
{
"href": "https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=XXX",
"rel": "approval_url",
"method": "REDIRECT"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/XXX/execute",
"rel": "execute",
"method": "POST"
}
]
}
While delving through the REST API I came across this
I believe this means you don't have to go about creating any "Profiles" as such, and can just pass them along with the payment call...
Further explanation as requested :)
Below is an example of passing PayPal experience parameters along with a particular payment call using the Client-side JS method for Express checkout.
payment: function(data, actions) {
return actions.payment.create({
payment: {
transactions: [
{
amount: { total: '1.00', currency: 'USD' }
}
]
},
experience: {
input_fields: {
no_shipping: 1
}
}
});
},
Hope that makes enough sense to you guys! :)
Unfortunately, the REST API is still a long ways behind the Classic API with features it provides. These features you mentioned are things I've seen come up quite a bit, and to my knowledge they are not yet available with the REST services.
I personally have stuck with Classic as they provide everything and I really see no advantage to using REST myself. If you happen to be working with PHP (which I always do) you may be interested in my class library for PayPal. It makes all of the Classic API calls very quick and easy for you, and it's available on Packagist so you can use with Composer easily.
The REST API now supports no-shipping with the Payment Experience APIs.
You need to create a web experience profile and supply no_shipping as an input field. Then use the profile ID when creating the payment.
The profile ID doesn't need to be recreated every time.
appreciate that this post hasn't had any activity for a while but...
i hit on the exact same problem and used this post as a start point for my own question:
paypal api: take immediate payment without a shipping address
it's taken a bit of trial and error but i you can create a one off web profile with 'no_shipping' set to 1, store the id that it creates and then pass that in with future payments that don't require a shipping address.
still haven't figured out how to get rid of the 'You're almost done. You will confirm your payment on xxx.' but my payment process is a far better place than it was!
hope this helps someone out there as this no shipping issue is one that a lot of people appear to be hitting with the paypal api...
I tried using the experience section of the API and apart from still not being able to force a locale code have been able to disable shipping and go straight to billing:
payment: {
transactions: [{
invoice_number: document.getElementById("ReqTxt").value,
amount: {
total: document.getElementById("InvoiceAmt").innerText,
currency: document.getElementById("Currency").innerText
}
}]
},
experience: {
input_fields: {
no_shipping: 1
},
flow_config: {
landing_page_type:'billing'
}
}
you can also see this page. It is possible to set the user action in the flow_config section too
For 'Pay Now' instead of 'Continue' on Paypal I use a special param in approval url:
$approvalUrl = $payment->getApprovalLink().'&useraction=commit';
Result:
https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token=TOKEN&useraction=commit