How to implement atomicity when working with webhooks in node? - node.js

I am working on a MERN application. I am trying to implement a stripe subscription in it.
Whenever a user tries to pay using the stripe checkout session the webhooks route is invoked and the database is updated according to the event type.
Here is my webhook controller code:
export const postStripeWebhook = async (req: ExtendedRequest, res: Response) => {
//code to determine the eventype from the stripe signature.
switch (eventType) {
case "checkout.session.completed":
subscription = event.data.object;
await prisma.users.update({
where: {
id: subscription.metadata.userId,
},
data: {
stripeUserId: subscription.customer,
subscriptionStatus: true,
},
});
break;
//other events type
}
res.sendStatus(200);
};
As one can see when the customer pays for the product, stripe sends a checkout.session.complete event. On receiving this event the subscriptionStatus of the customer is updated in the database and the product is provisioned to the user.
But what if an error occurs while updating the database? Now the user will be left with a situation where his account has been deducted with the subscription money but since the database is not updated he has not provisioned the product.
According to me, this situation can be eliminated if the whole process is made atomic i.e if the database does not change send the deducted amount to the user.
But I don't know whether this is the right way to tackle this problem. Also I don't know how to perform atomic operations in node.js.
Please guide me with the problem.

Related

Have one stripe connect checkout session with multiple products trigger correct payouts to multiple different connected accounts

I am building a marketplace for people to sell digital products with nextjs, firebase and firebase cloud functions. I am able to credit one account with a cart of one or multiple products by following the documentation here https://stripe.com/docs/connect/collect-then-transfer-guide. But I would say it would be important to be able to put items from different authors into your cart.
This is my code that works well for one or multiple products and one connected account.
export const checkoutSessionSingular = functions.https.onCall(async (data, context) => {
const session:any = await stripe.checkout.sessions.create({
line_items: data.line_items,
mode: "payment",
success_url: "***",
cancel_url: "***",
payment_intent_data: {
application_fee_amount: data.fee_amount,
transfer_data: {
destination: data.connectId,
},
},
});
return session;
});
I have tried putting payment data, transfer data or destination into an array, but that always breaks it.
I tried using transfers and transfer groups, but I seem to lose all the valuable data in the stripe dashboard about which product the money comes from and I can't manage to make it work properly with the very limited documentation.
I also tried using destination charges but couldn't make it work.
If something like transfer groups are the solution, I would welcome a link to a proper example implementation or something more helpful.
What should I do to just have a normal working cart for a multi vendor marketplace? I already looked at every page of their documentation 30 times. Thanks.
If you want to send the money to multiple connected accounts, then you have to use Separate Charges & Transfers. So in your case you need to:
Create the Checkout Session and collect the payment, without any application_fee_amount or transfer_data
Then manually create Transfers between your main Stripe account and the connected accounts, with this endpoint

Node.js - Handling a webhook and updating an express-session

I'm using Stripe to let my customers change their plans, and we send them to an external Stripe-hosted page to complete the checkout. After they checkout, Stripe sends them back to a thank-you page and initiates a webhook.
The webhooks are great, because it doesn't update any database information or changes their plan until after the payment has succeeded and their card has been charged.
I also use webhooks to "reset" their plan limits on a successful card charge. So each month, on each successful charge webhook, the limits update. But if the user is in an active session when they update, I should update that session.
BUT what I'm struggling with is, in a webhook event how do I update an active express session?
There's no req.session object, because the "event" is coming from Stripe's webhoook.
app.post('/webhook', async (req, res) => {
/* <-- Update the database, which is good --> */
database.updateUser()
/* <-- Update their session if it's active --> */
// This won't work, because there is no req.session and no cookie being sent!
req.session.user.plan = parseWebhookData()
// I can't access the store, because I don't know the user's session id
// Plus, all my store is encrypted so I can't do a MongoDb query search for their session.
store.get(sid, callback)
// How do I update their session??
})
The only solution I have so far is to have the database re-update their information on every page they visit. But that slows down my response time by 1s per page!
I solved this myself.
Solution #1: Recommended by Stripe: Update user on login, not with the webhook
Stripe recommends checking a user's subscription on login, and resetting their "plan usage" on login INSTEAD of resetting their plan usage on a webhook.
I thought this was very interesting and after implementing I've found this is a much better solution than trying to deal with Webhooks and session objects!
Solution #2: Get the session ID and update the express-session database!
Express-session gives you the sessionID in "req.sessionID" so you can search your database for the sessionID and update what you need to.
app.get('/getID', (req, res) => {
console.log("sessionID: " + req.sessionID);
res.send(req.sessionID);
});
It also helps to do MongoDB's atomic updates if you turn off the stringify feature in your express-session.
const store = MongoStore.create({
stringify:false
});

New Customer created by Checkout, then create Subscription on Customer results in Error: This customer has no attached payment source

New Customer created by Checkout, then create a new Subscription on the same Customer by Node SDK results in Error: This customer has no attached payment source.
However if I look at the Customer at the dashboard, there is a Card, but not set as Default. Once it is "Set as Default" by clicking the ... it works.
Here is the code I used to create a new Subscription on a Customer:
const customer = 'cus_xxxxxx'
const plan = 'plan_xxxxxx'
stripe.subscriptions.create({
customer,
items: [
{
plan
}
]
})
I'm not sure if this is a limitation of Checkout since https://stripe.com/docs/payments/checkout says
Better support for saving customer details and reusing saved payment methods
Right now my workaround is to use webhook to update Customer's invoice_settings.default_payment_method on payment_method.attached.
This works but it feels strange. Did I miss something? Why does Checkout not set the only Card as invoice_settings.default_payment_method?
This behavior seems intentional on Stripe's part, the card from Checkout is attached to the Customer as a Payment Method, and is not set as default.
The same thing happens if you create a Customer directly with a PM,
let cust = await stripe.customers.create({ payment_method: "pm_card_visa" });
Also, fwiw, one can create their subscription directly from Checkout, passing a plan instead of sku https://stripe.com/docs/stripe-js/reference#stripe-redirect-to-checkout
From Stripe support:
Checkout does not currently support the ability to reuse saved payment
methods. We are aware that this is a feature request for a lot of our
users, and we are working on implementing this in the future.
If you'd like, you can see a roadmap of the updates we'll be making to
Checkout in the document below.
https://stripe.com/docs/payments/checkout#checkout-roadmap
That said, the work around you're doing for the moment is the same
work around that we're suggesting to users for the time being.
After a lot of digging I realized there is one step that is easy to miss in the docs: take the attached payment method and set it up as a default.
Here is my full server node.js code for creating a subscription:
const customerResponse = await stripe.customers.create({ email, name })
const customer = customerResponse.id
await stripe.paymentMethods.attach(paymentMethodId, { customer })
await stripe.customers.update(customer, {
invoice_settings: { default_payment_method: paymentMethodId }
})
await stripe.subscriptions.create({
customer,
items: [{ price: 'price_XXXXXX' }]
})
paymentMethodId name and email come from the client.

Auth0 subscription plan app_metadata

I'm developing a quiz app which requires authorization for only-subscribed members can see.
How to do that? I'm thinking of putting metadata (is_subscribed) to true for subscribed member and give the scope so he/she can gain permissions.
But, I don't know how to do it. Please help. The docs is so confusing
There are two separate concerns here.
Where to keep the subscription information. app_metadata is fine, or you might choose to do so in a backend database (application specific). A client application will probably handle subscriptions and be in charge of updating that value. If you store the value in app_metadata, you will use Management API v2 to alter the user profile from the application that handles subscriptions.
Add an authorization scope based on the subscription status. In this case, you would use a rule to add a custom scope based on the value of the is_subscribed field. I.e.:
function(user, context, callback) {
if (user.app_metadata && user.app_metadata.is_subscribed) {
context.accessToken.scope = ['read:quiz'];
} else {
// remove the ability to read a quiz if not subscribed.
context.accessToken.scope = [];
}
callback(null, user, context);
}
If you decided to store the subscription information in a backend database instead of in the app_metadata, you would simply access the database from the rule in the above code.

How to get Paypal transactions in NodeJS

Problem description:
I go into my paypal buyer sandbox account and send 10 Pounds to my facilitator sandbox account. The transaction goes trough and all is fine. I log into my facilitator account and i see the transaction.
Now when i use the following nodejs code to get the transactions:
var paypal_api = require('paypal-rest-sdk');
var config_opts = {
'host': 'api.sandbox.paypal.com',
'port': '',
'client_id': 'HIDDEN',
'client_secret': 'HIDDEN'
};
var listPayment = {
'count': '10',
'start_index': '0'
};
paypal_api.payment.list(listPayment, config_opts, function (error, payment) {
if (error) {
throw error;
} else {
console.log("List Payments Response");
console.log(JSON.stringify(payment));
}
});
i get this in the console:
List Payments Response
{"count":0,"httpStatusCode":200}
So its not showing me any transactions.
How can i see those transactions?
I have read on another Stackoverflow thread that these might not show up because they are not done trough REST. If thats the case, would payments done trough the official PayPal mobile app appear in my NodeJS response?
Question 2:
Is there any way by using NodeJS to see transactions that are coming into my merchant account? (paid in from the PayPal app, or from their website)
Question 3:
How can i use WebHooks to see the transactions in NodeJS?
I would really appreciate your help guys, ive been looking everywhere for a response to these questions.
Thank you so much.
Alex
EDIT 1:
I have tried Webhooks and i still cant get anything to trigger when i do a transaction. An event triggers on my NodeJS side only when i use the Webhook simulator, but not when i do a normal transaction on Paypal using my sandbox accounts.
I wonder if there is any way to test things with the Paypal mobile app, does anybody know if you can do sandbox testing with the mobile app?
Click manage webhooks and Add your webhook listener URL(API endpoint), then select which event you want to get from PayPal.

Resources