How to fulfill orders after Stripe's checkout.session.completed event? - javascript

My problem is with Stripe's metadata object having a 500 character limit. I have a working checkout flow but my only restriction is the character limit for my cart. My cartItems object has extras and customer notes I want to include for each cart Item. With that being said, the metadata limit gets to 500 characters fast. I have read on another post here, implementing websockets into my app which would let me create the order using after listening to stripes event. How would I go about this? Any other workarounds?
let endpointSecret;
endpointSecret =
"whsec_bd73383ed0fcf9cfb27bd4929af341605ad32577dfd8825e1143425b846bb3c3";
router.post("/webhook", (request, response) => {
const sig = request.headers["stripe-signature"];
let data;
let eventType;
if (endpointSecret) {
let event;
try {
event = stripe.webhooks.constructEvent(
request.rawBody,
sig,
endpointSecret
);
} catch (err) {
response.status(400).send(`Webhook Error: ${err.message}`);
return;
}
data = event.data.object;
eventType = event.type;
} else {
data = request.body.data.object;
eventType = request.body.type;
}
// Handle the event
if (eventType === "checkout.session.completed") {
stripe.customers
.retrieve(data.customer)
.then((customer) => {
console.log("customer:", customer);
console.log("data:", data);
createOrder(customer, data);
})
.catch((err) => console.log(err.message));
}

You don't typically pass all cart information in metadata, you would typically store that information in your own database and then retrieve the necessary information based on a UUID that corresponds with that data and the order ID that you set on the Checkout Session via metadata which then gets returned via the Webhook.
To give an example of what I'm recommending above --
In your database you would have something like:
Order ID
Cart Item1
Cart Item1 Description
Cart Item2...
123456
hat
red
scarf
123457
sock
blue
Then when you create your Checkout Session you just pass your Order ID as metadata. When you receive your checkout.session.completed Webhook, that metadata will indicate the Order ID so now you have all your data necessary for fulfillment and reconciliation (and you can update your database accordingly).
Also, to clear up a misconception, with Stripe's metadata you can have 50 keys each with values up to 500 characters long.

Related

Firestore - Listen all deletions without downloading the entire collection (receiving "modified" events as "added")

Introduction
In order to listen all the docs of my collection that have beed removed, without downloading the entire collection, I am doing the same as commented here.
Rather than deleting docs completely, just add a "deleted: true" property and then listen on the query "db.collection("cities").where("deleted", "==", true)"
Problem
For some reason I don't understand, I am receiving all the "modified" events as "added" from my Firestore listener.
Chat Room Deletions Code
I am implementing a chat list screen with pagination, so I am just fetching old docs when the screen is mounted, and subscribing to all changes that happens on each chat room doc after new Date() (the time in which the screen is mounted).
For "removing" the chat room docs, I am doing the following:
async function deleteChatRoom(chatRoomId, userId) {
const chatRoomRef = firestore.collection("chats").doc(chatRoomId);
const chatRoomDoc = await chatRoomRef.get();
const chatRoomData = chatRoomDoc.data();
const messagesPath = `${chatRoomRef.path}/messages`;
// Check that the user is member of the room
if (!chatRoomData.members[userId]) {
throw chatErrors.notChatRoomMember();
}
// Delete all the chatroom messages
await deleteCollection(messagesPath);
// Delete the chat room
await chatRoomRef.update({
deleted: true, <----
lastMessage: admin.firestore.FieldValue.delete(), // Delete denormalized sensitive data
modifiedAt: admin.firestore.FieldValue.serverTimestamp(), <----
read: admin.firestore.FieldValue.delete(), // Delete denormalized sensitive data
});
}
As you can see, I am updating the "modifiedAt" and "deleted" fields.
Listener Code
With this, what I am trying to reach is being able to listen to all the chat room changes ("lastMessage" field updates, "read" field updates, "deletions"...) that happens after new Date() in the client side, as I commented before. As follows:
export function listenMyChats(
startAt = undefined,
onNext,
onError
) {
const currentUserId = getCurrentUser()?.uid;
if (!currentUserId) {
throw authErrors.authenticationRequired();
}
let query = firestore
.collection("chats")
.where("membersArray", "array-contains", currentUserId)
.orderBy("modifiedAt");
if (startAt) {
query = query.startAt(startAt);
}
return query.onSnapshot(onNext, onError);
}
Something that has sense to me, as I am avoiding reading the entire collection when using the field "startAt" and "orderBy(modifiedAt)".
Subscribing code
Then, to handle all and subscribe to the chat room changes, I am doing the following:
const handleChatsChanges = async (querySnapshot) => {
const changes = querySnapshot.docChanges();
await Promise.all(
changes.map(async (change) => {
if (change.type === "added") {
// NEW INCOMING CHATS
console.log("Added");
} else if (change.type === "modified") {
// MODIFIED CHATS ("deleted", "read", "lastMessage" fields...)
console.log("Modified");
}
});
);
};
useEffect(() => {
const listener = listenMyChats(
startAfter.current,
handleMyChatsChanges,
handleOnListenMyChatsError
);
return () => {
listener();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
The main problem I am experiencing is that, instead of getting the document modifications in the "modified" scope, I am getting "added" events... Why is this happening?
I thought that the listeners behavior were to get all the docs that match the query condition (as "added") when subscribing. And that, after this, the "added" scope was only executed when new docs were added to the target collection (matching the query conditions, of course).
So, why am I receiving the docs fields modifications as "added"?
As detailed in the comments above, the problem comes form the fact that the Firestore documents that are modified where not present in the first snapshot, because your listener is applied on the following query:
firestore
.collection("chats")
.where("membersArray", "array-contains", currentUserId)
.orderBy("modifiedAt");
And when you modify them they are returned by the above query and therefore they are considered as added by the listener.
They would have been considered as modified if, and only if, they were included in the first snapshot returned after setting the listener.

How Can I Query the Parent Record Using JS

To check if paymenttype equal credit account [entity form: auto_paymenttype]
Then , if payment amount <= resit amount, it will save else > prevent save (popup message invalid: payment amount should lower than resit amount)[entity form: auto_resittype]
Hi guys, It could be great if someone could re-code and help me on this.I am new in D365 and JS. Basically, I have entities which is auto_paymenttype and auto_resittype and their parent is Payment. How can I query the parent adjustment record using JS. I have provide my current code, please help me to review it. I have try everything but so far no luck. Sorry for my unprofessional picture. But I hope you understand it and could help me to code for this situation. Thank you.
function resitApproveAmount(executionContext) {
try {
const object = {};
object.fctx = executionContext.getFormContext();
object.saveEvent = object.fctx.getEventArgs();
object.paymentamount = object.fctx.getAttribute("auto_paymentamount").getValue();
object.resitamount = object.fctx.getAttribute("auto_resitamount").getValue();
object.paymenttype = Xrm.Page.getAttribute("auto_paymenttype").getValue();
if (object.paymenttype != null) {
object.autoGUID = object.paymenttype[0].id.substring(1, 37);
}
Xrm.WebApi.retrieveMultipleRecords("auto_paymenttype", "$select=auto_name").then(
function success(result) {
for (var i = 0; i < result.entities.length; i++) {
object.auto_name = result.entities[i]["auto_name"];}
if(object.auto_name == "Credit Account"){
if (object.paymenttamount >= object.resitamount) {
alert("Payment Amount cannot be more than Resit Amount.");
object.saveEvent.preventDefault();
}
else
{object.fctx.data.save();}
}
},
function (error) {
console.log(error.message);
}
);
}
catch (error) {
console.log(error);
}
}
click here for picture overview
You code implies auto_paymentamount and auto_resitamount exist on entity Payment but you ERD shows them existing on entity Resit Type.
If they are on the Payment entity then your can retrieve their values as in your question:
const paymentAmount = formContext.getAttribute("auto_paymentamount").getValue();
const resitAmount = formContext.getAttribute("auto_resitamount").getValue();
Otherwise you need to retrieve the Resit Type record related to your Payment record, using the WebApi. If this is the case I assume you have a lookup from Payment to Resit Type.
Assuming auto_paymentamount and auto_resitamount exist on entity Payment your code can be simplified to the following two solutions. Both solutions assume this function is called during the onsave event.
Solution 1 - Retrieve the related Payment Type Record
function resitApproveAmount(executionContext) {
try {
// Get the form context
const formContext = executionContext.getFormContext();
// Extract attribute values from the form
const paymentAmount = formContext.getAttribute("auto_paymentamount").getValue();
const resitAmount = formContext.getAttribute("auto_resitamount").getValue();
const paymentTypeLookup = formContext.getAttribute("auto_paymenttype").getValue();
// Exit as payment type is not set
if (!paymentTypeLookup) return;
// Extract the payment type record ID from the payment type lookup
const paymentTypeId = paymentTypeLookup[0].id.substring(1, 37);
// Retrieve a SINGLE auto_paymenttype based on lookup ID on form
Xrm.WebApi.retrieveRecord("auto_paymenttype", paymentTypeId, "$select=auto_name").then(
function (paymentType)
{
// If the payment type is credit account then check payment amount and resit amount
if (paymentType.auto_name.toLowerCase() == "Credit Account".toLowerCase())
{
if (paymentAmount >= resitAmount) {
formContext.getEventArgs().preventDefault();
Xrm.Navigation.openErrorDialog({message:"Payment Amount cannot be more than Resit Amount."})
}
}
//Otherwise do nothing
},
function (error)
{
console.log(error.message);
}
);
}
catch (error)
{
console.log(error);
}
}
Solution 2 - Hard Code the Payment Type Record ID
This can be used if you can guarantee your payment type record ID's will be the same across all environments (i.e. Payment type records are migrated using a data migration tool)
function resitApproveAmount(executionContext) {
try {
// Get the form context
const formContext = executionContext.getFormContext();
// Extract attribute values from the form
const paymentAmount = formContext.getAttribute("auto_paymentamount").getValue();
const resitAmount = formContext.getAttribute("auto_resitamount").getValue();
const paymentTypeLookup = formContext.getAttribute("auto_paymenttype").getValue();
// Exit as payment type is not set
if (!paymentTypeLookup) return;
// Extract the payment type record ID from the payment type lookup
const paymentTypeId = paymentTypeLookup[0].id.substring(1, 37);
// Exit if payment type id is not "Credit Account"
if (paymentTypeId.toLowerCase() !== "Hard Code Credit Account GUID here / retrieve environment variable") return;
if (paymentAmount >= resitAmount) {
formContext.getEventArgs().preventDefault();
Xrm.Navigation.openErrorDialog({message:"Payment Amount cannot be more than Resit Amount."})
}
}
catch (error)
{
console.log(error);
}
}
Notes
To help with questions like this in future I would recommend drawing an Entity Relationship Diagram. This helps us understand your entity model and relationships, which helps us answer questions accurately the first time.
Diagrams.net is a great way to draw an ERD for free
I've had another read of the question and think I better understand your entity model:
Like my previous answer this solution assumes this function is called during the onsave event on Payment. I use async/await and async onsave events to simplify the retrieval of Payment Type and Resit Type. Like my other answer, if you know Payment Type GUIDs will be the same across environments you can hard code the Payment Type ID, and check the ID against the lookup ID on payment, to save yourself a WebApi call.
/**
* On Save of Payment record
* #param executionContext
* #returns
*/
async function onSave(executionContext) {
try {
// Get the form context
const formContext = executionContext.getFormContext();
const paymentTypeLookup = formContext.getAttribute("auto_paymenttype").getValue();
const resitTypeLookup = formContext.getAttribute("aut_resittype").getValue();
// Exit as payment type is not set
if (!paymentTypeLookup || !resitTypeLookup) return;
// Extract related entity IDs
const paymentTypeId = paymentTypeLookup[0].id.substring(1, 37);
const resitTypeId = resitTypeLookup[0].id.substring(1, 37);
// Get related records
const [paymentTypeResult, resitTypeResult] = await Promise.all([
Xrm.WebApi.retrieveRecord("auto_paymenttype", paymentTypeId),
Xrm.WebApi.retrieveRecord("auto_resittype", resitTypeId)
]);
// Evaluate payment type and resit type and prevent save if required
if (paymentTypeResult.auto_name.toLowerCase() === "Credit Account".toLowerCase()
&& resitTypeResult.auto_paymentAmount >= resitTypeResult.auto_resitAmount)
{
formContext.getEventArgs().preventDefault();
Xrm.Navigation.openErrorDialog({message:"Payment Amount cannot be more than Resit Amount."})
}
}
catch (error)
{
console.log(error);
}
}

How do I Collect User IDs + Retrieve Corresponding Tokens + Send a Push Notification Via Firebase Cloud Function (JS)

The Problem:
I have been unable to use Firebase (Google) Cloud Functions to collect and utilize device tokens for the cloud messaging feature.
Context:
I am a self-taught android-Java developer and have no JavaScript experience. Despite that, I believe I have code that should work and am not sure what the problem is. To my understanding, it could be one of three things:
Somehow my Firebase Realtime Database references are being called incorrectly and I am not retrieving data as expected.
I may need to use Promises to wait for all calls to be made before proceeding, however I don't really understand how I would incorporate that into the code I have.
I may be using multiple return statements incorrectly (which I am also fuzzy on).
My error message on the Firebase Realtime Database console is as follows:
#firebase/database: FIREBASE WARNING: Exception was thrown by user callback. Error: Registration token(s) provided to sendToDevice() must be a non-empty string or a non-empty array.
at FirebaseMessagingError.FirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:42:28)
at FirebaseMessagingError.PrefixedFirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:88:28)
at new FirebaseMessagingError (/srv/node_modules/firebase-admin/lib/utils/error.js:254:16)
at Messaging.validateRegistrationTokensType (/srv/node_modules/firebase-admin/lib/messaging/messaging.js:729:19)
at Messaging.sendToDevice (/srv/node_modules/firebase-admin/lib/messaging/messaging.js:328:14)
at admin.database.ref.once.snapshot (/srv/index.js:84:12)
at onceCallback (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:4933:51)
at /srv/node_modules/#firebase/database/dist/index.node.cjs.js:4549:22
at exceptionGuard (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:698:9)
at EventList.raise (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:9684:17)
The above indicates I am not retrieving data either at all or by the time the return is called. My JavaScript function code is:
'use strict';
const admin = require('firebase-admin');
admin.initializeApp();
exports.pushNotification = functions.database.ref('/Chat Messages/{chatId}/{pushID}').onCreate((snapshot, context) => {
const valueObject = snapshot.after.val();
return admin.database().ref(`/Chat Basics/${valueObject.chatKey}/Chat Users`).once('value', statusSnapshot => {
var index = 0;
var totalkeys = statusSnapshot.numChildren();
var msgIDs = [];
statusSnapshot.forEach(msg=>{
msgIDs.push(msg.key.toString());
if(index === totalkeys - 1){
const payload = {
notification : {
title: valueObject.userName,
body: valueObject.message,
sound: "default"
}
}
sendNotificationPayload(valueObject.uid, payload);
}
index++;
});
});
});
function sendNotificationPayload(uid, payload){
admin.database()
.ref(`/User Token Data/${uid}`)
.once('value', snapshot=> {
var tokens = [];
//if(!snapshot.exists())return;
snapshot.forEach(item =>{
tokens.push(item.val())
});
admin.messaging()
.sendToDevice(tokens, payload)
.then(res => {
return console.log('Notification sent')
})
.catch(err => {
return console.log('Error in sending notification = '+err)
});
});
}
This code is mostly inspired by what was said to be a working example here from another Stack Overflow question here. I have successfully tested sending a notification to a single device by manually copying a device token into my function, so the function does run to completion. My Java code seems to be irrelevant to the problem, so I have not added it (please ask in the comments if you would like it added for further context).
What I Have Tried:
I have tried implementing promises into my code, but I don't think I was doing it properly. My main reference for this was here. I have also looked at the documentation for literally everything related to this topic, however my knowledge of JS is not sufficient to really apply barebones examples to my code.
My Firebase Realtime Database Nodes:
#1: Loop through chat members to collect user IDs:
"Chat Basics" : {
"1607801501690_TQY41wIfArhHDxEisyupZxwyHya2" : {
"Chat Users" : {
"JXrclZuu1aOwEpCe6KW8vSDea9h2" : true,
"TQY41wIfArhHDxEisyupZxwyHya2" : true
},
#2: Collect user tokens from collected IDs (ignore that tokens are matching):
"User Token Data" : {
"JXrclZuu1aOwEpCe6KW8vSDea9h2" : "duDR3KH3i3I:APA91bH_LCeslZlqL8akYw-LrM9Dv__nx4nU1TquCS0j6bGF1tlIARcheREuNdX1FheC92eelatBC8LO4t6gt8liRdFHV-NDuNLa13oHYxKgl3JBPPlrMo5rB5XhH7viTo4vfYOMftRi",
"TQY41wIfArhHDxEisyupZxwyHya2" : "duDR3KH3i3I:APA91bH_LCeslZlqL8akYw-LrM9Dv__nx4nU1TquCS0j6bGF1tlIARcheREuNdX1FheC92eelatBC8LO4t6gt8liRdFHV-NDuNLa13oHYxKgl3JBPPlrMo5rB5XhH7viTo4vfYOMftRi"
}
Conclusion:
Concrete examples would be much appreciated, especially since I am crunching right now. Thanks for your time and help!
Update:
After some more testing, it looks like the problem is definitely due to my lack of understanding of promises in two areas. Firstly, only one user is collected before the final return is called. Secondly, the final return is called before the 2nd forEach() loop can store snapshot data to an array.
For this code then, how may I modify (or rebuild) it so that it collects all keys before proceeding to retrieve token data from all keys - ultimately before returning the notification?
Just as with every question I post, I managed to figure out how to do it (tentatively) a few hours later. Below is a full example of how to send a notification to chat users based on a message sent (although it does not yet exclude the sender) to a given chat. The order of operations are as such:
User message is saved and triggers event. Relevant data the message contains are:
username, chat key, message
These are retrieved, with (username + message) as the (title + body) of the
notification respectively, and the chat key is used for user id reference.
Loop through chat user keys + collect.
Loop through array of chat user keys to collect array of device tokens.
Send notification when complete.
The code:
//Use firebase functions:log to see log
exports.pushNotification = functions.database.ref('/Chat Messages/{chatId}/{pushId}').onWrite((change, context) => {
const valueObject = change.after.val();
return admin.database().ref(`/Chat Basics/${valueObject.chatKey}/Chat Users`).once('value', statusSnapshot => {
var index = 0;
var totalkeys = statusSnapshot.numChildren();
var msgIDs = [];
statusSnapshot.forEach(msg=>{
msgIDs.push(msg.key.toString());
if(index === totalkeys - 1){
const payload = {
notification : {
title: valueObject.userName,
body: valueObject.message,
sound: "default"
}
}
let promises = [];
var tokens = [];
for(let i=0; i < msgIDs.length; i++){
let userId = msgIDs[i];
let promise = admin.database().ref(`/User Token Data/${userId}`).once('value', snapshot=> {
tokens.push(snapshot.val());
})
promises.push(promise);
}
return Promise.all(promises).then(() => {
return admin.messaging().sendToDevice(tokens, payload);
});
}
index++;
return false;
});
});
});

How can i handle multiple response in single route?

I Try to post an order. I get product's details as array of object(in that array I get product id and quantity). I'm using map function to iterate. Using id I here find all details of the product. Then I am doing before placing an order i am checking stock value and update it. When i try to send more than one object i am getting anUnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at ServerResponse.setHeader (_http_outgoing.js:485:11)How can i solve this warning.And Also another is is stock condition is not working. eg:`If stock is 0 then it should give res.(404) intstead i'm getting 200 ok status
//2.Create a Order
router.post('/orders', (req,res) => {
//products is a array of object we get from frontend
const { products,name,mobile,pincode,address,district,email } = req.body;
if(!products || !name || !mobile || !pincode || !address || !district || !email) return res.sendStatus(409).json({message:'Fields cannot be empty'});
//Using map method to itreate each object in array
products.map( async product => {
try {
const orderedItem = await Product.findById(product.id);
const currentOrder = new Order({
productId:orderedItem.productId,
quantity: product.quantity,
orderId: orderedItem.orderId,
name,
mobile,
pincode,
address,
district,
email
});
//Stock updating
if(orderedItem.stock <= 0){
console.log('stock less or equal to is checking...');
return res.sendStatus(404).json({message:'oops! Product Out of Stock'});
}
if(currentOrder.quantity > orderedItem.stock){
console.log('remove product condition is checking')
return res.status(404).json({message:orderedItem.name+'Please remove the product'});
} //return res.sendStatus(404)//.json({message:'Quantity you choose is not available please try using less quantity'});
orderedItem.stock = orderedItem.stock-currentOrder.quantity;
await orderedItem.save();
await currentOrder.save();
// await sendEmail(orderedItem,currentOrder);
} catch (error) {
return res.sendStatus(500).json({message:error});
}
});
res.json({
message : 'Order placed!'
});
});
There are a couple of things wrong about your code.
You are using async-await inside a arr.map() function, which will not block the main thread and your res.json({message : 'Order placed!'}) will get called before your array elements are processed. Use a 'for' loop with await to block your main thread.
Solution- https://pouchdb.com/2015/03/05/taming-the-async-beast-with-es7.html
2.You are sending multiple responses to the client for a single request which is not allowed and therefore you are getting the error Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at ServerResponse.setHeader (_http_outgoing.js:485:11). Change your code in such a way that it only sends one response for the incoming request.
Solution- https://stackoverflow.com/a/48123354/8393975

How to pass data of an object to HTML file

I have few posts in my app, and I want that when user selects one of them, he to be redirected to a Post.html page which contains all details about that specific product. I have two methods, createPost() for creating a product dynamically where I pass postId in order to keep track of that product, and getPosts() to get the posts from database. I am saving all posts in an array in localStorage to have data about the selected product in Post.html. I added an addEventListener() but not sure how to use it. The problem is that I am stuck how to get the information of that post and pass it to Post.html.
function getPosts() {
firebase
.firestore()
.collection("products")
.get().then(snapshot => {
let products = [];
snapshot.docs.forEach((doc) => {
products.push(doc.data());
createPost(
doc.data().title,
doc.data().description,
doc.data().price,
doc.data().postId
);
});
localStorage.setItem(`${products}`, JSON.stringify(products));
})
.catch((err) => {
console.log(err);
});
}
function createPost(title, description, price, postId) {
let div = document.createElement("div");
div.setAttribute("class", "product-home-show");
......
div.appendChild(divSellerRoundImage);
div.appendChild(divSellerName);
div.appendChild(divProductDescription);
div.appendChild(divProductName);
div.appendChild(divProductPrice);
productsCollection.appendChild(div);
div.addEventListener("click", function () {
// console.log(localStorage.getItem());
// window.location.href = "post.html";
});
}
You can get data from localStorage on another page. Use localStorage.getItem(keyName); Also keep in mind the first argument to setItem is the key name. I'd recommend changing your code to: localStorage.setItem("products", JSON.stringify(products));. Then you'll be able to retrieve your product list with they key "products."
Also, if you're saving an object, you'll need to parse it since it will be saved as a string. You can use JSON.parse
For example:
var retrievedData = localStorage.getItem("products");
var productListObject = JSON.parse(retrievedData);
You can save the selected post ID in another value in local storage, or a cookie. Lastly, you may want to consider using sessionStorage if you don't need the data stored after the session is over. See this link for more information

Categories