Checkout flow: how should I process a new order? - node.js

I'm trying to build an App which allows the customer to download a custom document after his order (so, the product I'm selling it's a digital custom document in pdf).
I'm trying to build that using React for the frontend, Node and Express for the backend, and PayPal Express Checkout (full-stack implementation) as payment.
What is not clear to me is what steps I should take to process an order.
I'm a this point:
Once customer clicks on PayPal button on client side it starts a call on http://localhost/api/paypal/orders endpoint which create the order on the Paypal side and return the PayPal order ID (e.g. id12345)
After the customer approves the payment on the PayPal popup, the client starts a call on http://localhost/api/paypal/orders/id12345/capture endpoint
then? What other endpoints should I crete on the server and what they should return? Maybe... 1 for save the actual store order on my MongoDB and the transaction details, 1 for create the invoice for the order, 1 for allowing the product download ???
Could you please clarify what steps I need to take, what are the endpoints I should create, and what each endpoints should return?
Maybe something like this?

You're trying to do too many things in too many routes. just create a route called something like process-order then you could have an async controller of that route which would contain separate functions for
Storing the order details in mongodb.
Create an invoice
Send a token that allows access to download page.
first , just await the function to complete then call the next.
exports.processOrder = async (req, res, next) => {
try {
const newOrder = await Order.create({
order: req.params.id,
details: req.body.details
})
const createInvoice = () => {
return newOrder._id;
}
const invoice = createInvoice();
const token = jwt.sign(
{
invoiceId = newOrder._id,
},
process.env.JWT_SECRET,
{
expiresIn: '24h'
}
);
return res.status(200).json({status: 'success', message: 'you have 24 hours to download your digital goods', token})
} catch (error) {
res.status(500).send(error)
}
}
This is a very basic idea, but basically store all of your info in one controller, then send a token with the id of the invoice, then when they go to download the book you would have a route where you verify the token. If it succeeds then the book is sent. like this:
app.use(
'/book-download',
expressJwt({ secret: process.env.JWT_SECRET, algorithms: ['HS256'] })
);
app.get('/book-download/success', async (req, res) => {
try{
const invoiceId = req.user.invoiceId;
const invoice = await Invoice.find({_id: invoiceId})
if (invoice) {
return res.status(200).json({status: 'success', message: 'congratulations on your new download', data: {E-book})
} else {
return.res.status(404).json({status: 'fail', message: 'could not find an invoice with that ID'})
} catch (error) {
return res.send(error)
}
});
You can choose to send the pdf via express, or you can allow them to enter a certain part of the website if the return is valid. There you go, that's an idea.

In Express check out, you need to define success / cancel handler
Ref here : https://www.section.io/engineering-education/nodejs-paypal-checkout/
In the success handler, based on the returned URI => you will get a Payer ID and payment ID. Based on that retrieve the transactio information (that internally contains yours order ID). Update your Order id as scucess / cancelled based on the paypal response.
app.get('/success', (req, res) => {
const payerId = req.query.PayerID;
const paymentId = req.query.paymentId;
const execute_payment_json = {
"payer_id": payerId,
"transactions": [{
"amount": {
"currency": "USD",
"total": "25.00"
}
}]
};
// Obtains the transaction details from paypal
paypal.payment.execute(paymentId, execute_payment_json, function (error, payment) {
//When error occurs when due to non-existent transaction, throw an error else log the transaction details in the console then send a Success string reposponse to the user.
if (error) {
console.log(error.response);
throw error;
} else {
console.log(JSON.stringify(payment));
res.send('Success');
}
});
});

Related

How do I know which user has made the payment in stripe

I am integrating stripe with my application. I want to receive a one time payment from a logged in user and once the payment is done maybe save the payment status in the database.
I have set up the stripe-checkout and stripe-webhook. But how would i know which logged in user from the client side has made the payment so that i can set the payment status for that user in the database.
Here is how my checkout and webhook look like.
app.post("/checkout-session", async (req, res) => {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
mode: "payment",
line_items: req.body.items.map((item) => {
const storeItem = storeItems.get(item.id);
return {
price_data: {
currency: "usd",
product_data: {
name: storeItem.name,
},
unit_amount: storeItem.priceInCents,
},
quantity: item.quantity,
};
}),
success_url: `${process.env.CLIENT_URL}/success`,
cancel_url: `${process.env.CLIENT_URL}/failure`,
});
res.json({ url: session.url });
} catch (e) {
res.status(500).json({ error: e.message });
}
});
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
let event;
// Only verify the event if you have an endpoint secret defined.
// Otherwise use the basic event deserialized with JSON.parse
if (process.env.STRIPE_WEBHOOK_SECRET) {
// Get the signature sent by Stripe
const signature = req.headers["stripe-signature"];
try {
event = stripe.webhooks.constructEvent(
req.body,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.log(`⚠️ Webhook signature verification failed.`, err.message);
return res.sendStatus(400);
}
}
// Handle the event
switch (event.type) {
case "payment_intent.succeeded":
const paymentIntent = event.data.object;
console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`);
console.log(paymentIntent);
// Then define and call a methstripe loginod to handle the successful payment intent.
// handlePaymentIntentSucceeded(paymentIntent);
break;
case "payment_method.attached":
const paymentMethod = event.data.object;
// Then define and call a method to handle the successful attachment of a PaymentMethod.
// handlePaymentMethodAttached(paymentMethod);
break;
case "payment_intent.payment_failed":
const failedpaymentIntent = event.data.object;
console.log(`PaymentIntent for ${paymentIntent.amount} failed!`);
// Then define and call a methstripe loginod to handle the successful payment intent.
// handlePaymentIntentSucceeded(paymentIntent);
break;
case "checkout.session.completed":
console.log(event.data.object);
// console.log(`PaymentIntent for ${paymentIntent.amount} failed!`);
// Then define and call a methstripe loginod to handle the successful payment intent.
// handlePaymentIntentSucceeded(paymentIntent);
break;
default:
// Unexpected event type
console.log(`Unhandled event type ${event.type}.`);
}
// Return a 200 response to acknowledge receipt of the event
res.send();
});```
In checkout.session.completed event, you will be able to find customer_details.email in the Checkout Session object to identify the customer.
Alternatively, your internal customer ID might be set under metadata parameter in Checkout Session Creation API. Metadata will be present in checkout.session.completed after successful payment which you can use to update the payment status in your database.
At the login time, you can use json web token (jwt) to send logged in user object in encrypted format and use local storage to store that token. and with request, send this token to backend and decode it. You will get user object from that and then you can able to find which user is sending request.
https://www.npmjs.com/package/jsonwebtoken

Integration of razorpay with node.js

I am building a website in which users have to subscribe to one of the given plans, and I want to collect money from them using RazorPay.
Now I haven't used any payment services like Stripe, Razorpay, or anything like that. This is the first time I am working on that, so I need some guidance regarding that.
How can I setup razorpay on the backend side?(The front end is in flutter).
Additionally, I want to save the payments data in the database as well.
The route to subscribe the package:
exports.subscribe_package = catchAsync(async (req, res) => {
try {
const razorPay = new RazorPay({
key_id: keys.razorPayKey, //Basic razorpay set-up
key_secret: keys.razorPaySecret,
});
const payment = await paymentService.getPayment(
req.data.id,
req.body.razorpay_id
);
const subscription = await subscribeService.addSubscriberPackage(
req.data.id,
req.params.id
);
const user_data = await userService.addPackage(
req.data.id,
req.params.id,
subscription._id
);
return res.succeed(user_data);
} catch (error) {
return res.failed(500, "Internal Server Error", error);
}
});
It is nothing but the updating of some data in the database and the set-up of basic razorpay things.
The currency will be "INR".
The front-end side will send the paymentID to the backend. I just want to make use of it to capture the payment if it is successful or not.
How can I do that?
const Razorpay = require('razorpay');
const instance = new Razorpay({ key_id: 'id', key_secret: 'secret' });
let response = await instance.payments.capture('PaymentID', '2121', 'INR')
if(response){
// save the payments data
return res.success({ data: response });
}else{
return res.failed({ data: {},message:"payment captured failed." });
}

register webhooks on nodejs when order created

I have a shopify store mystore and I have an nodejs app myapp. I need to do is when something happens on mystore a webhook will be created/registered in my nodejs app. I have tried https://www.npmjs.com/package/#shopify/koa-shopify-webhooks this package but it is not working for me and I don't think that it is the same thing that I want. I just want that when let suppose order is created in store a webhook is registered.
if you just have to register a webhook you can use this code.
You just have to change the webhook topic and the endpoint.
This is for orders/create webhook registration
add shopify-api-node and request-promise packages and require them
const ShopifyAPIClient = require("shopify-api-node");
const request = require("request-promise");
then
const createOrderWebhook = await registerWebhook(yourShopDomain, yourShopAccessToken, {
topic: "orders/create",
address: "Your node app end point" //www.example.com/webhooks/createOrder,
format: "json",
});
add your registerWebhook function
const registerWebhook = async function (shopDomain, accessToken, webhook) {
const shopify = new ShopifyAPIClient({
shopName: shopDomain,
accessToken: accessToken,
});
const isCreated = await checkWebhookStatus(shopDomain, accessToken, webhook);
if (!isCreated) {
shopify.webhook.create(webhook).then(
(response) => console.log(`webhook '${webhook.topic}' created`),
(err) =>
console.log(
`Error creating webhook '${webhook.topic}'. ${JSON.stringify(
err.response.body
)}`
)
);
}
};
for checking the webhook already not created at Shopify you can use following code
const checkWebhookStatus = async function (shopDomain, accessToken, webhook) {
try {
const shopifyWebhookUrl =
"https://" + shopDomain + "/admin/api/2020-07/webhooks.json";
const webhookListData = {
method: "GET",
url: shopifyWebhookUrl,
json: true,
headers: {
"X-Shopify-Access-Token": accessToken,
"content-type": "application/json",
},
};
let response = await request.get(webhookListData);
if (response) {
let webhookTopics = response.webhooks.map((webhook) => {
return webhook.topic;
});
return webhookTopics.includes(webhook.topic);
} else {
return false;
}
} catch (error) {
console.log("This is the error", error);
return false;
}
};
Happy coding :)
You can not create/register a new webhook when the order created.
Webhooks are a tool for retrieving and storing data from a certain event. They allow you to register an https:// URL where the event data can be stored in JSON or XML formats. Webhooks are commonly used for:
Placing an order
Changing a product's price
Notifying your IM client or your pager when you are offline
Collecting data for data-warehousing
Integrating your accounting software
Filtering the order items and informing various shippers about the order
Removing customer data from your database when they uninstall your app

Stripe Webhook Returning Error 401 and Success

I have a checkout session:
app.post("/create-checkout-session", async (req, res) => {
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: converted_items,
mode: "payment",
success_url: process.env.URL + "/order-success.html",
cancel_url: process.env.URL + `/order-page.html`,
billing_address_collection: 'required',
});
res.json({ id: session.id, order: req.body });
});
And I would like to set up a webhook so that after a payment is successfully made, it collects the data from the customer (name, address, products purchased)
I copied this right from the webhook docs:
const fulfillOrder = (session) => {
// TODO: fill me in
console.log("Fulfilling order", session);
}
app.post('/webhook', bodyParser.raw({ type: 'application/json' }), (request, response) => {
const payload = request.body;
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(payload, sig, endpointSecret);
} catch (err) {
return response.status(400).send(`Webhook Error: ${err.message}`);
}
console.log(event.type)
// Handle the checkout.session.completed event
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
console.log("did order")
// Fulfill the purchase...
fulfillOrder(session);
}
response.status(200);
});
How I am setting it up is I am first running my server on localhost and then I am running the testing webhook command from the terminal
stripe listen --forward-to localhost:3000/webhook --events=checkout.session.completed
When I make a payment, it shows me these messages in the terminal:
It looks like you've made some modifications to that webhook handler code from the docs. If you're using Checkout and following the docs, what made you switch the code to look for charge.succeeded?
You should add some logging to figure out why your server is returning 400, but most likely the verification is failing in constructEvent. The snippet you've shared doesn't show you setting endpointSecret, but you need to update the value using the secret shown to you when you run stripe listen.
You should review the Checkout fulfillment docs and focus on checkout.session.completed events. You can modify your CLI command to listen to online these events:
stripe listen --forward-to localhost:3000/webhook --events=checkout.session.completed
then your handler would look like the docs and you can implement fulfillOrder to meet your needs. If you want the details you listed, then you will need to retrieve the session with expansion to include the line items and payment details:
const session = await stripe.checkout.sessions.retrieve(
'cs_test_123',
{
expand: ['line_items', 'payment_intent.payment_method']
},
);
Then session.line_items will be what you created and the name, address and email will be on session.payment_intent.payment_method.billing_details.
Updated to suggest removing signature verification temporarily to diagnose:
The 400 errors suggest your server is returning an error, most likely as a result of signature verification. I recommend testing a basic endpoint without verification following this example, to ensure everything else is working as intended. When that is settled, re-adding signature verification is strongly recommended.

Add variable to a URL (node, express)

I'm using node and express. What I want to do is to do is make a mix of res.render and res.redirect.
Thing is, res.render can only receive a .ejs file and data, and redirect will go to a specific URL. What I need to do is go to a specific URL (e.g. /reviews/new/:id), render a .ejs file and give some data to it.
This is my code. I can't use session or cookies for this project.
This are the routes, user enters to edit a review of some show. If it is a new review, the id of the show is in the URL, if the user is editing one, the ID of the review is on the URL. Either way, if something fails, I have to append something to this URL and send data.
router.get('/new/:id', controller.newReview);
router.post('/store', controller.checkBeforeStoringReview);
router.get('/edit/:id', controller.editReview);
router.post('/update', controller.checkBeforeUpdatingReview);
This is the function to check auth before updating.
checkBeforeUpdatingReview: function(req, res) { // auth before updating review (can't use session or cookies)
console.log(req.body)
DB
.User
.findOne(
{
where : {
email: req.body.email,
},
}
)
.then (function (results) {
if (results[0] != '') {
if (bcrypt.compareSync(req.body.password, results.password)) {
return module.exports.updateReview(req, res, results)
} else { // same as catch
return res.render('reviews/edit/', { // i'm missing the ID (which i have in req.params.id) at the end of the route
id : req.params.id,
errors : "Incorrect username or password",
email : req.body.email,
});
}
}
})
.catch (function (error) {
console.log(error)
return res.render('reviews/edit/', { // i'm missing the ID (which i have in req.params.id) at the end of the route
id : req.params.id,
errors : "An unexpected error happened",
email : req.body.email,
});
})
},
If everything's ok, as seen above, it goes directly to this function
updateReview: function(req, res, results) { // update review
console.log(req.body)
DB
.Review
.update(req.body,
{
where : {
id: req.body.review_id,
}
}
)
.then(function (results) {
return res.redirect('/series/detail/' + req.body.series_id)
})
.catch (error => {
return res.send(error)
})
},
TL;DR: If auth fails, should go back to the review url and send the data that was sent so that the user does not lose it.
So that's it, if I could use sessions/cookies I think I would be able to go back to the last route, but I can't for this.
Thanks in advance!

Resources