I have some code from the previous version of Strapi that works, and the beta version of controllers is much different. There is multipart / santization boilerplate added and something has changed. Do not understand how to integrate my order object and stripe charge.
Here is the boilerplate added:
module.exports = {
async create(ctx) {
// New Boilerplate added with Strapi Beta - how to integrate this with custom stuff below?
let entity;
if (ctx.is('multipart')) {
const { data, files } = parseMultipartData(ctx);
entity = await service.create(data, { files });
} else {
entity = await service.create(ctx.request.body);
}
return sanitizeEntity(entity, { model });
}
}
Here is my custom code (The controller name is Order.js)
const { address, amount, products, postalCode, token, city } = ctx.request.body;
// Send charge to Stripe
const charge = await stripe.charges.create({
amount: amount * 100,
currency: 'usd',
description: `Order ${new Date(Date.now())} - User ${ctx.state.user.id}`,
source: token
});
// Create order in database
const order = await strapi.services.order.add({
user: ctx.state.user.id,
address,
amount,
products,
postalCode,
city
});
It looks like I would add my code to the second part of the if statement since it's not multipart, but not user if "entity" is a real variable name Strapi needs or a placeholder variable I rename to "order" Code works fine in Alpha, but read the Strapi docs and there is no explanation to how to use this structure with "entity", {model} and "data" variables.
In the previous version of Strapi, to upload a file to a new entry you had to first, create your entry and two, upload the image and specify the entry you want to link this image.
Now with the multipart, you can send your image at the same time as your entry attributes.
Now about your use-case, service. has to be replaced by strapi.api.order.service.order in your case.
I agree the doc is not clear! I will update that right now.
const { parseMultipartData, sanitizeEntity } = require('strapi-utils');
module.exports = {
async create(ctx) {
// New Boilerplate added with Strapi Beta - how to integrate this with custom stuff below?
let entity;
if (ctx.is('multipart')) {
const { data, files } = parseMultipartData(ctx);
entity = await strapi.api.order.services.order.create(data, { files });
} else {
const { address, amount, products, postalCode, token, city } = ctx.request.body;
// Send charge to Stripe
const charge = await stripe.charges.create({
amount: amount * 100,
currency: 'usd',
description: `Order ${new Date(Date.now())} - User ${ctx.state.user.id}`,
source: token
});
entity = await strapi.api.order.services.order.create({
user: ctx.state.user,
address,
amount,
products,
postalCode,
city
});
}
return sanitizeEntity(entity, { model: strapi.query('order').model });
}
}
Related
As the title suggests, I am trying to implement Stripe into my flutter app using the stripe extension for Firebase and using Javascript Firebase Cloud Functions for the server side. I believe the issue is on the server side when I try to create a customer and create a payment intent.
The server side code is here:
const functions = require("firebase-functions");
const stripe = require("stripe")("my test secret key"); // this works fine for the other stripe functions I am calling
exports.stripePaymentIntentRequest = functions.https.onRequest(
async (req, res) => {
const {email, amount} = req.body;
try {
let customerId;
// Gets the customer who's email id matches the one sent by the client
const customerList = await stripe.customers.list({
email: email,
limit: 1,
});
// Checks the if the customer exists, if not creates a new customer
if (customerList.data.length !== 0) {
customerId = customerList.data[0].id;
} else {
const customer = await stripe.customers.create({
email: email,
});
customerId = customer.data.id;
}
// Creates a temporary secret key linked with the customer
const ephemeralKey = await stripe.ephemeralKeys.create(
{customer: customerId},
{apiVersion: "2022-11-15"},
);
// Creates a new payment intent with amount passed in from the client
const paymentIntent = await stripe.paymentIntents.create({
amount: parseInt(amount),
currency: "gbp",
customer: customerId,
});
res.status(200).send({
paymentIntent: paymentIntent.client_secret,
ephemeralKey: ephemeralKey.secret,
customer: customerId,
success: true,
});
} catch (error) {
res.status(404).send({success: false, error: error.message});
}
},
);
Then my client-side code is:
try {
// 1. create payment intent on the server
final response = await http.post(
Uri.parse(
'https://us-central1-clublink-1.cloudfunctions.net/stripePaymentIntentRequest'),
headers: {"Content-Type": "application/json"},
body: json.encode({
'email': email,
'amount': amount.toString(),
}),
);
final jsonResponse = json.decode(response.body);
if (jsonResponse['error'] != null) {
throw Exception(jsonResponse['error']);
}
log(jsonResponse.toString());
//2. initialize the payment sheet
await Stripe.instance.initPaymentSheet(
paymentSheetParameters: SetupPaymentSheetParameters(
paymentIntentClientSecret: jsonResponse['paymentIntent'],
merchantDisplayName: 'Clublink UK',
customerId: jsonResponse['customer'],
customerEphemeralKeySecret: jsonResponse['ephemeralKey'],
style: ThemeMode.dark,
),
);
await Stripe.instance.presentPaymentSheet();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Payment completed!')),
);
} catch (e) {
if (e is StripeException) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error from Stripe: ${e.error.localizedMessage}'),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
}
}
I basically copied the flutter_stripe documentation to create the payment sheet with the necessary changes. Any help would be greatly appreciated!
Ok so I found what worked! I was being given a 403 status error with reason "forbidden". This meant I had to go to the google cloud console and update the permissions in the cloud functions tab.
UPDATED
I am trying to query my pricing data based on the user it is saved under and send it back in my stripe checkout cloud function. It keeps giving me an error stating that no value has been assigned to my variables when I have. I read the docs on how to do this, but I kinda got confused towards the end. I then saw something similar to what I was trying to do on a couple of other places, but then I got the codes mixed up. How can I call the variable names from the other function to put them in the pricing info?
Sources I used:
How to query specific data from Firebase using Cloud functions
How to run query from inside of Cloud function?
https://firebase.google.com/docs/database/extend-with-functions
https://firebase.google.com/docs/functions/database-events
This is how my data is set up in my real time database:
studiopick
studio
users
Gcsh31DCGAS2u2XXLuh8AbwBeap1
email : "Test#gmail.com"
firstName : "Test"
lastName : "one"
phoneNumber : "2223334567"
prices
| roomA
| serviceOne
| numberInput : "300"
| serviceType : "mix n master"
studioName : "Studio One"
uid : "Gcsh31DCGAS2u2XXLuh8AbwBeap1"
This is how my cloud function is set up:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
let price;
let info;
admin.initializeApp(functions.config().firebase);
exports.createStripeCheckout = functions.https.onCall(async (data, context) => {
const querySnapshot = await ref
.orderByChild("numberInput, serviceInput")
.equalTo(price, info)
.once("value");
// Stripe init
const stripe = require("stripe")(functions.config().stripe.secret_key);
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
mode: "payment",
success_url: "http://localhost:5500/success",
cancel_url: "http://localhost:5500/cancel",
shipping_address_collection: {
allowed_countries: ["US"],
},
line_items: [
{
quantity: 1,
price_data: {
currency: "usd",
unit_amount: price * 100, // 10000 = 100 USD
product_data: {
name: info,
},
},
},
],
});
return {
id: session.id,
};
});
exports.stripeWebhook = functions.https.onRequest(async (req, res) => {
const stripe = require("stripe")(functions.config().stripe.token);
let event;
try {
const whSec = functions.config().stripe.payments_webhook_secret;
event = stripe.webhooks.constructEvent(
req.rawBody,
req.headers["stripe-signature"],
whSec
);
} catch (err) {
console.error("⚠️ Webhook signature verification failed.");
return res.sendStatus(400);
}
const dataObject = event.data.object;
await admin.firestore().collection("orders").doc().set({
checkoutSessionId: dataObject.id,
paymentStatus: dataObject.payment_status,
shippingInfo: dataObject.shipping,
amountTotal: dataObject.amount_total,
});
return res.sendStatus(200);
});
Cloud Functions run in their own isolated containers when deployed.
When you call your retreivefromdatabase function, an instance of your Cloud Functions code is spun up, then the request is handled and the instance hibernates when it finishes (and it will be shut down later if not called upon again). When you call your createStripeCheckout function, a new instance of your Cloud Functions code is spun up, then the request is handled and the instance hibernates (and shuts down later).
Because these functions are hosted and handled by separate instances, you can't pass information between functions using global state.
Unfortunately the local testing emulator doesn't completely isolate functions in the same way (nor does it emulate throttling), which is what misled you to believe that it should function just fine in production.
I am trying to do a content upload progress bar using Firebase storage, but I am having some problems returning the task from my function.
I have implemented a Firebase Singleton, using React Context API. In the Firebase component I have multiples functions, one of them called 'uploadContent'
Here is the code:
uploadContent = async (postInfo) => {
const { uri, description, location, tags } = postInfo;
// Post UUID
const postId = uuid();
// Upload to firestore
const data = {
id: postId,
description,
location,
tags,
time: firestore.Timestamp.fromDate(new Date()), // The time when the image is uploaded
likes: [], // At the first time, when a post is created, zero users has liked it
comments: [], // Also, there aren't any comments
};
await this.db
.collection("posts")
.doc(this.auth.currentUser.uid)
.collection("userPosts")
.add(data);
// Create a storage referece
const storageRef = firebase.storage().ref("photos").child(postId);
// Uri to Blob
const response = await fetch(uri);
const blob = await response.blob();
// Upload to storage
const task = storageRef.put(blob);
return task;
};
The thing is, that when I call this function from my uploader component, and try to use one of the returned object functions I get "[Unhandled promise rejection: TypeError: undefined is not a function (near '...task.on...')]", and I don't know how to solve this problem.
Pd: If I call this function inside the "uploadContent" method (where I create the task), it works fine, but I need to return the task...
Here is the code of the function where I call my firebase method:
const upload = async () => {
const { firebase, navigation } = props;
console.log("Uploading...");
// Prepare post information
const postInfo = {
uri: photo.uri,
description: descriptionInput.current.props.value,
location: locationName, // TODO - Object with the location coords too
tags: [], // TODO - users tagged
};
// Upload to firebase
const task = await firebase.uploadContent(postInfo);
task.on("state_changed", (taskSnapshot) => {
console.log(
`${taskSnapshot.bytesTransferred} transferred out of ${taskSnapshot.totalBytes}`
);
});
// navigation.navigate("Profile"); // TODO: route params -> task
};
Any ideas? Thanks.
I wasted so much time on a similar problem, but solved it!
In this part of the code, you are resolving the task (that is implemented with promise) into the value undefined.
// Upload to firebase
const task = await firebase.uploadContent(postInfo);
Just remove the await to use the task itself.
I want to get data from my database on fire base and want to save that data to amount amount: snapshot, I did apply this const snapshot = firestore.collection('payment').doc(context.params.amount).get(); does that works in the same way? but I am getting an error that context is undefined.
I actually want to get data from database.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const firestore= admin.firestore();
const stripe = require('stripe')('');
const snapshot = firestore.collection('payment').doc(context.params.amount).get();
const customer = stripe.customers.create({
email: 'customer#example1.com',
});
stripe.customers
.create({
email: 'foo-customer#example.com',
})
.then((customer) => {
return stripe.customers.createSource(customer.id, {
source: 'tok_visa',
});
})
.then((source) => {
return stripe.charges.create({
amount: snapshot,
currency: 'usd',
customer: source.customer,
});
})
.then((charge) => {
// New charge created on a new customer
})
.catch((err) => {
// Deal with an error
});
you are trying to get amount through accessing params through context,
depends on your error, this means context is undefined which means you are trying to get params of undefined. you need to explain what is context means here, is it a global variable? is this code inside a cloud function? if yes you need to move this declaration const snapshot = firestore.collection('payment').doc(context.params.amount).get();
inside your cloud function ,
this is an example of firebase cloud function
I am working on billing in node.js and I created a new Model Stripecustomer where I save the stripe customer id and this customer's email. I kinda copied the main code form my other mongoose models and changed it. I had hoped to instantly start using it but when I tried to find a document in this model I got the following error:
⛔️ Error:
TypeError: Cannot read property 'findOne' of undefined
I have looked at it for half an hour and I can't see what I did wrong. Can anyone tell me where I did something wrong?
workspace.controller.js: here is where I try to create a subscription. Stripecustomer is undefined, but I don't get why since I imported it on top
const stripe = require("stripe")("sk_test_dvebbZQPA4Vk8kKZaEuN32sD");
const {
Group, User, Workspace, Stripecustomer
} = require('../models');
const { sendErr } = require('../../utils');
const billing = async (req, res) => {
try {
const email = 'tijl.declerck#outlook.com';
// get the payment plan
const plan = await stripe.plans.retrieve('plan_EK1uRUJLJcDS6e');
// get the stripe customer or create a new one
let customer;
const existingCustomerDoc = await Stripecustomer.findOne({ email: email });
// if we couldn't find an existing customer in our database...
if (!existingCustomerDoc[0]) {
// then we create a new customer
customer = await stripe.customers.create({
email,
source: 'src_18eYalAHEMiOZZp1l9ZTjSU0'
});
} else {
// we retrieve this customer in stripe
customer = await stripe.customers.retrieve(existingCustomerDoc.customer_id);
}
// subscribe the customer to the plan
// You now have a customer subscribed to a plan.
// Behind the scenes, Stripe creates an invoice for every billing cycle.
// The invoice outlines what the customer owes, reflects when they will be or were charged, and tracks the payment status.
// You can even add additional items to an invoice to factor in one-off charges like setup fees.
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ plan: plan.id }]
});
res.status(200).json({
message: 'payment complete',
obj: subscription
});
} catch (err) {
return sendErr(res, err);
}
};
stripecustomer.model.js
const mongoose = require('mongoose');
const { Schema } = mongoose;
const stripeCustomerSchema = new Schema({
email: {
type: String,
required: true
},
customer_id: {
type: String,
required: true
}
});
const Stripecustomer = mongoose.model('Stripecustomer', stripeCustomerSchema);
module.exports = Stripecustomer;
The error is probably coming from ur models index.js file, can u share ur models/index.js file to make this more clear, because findOne is a mongoose function, if u get undefined it means Stripecustome is not an instance of a mongoose model