我正在从 codecanyon 重新申请一个项目,当我尝试在其中设置条带付款时,我遇到了一些问题我是 stripe 和 firebase 的新手,即使按照文档给定的问题也没有解决。
我在 friebase stripeWebhook 函数中获取的日志
{ "textPayload": "error TypeError: Cannot read properties of undefined (reading 'data')", "insertId": "64579c3a00090291aaae197c", "resource": { "type": "cloud_function", "labels": { "region": "us-central1", "project_id": "doctor-app-57d5a", "function_name": "stripeWebhook" } }, "timestamp": "2023-05-07T12:40:26.590481Z", "labels": { "instance_id": "00c61b117c339eb21c36ca478c21da086a8cad9d70243f1bd85a81514efef99af71c2c7f5a0f19be12f16a8d14b8fcaf19a660988e2d1488d71f", "execution_id": "qd9n9rwpctsj" }, "logName": "projects/doctor-app-57d5a/logs/cloudfunctions.googleapis.com%2Fcloud-functions", "trace": "projects/doctor-app-57d5a/traces/04069e50580a94c2fe0eea1b931f6265", "receiveTimestamp": "2023-05-07T12:40:26.893559408Z" }
如果有人可以帮助我解决问题,我也准备付款我只是希望这个问题得到解决
下面是文件命名stripe-functions.js的代码
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.refundStripe = exports.stripeWebhook = exports.purchaseTimeslot = void 0;
const functions = require("firebase-functions");
const stripe_1 = require("stripe");
const collections_1 = require("./collections");
const constants_1 = require("./constants");
const firebase_admin_1 = require("firebase-admin");
const payment_functions_1 = require("./payment-functions");
const notification_function_1 = require("./notification-function");
const collections_2 = require("./collections");
const timeslot_functions_1 = require("./timeslot-functions");
const stripe = new stripe_1.default(process.env.STRIPE_SECRET_KEY, {
apiVersion: "2020-08-27",
});
/**
*
* function when client purchase timeslot, by default we use Stripe payment gateway
* you can change this to your payment gateway by following the stripe structure of purchaing the timeslot
*/
exports.purchaseTimeslot = functions.https.onCall(async (request, response) => {
try {
const choosedTimeSlot = (await collections_1.timeSlotCol.doc(request.timeSlotId).get()).data();
if (!choosedTimeSlot) {
throw "choosed timeslot is not found";
}
const paymentIntent = await stripe.paymentIntents.create({
amount: choosedTimeSlot.price * 100,
currency: constants_1.CURRENCY,
payment_method_types: ["card"],
});
await collections_1.orderCol.add({
createdAt: firebase_admin_1.firestore.Timestamp.fromDate(new Date()),
timeSlotId: request.timeSlotId,
userId: request.userId,
charged: false,
stripePaymentId: paymentIntent.id,
status: collections_1.OrderStatus.notPay,
paymentMethod: payment_functions_1.PaymentMethod.Stripe,
});
return paymentIntent.client_secret;
}
catch (error) {
throw error;
}
});
/**
* this function is for stripe webhook, when client successfully purchase the timeslot, stripe will call this webhook
* make sure you have setup the stripe webhook by following this tutorial {@link https://halodoctor.netlify.app/docs/payment-gateway/stripe-webook}
*/
exports.stripeWebhook = functions.https.onRequest(async (request, response) => {
var _a, _b;
let event;
try {
if (process.env.STRIPE_WEBHOOK_SECRET == undefined) {
throw new Error("Stripe webhook secret is undefined, please check .env file");
}
const stripeWebhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
event = stripe.webhooks.constructEvent(request.rawBody, request.headers["stripe-signature"], stripeWebhookSecret);
}
catch (error) {
console.error("Webhook signature verification failed");
response.sendStatus(400);
return;
}
switch (event.type) {
case "payment_intent.succeeded":
try {
const amount = request.body.data.object.amount_received / 100;
const currency = request.body.data.object.currency;
const linkReceipt = request.body.data.object.charges.data[0].receipt_url;
let order = await collections_1.orderCol
.where("stripePaymentId", "==", request.body.data.object.id)
.get();
let orderRef = order.docs[0];
orderRef.ref.update({
charged: true,
amount: amount,
status: payment_functions_1.PaymentStatus.PaymentSuccess,
linkReceipt: linkReceipt,
currency: currency,
});
//Get user info who book this timeslot
let bookByWho = (await collections_1.usersCol.doc(orderRef.data().userId).get()).data();
const timeSlot = await collections_1.timeSlotCol
.doc(orderRef.data().timeSlotId)
.get();
const timeSlotData = timeSlot.data();
if (!timeSlotData) {
throw "selected timeslot is null or undefined";
}
const doctor = await collections_1.doctorCol.doc(timeSlotData.doctorId).get();
timeSlot.ref.update({
charged: true,
available: false,
bookByWho: {
userId: orderRef.data().userId,
displayName: bookByWho === null || bookByWho === void 0 ? void 0 : bookByWho.displayName,
photoUrl: (bookByWho === null || bookByWho === void 0 ? void 0 : bookByWho.photoUrl) ? bookByWho === null || bookByWho === void 0 ? void 0 : bookByWho.photoUrl : "",
},
status: "booked",
doctor: {
doctorName: (_a = doctor.data()) === null || _a === void 0 ? void 0 : _a.doctorName,
doctorPicture: (_b = doctor.data()) === null || _b === void 0 ? void 0 : _b.doctorPicture,
},
purchaseTime: firebase_admin_1.firestore.Timestamp.fromDate(new Date()),
});
await (0, notification_function_1.orderedTimeslotNotification)(doctor.id);
await (0, notification_function_1.paymentSuccessNotification)(orderRef.data().userId, orderRef.id);
console.log("payment success");
break;
}
catch (error) {
console.log("error " + error);
}
case "payment_intent.canceled":
console.log("failed payment ");
break;
// ... handle other event types
default:
console.log(`Unhandled event type ${event.type}`);
}
// Return a 200 response to acknowledge receipt of the event
response.send();
});
/**
by default we use stripe refund system if you use other payment gateway you need to change this function to
you can just follow the structure of the function
*/
exports.refundStripe = functions.https.onCall(async (request, response) => {
try {
const { timeSlotId } = request;
const orderRef = await collections_1.orderCol
.where("timeSlotId", "==", request.timeSlotId)
.get();
const order = orderRef.docs[0];
const refund = await stripe.refunds.create({
payment_intent: order.data().stripePaymentId,
});
if (refund.status === "succeeded") {
let refundData = await collections_2.refundCol.add({
createdAt: firebase_admin_1.firestore.Timestamp.fromDate(new Date()),
timeSlotId: request.timeSlotId,
paymentId: order.data().stripePaymentId,
status: refund.status,
amount: refund.amount,
refundId: refund.id,
currency: refund.currency,
});
await (0, timeslot_functions_1.refundTimeslot)(timeSlotId, refundData.id);
console.log("refund success : " + JSON.stringify(refund));
}
else {
throw "refund failed";
}
}
catch (err) {
throw err;
}
});
//# sourceMappingURL=stripe-functions.js.map
还有一个文件命名为 stripe-function.js
"use strict";
const functions = require("firebase-functions");
const stripe = require("stripe")(functions.config().stripe.token);
//const stripe = require('stripe')('testing');
const admin = require("firebase-admin");
admin.initializeApp();
const db = admin.firestore();
const notificationFunction = require("./notification-function");
const timeslotFunction = require("./timeslot-function");
const { firestore } = require("firebase-admin");
exports.purchaseTimeslot = functions.https.onCall(async (request, response) => {
try {
let purchasedTimeSlot = await db
.collection("DoctorTimeslot")
.doc(request.timeSlotId)
.get();
purchasedTimeSlot = purchasedTimeSlot.data();
const paymentIntent = await stripe.paymentIntents.create({
amount: purchasedTimeSlot.price * 100,
currency: "usd",
payment_method_types: ["card"],
});
console.log("user id request : " + request.userId);
db.collection("Order").add({
createdAt: firestore.Timestamp.fromDate(new Date()),
timeSlotId: request.timeSlotId,
userId: request.userId,
charged: false,
stripePaymentId: paymentIntent.id,
status: "notPay",
});
return paymentIntent.client_secret;
}
catch (e) {
throw e;
}
});
exports.stripeWebhook = functions.https.onRequest(async (request, response) => {
let event;
try {
const stripeWebhookSecret = functions.config().stripe.webhook_secret;
event = stripe.webhooks.constructEvent(request.rawBody, request.headers["stripe-signature"], stripeWebhookSecret);
console.log("Webhook signature verification done");
}
catch (error) {
console.error("Webhook signature verification failed");
return response.sendStatus(400);
}
// Handle payment successfully event
switch (event.type) {
case "payment_intent.succeeded":
try {
console.log('Paymentintent was successful: ', paymentIntent.id)
const amount = request.body.data.object.amount_received / 100;
const currency = request.body.data.object.currency;
const linkReceipt = request.body.data.object.charges.data[0].receipt_url;
//Update Order
let order = await db
.collection('Order')
.where("stripePaymentId", "==", request.body.data.object.id)
.get()
.then(async (querySnapshot) => {
let orderData = {};
querySnapshot.forEach(function (doc) {
console.log(doc.id, " => ", doc.data());
doc.ref.update({
charged: true,
amount: amount,
status: "payment_success",
linkReceipt: linkReceipt,
currency: currency,
});
orderData = doc.data();
});
return orderData;
});
//Get user info who book this timeslot
let bookByWho = await db.collection("Users").doc(order.userId).get();
//Update DoctorTimeslot
let timeSlotRef = await db
.collection("DoctorTimeslot")
.doc(order.timeSlotId)
.get();
//Get doctor detail data
let doctor = await db
.collection("Doctors")
.doc(timeSlotRef.data().doctorId)
.get();
await timeSlotRef.ref.update({
charged: true,
available: false,
bookByWho: {
userId: order.userId,
displayName: bookByWho.data().displayName,
photoUrl: bookByWho.data().photoUrl
? bookByWho.data().photoUrl
: "",
},
status: "booked",
doctor: {
doctorName: doctor.data().doctorName,
doctorPicture: doctor.data().doctorPicture,
},
purchaseTime: firestore.Timestamp.fromDate(new Date()),
});
//send notification to doctor
await notificationFunction.orderedTimeslotNotification(doctor.id);
console.log("payment success");
break;
}
catch (error) {
console.log("error the error is here" + error);
}
case "payment_intent.canceled":
const session = event.data.object;
// Then define and call a function to handle the event checkout.session.async_payment_succeeded
console.log("failed payment ");
break;
// ... handle other event types
default:
console.log(`Unhandled event type ${event.type}`);
}
// Return a 200 response to acknowledge receipt of the event
response.send();
});
exports.refundTimeslot = functions.https.onCall(async (request, response) => {
try {
console.log("timeslot id : " + request.timeSlotId);
let orderSnapshot = await db
.collection("Order")
.where("timeSlotId", "==", request.timeSlotId)
.get();
let order = orderSnapshot.docs[0];
console.log("order obejct : " + JSON.stringify(order));
console.log("stripe payment id : " + order.data().stripePaymentId);
const refund = await stripe.refunds.create({
payment_intent: order.data().stripePaymentId,
});
console.log("refund object : " + JSON.stringify(refund));
if (refund.status == "succeeded") {
let refundData = await db.collection("Refund").add({
createdAt: firestore.Timestamp.fromDate(new Date()),
timeSlotId: request.timeSlotId,
stripePaymentId: order.data().stripePaymentId,
status: refund.status,
amount: refund.amount,
refundId: refund.id,
currency: refund.currency,
});
await timeslotFunction.refundTimeslot(request.timeSlotId, refundData.id);
console.log("refund success : " + JSON.stringify(refund));
}
else {
throw "refund failed";
}
}
catch (e) {
throw e;
}
});
//# sourceMappingURL=stripe-function.js.map