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
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.
I am trying to perform a segue to a "Success window" when a payment has been correctly processed. I am trying to do this by using the:
self.performSegue(withIdentifier: "successView", sender: self)
inside my addCardViewController function. (shown here:)
func addCardViewController(_ addCardViewController: STPAddCardViewController, didCreateToken token: STPToken, completion: #escaping STPErrorBlock) {
// Monetary amounts on stripe are based on the lowest monetary unit (i.e. cents),
// therefore, we need to multiply the dollar amount by 100 to get the correct amount.
let stripeAmount = toPay * 100
// Call the 'stripeCharge' Firebase cloud function, with user's card token and amount
functions.httpsCallable("stripeCharge").call(["token": token.tokenId, "amount": String(stripeAmount)]) { (result, error) in
if let error = error {
print("Error: \(error)")
}
// Get the charge id after successful payment
var chargeId: String
if let data = result?.data as? [String: Any] {
chargeId = data["chargeId"] as? String ?? "no id"
print("Charge id: \(chargeId)")
//send new info
//show successfull payment view with charge
//self.present(self.successViewController, animated: true, completion: nil)
self.performSegue(withIdentifier: "successView", sender: self)
}
completion(nil)
//self.performSegue(withIdentifier: "successView", sender: self)
}
}
but I keep getting the error "Attempt to present ... on ... whose view is not in the window hierarchy"
Anyone knows why this is? here is a picture of the main.storyboard
here is a picture of the main.storyboard
Could be that you are not on the main thread? Usually the callback functions of network calls are off of the main thread. Unless you're sure that that's not the problem, try adding it:
DispatchQueue.main.async {
self.performSegue(withIdentifier: "successView", sender: self)
}
I successfully call DropIn view from Braintree SDK. The BTDropInRequest settings should display three items:
PayPal
Credit Cards
Apple Pay
But for some reason in DropIn view renders only two items instead of three:
PayPal
Credit Cards
What I did wrong?
Preparation:
All certificates are created and uploaded
Apple Pay is enabled on the project capabilities
Merchant ID is added
The device on which I do testing is supported Apple Pay
Here is a code of method which does request:
func showDropIn(clientTokenOrTokenizationKey: String) {
BTUIKAppearance.darkTheme()
let request = BTDropInRequest()
let canMakePayments = PKPaymentAuthorizationViewController.canMakePayments() && PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: [.amex, .visa, .masterCard])
request.applePayDisabled = !canMakePayments
request.cardDisabled = false
let dropIn = BTDropInController.init(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{
switch result.paymentOptionType {
case .applePay ,.payPal,.masterCard,.discover,.visa:
if let paymentMethod = result.paymentMethod {
controller.dismiss(animated: true, completion: nil)
} else {
controller.dismiss(animated: true, completion: {
self.braintreeClient = BTAPIClient(authorization: clientTokenOrTokenizationKey)
let paymentRequest = self.paymentRequest()
if let vc = PKPaymentAuthorizationViewController(paymentRequest: paymentRequest)
as PKPaymentAuthorizationViewController?
{
vc.delegate = self
self.present(vc, animated: true, completion: nil)
} else {
print("Error: Payment request is invalid.")
}
})
}
default:
print("error")
controller.dismiss(animated: true, completion: nil)
}
}
}
self.present(dropIn!, animated: true, completion: nil)
}
Base on a document from Braintree, you should complete the Apple Pay integration and the customer's device and card type are supported.
https://developers.braintreepayments.com/guides/drop-in/setup-and-integration/ios/v4#apple-pay
Also, take note at this point
If using a client token with a customer id, the Apple Pay card will
not automatically be vaulted. You can use the payment method nonce to
create a payment method on your server.
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.
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.