PayPal payment method is not getting the updated price details which has to be passed as input parameter for creating payment.
The paypal.Button.render() method gets called initially with the input parameter - price which is 0.00. But when the price gets updated on field change, it is not taking the updated price value, and calling the payment function with the old value - 0.00.
function createPaypalPayment(price, recordId){
angularLoad.loadScript('https://www.paypalobjects.com/api/checkout.js')
.then(function() {
paypal.Button.render({
env: 'sandbox',
payment: function(data, actions) {
createPayment(price, recordId){
/*..some code goes here*/
}
.then(function(response) {
console.log(response)
})
}
});
})
}
Here the I'm using this in an AngularJS service.
How can I get the updated value of price here when I do a field change and call the createPaypalPayment(price, recordId) ?
Thanks
I would suggest you to use Paypal's Server side integration for this. That way you can easily change/update the payment price. Follow this Link.
Related
I am building an application which includes a custom donation form where users can select:
Frequency: monthly, single
Amount: 3 x Buttons with values, input for custom value
I am using the PayPal Donate SDK and cannot find a way to pass across the amount or frequency so that the amount is pre-filled and the "make this a monthly donation" checkbox selected/deselected based on the users input.
The PayPal button has been added to the page (just absolute styled over the form button) and the SDK opens the donation page in a modal which is my desired result.
This is the code to display my button:
window.PayPal.Donation.Button({
env: "sandbox",
hosted_button_id: "xxxxxxxxxxxx",
onComplete: function() {
// Redirect to success page, save the PP transaction details, etc.
}
}).render("#paypal-donate-button-container");
With the standard PayPal.Buttons API, I am able to pass in a createOrder function like so, where I can set the amount, for example;
window.PayPal.Buttons({
// ...
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: document.querySelector(".selected-premium").dataset.value
},
reference_id: document.querySelector(".selected-premium").dataset.days + "-days"
}]
});
}
}
Is there a way to pass through the amount and frequency using the SDK, similar to how I can do it with the standard payments API? If not, what are the alternatives in 2022 that don't rely on SO answers that use docs that no longer exist/deprecated methods?
With the PayPal Donations flow (this includes the Donate SDK)
You can pass a parameter for the amount and currency_code. If you do pass a prefilled amount, an option to make the donation recurring will not be available.
You cannot pre select the option to make the donation recurring in the Donations flow.
For the case of recurring donations -- since it's not possible to precheck the option to make them recurring in the Donations flow, nor is it possible to prefill the amount and have that choice of recurrence even be available -- what you could do is instead use a Subscription flow for when a recurrence is selected, and have this render in a separate div container that is shown/hidden based on your monthly vs single selection. A subscribe button for your account can be generated at: https://www.paypal.com/billing/plans . The plan it uses must have a specific amount, but this can be overridden in the JS createSubscription function (based on whatever selection or amount was entered on your site) by (in addition to the base plan_id) adding a plan object that contains a billing_cycles object with the amount/frequency you want.
I am new to PayPal integrations but I have managed to use the client-side JavaScript SDK to create a button and complete a transaction. I have also added a webhook that listens for PAYMENT.CAPTURE.* events and log the return data into my transactions table in my own database. The downside is I do not have a way of tracking for which service or customer the transaction was. So I would like to know how I can add at least one custom field in the button so that it is returned back to me in the webhook POST so that I can perform some business logic for that particular customer.
My initial alternative was to POST the data return onApprove:(data, actions)=>{} but I would have not recovery option if something catastrophic happens before that is done e.g Power outage or general Client-Server connection failure.
Here is my JS basic code for now:
try{
paypal.Buttons({
// Set up the transaction
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: charge.amount,
currency_code:'USD'
}
}]
});
},
// Finalize the transaction
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
console.log(details);
if(details.status == "COMPLETED"){
localStorage.clear();
window.location.href = "thank-you";
}
//alert('Transaction completed by ' + details.payer.name.given_name + '!');
});
}
}).render('#paypal-button-container');
}catch(e){
console.error('PayPal not loaded!');
}
Switch to a proper client-server integration.
Follow the PayPal Checkout integration guide and make 2 routes on your server, one for 'Create Order' and one for 'Capture Order' (see the optional step 5 in 'Add and modify the code'). Both of these routes should return only JSON data (no HTML or text). Inside the 2nd route, when the capture API is successful you should store its resulting payment details in your database (particularly purchase_units[0].payments.captures[0].id, which is the PayPal transaction ID) and perform any necessary business logic, such as saving additional data from form inputs.
(That data can be transfered as part of the fetch capture call by adding a body key to its options parameter, which can serialize a JSON object for you. Your 2nd server route can then parse that input as JSON and verify it is ok before proceeding with the capture.)
--
Pair those 2 routes with the frontend approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server
With the above method, you have an immediate, synchronous API response on payment capture. There is no need for an additional asynchronous notification from webhooks, so those will basically become superfluous to you.
You can switch to a client-server integration (as per #Preston PHK's answer), but for that may be overkill for some applications (and not what the OP asked for).
For a simple purchase where you just need to pass some specific information, you can try leveraging the purchase_units.invoice_id field (assuming you don't need that for something else).
The caveat is that the invoice_id needs to be unique every time you use it. This means that you can't just use your customer's internal id. You would need to add something extra to ensure uniqueness.
Your createOrder call might then look something like this:
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
invoice_id: customerId + "-" + randString,
amount: {
value: charge.amount,
currency_code:'USD'
}
}]
});
},
A 6 character random string of alphanumeric (upper and lower) characters gives over 56 billion possibilities, which for most purposes should provide sufficient uniqueness. If you want to be even safer, make it longer. I'm not sure of the exact limit on invoice_id but I think it's at least 40 characters.
When you process the transaction (from a webhook) on your server, you just throw away the extra characters.
$custId = substr($invoice_id, 0, -7);
You can add 'custom_id' field in creareOrder function like that :
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
custom_id: customValue,
amount: {
value: price
}
}]
});
},
I'm following the client side Client Side Rest API to create a donation button. The amount to charge is static in the code. I was able to pass in a variable after prompting the user to click a button to enter the amount, however, that is incredibly clunky and a default value is needed to prevent the PayPal window from closing as soon as it's opened if no values was entered.
// payment() is called when the button is clicked
payment: function(data, actions) {
// Make a call to the REST api to create the payment
return actions.payment.create({
payment: {
transactions: [
{
amount: { total: promptMe2(), currency: 'USD' }
}
]
}
});
},
You can see that I have the var donationAmount. That is set by default to a value. However, it can be changed by the user with:
<script>
var donationAmount = 16.11;
function promptMe(){
donationAmount = parseFloat(prompt("How much would you like to donate?"));
while (Number.isNaN(donationAmount)){
alert("Invalid number, try again!");
donationAmount = parseFloat(prompt("Amount to donate?"));
}
donationAmount = donationAmount.toFixed(2);
}
I tried to call promptMe() in place of the donationAmount variable (with an added return statement of donationAmount of course) and found that the page would just spam my error message for an invalid amount. When I hardcoded the value in it worked just fine. When I took the error checking out, it didn't even prompt me, it just instantly closed the paypal window. I was able to trigger an alert("Hello"); but that is pretty useless, I need to be able to accept user input and then pass it to Paypal.
I'm currently working on a PayPal Express checkout integration using the Client-side JS approach for taking payments. I'm looking to utilise their "Negative Testing" feature to try to simulate potential errors and provide appropriate responses to the customer.
Just a reference to the relevant doc page here for reference
It seems to enable negative testing you need to pass an extra header along with the the payment request specifying the particular error you would like to trigger for that payment.
This is my current JS for setting up the transaction:
<script>
//We need to convert our generated json string into a bonified javascript object
var paypal_transaction = JSON.parse(JSON.stringify(<?php echo $paypal_json; ?>));
paypal.Button.render({
env: 'sandbox', // 'production'/'sandbox',
commit: true, // Show a 'Pay Now' button - Allows us to capture the payment right away
client: {
sandbox: 'Ab_hPp7h70DoFKChLMSynNxacQQbGcb_tP1cDbzW9jC6a0rYIZH0CkEYYfkw6csvmlyTmfLnagelqB85',
production:''
},
//How we want the button to look
style: {
size: 'responsive',
color: 'gold',
shape: 'rect',
label: 'pay'
},
headers: {
'{"mock_application_codes":"INSTRUMENT_DECLINED"}'
}
payment: function(data,actions) {
return actions.payment.create({
//Pass our created transaction to paypal.
payment:paypal_transaction,
/**
* We have to set the following fields to prevent the client from
* changing their delivery address when they're at PayPal
*/
experience: {
input_fields: {
no_shipping: 0,
address_override:1
},
}
});
},
onAuthorize: function(data, actions) {
/**
* [description]
* #param payment - The authorised transaction returned from paypal
* #return redirect - We redirect the cutomer to our confirmation page as soon as we return from PayPal as we're confident we have the correct
*/
return actions.payment.execute().then(function(payment) {
actions.redirect();
});
},
onError: function(err) {
console.log(err);
// Show an error page here, when an error occurs
},
onCancel: function(data, actions) {
return actions.redirect();
// Show a cancel page or return to cart
}
}, '#paypal-button');
Essentially my question is where do I specify the mock application codes like this in the above implementation.
In the docs they give an example cURL request with the below as the extra header that needs to be passed:
"PayPal-Mock-Response:{\"mock_application_codes\":\"INSTRUMENT_DECLINED\"}"
I just don't know how to do this via the JS approach. Can negative testing only be used with a server side implementation?
Hope that's all clear enough!
Had similar issue myself, and the official answer I got was that it is not available:
"I understand this is a frustrating situation. Unfortunately we do not
have any way to offer negative testing for client side integrations.
It may possible for you to do so using Postman, however, we do not
have documentation to offer."
This is really sad though, other payment providers have fixed card numbers for certain error scenarios for example, or have special payment value based codes. PayPal only has that for the Payflow integration, and the request header based mocking stuff is also only possible if you are directly calling their REST APIs.
PayPal is really lame in these aspects, as even if you are mocking behavior with server integration (not that hard, for this at least they have proper code examples), this mocking is explicit and you control the error. If it would be implicit, and originate from an actually validated but invalid card for example, it would be more realistic.
We're currently struggling with Braintree PayPal payment in combination with regular bank transfer via IBAN. Basically, we present two subscription options to the visitor: PayPal (via Braintree) and IBAN transaction.
The PayPal method works fine but when we don't select PayPal but IBAN bank transfer, we're getting the following console error:
We understand that this is the correct behaviour since the PayPal fields are not filled, but how is it possible to have PayPal as an optional payment method without throwing an error when the fields are not filled?
We're using the basic js implemetion via DropUI.
<div class="bt-drop-in-wrapper" id="showpaypalfields">
<div id="bt-dropin" class="paypaldiv"></div>
</div>
<script src="https://js.braintreegateway.com/js/braintree-2.27.0.min.js"></script>
<script>
var client_token = "123TOKEN";
braintree.setup(client_token, "dropin", {
container: "bt-dropin"
});
</script>
UPDATE:
Both forms are visible on the page instantly, they are not loaded afterwards via Ajax or any kind. So, the PayPal option via Braintree should only validate if for example a checkbox is set. For example, the checkbox given in the screenshot below (toggles visibility of both fieldsets).
UPDATE #2:
For anyone interested in the final solution:
var btInstance;
$('input#paymentmethod-1').change(function(){
if ( $(this).is(':checked') == true ) {
teardown();
}
});
$('input#paymentmethod-2').change(function(){
if ( $(this).is(':checked') == true ) {
setup();
}
});
function setup() {
if (btInstance) {
return;
} else {
var client_token = "<ps:braintreetoken />";
braintree.setup(client_token, "dropin", {
container: "bt-dropin",
onReady: function (bt) {
btInstance = bt;
}
});
}
}
function teardown() {
if (!btInstance) {
return;
}
btInstance.teardown(function () {
btInstance = null;
});
}
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact support.
Drop-in UI is still loaded when you select the Lastschrift payment option, which is why you're receiving the validation errors.
One way to avoid these validation errors is to use the 'teardown' method in the 'onReady' callback in braintree.js to remove the Drop-in UI if a customer selects Lastschrift.
Alternatively, you can separate each of these payment methods into entirely different form elements on your page.