Apple Pay and Stripe: Token not being sent to Stripe - ios

Set up Apple Pay in my app and it seems to work fine when running on device. Using Stripe as the payment processor but it is not sending the token to Stripe, but seems to be charging the credit card listed in my iPhone's digital wallet.
The problem I am having is that once I press the "Pay with Touch ID", I get a check mark in my Apple Pay sheet but the following page appears in Xcode:
Code for Apple Pay & Stripe
var paymentSucceeded: Bool = false
func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController,
didAuthorizePayment payment: PKPayment, completion: #escaping (PKPaymentAuthorizationStatus) -> Void) {
STPAPIClient.shared().createToken(with: payment) { (token, error) in
print("I am here")
if error != nil {
completion(.failure)
print("failed")
} else {
self.paymentSucceeded = true
completion(.success)
print("woohoo")
}
self.createBackendCharge(with: token!, completion: completion)
print("created Backend Charge")
self.postStripeToken(token: token!)
print("posted stripe token")
}
} // paymentAuthorizationViewController( didAuthorizePayment )
func createBackendCharge(with token: STPToken, completion: #escaping (_: PKPaymentAuthorizationStatus) -> Void) {
//We are printing Stripe token here, you can charge the Credit Card using this token from your backend.
print("Stripe Token is \(token)")
completion(.success)
} // createBackendCharge func
func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
controller.dismiss(animated: true, completion: {
if (self.paymentSucceeded) {
// show a receipt page
}
})
} // paymentAuthorizationViewControllerDidFinish()
#IBAction func applePayPressed(_ sender: UIButton) {
// we have already accepted the request from viewDriverBids
// all that remains is to complete payment
print("enable apple pay")
// send user to Apple Pay to make payment
let paymentNetworks = [PKPaymentNetwork.visa, .masterCard, .interac, .discover, .amex]
if PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: paymentNetworks) {
paymentRequest = PKPaymentRequest()
paymentRequest.currencyCode = "CAD"
paymentRequest.countryCode = "CA"
paymentRequest.merchantIdentifier = "merchant.com.xxx"
paymentRequest.supportedNetworks = paymentNetworks
paymentRequest.merchantCapabilities = .capability3DS
paymentRequest.requiredShippingAddressFields = [.all]
paymentRequest.paymentSummaryItems = self.rydes()
let applePayVC = PKPaymentAuthorizationViewController(paymentRequest: paymentRequest)
applePayVC.delegate = self
self.present(applePayVC, animated: true, completion: {
rRydeHandler.Instance.completeApplePay()
self.paymentComplete = true
self.updateDriverInfoView()
})
} else {
print("Tell the user they need to set up Apple Pay!")
}
} // applePayPressed func ACTION
backend server func
func postStripeToken(token: STPToken) {
let URL = "http://localhost/donate/payment.php"
let params = ["stripeToken": token.tokenId,
"amount": Int(self.driverInfoView.rydeFare.text!)!,
"currency": "cad",
"description": self.riderName] as [String : Any]
let manager = AFHTTPSessionManager()
manager.post(URL, parameters: params, success: { (operation, responseObject) -> Void in
if let response = responseObject as? [String: String] {
let alertController = UIAlertController(title: response["status"], message: response["message"], preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(defaultAction)
self.present(alertController, animated: true, completion: nil)
}
}) { (operation, error) -> Void in
self.handleError(error as NSError)
print(error)
}
}
How can I resolve this issue?

Enable exception breakpoints then Xcode should crash on the line that is causing the issue.
It is almost certainly caused by one of the ! in your code.
Force unwrapping values is very dangerous. It's always better and safer to unwrap it in a guard let or if let.

Related

Stripe iOS didCreatePaymentResult never gets called

The problem seems simple, didCreatePaymentResult never gets called.
BUT, in my old sample project, taken from your iOS example for payment intent, that didCreatePaymentResult gets called every single time I create or select a card, here's the repo of the working project: https://github.com/glennposadas/stripe-example-ios-nodejs
BUT again, my main concern is my current project.
I use v19.2.0 in both of these projects, I even tried the v19.3.0.
I wanted to use Stripe Charge really, but I believe Stripe does not support Apple pay for that. So I have no choice but to use Stripe Payment Intent.
CoreService.swift (conforms to STPCustomerEphemeralKeyProvider)
extension CoreService: STPCustomerEphemeralKeyProvider {
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
orderServiceProvider.request(.requestEphemeralKey(stripeAPIVersion: apiVersion)) { (result) in
switch result {
case let .success(response):
guard let json = ((try? JSONSerialization.jsonObject(with: response.data, options: []) as? [String : Any]) as [String : Any]??) else {
completion(nil, NSError(domain: "Error parsing stripe data", code: 300, userInfo: nil))
return
}
completion(json, nil)
default:
UIViewController.current()?.alert(title: "Error stripe", okayButtonTitle: "OK", withBlock: nil)
}
}
}
}
PaymentController.swift
class PaymentViewController: BaseViewController {
// MARK: - Properties
private var paymentContext: STPPaymentContext!
private let paymentConstantValue: Int = 3000
// MARK: - Functions
// MARK: Overrides
override func viewDidLoad() {
super.viewDidLoad()
self.setupStripe()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.hideNavBar(animated: true)
}
#IBAction func creditCardButtonTapped(_ sender: Any) {
self.paymentContext.presentPaymentOptionsViewController()
}
private func setupStripe() {
let config = STPPaymentConfiguration.shared()
config.appleMerchantIdentifier = "merchant.com.gsample.app"
config.companyName = "Scoutd LLC"
config.requiredBillingAddressFields = .none
config.requiredShippingAddressFields = .none
config.additionalPaymentOptions = .applePay
let customerContext = STPCustomerContext(keyProvider: CoreService())
let paymentContext = STPPaymentContext(
customerContext: customerContext,
configuration: config,
theme: STPTheme.default()
)
let userInformation = STPUserInformation()
paymentContext.prefilledInformation = userInformation
paymentContext.paymentAmount = self.paymentConstantValue
paymentContext.paymentCurrency = "usd"
self.paymentContext = paymentContext
self.paymentContext.delegate = self
self.paymentContext.hostViewController = self
}
}
// MARK: - STPPaymentContextDelegate
extension PaymentViewController: STPPaymentContextDelegate {
func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
print("paymentContextDidChange")
}
func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
// error alert....
}
func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: #escaping STPPaymentStatusBlock) {
print("didCreatePaymentResult ✅")
}
func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
switch status {
case .success:
// success
case .error:
// error alert....
default:
break
}
}
}
SOLVED! This should help engineers struggling with Stripe implementation in the future.
So in my case, I have two buttons:
Apple Pay
Credit card.
The absolute solution for me is handle the selectedPaymentOption of the paymentContext.
Scenarios:
If the apple pay button is tapped, present apple pay sheet and don't present add/select card UI of Stripe.
If the credit card button is tapped, don't present apple pay sheet and instead present select card.
Related to #2, call requestPayment() if there's a selected option.
Voila! The didCreatePaymentResult now gets invoked!
// MARK: IBActions
#IBAction func applePayButtonTapped(_ sender: Any) {
if self.paymentContext.selectedPaymentOption is STPApplePayPaymentOption {
self.paymentContext.requestPayment()
}
}
#IBAction func creditCardButtonTapped(_ sender: Any) {
if let selectedPaymentOption = self.paymentContext.selectedPaymentOption,
!(selectedPaymentOption is STPApplePayPaymentOption) {
self.paymentContext.requestPayment()
return
}
self.paymentContext.presentPaymentOptionsViewController()
}

BrainTree UIDropIn VC not display amount in iOS integration

I create a demo for payment using Braintree iOS v4 SDK. All thinks going great but I have a query regarding the amount. I used DropIN VC for UI.
//MARK: Show Dropin VC
func showDropIn(clientTokenOrTokenizationKey: String) {
let request = BTDropInRequest()
request.amount = "1.0"
request.currencyCode = "USD"
request.threeDSecureVerification = true;
let dropIn = BTDropInController(authorization: clientTokenOrTokenizationKey, request: request)
{ (controller, result, error) in
if (error != nil) {
print("ERROR")
} else if (result?.isCancelled == true) {
print("CANCELLED")
} else if let result = result {
if (result.paymentOptionType == BTUIKPaymentOptionType.applePay){
controller.dismiss(animated: true, completion: nil)
self.paymentApplePay()
}else{
print(result.paymentMethod?.nonce as Any)
print(result.description)
}
}
controller.dismiss(animated: true, completion: nil)
}
self.present(dropIn!, animated: true, completion: nil)
}
when I select Paypal option I redirect to the checkout screen. check images
here is the problem that I can't see the amount I entered at the time of the request. How can I see that? Thanks in advance.

Cannot get Plaid sandbox to work on iOS with Swift 3

When I use the un:user_good and pw:pass_good I get the attached screen
And it does not return a accessToken
I have already added my public key in my AppDelegate
[![#IBAction fu][1]][1]nc plaidConnectButton(_ sender: Any) {
let plaidLink = PLDLinkNavigationViewController(environment: .tartan, product: .connect)!
plaidLink.linkDelegate = self
plaidLink.providesPresentationContextTransitionStyle = true
plaidLink.definesPresentationContext = true
plaidLink.modalPresentationStyle = .custom
self.present(plaidLink, animated: true, completion: nil)
}
func linkNavigationContoller(_ navigationController: PLDLinkNavigationViewController!, didFinishWithAccessToken accessToken: String!) {
print("success \(accessToken)")
myAPIClient.connectAddBank(bankToken: accessToken, completion:{
(error) in
if(error != nil) {
print(error.debugDescription)
} else {
print("successfully added bank")
self.dismiss(animated: true, completion: nil)
}
})
}
func linkNavigationControllerDidFinish(withBankNotListed navigationController: PLDLinkNavigationViewController!) {
print("Manually enter bank info?")
self.performSegue(withIdentifier: "unlistedBankSegue", sender: self)
}
func linkNavigationControllerDidCancel(_ navigationController: PLDLinkNavigationViewController!) {
self.dismiss(animated: true, completion: nil)
}
Please help
Set your environment to .sandbox during the init process.
Plaid support link for this: https://github.com/plaid/link/issues/140

CXCallObserver is not working properly and App getting crash when running the app more than one (when includes contacts image data)

I am facing two major problem first one is :
1. I am trying to detect incoming call, outgoing call , dialing call for this i am using this code :
import UIKit
import CoreTelephony
import CallKit
class ViewController: UIViewController,CXCallObserverDelegate {
let callObserver = CXCallObserver()
var seconds = 0
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
callObserver.setDelegate(self, queue: nil)
}
override func viewWillAppear(_ animated: Bool) {
print("viewWillAppear \(seconds)")
}
fileprivate func runTimer(){
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateTimer), userInfo: nil, repeats: true)
}
func updateTimer() {
seconds += 1
print("Seconds \(seconds)")
}
#IBAction func callButton(_ sender: UIButton) {
if let url = URL(string: "tel://\(12345879)"){
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
if call.hasEnded == true {
print("Disconnected")
seconds = 0
self.timer.invalidate()
}
if call.isOutgoing == true && call.hasConnected == false {
print("Dialing call")
self.runTimer()
}
if call.isOutgoing == false && call.hasConnected == false && call.hasEnded == false {
print("Incoming")
}
if call.hasConnected == true && call.hasEnded == false {
print("Connected")
}
}
}
It working fine when i am dialing a number it shows "Dialling" but when i cut the call then it shows "Disconnected" then again "Dialing" State.
Another problem is when i am fetching all contacts information from the device it works fine when i am not fetching imageData but when i am fetching contacts image it works fine for the very first time . Then if i run it again app become slow . then next it crash shows found nil while unwrapping a value.
i wrote my contact data fetching function in AppDelegate . it is calling when the app start . this is the code :
func fetchContactList(){
let loginInformation = LoginInformation()
var contactModelData: [ContactsModel] = []
var profileImage : UIImage?
let store = CNContactStore()
store.requestAccess(for: .contacts, completionHandler: {
granted, error in
guard granted else {
let alert = UIAlertController(title: "Can't access contact", message: "Please go to Settings -> MyApp to enable contact permission", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.window?.rootViewController?.present(alert, animated: true, completion: nil)
return
}
let keysToFetch = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName),CNContactPhoneNumbersKey, CNContactEmailAddressesKey, CNContactPostalAddressesKey, CNContactImageDataKey, CNContactImageDataAvailableKey,CNContactThumbnailImageDataKey,CNContactThumbnailImageDataKey] as [Any]
let request = CNContactFetchRequest(keysToFetch: keysToFetch as! [CNKeyDescriptor])
var cnContacts = [CNContact]()
do {
try store.enumerateContacts(with: request){
(contact, cursor) -> Void in
cnContacts.append(contact)
}
} catch let error {
NSLog("Fetch contact error: \(error)")
}
for contact in cnContacts {
let fullName = CNContactFormatter.string(from: contact, style: .fullName) ?? "No Name"
var phoneNumberUnclean : String?
var labelofContact : String?
var phoneNumberClean: String?
for phoneNumber in contact.phoneNumbers {
if let number = phoneNumber.value as? CNPhoneNumber,
let label = phoneNumber.label {
let localizedLabel = CNLabeledValue<CNPhoneNumber>.localizedString(forLabel: label)
print("fullname \(fullName), localized \(localizedLabel), number \(number.stringValue)")
phoneNumberUnclean = number.stringValue
labelofContact = localizedLabel
}
}
if let imageData = contact.imageData {
profileImage = UIImage(data: imageData)
print("image \(String(describing: UIImage(data: imageData)))")
} else {
profileImage = UIImage(named: "user")
}
self.contactModelData.append(ContactsModel(contactName: fullName, contactNumber:phoneNumberUnclean!, contactLabel: labelofContact!, contactImage: profileImage!, contactNumberClean: phoneNumberUnclean!))
}
self.loginInformation.saveContactData(allContactData: self.contactModelData)
})
}
I have solved this two problems using this :
for number one when i disconnect a call then if unfortunately it goes to "Dialling" option again i checked the "seconds" variable's value if it greater than 0 in "Dialing" then invalidate the thread.
for number two problem :
I used Dispatch.async.main background thread and take the thumbnail image

Text message function returns error on device

I am retrieving the phone number from my Firebase database, no matter how I enter it into the database it returns an error on my device:
"Message sent to invalid destination. Please check your number and try again. Msg 2127"
The message comes in as an automatic reply immediately after the text is sent out. I have tried entering it into firebase as 111-111-1111 and 1111111111 (not actually using a number 1 but a real phone number).
The calling function works fine however the same number being texted is not working.
var contact: Int!
#IBAction func textButton(_ sender: Any) {
if canSendText() {
if let contactopt = contact{
var messageVC = MFMessageComposeViewController()
messageVC.recipients = ["tel:\(contactopt)"]
messageVC.messageComposeDelegate = self;
self.present(messageVC, animated: false, completion: nil)}}
else {
let errorAlert = UIAlertView(title: "Cannot Send Text Message", message: "Your device is not able to send text messages.", delegate: self, cancelButtonTitle: "OK")
errorAlert.show()
}
}
func canSendText() -> Bool {
return MFMessageComposeViewController.canSendText()
}
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
controller.dismiss(animated: true, completion: nil)
}
#IBAction func callButton(_ sender: UIButton) {
if let contactopt = contact{
if let url = NSURL(string: "tel://\(contactopt)") {
// UIApplication.shared.openURL(url as URL)
UIApplication.shared.open(url as URL, options: [:], completionHandler: nil)
}
}
}
I had to change
messageVC.recipients = ["tel:(contactopt)"]
messageVC.recipients = [String(contactopt)]

Resources