Reputation: 67
I am attempting to setup STPpaymentmethodsviewcontroller using stripe documentation. it states I need to setup STPcustomercontext but it doesn't specify where. below is my current MYAPICLIENT code, code inside viewcontroller for STPpaymentmethodsviewcontroller, backend function code (customer is created upon account creation with firebase function working, writes customer id to database), firebase database structure, and error being presented upon attempt to load STPpaymentmethodsviewcontroller.
MYAPICLIENT:
class MyAPIClient: NSObject, STPEphemeralKeyProvider {
static let sharedClient = MyAPIClient()
var baseURLString: String? = "https://fresh-9883c.firebaseio.com/"
var baseURL: URL {
if let urlString = self.baseURLString, let url = URL(string: urlString) {
return url
} else {
fatalError()
}
}
func createCustomerKey(withAPIVersion apiVersion: String, completion: @escaping STPJSONResponseCompletionBlock) {
let url = self.baseURL.appendingPathComponent("ephemeral_keys")
Alamofire.request(url, method: .post, parameters: [
"api_version": apiVersion,
])
.validate(statusCode: 200..<500)
.responseJSON { responseJSON in
switch responseJSON.result {
case .success(let json):
completion(json as? [String: AnyObject], nil)
case .failure(let error):
completion(nil, error)
}
}
}
func completeCharge(_ result: STPPaymentResult, amount: Int, completion: @escaping STPErrorBlock) {
// 1
let url = baseURL.appendingPathComponent("charge")
// 2
let params: [String: Any] = [
"source": result.source.stripeID,
"amount": amount,
"currency": Constants.defaultCurrency,
"description": Constants.defaultDescription
]
// 3
Alamofire.request(url, method: .post, parameters: params)
.validate(statusCode: 200..<300)
.responseString { response in
switch response.result {
case .success:
completion(nil)
case .failure(let error):
completion(error)
}
}
}
}
CartViewController:
func handlePaymentMethodsButtonTapped(){
let customerContext = STPCustomerContext(keyProvider: MyAPIClient.sharedClient)
let paymentMethodsViewController = STPPaymentMethodsViewController(configuration: STPPaymentConfiguration.shared(), theme: STPTheme.default(), customerContext: customerContext, delegate: self)
let navigationController = UINavigationController(rootViewController: paymentMethodsViewController)
present(navigationController, animated: true)
}
@IBAction func confirmOrder(_ sender: UIButton) {
// append cart to database and dequeue in my orders (active , completed)
guard CartSingleton1.sharedInstance.orderDict.count > 0 else {
let alertController = UIAlertController(title: "Warning", message: "Cart is Empty", preferredStyle: .alert)
let alertAction = UIAlertAction(title: "OK", style: .default)
alertController.addAction(alertAction)
present(alertController, animated: true)
return
}
handlePaymentMethodsButtonTapped()
}
}
extension CartViewController: STPPaymentMethodsViewControllerDelegate, STPPaymentContextDelegate {
func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
self.navigationController?.popViewController(animated: true)
}
func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
}
func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPErrorBlock) {
MyAPIClient.sharedClient.completeCharge(paymentResult, amount: 100, completion: { (error: Error?) in
if let error = error {
completion(error)
} else {
completion(nil)
}
})
}
func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
switch status {
case .error:
print(error as Any)
case .success:
print("success")
//self.showReciept()
case .userCancellation:
return
}
}
func paymentMethodsViewController(_ paymentMethodsViewController: STPPaymentMethodsViewController, didSelect paymentMethod: STPPaymentMethod) {
let selectedPaymentMethod = paymentMethod
print("selectedPaymentMethod",selectedPaymentMethod)
}
func paymentMethodsViewController(_ paymentMethodsViewController: STPPaymentMethodsViewController, didFailToLoadWithError error: Error) {
print("did fail to load paymentmethodsview",error)
dismiss(animated: true)
}
func paymentMethodsViewControllerDidFinish(_ paymentMethodsViewController: STPPaymentMethodsViewController) {
dismiss(animated: true)
}
func paymentMethodsViewControllerDidCancel(_ paymentMethodsViewController: STPPaymentMethodsViewController) {
dismiss(animated: true)
}
}
Firebase Cloud Functions:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const logging = require('@google-cloud/logging')();
admin.initializeApp(functions.config().firebase);
const stripe = require('stripe')(functions.config().stripe.token);
const currency = functions.config().stripe.currency || 'USD';
//[START chargecustomer]
//charge the stripe customer whenever an amount is written to the realtime database
exports.createStripeCharge = functions.database.ref('/stripe_customers/{userId}/charges/{id}').onWrite((event) => {
const val = event.data.val();
if (val === null || val.id || val.error) return null;
return admin.database().ref(`/stripe_customers/${event.params.userId}/customer_id`).once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
const amount = val.amount;
const idempotency_key = event.params.id;
let charge = {amount, currency, customer};
if (val.source !== null) charge.source = val.source;
return stripe.charges.create(charge, {idempotency_key});
}).then((response) => {
return event.data.adminRef.set(response);
}).catch((error) => {
return event.data.adminRef.child('error').set(userFacingMessage(error));
}).then(() => {
return reportError(error, {user: events.params.userId});
});
});
// [end chargecustomer]]
// when user is created register them with stripe
exports.createStripeCustomer = functions.auth.user().onCreate((event) => {
const data = event.data;
return stripe.customers.create({
email: data.email,
}).then((customer) => {
return admin.database().ref(`/stripe_customers/${data.uid}/customer_id`).set(customer.id);
});
});
// add a payment source (card) for a user by writing a stripe payment source token to realtime database
exports.addPaymentSource =. functions.database.ref('/stripe_customers/{userId}/sources/{pushId}/token').onWrite((event) => {
const source = event.data.val();
if (sourve === null) return null;
return admin.database.ref(`/stripe_customers/${event.params.userId}/customer_id`).once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.createSource(customer, {source});
}).then((response) => {
return event.data.adminRef.parent.set(response);
}, (error) => {
return event.data.adminRef.parent.child('error').set(userFacingMessage(error));
}).then(() => {
return reportError(error, {user: event.params.userId});
});
});
// when a user deletes their account, clean up after the
exports.cleanupUser = functions.auth.user().onDelete((event) => {
return admin.database().ref(`/stripe_customers/${event.data.uid}`).once('value').then((snapshot) => {
return snapshot.val();
}).then((customer) => {
return stripe.customers.del(customer.customer_id);
}).then(() => {
return admin.database().ref(`/stripe_customers/${event.data.uid}`).remove();
});
});
function reportError(err, context = {}) {
const logName = 'errors';
const lof = logging.log(logName);
const metadata = {
resource: {
type: 'cloud_function',
labels: {function_name: process.env.FUNCTION_NAME},
},
};
const errorEvent = {
message: err.stack,
serviceContext: {
service: process.env.FUNCTION_NAME,
resourceType: 'cloud_function',
},
context: context,
};
return new Promise((resolve, reject) => {
log.write(log.entry(metadata, errorEvent), (error) => {
if (error) {
reject(error);
}
resolve();
});
});
}
// end [reportError]
// sanitize the error message for the user
function userFacingMessage(error) {
returnerror.type ? error.message : 'an error occurred, developers have been altered';
}
Firebase Database Structure:
(I removed my email from picture, writing function working)
ERROR in Xcode Console:
did fail to load paymentmethodsview
responseSerializationFailed(Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}))
Upvotes: 0
Views: 1475
Reputation: 748
It's hard to say what's going on here without more context. However, here's my guess: you haven't defined an ephemeral keys method on your Firebase backend, and so createCustomerWithKey's JSON request is failing: it's expecting the exact ephemeral key JSON your backend receives from the Stripe API. You need to implement the "/ephemeral_keys" endpoint in your backend application using the code here:
https://stripe.com/docs/mobile/ios/standard#prepare-your-api
Upvotes: 1