Stripe SCA need to handle next action - ios

I am using string in this application perviously but all the things done by backend developer but now stripe change their implementation and give support for European payment change (Supporting 3D Secure Authentication) with SCA so now i need to add library in my local code but i stuck at this place.
I got the client id from server but handle next action will give me error something like this.
Error Domain=STPPaymentHandlerErrorDomain Code=1 "There was an unexpected error -- try again in a few seconds" UserInfo={com.stripe.lib:ErrorMessageKey=The PaymentIntent requires a PaymentMethod or Source to be attached before using STPPaymentHandler., NSLocalizedDescription=There was an unexpected error -- try again in a few seconds}
I did not find any relative document here
Here is my code
//Api called on server for client id
MyAPIClient.sharedClient.createAndConfirmPaymentIntent(paymentResult,
amount: self.paymentContext.paymentAmount,
returnURL: "payments-example://stripe-redirect",
shippingAddress: self.paymentContext.shippingAddress,
shippingMethod: self.paymentContext.selectedShippingMethod) { (clientSecret, error) in
guard let clientSecret = clientSecret else {
completion(.error, error ?? NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Unable to parse clientSecret from response"]))
return
}
STPPaymentHandler.shared().handleNextAction(forPayment: clientSecret, authenticationContext: paymentContext, returnURL: "payments-example://stripe-redirect") { (status, handledPaymentIntent, actionError) in
switch (status) {
case .succeeded:
guard let handledPaymentIntent = handledPaymentIntent else {
completion(.error, actionError ?? NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Unknown failure"]))
return
}
if (handledPaymentIntent.status == .requiresConfirmation) {
// Confirm again on the backend
MyAPIClient.sharedClient.confirmPaymentIntent(handledPaymentIntent) { clientSecret, error in
guard let clientSecret = clientSecret else {
completion(.error, error ?? NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Unable to parse clientSecret from response"]))
return
}
// Retrieve the Payment Intent and check the status for success
STPAPIClient.shared().retrievePaymentIntent(withClientSecret: clientSecret) { (paymentIntent, retrieveError) in
guard let paymentIntent = paymentIntent else {
completion(.error, retrieveError ?? NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Unable to parse payment intent from response"]))
return
}
if paymentIntent.status == .succeeded {
completion(.success, nil)
}
else {
completion(.error, NSError(domain: StripeDomain, code: 123, userInfo: [NSLocalizedDescriptionKey: "Authentication failed."]))
}
}
}
} else {
// Success
completion(.success, nil)
}
case .failed:
completion(.error, actionError)
case .canceled:
completion(.userCancellation, nil)
}
}

My fix was to call paymentManager.confirmPayment(withParams: params, authenticationContext: self) instead of handleNextAction

Related

Stripe Client intent error swift: No such payment_intent

I am having issues utilizing a pay function given by the stripe documentation
Stripe documentation: https://stripe.com/docs/payments/accept-a-payment
All my server side & client side keys are the same testing keys.
I have successfully retrieved a payment intent with the value
pi_1ITiM3KCSIKL5fqUAMGk32Do_secret_cJfvlRxtnnTuU8VIScj8NCz12
Error message:
No such payment_intent: \ 'pi_1ITiM3KCSIKL5fqUAMGk32Do \ '
Not sure why the error message includes: (possibly json issue)
\ '
Any help would be much appreciated on why this is happening.
var paymentIntentClientSecret: String?
func pay() {
guard let paymentIntentClientSecret = paymentIntentClientSecret else {
return;
}
print(paymentIntentClientSecret) // pi_1ITiM3KCSIKL5fqUAMGk32Do_secret_cJfvlRxtnnTuU8VIScj8NCz12
// Collect card details
let cardParams = creditCardField.cardParams//cardTextField.cardParams
let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: nil, metadata: nil)
let paymentIntentParams = STPPaymentIntentParams(clientSecret: paymentIntentClientSecret)
print(paymentIntentParams)
paymentIntentParams.paymentMethodParams = paymentMethodParams
// Submit the payment
let paymentHandler = STPPaymentHandler.shared()
paymentHandler.confirmPayment(paymentIntentParams, with: self) { (status, paymentIntent, error) in
switch (status) {
case .failed:
//self.displayAlert(title: "Payment failed", message: error?.localizedDescription ?? "")
print("failed: \(String(describing: error?.localizedDescription))")
break
case .canceled:
//self.displayAlert(title: "Payment canceled", message: error?.localizedDescription ?? "")
print("cancled: \(String(describing: error?.localizedDescription))")
break
case .succeeded:
//self.displayAlert(title: "Payment succeeded", message: paymentIntent?.description ?? "", restartDemo: true)
print("succeeded: \(String(describing: error?.localizedDescription))")
break
#unknown default:
fatalError()
break
}
}
}
Response body:
{
"error": {
"code": "resource_missing",
"doc_url": "https://stripe.com/docs/error-codes/resource-missing",
"message": "No such payment_intent: 'pi_1ITh8QKCSIKL5fqU7VjJqHH0'",
"param": "intent",
"type": "invalid_request_error"
}
}
“No such...” errors are usually caused by either a mismatch in API keys (e.g. using a mixture of your test plus live keys) or by trying to access objects that exist on a different account (e.g. trying to perform an operation from your platform account on an object that was created on a connected account).
Most likely you're using a publishable key from one account and a secret key from another (assuming you're not using Connect).

Verification ID used to create the phone auth credential is invalid iOS

I can't login and always see this error message:
The verification ID used to create the phone auth credential is invalid
I tried to add my phone like tester, but I wasn't succeed on it.
Sign in method is turn on in Firebase console.
I found the same issue here, but without answer.
I used firebase's docs.
Help me please to figure it out.
Here my code:
func verifyPhone(_ phone: String) -> AsyncTask<String?> {
return AsyncTask<String?>({ observer, lifetime in
guard !lifetime.hasEnded else {
observer.sendInterrupted()
return
}
Auth.auth().languageCode = "ua"
PhoneAuthProvider.provider().verifyPhoneNumber(phone, uiDelegate: nil) { verificationID, error in
if let error = error {
observer.send(error: AppError(error))
return
}
observer.send(value: verificationID)
observer.sendCompleted()
}
})
}
func signInViaPhoneNumber(usingCode smsCode: String) -> AsyncTask<Void> {
return AsyncTask<Void>({ observer, lifetime in
guard !lifetime.hasEnded else {
observer.sendInterrupted()
return
}
guard let verificationCode = UserDefaultsStorage.verificationCode else {
observer.send(error: AppError.logic("Відсутній код верифікації"))
return
}
let credential = PhoneAuthProvider.provider().credential(withVerificationID: smsCode,
verificationCode: verificationCode)
Auth.auth().signIn(with: credential) { result, error in
if let error = error {
observer.send(error: AppError(error))
return
}
guard let firUser = result?.user else {
observer.send(error: AppError.logic("Відсутній юзер"))
return
}
let factory: ModelFactory = ModelFactoryImpl()
let appUser = factory.makeUser(id: firUser.uid, name: firUser.displayName ?? "", phone: firUser.phoneNumber ?? "")
AppService.shared.user = appUser
observer.send(value: ())
observer.sendCompleted()
}
})
}
It's hard for me to see the complete code flow because you are using AsyncTask and delegating all the results outside.
The issue that pops out in your code is the line
PhoneAuthProvider.provider().credential(withVerificationID: smsCode,
verificationCode: verificationCode)
In this method verificationCode should be the code you received via SMS and withVerificationID should be the verificationId that you got in the verifyPhoneNumber callback.

How to resolve STPPaymentHandlerAction Status error Optional("No such setupintent: (null)")

i did all the code regarding to the Stripe and i followed the documentation of the stripe in which i i am calling one api to get the secret key from the server n which is coming..and after that i am passing it ot the "STPPaymentHandler.shared()"..but i am getting the error STPPaymentHandlerActionStatus error Optional("No such setupintent: (null)") a..please help me if you know thank you in advance
below the code which i did
i followed this documentation
https://stripe.com/docs/payments/accept-a-payment
//method to get secrete key
func checkout() {
let apikey = UserDefaults.standard.value(forKey: keyParameter.apikey.rawValue) as! String
let dic = ["apikey":apikey,"amount":"5000"]
Webhelper.sharedInstance.apiPostRequest(apiUrl: GlobalConstant.stripe_token, parameter: dic, success: { (success) in
print("success from the server is \(success)")
do{
let responseResult = try Global.sharedInstance.decode(jwtToken: success)
let message = Global.sharedInstance.getStringValue(responseResult["Message"] as AnyObject)
if let status = responseResult["result"] as? NSNumber {
if status == 1{
let responseDic = responseResult["Secrate"] as! NSDictionary
self.setupIntentClientSecret = responseDic.value(forKey: "clientSecret") as! String
let apikey = responseDic.value(forKey: "publishableKey") as! String
Stripe.setDefaultPublishableKey(apikey)
print(" self.setupIntentClientSecret self.setupIntentClientSecret \(self.setupIntentClientSecret)")
}else if status == 0{
Global.sharedInstance.ShowMessagePopup(Viewobj: self, title: "", Message: message)
}
}
}catch{
print("cought error in the catch \(error.localizedDescription)")
}
}) { (error) in
print("error for the server is \(error.localizedDescription)")
}
}
///this is the method to complete the payment
#objc func pay() {
// Collect card details
let cardParams = self.paymentTextField.cardParams
// Collect the customer's email to know which customer the PaymentMethod belongs to.
let billingDetails = STPPaymentMethodBillingDetails()
billingDetails.email = "sunil#gmial.com"//emailTextField.text
// Create SetupIntent confirm parameters with the above
let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: billingDetails, metadata: nil)
print("setupIntentClientSecret inside method\(setupIntentClientSecret)")
let setupIntentParams = STPSetupIntentConfirmParams(clientSecret: setupIntentClientSecret)
setupIntentParams.paymentMethodParams = paymentMethodParams
print("secrete \(setupIntentClientSecret)")
// Complete the setup
let paymentHandler = STPPaymentHandler.shared()
paymentHandler.confirmSetupIntent(withParams: setupIntentParams, authenticationContext: self) { status, setupIntent, error in
print("success from the server is setupIntent\(setupIntent) status \(status) error \(error?.localizedDescription)")
switch (status) {
case .failed:
self.displayAlert(title: "Setup failed", message: error?.localizedDescription ?? "")
// print("\(error?.localizedDescription)")
break
case .canceled:
self.displayAlert(title: "Setup canceled", message: error?.localizedDescription ?? "")
break
case .succeeded:
self.displayAlert(title: "Setup succeeded", message: setupIntent?.description ?? "", restartDemo: true)
break
#unknown default:
fatalError()
break
}
}
}

Get firebase server time cloud function

I'm trying to write a function that I can call on my iOS app to return me the current (or approximate) firebase server time.
I couldn't get any success with the following attempts. am I doing something wrong?
Obs.: The second one return an object of type FIRHTTPSCallableResult and I couldn't find a way to parse it, neither see the content into it to be sure if it worked.
exports.currentTime = functions.https.onRequest((req, res) => {
res.send({"timestamp":new Date().getTime()})
});
exports.currentTimeTwo = functions.https.onCall((data, context) => {
return {"timestamp":new Date().getTime()};
});
exports.currentTimeThree = functions.https.onRequest((req, res) => {
const data = {"timestamp":new Date().getTime()}
res.send(data)
});
iOS Code:
static func getServerTime(){
Functions.functions().httpsCallable("currentTimeTwo").call { (result, error) in
if let error = error as NSError? {
print("error \(error)")
if error.domain == FunctionsErrorDomain {
print("error domain: \(error.domain)")
let code = FunctionsErrorCode(rawValue: error.code)
print("error code: \(code)")
let message = error.localizedDescription
print("error message: \(message)")
let details = error.userInfo[FunctionsErrorDetailsKey]
print("details: \(details)")
}
}
print(result)
}
}
exports.currentTimeTwo = functions.https.onCall((data, context) => {
return {"timestamp":new Date().getTime()};
});
The function above is right. As Frank informed in the comments of my question, I was missing to access the property data from swift code result.
New Swift Code:
static func getServerTime(){
Functions.functions().httpsCallable("currentTimeTwo").call { (result, error) in
if let error = error as NSError? {
print("error \(error)")
if error.domain == FunctionsErrorDomain {
print("error domain: \(error.domain)")
let code = FunctionsErrorCode(rawValue: error.code)
print("error code: \(code)")
let message = error.localizedDescription
print("error message: \(message)")
let details = error.userInfo[FunctionsErrorDetailsKey]
print("details: \(details)")
}
}
let resultFireBase = result as! HTTPSCallableResult
print(result?.data)
}
}
}
Only the functions.https.onCall in your Cloud Functions code matches with the Functions.functions().httpsCallable in your Swift code, so the other ones are meaningless here.
When you call the httpsCallable, you get a FIRHTTPSCallableResult whose data property contains whatever your Cloud Function returns, as long as it's a valid JSON type, which your new Date().getTime() seems to be

How does Any, Optional and Protocol work together in Swift?

Can somebody explain why the first snippet is failing and the second is working?
Error is optional type
var error:Error? = NSError(domain: "MyDomain", code: 1001, userInfo: nil)
var anyError:Any = error
if let err = anyError as? Error
{
print("success")
}
else {
print("failure") // prints failure
}
NSError is optional type
var error:NSError? = NSError(domain: "MyDomain", code: 1001, userInfo: nil)
var anyError:Any = error
if let err = anyError as? NSError
{
print("success") // prints success
}
else {
print("failure")
}

Resources