Paypal subscription confusion - javascript

I've been working on a project for over 2 years and I am finally ready to launch, but first I have to integrate a subscription based payment option so I can actually make some money off of this thing. I've been trying to integrate paypal subscriptions for like 2 months now and it's a major hold up. Also, this is causing me to go bald. Please help!
I think it would be really helpful to have a kind of overview explanation describing the definate process that I need to follow in order to accept subscription based payments. The level of detail would include where each of the steps should occure; frontend or backend (server), and any intermediate steps necessary to understand what data is flowing where. Second to that, the actual code for the smart button with some comments indicating what part of the process the code is addressing. Maybe that's a lot to ask, but it would be greatly appreciated and I believe a great resource for others looking to do the same as I am currently.
At the moment, my primary issue is that when I set the URL pointing to the paypal SDK in my script take to include &intent=authorize, I am told in the error message that I need to set intent=capture, but when I set intent=capture I'm told I need to set intent=authorize. So now I'm confused as to what I am supposed to do; authorize the transaction or capture the transaction. I've been provided links to 2 different guides on the paypal developer website from paypal technical support which seem to contradict each other - the first link said nothing about capture or authorizing payments, the 2nd link does. But I don't understand the context on the second link. The first link is all client side, the second link is on client side and server side. Why would these intent=ca[ture/authorize be needed? I thought that once someone agrees to and completes signing up for a subscription, and I've captured their subscription id, that I don't need to do anything else in order to receive funds on the monthly basis setup in my plan, I would only have to query the paypal APIs to find out if they've paid up upon the customer signing in to my service.
I have setup a sandbox account and I've created a product and a plan. I've got the smart button rendering with my plan ID after the customer logs in.
If I set intent=capture in the paypal SDK script URL, the paypal window opens, you select payment and agree, and then I get this error in my console:
env: "sandbox"
err: "Error: Use intent=authorize to use client-side authorize"
referer: "localhost:88"
timestamp: "1589939180937"
uid: "fad5852fa3_mde6ndq6mdu"
But if I set intent=authorize, I click the smart button, the paypal window shows up and disappears quickly and then I get this error:
buttonSessionID: "e3bf3c6c3d_mdi6mdi6mzq"
env: "sandbox"
err: "Uncaught Error: Expected intent from order api call to be authorize, got capture. Please ensure you are passing intent=capture to the sdk url"
referer: "www.sandbox.paypal.com"
sessionID: "fad5852fa3_mde6ndq6mdu"
timestamp: "1589940160835"
Here is my code:
<!DOCTYPE html>
<head>
<!-- Add meta tags for mobile and IE -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
</head>
<body>
<!-- Set up a container element for the button -->
<div id="paypal-button-container"></div>
<!-- Include the PayPal JavaScript SDK -->
<script
src="https://www.paypal.com/sdk/js?client-id=CLIENT-ID-HERE&currency=USD&vault=true&intent=capture"></script>
<script>
let planid = 'P-48A5110983270751ML2P5NVI';
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
// Set up the transaction
createSubscription: function (data, actions) {
// Create Subscription
return actions.subscription.create({ "plan_id": planid });
},
onApprove: function (data, actions) {
// Authorize the transaction
actions.order.authorize().then(function (authorization) {
// Get the authorization id
var authorizationID = authorization.purchase_units[0]
.payments.authorizations[0].id
// Call your server to validate and capture the transaction
return fetch('/api/company/paypal-transaction-complete', {
method: 'post',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
orderID: data.orderID,
authorizationID: authorizationID,
data: data,
authorization: authorization
})
});
});
}
// Finalize the transaction? Which one do I want, to authorize or to finalize??
// onApprove: function (data, actions) {
// let result = actions.subscription.get();
// return actions.order.capture().then(function(details) {
// // Do I need to send something to my server here?
// // Show a success message to the buyer
// alert('Transaction completed by ' + details.payer.name.given_name + '!');
// });
// }
}).render('#paypal-button-container');
</script>
</body>
Thanks in advance for your help. This has been a most frustrating project.

Why are you using intent=authorize / intent=capture in the URL with subscriptions?
Why are you using actions.order.authorize() with subscriptions?
Who told you to do either of these things with subscriptions?
Please see the Subscriptions Integration guide, which does not include any mention of those things.

Related

Stripe Checkout Subscription Payment failure redirects to Expire Link Page

I'm trying to integrate Stripe One time and Subscription Payment using their Checkout API.
I have also enabled 3D secure payments.
In one time payment when payment is successful it redirects to our success page. And when a payment is failed, it shows error message in checkout form. Which is as expected.
In Checkout subscription, when payment is successfully it perfectly redirects to success page. For failed payment it also shows error messages after 3D authentication, but when I try to pay with another card or the same card after a failed attempt, it redirects me to Expired link page.
I checked in stripe demo checkout page (https://checkout.stripe.dev/preview) where it works fine but don't know what I'm missing.
What I understand from the stripe docs is that, for failed payment I should handle error and tell users/redirects to use a different payment method.
I have registered the following webhook events:
invoice.payment_action_required
charge.failed
customer.subscription.deleted
customer.subscription.created
checkout.session.completed
invoice.paid
When invoice.payment_action_required event is triggered, I have to manually confirm the payment for 3D secure authentication (it's required for subscription). I have written the following code to confirm the payment.
if (paymentIntent.status === 'requires_action') {
await stripe.paymentIntents.confirm(payment_intent);
}
Here is the code snippet to create a subscription session:
const session = await stripePrivate.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
line_items: [
{
price: planId,
quantity: 1
}
],
metadata: {
transactionId
},
subscription_data: {
metadata: {
transactionId
}
},
success_url: `${merchantCallbackURL}?sessionId={CHECKOUT_SESSION_ID}&status=success&orderId=${orderId}`,
cancel_url: `${merchantCallbackURL}?sessionId={CHECKOUT_SESSION_ID}&status=canceled&orderId=${orderId}`
});
const callbackUrl = session.url;
And below is the page, I'm redirected to if a subscription payment is failed (from 2nd attempt):
Here I'm adding some checkout URL for testing:
Test card 1: 4000008260003178 (insufficient balance)
Test card 2: 4000002500003155
https://checkout.stripe.com/pay/cs_test_a1SAsf7YCjXOZPNKf0K9AXNDHSm8lMLFwD80VZEajKxEAgpeD9GiZBH2wr#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
https://checkout.stripe.com/pay/cs_test_a1RTXGddpYeZy0zRfvuJrGWqtT3KiURrJFCjSDS9fK8OIhdmTPFBD0Mzx8#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
https://checkout.stripe.com/pay/cs_test_a1ZMzrim1XQWNVgHCceiSw9mjrtMMdTricwdGhzf7wdHYcEsSabFTxRGcv#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
https://checkout.stripe.com/pay/cs_test_a1iheiAZbEXl3hhuVPBSNARja4XkYL2su4bt0JRqlNQMaVnd4V2Hg5BEWD#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
https://checkout.stripe.com/pay/cs_test_a1iheiAZbEXl3hhuVPBSNARja4XkYL2su4bt0JRqlNQMaVnd4V2Hg5BEWD#fidkdWxOYHwnPyd1blpxYHZxWjA0TGF0TDFDQWA3QFN8cm9dRjBzRk1tVHxEfHBHbnBJf280clBGblNsX0NSMGE2bGY1QFdsYktWaUY2M0JTN1dATERKaEBXcExGf1VBTzxKdz1%2FM3RmSjVxNTVJf1dTY3NmRycpJ2N3amhWYHdzYHcnP3F3cGApJ2lkfGpwcVF8dWAnPyd2bGtiaWBabHFgaCcpJ2BrZGdpYFVpZGZgbWppYWB3dic%2FcXdwYHgl
Above URL will be expired within 24 hours. Please ask if you need
another active url for testing.
How to test?
Use the given test card 1 and complete 3D authentication
Now use test card 1 or 2 and try to subscribe again, you'll be redirected to Expired link page
How do I fix this issue? What am I missing here?
Checkout pages are single use. If you use one to create a subscription, it is then "consumed" and can't be used again.
It sounds like you're trying to deal with the case where a subscription is created, but a future invoice's payment fails due to the payment being in the requires_action status. This will happen if the card in question requires 3DS and must be confirmed on the client by the user. See the PaymentIntents flow: https://stripe.com/docs/payments/intents
You wouldn't redirect to the same Checkout session to action a card in the requires_action status, instead you'd either build your own UI or use the Customer portal: https://stripe.com/docs/billing/subscriptions/customer-portal.

Paypal Smart Payment Buttons — how to create order server-side and pass order ID to Paypal Buttons setup?

I'm integrating Paypal Checkout and following in their documentation for how to set up the client SDK here.
I would like to not set up the order items and sum on the client side, but instead use the server side setup for orders mentioned at the end of step 4. Using their PHP SDK I can manage to setup the order no problem, but where I am struggling to get things to work is how to pass this created order to the client. The return object for the created order has the ID and URLs to initiate capture it, but following the links actually provides a different UX from clicking the Paypal Button, so I would like to use the Button still, but with the created order.
Using their example code:
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: [{
amount: {
value: '0.01'
}
}]
});
},
onApprove: function(data, actions) {
// 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.
alert('Transaction completed by ' + details.payer.name.given_name);
});
}
}).render('#paypal-button-container');
//This function displays Smart Payment Buttons on your web page.
Essentially I have an order ID from my server side call and would not need to use the "createOrder" call, but how can I pass my order ID to the paypal.Buttons() setup so that when the user clicks on the rendered Paypal Button they are sent to approve the order I created?
P.S.: I get that you verify and capture the order after, but why would anybody ever want to set up the order charge in their client side Javascript? To me that just seems like a fundamentally wrong idea. Am I missing something here?
The solution was fairly obvious, but somehow I didn't stumble upon it when looking earlier. createOrder needs to return the order ID, this can be done as documented:
method: 'post',
headers: {
'content-type': 'application/json'
}
}).then(function(res) {
return res.json();
}).then(function(data) {
return data.orderID; // Use the same key name for order ID on the client and server
});

Firebase FCM error: 'InvalidRegistration'

I am currently trying to send a PushNotification to a Device Group using FCM with the help of Firebase Cloud Functions but once the notification is sent, it returns with code 200 but with failure :
SUCCESS response= {
multicast_id: 8834986220110966000,
success: 0,
failure: 1,
canonical_ids: 0,
results: [ { error: 'InvalidRegistration' } ]
}
Here is the code I am using to send this notification... what am I missing?
const options = {
method: 'POST',
uri: 'https://fcm.googleapis.com/fcm/send',
headers: {
'Authorization': 'key=' + serverKey,
},
body: {
to: groupId,
data: {
subject: message
},
notification: {
title: title,
body: body,
badge: 1,
},
content_available: true
},
json: true
};
return rqstProm(options)
.then((parsedBody) => {
console.log('SUCCESS response=', parsedBody);
})
.catch((err) => {
console.log('FAILED err=', err);
});
Where JSON values title, body, subject, message are String
In my case, I was sending notifications to topic ("topics/my-topic"). I was missing prepending / in the starting of topic so I was getting the same issue. SO topic should be /topics/my-topic.
May be this helps!!
There is an easier way to send a message to a device group from a Cloud Function. Use admin.messaging().sendToDeviceGroup(). Sample code and instructions are in this guide.
I think your current method is failing because there is something wrong with the group notification key provided in groupId. It should be the string key value that was returned when you created the device group. The error codes are listed in this table. For 200/InvalidRegistration it says:
Check the format of the registration token you pass to the server.
Make sure it matches the registration token the client app receives
from registering with Firebase Notifications. Do not truncate or add
additional characters.
I was losing my mind with this InvalidRegistration error.
Eventually the problem was that I was subscribing my device to "example" but sending the notification json to: "example".
But we actually need to send to "/topics/example"
2 hours of my life wasted..
A registration token is tied to a certain group of senders. When a client app registers for FCM, it must specify which senders are allowed to send messages. You should use one of those sender IDs when sending messages to the client app.
Al you need to do is add a http header 'project_id' with your sender id.
I was getting InvalidRegistration:
Basic meaning: you are using the wrong token. Why? This may happen when you a new registrationToken is given to you in onNewToken (docs), but for some reason you are using the old token. That could happen when:
You're using a different push notification library which remembers token (stores it somewhere locally) and you didn't update that library with the new token.
Your application (or other library dependencies) implements another FirebaseMessagingService, and they conflict. Only one service can accept (react to) to the action sent by the FirebaseMessaging Android library's when a new token is given to it. You can double check this by opening the AndroidManifest.xml in Android Studio and selecting the Merged Manifest tab at the bottom of the tab. You can also place debuggers in each Service from each library you use. You'll see that only one service's onNewToken gets called.
When they conflict, one doesn't get the correct token, and the FCM registration token that gets registered would be wrong. Sending a message to a wrong registration, gets you InvalidRegistration.
for me, it was a mistake that I was passing an Id from my models instead of the tokens of the users
InvalidRegistration simply means that the token is either invalid or expired. You can uninstall the app and then reinstall and get a new token and then try with that token. This will definitely solve your problem.
You can read more here.

Paypal Express Checkout - Authorization not appearing in dashboard

I am creating & sending the following payment via the Express checkout API V4:
return paypal.rest.payment.create(env, client, {
intent: 'authorize',
payer: {
"payment_method": "paypal"
},
transactions: [
{
amount: { total: '0.01', currency: 'GBP' }
}
]
});
and I'm returning the following object:
Which all seems to be on the right track. The problem is, there is no sign of this payment auth in the sandbox dashboard.
I've even tried using a live account, and sending a real penny, but there is no sign of the transaction in either the buyer or seller account.
If this payment is not being successfully created, why am I seeing the object returned with a state of "created"?
FYI: If I send a payment using intent: 'sale' it processes successfully and appears in the dashboard.
I had the same problem. API and documentation of PayPal is something awful...
The logic is this:
1. You have to make execute payment.
I tried to do it in different ways, but the easiest way ended up like this:
In the examples on the Paypal site show your complete code with onAuthorize: function(data, actions) , so this function should look like this:
onAuthorize: function(data, actions) {
return actions.payment.get().then(function(payment) {
console.log(payment);
var b = payment.payer;
var bb = b.payer_info;
// alert (bb.payer_id);
var newUrl = "http://YOURDOMAIN.COM/execute.php?paymentId="+payment.id+"&token=EC-"+payment.cart+"&PayerID="+bb.payer_id;
console.log(newUrl);
// go to the execute.php and send to paypal payment confirmation
window.location.replace(newUrl);
});
}
Once you got the object with a transaction you must still confirm it!
Go here https://github.com/paypal/PayPal-PHP-SDK/wiki/Installation download the PHP SDK, it will need to file execute.php to easily confirm the payment and it showed up in the admin panel PayPal. I downloaded the SDK for a direct link, without Composer.
Then in the newly created file execute.php connect this directly SDK without Composer.
// Use below for direct download installation
require __DIR__ . '/PayPal-PHP-SDK/autoload.php';
Then copy the contents of the file itself execute.php and replace it PayPal client ID and client secret. Full code of execute.php here http://pastebin.com/K750qcxE
I couldn't paste here all the code. Citation of code here is terribly implemented, as well as PayPal API :)
p.s. in the script I sent to return url, but I don't know why paypal did not redirect me to it, so I redirect using javascript when you get the transaction object.
sorry for my english

Refresh Office365 Authorization Token from Javascript

I currently have an issue with one of my apps.
The issue is that a user can keep the page open for a prolonged period of time before entering any data, so occasionally, when they enter data and hit the submit button, they are redirected to o365 for authentication and therefore lose the entered data.
I have all the standard authentication working for the app. However, i believe in order to do this, i would need to get a refreshed token in javascript when the submit button is clicked, and send this token to an api method in order to give access.
Is this possible and does anybody know how to go about it?
It is an MVC ASP.NET application using Owin O365 security with Microsoft Azure AD.
I am not sure what information or code snippets would be relevant here so if there is anything i can provide, please ask.
I have found multiple examples of getting tokens etc with angular, however, this is not an SPA and does not use angular.
Many Thanks in advance.
UPDATE
I have attempted to retrieve a token using ADAL JS using the following code but it doesnt seem to recognise the AuthorizationContext(config) call:
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.0/js/adal.min.js"></script>
$('#btnSubmit').on('click', function (e) {
e.preventDefault();
CheckUserAuthorised();
});
function CheckUserAuthorised() {
window.config = {
instance: 'https://login.microsoftonline.com/',
tenant: '##################',
clientId: '###################',
postLogoutRedirectUri: window.location.origin,
cacheLocation: 'localStorage'
};
var authContext = new AuthorizationContext(config); //THIS LINE FAILS
var user = authContext.getCachedUser();
if (!user) {
alert("User Not Authorised");
authContext.login();
}
else {
alert('User Authorized');
}
}
This gives the following error in console:
'AuthorizationContext' is undefined
UPDATE
I have no got passed the undefined error. This was because i was calling AuthorizationContext rather than AuthenticationContext. Schoolboy error. However now, whenever i check the user property of the context, it is always null. And i do not know a way around this as the context is initialised on page load.
There is a lack of a step in your code, here is a simple code sample, hope it will help you:
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.10/js/adal.min.js"></script>
<body>
login
access token
get user
</body>
<script type="text/javascript">
var configOptions = {
tenant: "<tenant_id>", // Optional by default, it sends common
clientId: "<client_id>",
postLogoutRedirectUri: window.location.origin,
}
window.authContext = new AuthenticationContext(configOptions);
var isCallback = authContext.isCallback(window.location.hash);
authContext.handleWindowCallback();
function getToken(){
authContext.acquireToken("https://graph.microsoft.com",function(error, token){
console.log(error);
console.log(token);
})
}
function login(){
authContext.login();
}
function getUser(){
var user = authContext.getCachedUser();
console.log(user);
}
</script>
The code sample is from the answer of No 'Access-Control-Allow-Origin' header with Microsoft Online Auth. The issues are different but they are in the same scenario.
any further concern, please feel free to let me know.
I found a similar example on the web and your problem seems to be related to the object you are instantiating.
Instead of
new AuthorizationContext(window.config);
try
new AuthenticationContext(window.config);
The code ran just fine showing that the user was not authenticated.

Categories

Resources