I'm trying to retrieve the prorated invoice price from stripe when the customer increases the quantity of a subscription item. For example, the basic plan is $10 and the option to add an extra 2000 api calls per month is $4.99. If the customers wants 4000 more api calls per month then they would be increasing the subscription items quantity for the $4.99 price from 1 to 2. The price will vary per say the customer decided to increase the quantity half way through the billing period. In this case they should be charged $2.49. The next billing period should then charge the $4.99 at the start.
After attempting to retrieve the upcoming invoices using stripe.invoices.retrieveUpcoming({...}) It returns the wrong price each time. Its always more than it needs to be. Seems to be 2 times the base plan of $10 and only one of the $4.99 prices. This is my code from my backend (its an array element in an array of routes.):
{
url: '/invoice-amount',
type: eRequestType.GET,
handler: async (req, res) => {
const { proration_date, subscription, price, customer } = JSON.parse(req.headers["invoice-details"])
try {
const sub = await stripe.subscriptions.retrieve(subscription)
let siID = null, oldQuantity = null
for (let si of sub.items.data) { if (si.price.id === price) { siID = si.id; oldQuantity = si.quantity } }
if(!siID) {
const invoice = await stripe.invoices.retrieveUpcoming({
customer,
subscription_items: [{price}],
// subscription_proration_date: proration_date
})
return res.json({"amount": invoice.amount_due})
}
else {
const invoice = await stripe.invoices.retrieveUpcoming({
// customer,
subscription,
subscription_items: [{ id: siID, price, quantity: oldQuantity + 1}],
subscription_proration_date: proration_date
})
return res.json({"amount": invoice.amount_due})
}
}
catch (error) {
console.log(error)
res.json({ "error": error.message })
}
}
}
Calling that route from the front end looks like this:
async function fetchInvoiceAmount() {
return axios.get('/invoice-amount', {
headers: {
"invoice-details": JSON.stringify({
"proration_date": Math.floor(Date.now() / 1000),
"subscription": props.subId,
"price": ePrices.EXTRA_API_CALLS,
"customer": props.user.cust_id
})
}
})
}
When I test This code with a customer subscribed to my monthly plan at $10 per month and they are not paying for then extra api calls the route returns {"amount": 499} or $4.99. This seems to be correct. After reviewing the stripe docs, it mentions not passing in a subscription id and only passing in subscription_items will return the amount if the item was added to the subscription. But like from earlier, what if the customer signs up half way through the month? It should not be returning {"amount": 499} but should be returning {"amount": 249}. The real problem arises when the customer already has at least one subscription item to the $4.99 price. The route then returns {"amount": 2497}. When analyzing this output, I believe that its increasing the base price of $10 to a quantity of 2 and not touching the api price of $4.99.
How do I get this to return the prorated amount for only the one quantity increase of the api call price?
If all you want to do is preview the change in quantity, you should be able to do it like this:
const invoice = await stripe.invoices.retrieveUpcoming({
// customer,
subscription,
subscription_items: [{ id: siID, quantity: oldQuantity + 1}],
subscription_proration_date: proration_date
})
Related
I'd really appreciate if anyone can help with a mongodb + nodejs problem.
So Id like to write a job that updates, for example, a rating field in every document of a restaurant profile collection. The new value is going to be a calculated average rating of all the users who rated that profile, in MongoDB + Nodejs.
Lets say I have a restaurant profile collection whose schema looks like the following:
{
_id: XXXX,
name: "Taco Bell",
cuisine: "Mexican",
address: "876 SomeRoad Rd, New York, NY, 10020",
averageRating: 4,
ratings: [{
value: 1,
byId: ObjectId("XXXXXXX")
}, {
value: 3,
byId: ObjectId("XXXXXXX")
}, {
value: 5,
byId: ObjectId("XXXXXXX")
}]
}
So as users keep rating that restaurant, I keep pushing the new ratings to the "ratings" field, but then I d like to run a job that updates the "averageRating" based off the "ratings" field periodically.
How can I do that ?
Your problem is a good use case for database hooks. Considering you are using mongoose ORM, you can add a pre update hook on your schema and calculate and update the new average rating in the same update query. Following is an example to implement a pre update hook:
your_schema.pre('update', function(next) {
const recordsToUpdate = this.getUpdate();
// calculate the new average rating and append to the records
next();
})
});
You can check here for reference. Hope this helps.
so I want to take a bills that's not been paid since last month and take a bills for this month so I have this query to check that
const pastBills = await Customer.find({
id_customer: (
await Meteran.find({
$and: [
{
id_usage: {
$nin: (await Bills.find()).map((bill) => bill.id_usage),
},
},
],
})
).map((meter) => meter.id_customer).length === 2,
});
I want to check which customer_id is the same with the customer_id in meteran that have two different id_usage that's not in the bills. but even thought I have two different id_usage that's not in the bills it returned an empty array can anyone help me to solve this?
I am building an order form that limits how many items you can order based on the stock of the item. I have a menu collection which has items
// menu
{ id: "lasagna", name: "Lasagna", price: 10, stock: 15 }
{ id: "carrot-soup", name: "Carrot Soup", price: 10, stock: 15 }
{ id: "chicken-pot-pie", name: "Chicken Pot Pie", price: 10, stock: 15 }
And an orders collection
// orders
{ id: <auto>, name: "Sarah", cart: {lasagna: 1, carrot-soup: 3}, ... }
{ id: <auto>, name: "Wendy", cart: {chicken-pot-pie: 2, carrot-soup: 1}, ... }
{ id: <auto>, name: "Linda", cart: {lasagna: 3}, ... }
4 carrot-soup has been ordered so the stock should be updated
// updated stock
{ id: "carrot-soup", name: "Carrot Soup", stock: 11 }
Orders are inserted from my Form component
function Form(props) {
// ...
// send order to firestore
const onSubmit = async _event => {
try {
const order = { cart, name, email, phone, sms }
dispatch({ action: "order-add" })
const id = await addDocument(store, "orders", order)
dispatch({ action: "order-add-success", payload: { ...order, id } })
}
catch (err) {
dispatch({ action: "order-add-error", payload: err })
}
}
return <form>...</form>
}
This is my database addDocument function
import { addDoc, collection, serverTimeStamp } from "firebase/firestore"
async function addDocument(store, coll, data) {
const docRef = await addDoc(collection(store, coll), { ...data, timestamp: serverTimestamp() })
return docRef.id
}
How should I decrement the stock field in my menu collection?
Ideally the client should have only read access to menu but to update the stock the client would need write access.
Another possibility is to have the client query the orders, sum the items, and subtract them from the read-only menu. But giving the client read access to other people's orders seems wrong too.
I am new to firestore and don't see a good way to design this.
You should deffinitely use a cloud function to update the stock. Create a function onCreate and onDelete functions trigger. If users can change data you would also need to onWrite function trigger.
Depending on the amount of data you have you woould need to create a custom queue system to update the stock. Belive me! It took me almost 2 years to figure out to solve this. I have even spoken with the Firebase engeeners at the last Firebase Summit in Madrid.
Usualy you would use a transaction to update the state. I would recommend you to do so if you don't have to much data to store.
In my case the amount of data was so large that those transactions would randomly fail so the stock wasn't correct at all. You can see my StackOverflow answer here. The first time I tought I had an answer. You know it took me years to solve this because I asked the same question on a Firebase Summit in Amsterdam. I asked one of the Engeeners who worked on the Realtime Database before they went to Google.
There is a solution to store the stock in chunks but even that would cause random errors with our data. Each time we improved our solution the random errors reduced but still remained.
The solution we are still using is to have a custom queue and work each change one by one. The downside of this is that it takes some time to calculate a lot of data changes but it is 100% acurate.
Just in case we still have a "recalculator" who recalculates one day again and checks if everything worked as it should.
Sorry for the long aswer. For me it looks like you are building a similar system like we have. If you plan to create a warehouse management system like we did I would rather point you to the right direction.
In the end it depends on the amount of data you have and how often or fast you change it.
Here is a solution based on Tarik Huber's advice.
First I include functions and admin
const functions = require("firebase-functions")
const admin = require("firebase-admin")
admin.initializeApp()
Then I create increment and decrement helpers
const menuRef = admin.firestore().collection("menu")
const increment = ([ id, n ]) =>
menuRef.doc(id).update({
stock: admin.firestore.FieldValue.increment(n)
})
const decrement = ([ id, n ]) =>
increment([ id, n * -1 ])
Here is the onCreate and onDelete hooks
exports.updateStockOnCreate =
functions
.firestore
.document("orders/{orderid}")
.onCreate(snap => Promise.all(Object.entries(snap.get("cart") ?? {}).map(decrement)))
exports.updateStockOnDelete =
functions
.firestore
.document("orders/{orderid}")
.onDelete(snap => Promise.all(Object.entries(snap.get("cart") ?? {}).map(increment)))
To handle onUpdate I compare the cart before and after using a diff helper
exports.updateStockOnUpdate =
functions
.firestore
.document("orders/{orderid}")
.onUpdate(snap => Promise.all(diff(snap.before.get("cart"), snap.after.get("cart")).map(increment)))
Here is the diff helper
function diff (before = {}, after = {}) {
const changes = []
const keys = new Set(Object.keys(before).concat(Object.keys(after)))
for (const k of keys) {
const delta = (before[k] ?? 0) - (after[k] ?? 0)
if (delta !== 0)
changes.push([k, delta])
}
return changes
}
I am building an auto task manager that will automatically assign a task to users with the same role as the role of the task. Right now I am getting the total number of users with the same role as the task and then use math.random() to generate a number from 0 to the length of the users that have the same role and the assign the task to that random user. This isn't efficient as a user can have 10 tasks and another user has just 4 tasks. Is there a way I can assign tasks sequentially to all the users with the same role with the task??
This is the code to that creates a new task:
exports.createNewTask = async (req, res) => {
try {
let task = new Task({
title: req.body.title,
description: req.body.description,
role: req.body.role,
priority:req.body.priority
});
let role = req.body.role;
let user = await User.find({ role: role });
if(user.length == 0) {
res.status(500).json({message:"Please Create a User With this role" });
}
let random = Math.floor(Math.random() * user.length);
let assignedUser = user[random]._id;
task.user = assignedUser;
let assignedTask = await task.save()
res.status(200).json({ assignedTask });
} catch (err) {
console.log(err);
res.status(500).json({ error: err });
}
};
i'd add a new field to User such as NumTasksAssigned which would keep a count of how many tasks a user has. if you don't like to manage this count manually, you could also get this count by doing a $lookup when retrieving the users for a given role.
when getting the list of users for a given role, retrieve them sorted in ascending order by the NumTasksAssigned field. also limit the number of users returned by how many tasks you have at hand.
iterate over the user list and assign tasks to users and increase their task count.
if the number of tasks at hand is greater than the amount of users retrieved, don't break the loop and keep assigning until there are no more tasks to assign.
the main problem with this approach is that users who already had some tasks would get more tasks assigned to them.
we could come up with some algorithm to prevent this from happening and assign tasks evenly. but it may not be a complication you'd wanna deal with.
update: added lookup query
var numTasks = 10;
var roleName = "some role";
db.users.aggregate([
{
$match: { role: roleName }
},
{
$lookup: {
from: "tasks",
localField: "_id",
foreignField: "user",
as: "tasks"
}
},
{
$addFields: {
tasks: { $size: "$tasks" }
}
},
{
$sort: { tasks: 1 }
},
{
$limit: numTasks
}
])
i have my model called "Conversations", and my model "Messages", right now i want to retrieve all conversations with the last Message attached (only 1 message per conversation), so i filtered the conversationids and i queried the messages, but i'm not able to get this messages (last messages) for each conversation, thanks in advance.
let conversations = await ConversationModel.find({});
const conversationIds = conversations.map(conversation => conversation._id)
// ConversationIds is basically ["conversation1", "conversation2", "conversation3"]
// Te problem is here, i want to attach the las message for each conversation, if i put limit(1)
// i will get 1 record for all query, but i want the last message record for each conversation.
MessageModel.find({ _id: { "$in" : conversationIds} }, ...);
From information gathered in comments; This is possible to achieve in a case where MessageModel documents contain a time-stamp to identify latest of them.
Idea: Filter messages based on conversationIds via $match, sort them by timestamp for the next stage where $group on conversation reference (lets say conversation_id) and pick latest of them by $first accumulator.
Aggregation Query: playground link
db.collection.aggregate([
{
$match: {
conversation_id: {
$in: conversationIds
}
}
},
{
$sort: {
timestamp: -1
}
},
{
$group: {
_id: "$conversation_id",
latest_doc: {
$first: "$$ROOT"
}
}
}
]);