我正在学习如何使用Stripe支付网关构建一个电子商务IOS应用程序,直到我运行这行代码:
paymentContext.requestPayment()
它给了我这个错误:
不能向没有有效卡的客户收费
我已经尝试了所有可能的解决方案,但还是得到相同的错误。
- 我认为它的发生是因为没有默认的src因为当我将卡添加给用户时它不会显示在条纹仪表板的客户列表中,就像这样👇
if let error = error {
print(error.localizedDescription)
return
}
}
这个在我的index.js
exports.attatchPaymentMethod = functions.https.onCall(async (data , context) => {
const paymentMethod = data.paymentMethod;
const customer = data.customer;
const updatedCustomer = await stripe.customers.update(
customer,
{invoice_settings: {default_payment_method: paymentMethod}}
);
return updatedCustomer
})
但是当我手动将卡添加到stripe网站时,没有问题发生,我没有得到这个错误。我发现app添加的卡片和网站添加的卡片有差异
这是应用程序添加的卡片:
app添加的卡片
这是网站添加的卡片:
网站添加的卡片
我花了很多很多的时间试图解决,但我不能😫😫😫
。是我完整的checkoutVC代码
class CheckoutViewController: UIViewController, CartItemDelegate {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var selectPaymentMethodBtn: UIButton!
@IBOutlet weak var serviceFeeLabel: UILabel!
@IBOutlet weak var delivaryFeeLabel: UILabel!
@IBOutlet weak var subtotalLabel: UILabel!
@IBOutlet weak var totalLabel: UILabel!
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
var paymentContext : STPPaymentContext!
var sourceParams : STPSourceParams!
var cardParams : STPCardParams!
var paymentResult : STPPaymentResult!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupTableView()
setupPaymentInfo()
setupStripeConfig()
}
func setupTableView(){
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: K.cellIdentifiers.CartItemCell, bundle: nil), forCellReuseIdentifier: K.cellIdentifiers.CartItemCell)
}
func setupPaymentInfo(){
serviceFeeLabel.text = StripeCart.proccessingFees.penniesToFormattedCurrency()
delivaryFeeLabel.text = StripeCart.shippingFees.penniesToFormattedCurrency()
subtotalLabel.text = StripeCart.subtotal.penniesToFormattedCurrency()
totalLabel.text = StripeCart.total.penniesToFormattedCurrency()
}
func setupStripeConfig(){
let config = STPPaymentConfiguration.shared
config.requiredBillingAddressFields = .full
let customerContext = STPCustomerContext(keyProvider: StripeApi)
paymentContext = STPPaymentContext(customerContext: customerContext, configuration: config, theme: .defaultTheme)
paymentContext.paymentAmount = StripeCart.total
paymentContext.delegate = self
paymentContext.hostViewController = self
}
@IBAction func selectPaymentMethodClicked(_ sender: UIButton) {
paymentContext.pushPaymentOptionsViewController()
}
func removeItem(product: Product) {
StripeCart.removeItemFromCart(item: product)
tableView.reloadData()
setupPaymentInfo()
paymentContext.paymentAmount = StripeCart.total
}
@IBAction func placeOrderClicked(_ sender: RoundedButton) {
paymentContext.requestPayment()
activityIndicator.startAnimating()
}
}
这是STPPaymentContextDelegate code
extension CheckoutViewController : STPPaymentContextDelegate {
func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
if let paymentMethod = paymentContext.selectedPaymentOption {
selectPaymentMethodBtn.setTitle(paymentMethod.label, for: .normal)
paymentResult = STPPaymentResult.init(paymentOption: paymentMethod)
var myPaymentMethod = (paymentResult.paymentMethod)
print("myPaymentMethod IS (myPaymentMethod)")
print("The paymentMethod is (paymentMethod) and the user is (UserService.user.stripID)")
let data : [String : Any] = [
"paymentMethod" : (paymentResult.paymentMethod?.stripeId)! ,
"customer" : UserService.user.stripID
]
Functions.functions().httpsCallable("setupIntent").call(data) { result, error in
if let error = error {
print(error.localizedDescription)
return
}
}
Functions.functions().httpsCallable("attatchPaymentMethod").call(data) { result, error in
if let error = error {
print(error.localizedDescription)
return
}
}
} else {
selectPaymentMethodBtn.setTitle("Select Method", for: .normal)
}
}
func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
let alertController = UIAlertController(title: "ERROR", message: error.localizedDescription, preferredStyle: .alert)
let cancel = UIAlertAction(title: "Cancel", style: .cancel) { action in
self.navigationController?.popViewController(animated: true)
}
let retry = UIAlertAction(title: "Retry", style: .default) { action in
self.paymentContext.retryLoading()
}
}
func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPPaymentStatusBlock) {
let idempotency = UUID().uuidString.replacingOccurrences(of: "-", with: "")
let data : [String : Any] = [
"total" : StripeCart.total ,
"customerID" : UserService.user.stripID ,
"idempotency" : idempotency
]
Functions.functions().httpsCallable("makeCharge").call(data) { result, error in
if let error = error {
print(error.localizedDescription)
completion(STPPaymentStatus.error , error)
return
}
StripeCart.clearCart()
self.tableView.reloadData()
self.setupPaymentInfo()
completion(STPPaymentStatus.success, nil)
}
}
func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
let title : String
let msg : String
switch status {
case .success:
activityIndicator.stopAnimating()
title = "SUCCESS"
msg = "THX FOR YOUR SHOPPING"
case .error:
activityIndicator.stopAnimating()
title = "ERROR"
msg = error?.localizedDescription ?? ""
print(error?.localizedDescription ?? "")
case .userCancellation:
return
}
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .default) { action in
self.navigationController?.popViewController(animated: true)
}
alert.addAction(action)
self.present(alert, animated: true , completion: nil)
}
}
这是我的index.js
const functions = require("firebase-functions");
const admin = require('firebase-admin');
const { user } = require("firebase-functions/v1/auth");
admin.initializeApp()
const stripe = require("stripe")(functions.config().stripe.secret_test_key)
var customer ;
exports.createStripeCustomer = functions.firestore.document('users/{id}').onCreate(async (snap, context) => {
const data = snap.data();
const email = data.email;
customer = await stripe.customers.create({ email: email })
return admin.firestore().collection('users').doc(data.id).update({stripID : customer.id})
});
exports.attatchPaymentMethod = functions.https.onCall(async (data , context) => {
const paymentMethod = data.paymentMethod;
const customer = data.customer;
const updatedCustomer = await stripe.customers.update(
customer,
{invoice_settings: {default_payment_method: paymentMethod}}
);
return updatedCustomer
})
exports.setupIntent = functions.https.onCall(async (data , context) => {
const paymentMethod = data.paymentMethod;
const customer = data.customer;
const setupIntent = await stripe.setupIntents.create({customer: customer , payment_method: paymentMethod , payment_method_types: ['card'] , usage: 'off_session'});
return setupIntent
})
exports.makeCharge = functions.https.onCall(async (data , context) => {
const customerID = data.customerID;
const total = data.total;
const idempotency = data.idempotency;
const uid = context.auth.uid;
if (uid === null) {
console.log('Illegal Access')
throw new functions.https.HttpsError('permission-denied' , 'Illegal access attempt')
}
return stripe.charges.create({
amount: total,
currency: 'JOD',
customer: customerID
}, {
idempotency_key: idempotency
}).then( _ => {
return
}).catch(err => {
console.log(err);
throw new functions.https.HttpsError('internal' , err)
});
})
exports.createEphemeralKey = functions.https.onCall(async (data, context) => {
const customerID = data.customer_id;
const stripeVersion = data.stripe_version;
const uid = context.auth.uid;
if (uid === null) {
console.log('Illegal Access')
throw new functions.https.HttpsError('permission-denied' , 'Illegal access attempt')
}
return stripe.ephemeralKeys.create(
{customer: customerID} ,
{stripe_version: stripeVersion}
).then((key) => {
return key
print(key)
}).catch((err) => {
console.log(err)
print(err)
throw new functions.https.HttpsError('internal' , 'unable to create ephemeral key')
})
})
这行代码:paymentContext.requestPayment ()它给了我这个错误:不能向没有活动卡的客户收取费用
嗯,你永远不会从直接调用STPPaymentContext上的requestPayment得到那个错误。只有当你在后端服务器上调用已弃用的/v1/charges API时,你才会得到。
所以错误实际上是来自你的服务器,这是你从Functions.functions().httpsCallable("makeCharge")
得到的响应
你没有共享后端代码,但你可能只使用客户ID调用Stripe Charge API,这会给你提到的错误,并且不会工作。首先手动将卡片添加到客户对象的解决方案并不是解决此问题的最佳方法。
实现它的正确方法是从didCreatePaymentResult
委托函数的PaymentResult中读取PaymentMethod对象ID:
paymentResult.paymentMethod
https://stripe.dev/stripe-ios/docs/Protocols/STPPaymentContextDelegate.html/c: @M@Stripe@objc (pl) STPPaymentContextDelegate (im) paymentContext: didCreatePaymentResult:完成:
https://stripe.com/docs/mobile/ios/basic submit-payment
现在你可能只使用客户ID调用Stripe Charge API,这会给你你提到的错误,并且不会工作。你应该调用后端服务器来获取一个PaymentIntent对象,然后调用STPPaymentHandler.shared().confirmPayment
来用委托回调中的PaymentMethod对象来确认这个PaymentIntent。在上面的Stripe docs链接中有一个示例代码。
总的来说,你正在做一些令人困惑的事情,因为你使用STPPaymentContext而不是新的PaymentSheet,这是默认的集成。在iOS应用程序中使用Stripe的指南在https://stripe.com/docs/payments/accept-a-payment?platform=ios,这是正确的集成使用,现在你使用的是一种遗留方法,文档较少,并且有一些令人困惑的行为。