I implemented apple pay with stripe in ios using swift. So when the time of testing a program it is runs fine with simulators but not in real device. To test the apple pay with real device it doesn't show the payment sheet to process the transaction. My doubt is to test with apple pay with real device can we add test cards to wallet or have the sandbox account ? is required adding test cards to wallet when using stripe gate way? Could you please give me a clarity?
This is my code :
import UIKit
import PassKit
import Stripe
class ViewController: UIViewController {
let request = PKPaymentRequest()
var paymentStatus = PKPaymentAuthorizationStatus.failure
var paymentController: PKPaymentAuthorizationController?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func applepay(_ sender: Any) {
let merchantIdentifier = "merchant.Appplepay"
let paymentNetworks = [PKPaymentNetwork.masterCard, PKPaymentNetwork.visa,PKPaymentNetwork.amex,PKPaymentNetwork.discover,PKPaymentNetwork.interac]
if PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: paymentNetworks)
{
request.merchantIdentifier = "merchant.Appplepay"
request.countryCode = "US"
request.currencyCode = "USD"
request.supportedNetworks = paymentNetworks
request.merchantCapabilities = .capability3DS
request.requiredShippingAddressFields = [.phone, .email,.postalAddress,.all]
request.paymentSummaryItems = [PKPaymentSummaryItem(label: "Fancy Hat", amount: 50.00),
// The final line should represent your company;
// it'll be prepended with the word "Pay" (i.e. "Pay iHats, Inc $50")
PKPaymentSummaryItem(label: "iHats, Inc", amount: 50.00),]
} else {
// Traditional checkout flow
}
func applePaySupported() -> Bool {
return PKPaymentAuthorizationViewController.canMakePayments() && PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: [.amex, .visa, .masterCard])
}
// Setup payment authorization view controller
if Stripe.canSubmitPaymentRequest(request) {
// Setup payment authorization view controller
let paymentAuthorizationViewController = PKPaymentAuthorizationViewController(paymentRequest: request)
paymentAuthorizationViewController?.delegate = self
// Present payment authorization view controller
self.present((paymentAuthorizationViewController)!, animated: true,completion: nil)
}
else {
// There is a problem with your Apple Pay configuration
print("Failed to present PaymentSheet");
}
}
}
extension ViewController : PKPaymentAuthorizationViewControllerDelegate {
func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, completion: #escaping (PKPaymentAuthorizationStatus) -> Void) {
if payment.shippingContact?.emailAddress == nil || payment.shippingContact?.phoneNumber == nil {
paymentStatus = .invalidShippingContact
} else {
STPAPIClient.shared().createToken(with: payment) { (token: STPToken?, error)-> Void in
print("Stripe token is \(String(describing: token!))")
//self.paymentStatus = .success
completion(.success)
}
}
}
func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
controller.dismiss(animated: true, completion: nil)
}
}
To answer your questions about testing on a real device: You can only add real cards to your wallet to use with Apple Pay, however if you use a test stripe api key, that is detected by stripe and your real card will not be charged
Not sure why the payment sheet isn't displaying on the real device without seeing some code, but you would want to make sure that the device is set up for apple pay, mainly that it has cards added to the wallet (that you support)
Not all countries support Apple Pay so there isn't always an option to add a card to the wallet, this would mean that the Apple Pay sheet wouldn't display due to if Stripe.canSubmitPaymentRequest(request) returning false.
Related
I am integrating apple pay. I am following applepay documentation. Here is my code.
import UIKit
import PassKit
class ApplePayViewController: UIViewController {
#IBOutlet var lblLoading : UILabel!
#IBOutlet var loader : UIActivityIndicatorView!
var totalAmount = "100.0"
let paymentRequest = PKPaymentRequest()
override func viewDidLoad() {
super.viewDidLoad()
// Set up the payment request
paymentRequest.merchantIdentifier = "merchant.com.apple.example"
paymentRequest.supportedNetworks = [.visa, .masterCard, .amex, .discover]
paymentRequest.merchantCapabilities = .capability3DS
paymentRequest.countryCode = "US"
paymentRequest.currencyCode = "USD"
// Add a payment item
let item = PKPaymentSummaryItem(label: "Total", amount: NSDecimalNumber(string: totalAmount))
paymentRequest.paymentSummaryItems = [item]
// Check if the device is capable of making payments
if PKPaymentAuthorizationViewController.canMakePayments() {
let authorizationViewController = PKPaymentAuthorizationViewController(paymentRequest: paymentRequest)
authorizationViewController?.delegate = self
present(authorizationViewController!, animated: true, completion: nil)
} else {
SharedManager.showAlertWithMessage(title: NSLocalizedString("Sorry", comment: ""), alertMessage: "This device is not capable of making payments.", viewController: self)
}
}}
extension ApplePayViewController: PKPaymentAuthorizationViewControllerDelegate {
func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
controller.dismiss(animated: true, completion: nil)
}
func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, handler completion: #escaping (PKPaymentAuthorizationResult) -> Void) {
// Verify the payment with your server
// ...
completion(PKPaymentAuthorizationResult(status: .success, errors: nil))
}}
In didAuthorizePayment delegate i dont know how to verify the payment with server. I cannot find any function or post data related to this in document. Can you help me with this.
Thank you
You need to have a payment processor (like Stripe) to actually do the transaction.
See this tutorial: https://www.kodeco.com/2113-apple-pay-tutorial-getting-started
It explains how to do this with Stripe.
In this function, if Stripe is successful, you will also have to tell your own backend all of the information it needs to actually send the product or whatever the user ordered.
Apple Pay token contains wrong transactionAmount info. E.g if the transaction amount is $1.10, on iOS device, Apple Pay screen stills show correct amount but paymentToken returns 110 when decrypted
I used this library to decrypt Apple Pay token using my own public-private key pair
https://github.com/sidimansourjs/applepay-token
My sample code
class ViewController: UIViewController {
...
#IBAction func payBtn(_ sender: UIButton) {
do {
let paymentItem = PKPaymentSummaryItem.init(label: "Test item", amount: NSDecimalNumber(value: 1.10))
let paymentNetworks = [PKPaymentNetwork.amex, .discover, .masterCard, .visa]
if PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: paymentNetworks) {
let request = PKPaymentRequest()
request.currencyCode = "SGD" // 1
request.countryCode = "SG" // 2
request.merchantIdentifier = "merchant.com.xxxxx" // 3
request.merchantCapabilities = PKMerchantCapability.capability3DS // 4
request.supportedNetworks = paymentNetworks // 5
request.paymentSummaryItems = [paymentItem] // 6
guard let paymentVC = PKPaymentAuthorizationViewController(paymentRequest: request) else {
displayDefaultAlert(title: "Error", message: "Unable to present Apple Pay authorization.")
return
}
paymentVC.delegate = self
self.present(paymentVC, animated: true, completion: nil)
} else {
displayDefaultAlert(title: "Error", message: "Unable to make Apple Pay transaction.")
}
} catch {
print(error.localizedDescription)
}
}
}
extension ViewController: PKPaymentAuthorizationViewControllerDelegate {
func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
dismiss(animated: true, completion: nil)
}
func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, handler completion: #escaping (PKPaymentAuthorizationResult) -> Void) {
let token = String(data: payment.token.paymentData, encoding: .utf8)
let utf8str = token!.data(using: .utf8)
if let base64Encoded = utf8str?.base64EncodedString()
{
print("Encoded: \(base64Encoded)")
//Send token to backend server to decrypt
}
}
}
I discovered this when try to make payment request to Adyen (a payment gateway). Request must contain both amount field and paymentToken field but was never successful because amount in amount and paymentToken is mis-matched. Then I tried amount 110 instead of 1.10 then request was success, but ironically in Adyen dashboard somehow it still understood transaction amount was $1.10
I expected transactionAmount in Apple Pay token to be 1.10
Please help to explain why there's this difference in Apple Pay amount shown on device and in token
Found answer as suggested by #Don
Singapore Dollar SGD's smallest unit is cent, which is 1/100 of a dollar. Therefore I need to convert dollar amount into cent integer by multiply with 100. Then, use this value when send over to Adyen
I am trying to find a way to skip provider options screen in FirebaseUI.
I just need phone authentication and there is no need to show user provider options.
Is there a way to take user directly to phone authentication screen?
Here is my code on viewcontroller
override func viewDidLoad() {
super.viewDidLoad()
//createGradientLayer()
checkLoggedIn()
}
func checkLoggedIn() {
Auth.auth().addStateDidChangeListener { auth, user in
if user != nil {
// User is signed in.
} else {
// No user is signed in.
self.login()
}
}
}
func login() {
let authUI = FUIAuth.defaultAuthUI()
authUI?.delegate = self as? FUIAuthDelegate
let providers: [FUIAuthProvider] = [
FUIPhoneAuth(authUI:FUIAuth.defaultAuthUI()!),
]
authUI?.providers = providers
FUIAuth.defaultAuthUI()?.isSignInWithEmailHidden = true
let authViewController = authUI?.authViewController()
self.present(authViewController!, animated: true, completion: nil)
}
func authUI(_ authUI: FUIAuth, didSignInWith user: User?, error: Error?) {
if error != nil {
//Problem signing in
login()
}else {
//User is in! Here is where we code after signing in
}
}
You were almost there. After FUIAuthProvider initialization start Phone Auth flow directly:
FUIPhoneAuth *provider = self.authUI.providers.firstObject;
[provider signInWithPresentingViewController:self];
Here is sample code.
In order to add logo to Welcome screen subclass FUIAuthPickerViewController and implement FUIAuthDelegate delegate method:
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController
Here is one more sample for this.
Let's supose that you have a view controller with a button to start the phone validation. This is the code that should be included in the button (obj-c)
- (IBAction)btnPhoneValidation:(id)sender {
FUIAuth *authUI = [FUIAuth defaultAuthUI];
authUI.delegate = self;
//The following array may contain diferente options for validate the user (with Facebook, with google, e-mail...), in this case we only need the phone method
NSArray<id<FUIAuthProvider>> * providers = #[[[FUIPhoneAuth alloc]initWithAuthUI:[FUIAuth defaultAuthUI]]];
authUI.providers = providers;
//You can present the screen asking for the user number with the following method.
FUIPhoneAuth *provider = authUI.providers.firstObject;
[provider signInWithPresentingViewController:self phoneNumber:nil];
//This is the default way to present several options.
// UINavigationController *authViewController = [authUI authViewController];
// [self presentViewController:authViewController animated:YES completion:nil];
}
The same process but with e-mail authentication, replacing the provider type:
NSArray<id<FUIAuthProvider>> * providers = #[[[FUIEmailAuth alloc]init]];
authUI.providers = providers;
FUIEmailAuth *provider = authUI.providers.firstObject;
[provider signInWithPresentingViewController:self email:nil];
According to the FirebaseAuthUI documentation, you cannot customize the flow. (See the section on custom email/password screens)
It is important to check are In-App Purchases enabled to block UI properly, RayWenderlich blog says:
Apple requires that you handle this situation gracefully; not doing so will likely result in an app rejection.
When you disable In-App Purchase, the Restrictions SKPaymentQueue.canMakePayments() should return false but it always returns true no matter what. I tried with 2 different projects, including, this one from RayWenderlich.
I tested this only with iOS 9.
How to recognise that In-App Purchases disabled with parental restrictions?
Update.
Somebody requested to share my code. I don't think it is necessary, the code is obvious and doesn't have errors. I can reproduce this problems with the Ray's project too.
// This function is called in from viewDidLoad()
// And after SKProduct is updated.
func addTextFromProduct(p: SKProduct) {
if let title = p.localizedTitle as String? {
self.navigationBar.topItem?.title = title
}
if let description = p.localizedDescription as String? {
if dailyLimit {
self.informationLabel.text? = "\(waitingTime)\(description)"
} else {
self.informationLabel.text? = "\(description)"
}
if SKPaymentQueue.canMakePayments() {
unblockButtons()
}
} else {
self.informationLabel.text? = "\(waitingTime)\(description)\n\nIn-App Purchase is unavailable at this moment."
blockButtons()
}
if SKPaymentQueue.canMakePayments() {
self.priceFormatter.locale = p.priceLocale
let localPrice: String! = self.priceFormatter.stringFromNumber(p.price)
let label = "\(localPrice)"
self.buyButton.setTitle(label, forState: UIControlState.Normal)
} else {
blockButtons()
buyButton.setTitle("Not Available", forState: UIControlState.Disabled)
}
}
You need to check in your viewDidLoad method if the user has enabled IAP.
if(SKPaymentQueue.canMakePayments()) {
print("IAP is enabled, loading")
// Your Products
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
}
else {
print("IAP not allowed")
//Do something like show an alert, the user has not allowed in app purchases
}
I'm trying to setup Apple Pay on my app, but I don't have an iPhone 6 with me at the moment. So I'm trying to get everything up and running with the simulator before I can go buy one, or try to get someone to lend one to me.
Anyway, I got to the point of actually showing the ApplePay view controller:
But when I tap on "Pay with Passcode" nothing happens, so I can't go any further and finish testing everything with the server.
Here are the relevant parts of my code:
class PaymentVC: UIViewController,PKPaymentAuthorizationViewControllerDelegate {
#IBAction func onPaymentSubmit(sender: AnyObject) {
if PKPaymentAuthorizationViewController.canMakePaymentsUsingNetworks(supportedPaymentNetworks) {
let applePayMerchantID = "merchant.com.id"
let request = PKPaymentRequest()
request.merchantIdentifier = applePayMerchantID
request.supportedNetworks = supportedPaymentNetworks
request.merchantCapabilities = PKMerchantCapability.Capability3DS
request.countryCode = "US"
request.currencyCode = "USD"
request.paymentSummaryItems = [
PKPaymentSummaryItem(label: "Custom Order", amount: NSDecimalNumber(float: total))
]
let applePayController = PKPaymentAuthorizationViewController(paymentRequest: request)
applePayController.delegate = self
self.presentViewController(applePayController, animated: true, completion: nil)
}
}
//MARK: Apple Pay
func paymentAuthorizationViewController(controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, completion: (PKPaymentAuthorizationStatus) -> Void) {
}
func paymentAuthorizationViewControllerDidFinish(controller: PKPaymentAuthorizationViewController) {
controller.dismissViewControllerAnimated(true, completion: nil)
}
}
Any idea what might be wrong?
You need to return a payment status in paymentAuthorizationViewController. You'll see that delegate method has a completion handler that you must call to indicate whether you were able to process the payment successfully or not.