Using Negative Testing via Paypal Express Checkout client-side JS button implementation - javascript

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.

Related

Card payments with the Payment Request API not working

I am looking into the Payment Request API as a way to streamline payments on our site checkout and have run into a bit of a brick wall on how to use it with card payments. There are a few demos explaining how to use the payment request API, this one seemed to be the simplest and easiest to follow, but it is also quite out of date (as a lot of PR API demos i've found seem to be). Here is my code than runs when the payment button is clicked:
const paymentMethods = [
{
supportedMethods: ['basic-card', 'visa', 'mastercard', 'amex'],
data: {
supportedNetworks: ['visa', 'mastercard', 'amex']
}
}
];
const details = { total: { label: 'Test payment', amount: { currency: 'GBP', value: '1.00' } } };
// Show a native Payment Request UI and handle the result
const request = new PaymentRequest(paymentMethods, details);
request.show()
.then(response => {
console.log('yep');
console.log(response);
})
.catch(err => {
console.error(err);
});
I get the following error in the console:
Uncaught RangeError: Failed to construct 'PaymentRequest': Invalid payment method identifier format
I did some digging and found that the 'basic-card' supportedMethod is deprecated, and simple references to credit card providers don't seem to be supported anymore. The MDN docs for supportedMethods actually state
Starting with more recent browsers, this parameter is more generic than credit cards, it is a single string, and the meaning of the data parameter changes with the supportedMethods. For example, the Example Pay payment method is selected by specifying the string https://example.com/pay here.
Which is all well and good, but if i want to support Visa or Mastercard credit and debit card payments what URL do i use for them under supported methods? The payment processor for our website is Worldpay, so is this something i'd ask them for? Or do i need to find individual urls for Visa and Mastercard? I have done some googling and can't find anything relevant for either of them. Maybe i'm just bad at googling, but i don't really know how to proceed with this and wondered if anyone else had experience in setting up the payment request API to use credit and debit cards.
Oh, and i'm testing this in Chrome 103 btw.
Thanks

nodeJS Paypal REST API to revise plan give broken but working link

I'm trying to integrate Paypal into my app in NodeJS. I'm using REST API since the official npm packet is deprecated.
Let's suppose I have a product with two plans, planA and planB, necessary for recurring payments like subscriptions. Suppose a customer subscribe to planA, which costs 10$. After a while, he wants to switch to planB, which costs 20$, to unlock premium content in the platform.
I found the API: POST/v1/billing/subscriptions/{id}/revise
with which one should be able to send the planID planB to switch to it. You can also send effective_time field to specify when the change is effective. After calling this API, Paypal reply with 6 links, and I use the first (approve) to redirect the customer to Paypal domain to confirm it's will to switch the plan. After the user login, confirm and click "Accept and subscribe" to the new plan, the page always give me the following error: Things don't appear to be working at the moment. Please try again later.
, despite the plan change goes fine (I can verify it through dashboard).
I'm wondering what can I do to avoid that error.
I want to clarify that in the settings, through the dashboard, under Account settings -> Website payments -> Website preferences, I temporarily have the option Block non-encrypted website payment to Off.
Thank you all in advance!
The setting "Block non-encrypted website payment" is not relevant to this issue, and will have no effect. It applies exclusively to legacy HTML-only payments, which you should not concern yourself with.
Edit: ah yes, a redirect integration requires an application_context with a return_url. For usage with the SDK, no redirect_url is used, hence why the field is not required by the API.
Previous answer follows:
The issue you describe seems to be a problem with the PayPal site, and possibly only occurs in sandbox mode or with certain browsers/cookies. You can test as desired and contact PayPal's support if needed.
It is also possible to do a revise with the JS SDK rather than a redirect. For a client-side-only integration (no API), this can be done using actions.subscription.revise. Search for that text within the SDK reference.
To combine the JS SDK with the API call you are using, have your button code fetch the revised subscription ID from your server. Here is a sample for a create, which you can adapt to be a revise as it's essentially the same thing (you'd just likely be using a /revise endpoint /path/on/your/server)
<script src="https://www.paypal.com/sdk/js?client-id=..........&vault=true&intent=subscription"></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>

How to Send Custom data fields in PayPal Smart Button transaction

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
}
}]
});
},

Automatically assign a customer to a specific customer group on sign-up - Bigcommerce

I've been told by BC support that this isn't possible, but I would be surprised if there really wasn't a way.
I need to be able to automatically assign a customer to a specific customer group when they create an account. My thought:
I would add an extra field to the sign-up form
Provide a user with a code (a string or number)
User enters code when creating new account
User hits submit
On form submit I would grab the value of the extra field:
var codeInput = document.getElementById('code-input').value;
I would then compare that value to a pre-defined string, and if there is a match, I would assign that customer to groupX (with a group id of 8):
if ( codeInput === "codeIGaveToTheUser" ) {
currentUserGroupID = 8;
}
Is it possible to assign a customer to a specific group on sign-up like this (or any other way)?
Any help is much appreciated.
Although using BigCommerce webhooks would ensure the highest success rate of executing your customer group assignment app, it requires quite a bit of setup on BigCommerce (creating a draft app, getting an oAuth key, jumping jacks, etc), and may be a bit of overkill for your requirements.
Here's an easier way, in my {mostly} humble opinion, that takes advantage of much of what you included in your original question. Any solution though will nonetheless require an external server to handle the customer group assignment through the BigCommerce API.
Within the BigCommerce control panel, add in the extra field to the user sign up form like you mentioned.
So as you can see, this new input field has been added natively to the default registration page:
So now, when a user creates an account on your site, the value for the Signup Code (the custom field created) will be directly accessible through the API for that customer's account. Take a look at what that JSON data looks like:
Okay, so this is nice and all, but how do we automate it?
To do so, we will have to let our external application know that a customer just registered. Furthermore, our external application will need some sort of reference to this newly created customer, so that it knows which customer to update the customer group for. Normally a BigCommerce webhook would notify us of all this, but since we aren't using a BigCommerce webhook, here's the alternative method to triggering the external script.
We will trigger our external application via the BigCommerce Registration Confirmation page - createaccount_thanks.html. This page is loaded immediately after a customer creates an account, so it is the perfect place to insert our trigger script.
Additionally, now that the customer is logged in, we can access the customer's email address via a BigCommerce Global system variable -%%GLOBAL_CurrentCustomerEmail%%.
We should make an HTTP request from this page to our external application along with the customer's email address. Specifically, we can make an XMLHttpRequest via JavaScript, or to be modern, we'll use Ajax via jQuery. This script should be inserted before the closing </body> tag on createaccount_thanks.html.
Example of POST request (although a GET would suffice as well):
<script>
$(function() {
$('.TitleHeading').text('One moment, we are finalizing your account. Please wait.').next().hide(); // Let the customer know they should wait a second before leaving this page.
//** Configure and Execute the HTTP POST Request! **//
$.ajax({
url: 'the_url_to_your_script.com/script.php',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({email:"%%GLOBAL_CurrentCustomerEmail%%"}),
success: function() {
// If the customer group assignment goes well, display page and proceed normally. This callback is only called if your script returns a 200 status code.
$('.TitleHeading').text('%%LNG_CreateAccountThanks%%').next().show();
},
error: function() {
// If the customer group assignment failed, you might want to tell your customer to contact you. This callback is called if your script returns any status except 200.
$('.TitleHeading').text('There was a problem creating your account').after('Please contact us at +1-123-456-7890 so that we can look into the matter. Please feel free to continue shopping in the meantime.');
}
});
});
</script>
Now finally, you just need to create your serverside application responsible for handling the request above, and updating the customer's customer group. You can use any language that you desire, and BigCommerce even offers several SDK's you can use to save mega development time. Just remember that you need to host it somewhere online, and then insert its URL to the JS script above.
PHP Example (quick & dirty):
git clone https://github.com/bigcommerce/bigcommerce-api-php.git
curl -sS https://getcomposer.org/installer | php && php composer.phar install
<?php
/**
* StackOverflow/BigCommerce :: Set Customer Group Example
* http://stackoverflow.com/questions/37201106/
*
* Automatically assigning a customer group.
*/
//--------------MAIN------------------------//
// Load Dependencies:
require ('bigcommerce-api-php/vendor/autoload.php');
use Bigcommerce\Api\Client as bc;
// Define BigCommerce API Credentials:
define('BC_PATH', 'https://store-abc123.mybigcommerce.com');
define('BC_USER', 'user');
define('BC_PASS', 'token');
// Load & Parse the Email From the Request Body;
$email = json_decode(file_get_contents('php://input'))->email;
// Execute Script if API Connection Good & Email Set:
if ($email && setConnection()) {
$customer = bc::getCollection('/customers?email=' .$email)[0]; //Load customer by email
$cgid = determineCustomerGroup($customer->form_fields[0]->value); //Determine the relevant customer group ID, via your own set string comparisons.
bc::updateCustomer($customer->id, array('customer_group_id' => $cgid)) ? http_send_status(200) : http_send_status(500); //Update the customer group.
} else {
http_send_status(500);
exit;
}
//-------------------------------------------------//
/**
* Sets & tests the API connection.
* #return bool true if the connection successful.
*/
function setConnection() {
try {
bc::configure(array(
'store_url' => BC_PATH,
'username' => BC_USER,
'api_key' => BC_PASS
));
} catch (Exception $e) {
return false;
}
return bc::getResource('/time') ? true : false; //Test Connection
}
/**
* Hard define the customer group & signup code associations here.
* #param string The code user used at signup.
* #return int The associated customergroup ID.
*/
function determineCustomerGroup($signupCode) {
switch ($signupCode) {
case 'test123':
return 1;
case 'codeIGaveToTheUser':
return 8;
default:
return 0;
}
}
So then you would do your customer group string comparisons directly in the serverside program. I'd recommend you rewrite your own BC API script as the one above in quality is really something along the lines of functional pseudo-code, but more so present to show the general idea. HTH
You would need to set up a server to listen for webhooks unless you wanted to do a cron job. We have some basic information on the developer portal, but I included more resources below. From there, you'd need to choose your server language of choice to listen for the webhooks once they been created, respond correctly (200 response if received), execute code based on this information, and then take action against the BC API.
So if you were looking for a code, you'd need to listen for the store/customer/created webhook, and have your code look for a custom field that contained the code. If it was present, then take action. Else, do nothing.
https://developer.github.com/webhooks/configuring/
http://coconut.co/how-to-create-webhooks
How do I receive Github Webhooks in Python

Paypal Embedded Flow not using returnUrl or cancelUrl

I am using Paypals Adaptive Payments and Embedded flow feature to provide checkout via a minibrowser. Everything seems to be working correctly in the sandbox environment except that when the payment is completed successfully, the user is never redirected to my returnUrl set in the PAY API request. Same goes for my cancelUrl.
After the payment is complete, the user is shown an order overview in the minibrowser and a button labelled "close". If a user clicks this button, the minibrowser is closed.
If a user clicks cancel at any time, the minibrowser is closed.
There doesn't seem to be a way to have my page aware of the change besides setting up some polling or something which doesn't make sense, my returnUrl and cancelUrl should be used somewhere, right?
this is my code to get the redirect url (using adaptive payments gem):
pay_request = PaypalAdaptive::Request.new
data = {
'requestEnvelope' => {'errorLanguage' => 'en_US'},
'currencyCode' => 'USD',
'receiverList' =>
{ 'receiver' => [
{'email' => '...', 'amount'=> 10.00}
]},
'actionType' => 'PAY',
'returnUrl' => 'http://www.example.com/paid',
'cancelUrl' => 'http://www.example.com/cancelled',
'ipnNotificationUrl' => 'http://www.example.com/ipn'
}
pay_response = pay_request.pay(data)
redirect_to pay_response.approve_paypal_payment_url "mini"
And here is how I am setting up the paypal js:
var dg = new PAYPAL.apps.DGFlowMini({ trigger: "buyit", expType: "mini" });
It all seems pretty straight forward, not sure what I am missing.
Well - seems to be a bug on our side - just tried it myself and confirmed with our integration teams. :-(
Unfortunately the other short term fix I can think of other than what you've mentioned (checking for the existence of the popup window) is to call the PaymentDetails API from your server side to check the status of the Payment. I've opened the bug on our side but don't have an ETA.
Edit 10/18: Sorry I'm wrong. This is working - it's just that our developer guide is not providing all the required information. In case of the mini-browser flow, you would need to provide a 'callbackFunction' and also name your dgFlow variable as 'dgFlowMini'. (the latter is important - as apdg.js is expecting the 'dgFlowMini' variable to be defined) Here is the code that works:
var returnFromPayPal = function(){
alert("Returned from PayPal");
// Here you would need to pass on the payKey to your server side handle to call the PaymentDetails API to make sure Payment has been successful or not
// based on the payment status- redirect to your success or cancel/failed urls
}
var dgFlowMini = new PAYPAL.apps.DGFlowMini({trigger: 'em_authz_button', expType: 'mini', callbackFunction: 'returnFromPayPal'});
I have a working sample here: https://pp-ap-sample.appspot.com/adaptivesample?action=pay (make sure you select mini as the Experience Type)
We will get our docs updated and also cleanup apdg.js to remove the dependency on the JS variable name.
Looks like the PayPal experience for embedded flows has gotten worse. Now you'll receive an error message after invoking mini or lightbox that says "Payment can't be completed. This feature is currently unavailable."

Categories