Sales tax not updated when shipping option is changed if buyer pays with Credit/Debit rather than PayPal? - paypal

In the PayPal JavaScript SDK when the order is created the client passes the shipping cost options. If the purchaser changes the shipping option from within the PayPal window, the sales tax must be recalculated because sales tax applies to shipping for this product. The sales tax is not passed in the order creation but the onShippingChange event is fired/invoked after the address is first applied in PayPal; and then the new values, including sales tax, are passed through return actions.order.patch().
If the purchaser pays through clicking the PayPal button, all works fine because the onShippingChange event is fired/invoked when the shipping option is changed. However, when the purchaser uses the Credit/Debit button to pay, the onShippingChange event is fired/invoked only once and not again when the shipping option is changed. If the user changes the shipping option, the cost appears to be taken from the data passed in the order creation and since the onShippingEvent is not run again, the sales tax remains based upon the initial shipping option.
The only difference in these two scenarios is which button the purchaser uses to pay: either PayPal for an account, or Credit/Debit to pay as a guest. All the JS code is the same.
Am I doing something wrong that is causing the onShippingChange event code to not be invoked? Thank you.
A couple things I've noticed while working on this. One is that the messages of "contacting merchant" and "processing" vary, such that when the shipping option is changed when paying with credit/debit, the message is only "processing". When paying with PayPal the message is "contacting merchant." Another is that even after the values are updated, with the sales tax inaccurate, the total cost shown in the top right corner of the PayPal window differs from the total in larger font in the message by difference in shipping options. The one in large font never changes when the shipping option is changed.
In the image below, the shipping option was changed from $9.25 priority to $4.00 standard. The initial total was $28.83. When shipping was changed to standard, the cost changed as $(28.83-9.25+4.00) = $23.58. Sales tax was not adjusted; and it should be $23.27.
I asked a similar question a day or so ago, but thought the values were correct in both payment scenarios but now see they are not.
Update:
After messing around with various options it occurred to me that I don't need to provide shipping options in the PayPal window. If the buyer wants to change the option, he'll have to cancel and change the order in the web page. That eliminates the issue of onShippingChange not firing. Not ideal; but good enough for our little case, especially since few buyers will be changing their shipping option in PayPal, for they see all that cost info. before clicking a payment button.
paypal.Buttons({
// Sets up the transaction when a payment button is clicked
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
reference_id: "HardCopy_0001",
amount: {
value: order.discSub.toFixed(2)
},
shipping: {
options: [
{
id: "STD",
label: "USPS Standard",
type: "SHIPPING",
selected: order.el_shipOptsInp[0].checked,
amount: {
value: order.shipStdVal.toFixed(2),
currency_code: "USD"
}
},
{
id: "PRI",
label: "USPS Priority",
type: "SHIPPING",
selected: order.el_shipOptsInp[1].checked,
amount: {
value: order.shipPriVal.toFixed(2),
currency_code: "USD"
}
}
]
}
}],
});
},
// Finalize the transaction after payer approval
onApprove: function(data, actions) {
return actions.order.capture().then(function(orderData) {
/* ... */
});
},
onShippingChange: function (data, actions) {
try {
// Reject non-US addresses
if (data.shipping_address.country_code !== 'US') {
return actions.reject();
}
const taxRate = data.shipping_address.state === 'PA' ? order.taxRate : 0,
shippingAmount = parseFloat(data.selected_shipping_option.amount.value),
tax_total = taxRate === 0 ? 0 : Math.round( (order.discSub + shippingAmount) * order.taxRate * 100)/100,
oTotal = order.discSub + shippingAmount + tax_total;
if ( data.selected_shipping_option.id === "STD" ) {
if ( !order.el_shipOptsInp[0].checked ) {
order.el_shipOptsInp[0].checked = true;
order.change();
}
} else if ( !order.el_shipOptsInp[1].checked ) {
order.el_shipOptsInp[1].checked = true;
order.change();
}
return actions.order.patch([{
op: "replace",
//path: "/purchase_units/#reference_id==\'default\'/amount",
path: "/purchase_units/#reference_id=='HardCopy_0001'/amount",
value: {
value: oTotal.toFixed(2),
currency_code: "USD",
breakdown: {
item_total: {
currency_code: "USD",
value: order.discSub.toFixed(2)
},
shipping: {
currency_code: "USD",
value: shippingAmount.toFixed(2)
},
tax_total: {
currency_code: "USD",
value: tax_total.toFixed(2)
}
}
}
}
]);
} catch (err) {
console.log("We apologize. There was an error in the onShippingChange event. Reason is provided below.");
console.log(err);
order.error();
return actions.reject();
}
},
onError: function(err) {
console.log(err);
// For example, redirect to a specific error page
//window.location.href = "/your-error-page-here";
order.error();
}
}).render('#paypal-button-container');

Related

How to remove shipping from the PayPal subscribe button and default other values?

I'm creating a subscription button using the JavaScript SDK from PayPal. Here's the basic code snippet I'm following:
paypal.Buttons({
createSubscription: function(data, actions) {
return actions.subscription.create({
'plan_id': 'P-2UF78835G6983425GLSM44MA'
});
},
onApprove: function(data, actions) {
alert('You have successfully created subscription ' + data.subscriptionID);
}
}).render('#paypal-button-container');
When a user selects Credit Card (non PayPal account option),the next PayPal popup Window has a long form, collecting Credit Card, Billing Address, Shipping Address, Phone Number, and Email. For my needs, I don't need a shipping address and I'd like to be able to default things like Billing Address, Phone, and Email.
The PayPal SDK documentation is large but somehow lacking critical details around this library. My questions are:
How can I exclude shipping address collection from this form?
How can I default the other info I have already collected from the user (phone, email, etc)?
Thanks to Preston PHX, I was able to get the collection of shipping info removed from the mile long form, but for some reason, my subscriber information is not be pre-filled into the PayPal popup window.
Here's my update code section:
createSubscription: function (data, actions) {
return actions.subscription.create({
/* Creates the subscription */
plan_id: 'P-2UF78835G6983425GLSM44MA',
subscriber: {
name: {
given_name: "FirstName",
surname: "LastName",
},
email_address: "test#example.com",
phone: {
phone_type: "MOBILE",
phone_number: {
national_number: "2145551212",
}
},
address: {
address_line_1: "123 Main Street",
address_line_2: "Suite 101",
admin_area_1: "Addison",
admin_area_2: "TX",
postal_code: "75001",
country_code: "US"
}
},
application_context: {
shipping_preference: "NO_SHIPPING"
}
});
},
However, when the popup is rendered, here's what I see:
Note, the shipping elements are no longer rendering, but the form is not getting prefilled.
It would seem that I'm close to doing this right because if I put an phone number in that is not a well-formed phone number, the API spits out errors about the number not being valid.
All available parameters are the same as the create subscription API operation.
Set application_context.shipping_preference to NO_SHIPPING
Phone, email, and other information can be set in the subscriber object.

How to test failed transactions with the PayPal JS SDK

We are using the PayPal JS SDK ^5.1.0 and I'm using it to generate a pay button like described in the docs:
const paypal = await loadScript({
"client-id": conf.client_id,
"currency": conf.currency_code
});
await paypal.Buttons({
// Sets up the transaction when a payment button is clicked
createOrder: (coData, actions) => {
if (price.getTotal()) {
data.data.amount = price.getTotal();
return actions.order.create({
purchase_units: [{
description: config.registrationCenterDisplayName,
amount: {
value: price.getTotal().toFixed(2) // Can also reference a variable or function
},
}],
application_context: {
shipping_preference: 'NO_SHIPPING'
}
});
} else {
throw new Error('Amount can not be 0');
}
},
// Finalize the transaction after payer approval
onApprove: (oaData, actions) => {
return actions.order.capture().then(function(orderData) {
// Successful capture! For dev/demo purposes:
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
const transaction = orderData.purchase_units[0].payments.captures[0];
data.data.id = transaction.id;
data.data.status = transaction.status;
dataValid = true;
submit();
});
}
}).render(buttonWrapper[0]);
It seems not to work well with error case i live. How can I provoke failed transactions or capture erros in sandbox mode?
I found the negative testing mode of a sandbox, but it does not change the behaviour for the button. I still get only positive responses.
I opened also an issue in the github repo: https://github.com/paypal/paypal-js/issues/273
Thx a lot for any help! I might overlook something very obvious...
For client-side captures, actions.order.capture().then( is only triggered when a capture is successful.
Failure cases are handled by the JS SDK itself, there is no need to test anything as it is not your code.
For server-side capturing (not your question) and using the JS SDK for approval, see the demo code here. Negative testing can be used from server API calls for failures if desired.

How to customise the amount of a Paypal subscription made using JS SDK

I'm trying to upgrade a legacy system that uses Paypal for subscriptions. The older way redirects to Paypal (Checkout?) and the new way is trying to use their JavaScript SDK:
https://developer.paypal.com/sdk/js/reference/
return actions.subscription.create({
'plan_id': 'P-2UF78835G6983425GLSM44MA'
});
The older system created Payment Plans on the fly, and so the payer was able to adjust the amount rather than sticking with the amount stored in a pre-made plan. I hope to replicate this behaviour using the JS sdk.
The above pages links to here: https://developer.paypal.com/api/subscriptions/v1/#subscriptions_create "for allowed options defined in the request body."
One of the parameters within the subscription creation is:
plan [object]
"An inline plan object to customise the subscription. You can override plan level default attributes by providing customised values for the subscription in this object."
This seems like the thing I am after but I am unable to get Papal to use anything I put inside there, ie:
plan_id: plan_id,
custom_id: "Does this thing work?",
plan_overridden: true,
plan: {name: "Testing ABC123 Special"}
"Does this thing work?" will carry but nothing within the plan.
One other thing of note is that the older system didn't pass a plan_id, which doesn't seem possible with the JS SDK.
I think the older way is using Checkout, are these two methods completely imcompatible?
Thank you so much!
name cannot be overridden.
Keys that can be overridden are documented in the plan_override object.
If you need to customize more plan details, create the subscription on the server side and return its id to the calling JS (within that server route, the plan_id can first be created on the fly if one doesn't already exist for the name you need)
<script src="https://www.paypal.com/sdk/js?client-id=XXXXXXXXXXXXX&vault=true&intent=subscription&currency=USD"></script>
<div id="paypal-button-container"></div>
<script>
paypal.Buttons({
style: {
label:'subscribe' //Optional text in button
},
createSubscription: function(data, actions) {
return fetch('/path/on/your/server/paypal/subscription/create/', {
method: 'post'
}).then(function(res) {
return res.json();
}).then(function(serverData) {
console.log(serverData);
return serverData.id;
});
},
onApprove: function(data, actions) {
/* Optional: At this point, notify your server of the activated subscription...
fetch('/path/on/your/server/paypal/subscription/activated/' + data.subscriptionID , {
method: 'post'
}).then(function(res) {
return res.json();
}).then(function(serverData) {
//
});
*/
//You could additionally subscribe to a webhook for the BILLING.SUBSCRIPTION.ACTIVATED event (just in case), as well as other future subscription events
//Ref: https://developer.paypal.com/docs/api-basics/notifications/webhooks/event-names/#subscriptions
// Show a message to the buyer, or redirect to a success page
alert('You successfully subscribed! ' + data.subscriptionID);
}
}).render('#paypal-button-container');
</script>
My answer was found on this page:
https://developer.paypal.com/docs/multiparty/subscriptions/customize/bill/
billing_cycles: [
{
sequence: 1,
total_cycles: 0,
pricing_scheme: {
fixed_price: {
value: "8",
currency_code: "USD",
},
},
},
],
It didn't make the name change but it did make the amount change which is actually all I care about :)

PayPal Subscription Button Coupons

I am using Paypal buttons to subscribe customers to a plan.
I am trying to apply coupons on the Paypal buttons without any luck.
This is the code that generate the Paypal button on the frontend:
paypal.Buttons({
style: {
shape: 'pill',
color: 'blue',
layout: 'vertical',
label: 'subscribe'
},
createSubscription: function (data, actions) {
return actions.subscription.create({
"plan_id": "<PAYPAL_PLAN_ID>"
});
},
onApprove: function (data, actions) {
console.log('success');
}
}).render('#paypal-button-container');
On the documentation I saw that I need to define a javascript variable and I tried
var discnt=10;
And:
var discount_amount=10;
None of this variables are working.
Even if the discount will work this way, I want to give the marketing team to define Coupons on their own, and give it to the customers.
Anyone might know how to add coupons to paypal.Buttons?
In addition to the plan_id , you can pass a plan object which overrides the provided plan's details with a different amount, and with a description that reflects the text "discount" or "coupon applied" somewhere.
Parameters for a subscription create call are documented here: https://developer.paypal.com/api/subscriptions/v1/#subscriptions-create-request-body
So for instance, something like
return actions.subscription.create({
"plan_id": "<PAYPAL_PLAN_ID>",
"plan" : {
"name" : "My Service - $10 coupon",
"billing_cycles" {
/* New billing cycles object with lower amount */
}
}
}

Paypal Smart Button Check Postal Code onApprove

The problem:
I am trying to figure out how to check the if postal code (entered during the paypal smart button check-out) meets specific requirements.
I have the createOrder, and onApprove object attributes I would like to check the postal code before i capture the payment so i can reject it, and cancel the order.
paypal.Buttons(
{
createOrder: function(data, actions) {
// This function sets up the details of the transaction, including the amount and line item details.
return actions.order.create({
purchase_units:
[
{
description: "Raffle Tickets",
custom_id: $('#cartID').val(),
soft_descriptor: "soft_descriptor",
amount: {
currency_code: "CAD",
value: cartVal,
breakdown: {
item_total: {
currency_code: "CAD",
value: cartVal
}
}
},
items: itemList
}
]
});
},
onApprove: function(data, actions) {
//id like to check the postal code here, or maybe do an ajax call then on success, call the
//capture function below to finalize the payment.
// This function captures the funds from the transaction.
return actions.order.capture().then(function(details) {
// This function shows a transaction success message to your buyer.
$('#details').val(JSON.stringify(details));
$('#frmConfirm').submit();
});
}
}).render('#paypal-button-container');
return actions.order.get().then(function(data,actions) {
console.log(data);
/* if logic on data.payer.address.postal_code goes here */
actions.order.capture().then(function(details) {
$('#details').val(JSON.stringify(details));
$('#frmConfirm').submit();
}
});
You mentioned AJAX; there are server-side equivalent APIs, if you want to switch from this client-side implementation to one that uses code on the server to validate. In that case the front-end would look more like this: https://developer.paypal.com/demo/checkout/#/pattern/server , and there is a v2/orders API call to get the details of the approved order before capturing it.