How to update Stripe subscription quantity? (Node.js) - javascript

I'm trying to update the quantity of a Stripe subscription that has already been created. But I keep getting this error:
"error": {
"message": "Invalid array",
"param": "items",
"type": "invalid_request_error"
}
I am first retrieving the Stripe subscription, updating the value, and then posting the updated value. Here is the code:
const subscription = await stripe.subscriptions.retrieve(
stripe_sub_id
);
subscription.items.data[0].quantity = newCount;
stripe.subscriptions.update(
stripe_sub_id,
{items: { data: subscription.items.data }}
)
What am I doing wrong? How do I update the value of "quantity" within the items.data array?

You can't really just mutate items.data and pass it directly due to the way the Stripe API works(the format of items returned in a retrieve call is not the same as format of the parameters when POSTing, they're different).
So you need to actually write some more custom code/business logic to explicitly create the params object for the change you want. Something like this maybe.
const subscription = await stripe.subscriptions.retrieve(
stripe_sub_id
);
let IdOfPriceToUpdate = "price_xxx";
let newQuantity = 5;
let updatedItemParams = subscription.items.data.
filter(item => item.price != IdOfPriceToUpdate). // find what to change
map(item => {return { id:item.id, quantity:newQuantity}}) // change it
await stripe.subscriptions.update(
stripe_sub_id,
{items: updatedItemParams}
)
https://stripe.com/docs/billing/subscriptions/upgrade-downgrade#changing

Related

How can I build a counter with a database to persist the value in NextJS?

I am building a like-counter in a NextJS ReactJS app, which stores a persistent incrementable count value. The max-count of clicks / likes is to be set to 100. The button will not increment the count any further than this value. The like-counter value is stored in a FaunaDB.
I can render the value stored in the db on the front-end, but I can't get the onClick handler to update the value stored in the db.
Here is the (commented) code so far:
Here, State is declared via a useState hook.
const [likes, setLikes] = useState([]);
The updateLikes function
async function updateLikes() {
await fetch("/api/newLikes", requestOptionsLikes)
.then(() => getLikes())
.catch((e) => console.log(e))
}
The PUT method is instantiated:
const requestOptionsLikes = {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ likes })
}
A useEffect hook:
useEffect(() => {
getLikes();
}, []);
The click function:
const handleSubmitLikes = (e) => {
e.preventDefault();
updateLikes();
}
The JSX that renders the clickable LIKE button and displays the value from the db.
<div>
<div onClick={handleSubmitLikes}>LIKE</div>
</div>
{
likes.map((d) => (
<div>
<div>{d.data.like}</div>
</div>
))}
</div>
The code is correctly fetching the likes from the DB so I think that there is no need to display the getLikes API. However the newLikes API is returning a 500 Internal Server Error, so I display it below:
const faunadb = require("faunadb");
// your secret hash
const secret = process.env.FAUNADB_SECRET_KEY;
const q = faunadb.query;
const client = new faunadb.Client({ secret });
console.log("CALLLED API")
module.exports = async (req, res) => {
const formData = req.body.data;
console.log("API CALL");
try {
const dbs = await client.query(
console.log("API UPDATE"),
console.log(formData),
q.Update(
q.Ref(q.Collection('Likes'), '305446225474224651'),
{
data: {
like: formData.likes[0].data.like + 1
},
}
)
);
// ok
res.status(200).json(dbs.data);
} catch (e) {
// something went wrong
res.status(500).json({ error: e.message });
}
};
Basically, I can't update the Database via the PUT method.
Your demonstrated code appears to increment that value provided in the PUT request, which potentially allows the current client to specify any value. There is also no evidence that you are capping the count at 100.
You can perform the counter increment entirely in FQL with a query like this:
q.Let(
{
counterRef: q.Ref(q.Collection('Likes'), '305446225474224651'),
counterDoc: q.Get(q.Var('counterRef'),
counterVal: q.Select(['data', 'like'], q.Var('counterDoc')
newCounterVal: q.If(
q.GTE(q.Var('counterVal'), 100),
100,
q.Add(q.Var('counterVal'), 1)
),
update: q.If(
q.Equals(q.Var('counterVal'), q.Var('newCounterVal')),
null,
q.Update(
q.Var('counterRef'),
{ data: { like: q.Var('newCounterVal') } }
)
)
},
q.Var('newCounterVal')
)
This uses Let to assign some named values to make it easy to re-use values. newCounter value is set to the incremented counterVal only when counterVal is less than 100. Updating the counter only happens when the counter actually changes (e.g. for values less than 100). The query returns the value of newCounterVal.
Ideally, you'd embed this query in a user-defined function (UDF). Whenever your rule to cap counts at 100 needs to change, only the UDF needs to be updated, and not every location in your application code where 100 might appear.
This was my solution. I found the FaunaDB forums build issues like this and the related. The below solution will increment the value by 1.
q.Update(
q.Ref(q.Collection('Likes'), '__ref_id__'),
{
data: {
like: q.Add(
q.Select(
['data', 'like'],
q.Get(
q.Ref(
q.Collection('Likes'),
'__ref_id__'
)
)
),
1
)
}
},
)
The counter button itself, written in NextJS / ReactJS, caps the functionality of the button to a null onClick for values beyond 100.
<div onClick={d.data.like < 100 ? handleSubmitLikes : null}
handleSubmitLikes access the API.

mongoosejs - find() using nested objects

question is possibly a duplicate but I haven't found anything that provides an appropriate answer to my issue.
I have an ExpressJS server which is used to provide API requests to retrieve data from a MongoDB database. I am using mongoosejs for the MongoDB connection to query/save data.
I am building a route that will allow me to find all data that matches some user input but I am having trouble when doing the query. I have spent a long while looking online for someone with a similar issue but coming up blank.
I will leave example of the code I have at the minute below.
code for route
// -- return matched data (GET)
router.get('/match', async (req, res) => {
const style_data = req.query.style; // grab url param for style scores ** this comes in as a string **
const character_data = req.query.character; // grab url param for character scores ** this comes in as a string **
// run matcher systems
const style_matches = style_match(style_data);
res.send({
response: 200,
data: style_matches
}); // return data
});
code for the query
// ---(Build the finder)
const fetch_matches_using = async function(body, richness, smoke, sweetness) {
return await WhiskyModel.find({
'attributes.body': body,
'attributes.richness': richness,
'attributes.smoke': smoke,
'attributes.sweetness': sweetness
});
}
// ---(Start match function)---
const style_match = async function (scores_as_string) {
// ---(extract data)---
const body = scores_as_string[0];
const richness = scores_as_string[1];
const smoke = scores_as_string[2];
const sweetness = scores_as_string[3];
const matched = [];
// ---(initialise variables)---
let match_count = matched.length;
let first_run; // -> exact matches
let second_run; // -> +- 1
let third_run; // -> +- 2
let fourth_run; // -> +- 3
// ---(begin db find loop)---
first_run = fetch_matches_using(body, richness, smoke, sweetness).then((result) => {return result});
matched.push(first_run);
// ---(return final data)---
return matched
}
example of db object
{
_id: mongoid,
meta-data: {
pagemd:{some data},
name: whiskyname
age: whiskyage,
price: price
},
attributes: {
body: "3",
richness: "3",
smoke: "0",
sweetness: "3",
some other data ...
}
}
When I hit the route in postman the JSON data looks like:
{
response: 200,
data: {}
}
and when I console.log() out matched from within the style match function after I have pushed the it prints [ Promise(pending) ] which I don't understand.
if I console.log() the result from within the .then() I get an empty array.
I have tried using the populate() method after running the find which does technically work, but instead of only returning data that matches it returns every entry in the collection so I think I am doing something wrong there, but I also don't see why I would need to use the .populate() function to access the nested object.
Am I doing something totally wrong here?
I should also mention that the route and the matching functions are in different files just to try and keep things simple.
Thanks for any answers.
just posting an answer as I seem to have fixed this.
Issue was with my .find() function, needed to pass in the items to search by and then also a call back within the function to return error/data. I'll leave the changed code below.
new function
const fetch_matches_using = async function(body, richness, smoke, sweetness) {
const data = await WhiskyModel.find({
'attributes.body': body,
'attributes.richness': richness,
'attributes.smoke': smoke,
'attributes.sweetness': sweetness
}, (error, data) => { // new ¬
if (error) {
return error;
}
if (data) {
console.log(data)
return data
}
});
return data; //new
}
There is still an issue with sending the found results back to the route but this is a different issue I believe. If its connected I'll edit this answer with the fix for that.

How to properly use Firestore's serverTimestamp to set the value of document thats being subscribed to?

Overview/Environment:
a react-native project v0.61.5
using react-native-firebase package
using actions to populate redux state, display firestore data through props
Goal:
Listen to collection of documents
use Firestore's FieldValue.serverTimestamp() to set time value of a document in said collection
use serverTimestamp's toMillis() function inside a snapshot listener
Observations/Errors:
when creating a document in said collection, the document gets created fine, and displays fine
while the doc/time value is created, the applications crashes due to the call to doc.get('time').toMillis() which is inside the snapshot listener: TypeError: null is not an object (evaluating 'doc.get('time').toMillis()')
So far I've tried all the suggestions noted here: Why is Firestore's 'doc.get('time').toMillis' producing a null Type Error?
Nothing seems to resolve this crash.
here's the snapshot listener:
.onSnapshot({ includeMetadataChanges: true }, (querySnapshot) => {
if (querySnapshot.metadata.fromCache && querySnapshot.metadata.hasPendingWrites) {
// ignore cache snapshots where new data is being written
return;
}
const messages = [];
querySnapshot.forEach((doc) => {
const estimateTimestamps = { serverTimestamps: 'estimate' }
const msg = doc.data();
msg.docId = doc.id;
msg.time = doc.get('time', estimateTimestamps).toMillis();
const timestamp = doc.get('time', estimateTimestamps);
if (timestamp) {
msg.time = timestamp.toMillis();
} else {
debugger
console.error(doc.id + ' is missing "time" field!');
}
messages.push(msg);
});
dispatch({ type: types.LOAD_MSGS, payload: messages });
resolve();
});
Here's how document is created:
const addMsg = (msg, userConvos) => {
return firebase.firestore().collection('messages').add({
time: firebase.firestore.FieldValue.serverTimestamp(),
sender: msg.sender,
read: false,
userConvos: [userConvos.sender, userConvos.receiver],
content: {
type: 'msg',
data: msg.text
}
});
};
I understand the value may be null fora small amount of time, I need a way to prevent the app from crashing during that period.
The error is pointing you to this code:
doc.get('time').toMillis()
It's saying that doc.get('time') returns null, and therefore, you can't call toMillis() on that.
The answer to the question you linked to explains exactly why that is. If it's still unclear, I suggest reading it again. The timestamp will simply be null if the event that a server timestamp has not reached the server.
Perhaps you meant to check if the timestamp is null like this, without calling toMillis():
msg.isPending = doc.get('time') === null;
After #DougStevenson helped me understand. Somewhat confusing but its important to understand the listener is constantly running, so once the Time value is available it will be set, so no real performance issues. I reformulated my approach to this, its working:
querySnapshot.forEach((doc) => {
const estimateTimestamps = { serverTimestamps: 'estimate' }
const msg = doc.data();
msg.docId = doc.id;
msg.time = doc.get('time', estimateTimestamps).toMillis();
const timestamp = doc.get('time', estimateTimestamps)
if (doc.get('time') !== null) {
msg.time = doc.get('time').toMillis()
}
messages.push(msg);
});

How to create multiple copies of a deeply nested immutable Object?

I'm trying to get data from firebase/firestore using javascript so i made a function where i get my products collection and passing this data to reactjs state.products by setState() method
My goal is to pass these products to my react state but also keeping the original data and not changing it by manipulating it. I understand that in JavaScript whenever we are assigning the objects to a variable we are actually passing them as a reference and not copying them, so that' why i used the 3 dots (spread syntax) to copy firestore data into tempProducts[] same way to copy it in virgins[]
getData = () => {
let tempProducts = [];
let virgins = [];
db.collection("products")
.get()
.then(querySnapshot => {
querySnapshot.forEach(item => {
const singleItem = { ...item.data() };
virgins = [...virgins, singleItem];
tempProducts = [...tempProducts, singleItem];
});
tempProducts[0].inCart = true;
this.setState(
() => {
return { products: tempProducts };
},
() => {
console.log(
"this.state.products[0].inCart: " + this.state.products[0].inCart
);
console.log("tempProducts[0].inCart: " + tempProducts[0].inCart);
console.log("virgins[0].inCart: " + virgins[0].inCart);
}
);
});
};
then calling this method in componentDidMount
componentDidMount() {
this.getData();
}
So I changed the first product inCart value in tempProducts to true when I console log tempProducts value and state.products value it gives me true all fine but I'm expecting to get false when i console log virgins value but i did not. I should also mention that all inCart values are false in firestore data.
I solved the issue by passing the original firestore data to virgins[] instead of the singleItem as it is an object referenced by tempProducts[] like so virgins = [...virgins, item.data()]; also it works if i copied the singleItem object instead of referencing it like so virgins = [...virgins, { ...singleItem }]; keep in mind that i have no idea if this solutions are in fact efficient (not "memory waste") or not.

How to update Array field?

Im using Firebase Firestore and want to update an array field under a userprofile with the latest chat thread's id.. Im guessing that I have to pull the entire array (if it exists) from the chat node under that user, then I need to append the new id (if it doesnt exist) and update the array.. It works when theres only 1 value in the array then it fails after that with the following error:
Transaction failed: { Error: Cannot convert an array value in an array value.
at /user_code/node_modules/firebase-admin/node_modules/grpc/src/node/src/client.js:554:15 code: 3, metadata: Metadata { _internal_repr: {} } }
and here is my firebase cloud function, can anyone tell me where im going wrong ?
exports.updateMessages = functions.firestore.document('messages/{messageId}/conversation/{msgkey}').onCreate( (event) => {
/// console.log('function started');
const messagePayload = event.data.data();
const userA = messagePayload.userA;
const userB = messagePayload.userB;
// console.log("userA " + userA);
// console.log("userB " + userB);
// console.log("messagePayload " + JSON.stringify(messagePayload, null, 2) );
const sfDocRef = admin.firestore().doc(`users/${userB}`);
return admin.firestore().runTransaction( (transaction) => {
return transaction.get(sfDocRef).then( (sfDoc) => {
const array = [];
array.push(...[event.params.messageId, sfDoc.get('chats') ]);
transaction.update(sfDocRef, { chats: array } );
});
}).then( () => {
console.log("Transaction successfully committed!");
}).catch( (error) => {
console.log("Transaction failed: ", error);
});
});
You're nesting arrays in your code here:
const array = [];
array.push(...[event.params.messageId, sfDoc.get('chats') ]);
This leads to an array with two values, the first one being the new messageId and the second value contains an array all of your previous values, e.g.
[ "new message id", ["previous id", "older id"] ]
This type of nested array is something that Firestore (apparently) doesn't allow to be stored.
The solution is simple:
const array = [event.params.messageId, ...sfDoc.get('chats')];
The fact that you have to first load the array to then add a single element to it is one of reasons Firebasers recommend not storing data in arrays. Your current data looks like it'd be better off as a set, as shown in the Firestore documenation:
{
"new message id": true,
"previous id": true,
"older id": true
}
That way adding a chat ID is as simple as:
sfDocRef.update({ "chats."+event.params.messageId, true })
I have looked further into the matter, and I would follow the advice that Frank gave you in his post; allocate the data in collections rather than with arrays as they have greater versatility for Firebase 1. Researching under the examples listed in the Firebase website looking for anything related to a chat, I’ve found the data structure and code for messages that are used by Firechat as they might be of use for you.
In the source code, they use a collection for the their message-id -userId pair with the following topology 2 :
The exact way how the saving is executed at the repository is 3 :
It executes an append of the message into the Room-id collection. Instead of this structure, you could use an userID - messageID pair as it might fit you better.

Categories

Resources