After a user registers on my app I want them to add a payment method. Their stripe customer account is created as soon as they register and from there they are transferred to the 'AddPaymentMethod' screen. As soon as the ' AddPaymentMethod' screen appears, I send a request to my server to create a setupIntent.
Creating Setup Intent:
exports.createSetupIntent = functions.https.onCall(async (data, context) => {
const userId = data.userId;
const snapshot = await db
.collection("development")
.doc("development")
.collection("users")
.doc(userId).get();
const customerId = snapshot.data().customer_id;
const setupIntent = await stripe.setupIntents.create({
customer: customerId,
});
const clientSecret = setupIntent.client_secret;
return {
clientsecret: clientSecret,
};
});
Calling the function when the screen appears on my client (This successfully creates the client secret key and stores it in a variable in the frontend):
FirebaseReferenceManager.functions.httpsCallable("createSetupIntent").call(["userId": Auth.auth().currentUser?.uid]) { (response, error) in
if let error = error {
print(error.localizedDescription)
}
if let response = (response?.data as? [String: Any]) {
let clientSecretKey = response["clientsecret"] as! String?
self.clientSecret = clientSecretKey ?? "-"
print("created client secret key: \(clientSecretKey!)")
}
}
Next, the user enters their credit card information and creates a payment method. Here is the function on my server:
exports.createPaymentMethod = functions.https.onCall(async (data, context) => {
const number = data.number;
const expMonth = data.expMonth;
const expYear = data.expYear;
const cvc = data.cvc;
const paymentMethod = await stripe.paymentMethods.create({
type: "card",
card: {
number: number,
exp_month: expMonth,
exp_year: expYear,
cvc: cvc,
},
});
const pmId = paymentMethod.id;
return {
paymentMethodId: pmId,
};
});
I call this function from the frontend when the user presses the "Save payment method" button. This successfully creates a payment method and returns the payment method id which is stored in a variable on the front end.
Lastly, using the client secret id and payment method id that was returned from the previous functions, I call the last function to confirm the setupIntent.
This function is called when a payment method is created successfully:
exports.confirmSetupIntent = functions.https.onCall(async (data, context) => {
const clientSecretKey = data.clientSecretKey;
const paymentMethodId = data.paymentMethodId;
const setupIntent = await stripe.setupIntents.confirm(
clientSecretKey,
{payment_method: paymentMethodId}
);
});
This is how the createPaymentMethod and confirmSetupIntent functions are called from the frontend:
FirebaseReferenceManager.functions.httpsCallable("createPaymentMethod").call(["number": self.cardNumber, "expMonth": self.expMonth, "expYear": "20\(self.expYear)", "cvc": self.cvvCode]) { (response, error) in
if let error = error {
print("error occured when creating payment method: \(error.localizedDescription)")
}
if let response = response?.data as? [String: Any] {
let paymentMethodId = response["paymentMethodId"] as! String?
self.paymentMethodID = paymentMethodId ?? "-"
print(paymentMethodId!)
FirebaseReferenceManager.functions.httpsCallable("confirmSetupIntent").call(["clientSecretKey": self.clientSecret, "paymentMethodId": self.paymentMethodID]) { (response, error) in
if let error = error {
print("error occured when confirming setup intent: \(error.localizedDescription)")
}
print("setup intent confirmed")
}
}
}
In the debug console on the frontend it says that the error from confirming the setupIntent was INTERNAL. When I check the logs on my server I it says: StripeInvalidRequestError: No such setupintent: 'seti_...'
Note that I am using SwiftUI and custom screens/textfields for the stripe integration.
Any help is appreciated!
The No such setupintent
error indicates you have a mismatch in your API keys, and you should double check that your server secret key and client publishable are a matched pair for the same account and both for test mode, eg.
Of greater concern is that you appear to be passing payment details to your server to create the payment method. This is not recommended, and has significant PCI Compliance implications. Instead of creating the payment method like this on your server, you should use Elements and provide a reference to the Card Element when you use confirmCardSetup
(docs):
stripe.confirmCardSetup(
clientSecret,
{
payment_method: {
card: cardElement,
},
}
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With