集成苹果支付(swift4.0)
2019-01-03 本文已影响0人
Jason_风筝
苹果支付虽然在国内用的不多, 但一些海外的app很大部分会用到,所以这里我们讨论一下怎么swift 怎么集成苹果
你要知道
- 建议ios 系统 > 9 . 因为苹果支付, 从ios9 开始慢慢才完善.
- 调起的wallet , 它只是一个桥梁,它并不会帮你扣钱,但只是获取到用户信用卡的资料和支付密码返回给你,连密码正确错误它都不知道.
- 一般来说, 一个公司想用apple pay , 不会自己跑去和每个银行签约, 一般都是借助第三方. 因为第三方已经和银行签约了,比如: Stripe / mpgs
你要准备
- 你的开发证书需要开通apple pay 功能, 从而获得 Merchant Identifier
-
在xcode 中设置
- 最后支付完成, 你还要调用它的方法告诉它结果, 然后它会显示给用户看, 再dismiss .
- 所以大致可以分为: 调起支付 -> 拿到支付结果 -> 传给server -> 获取支付结果来自server -> 调用苹果支付的方法, 告诉它结果. 它自动dismiss .
-
Apple Pay Docs
接下来就开始了, 我们直接上代码
import UIKit
import PassKit
import AddressBook
class ApplepayService {
private var payResultCallback: CallbackHandler? = nil
var merchantIdentifier: String? = "你的appleMerchantIdentifier, 就是上图那个"
// 支持的支付类型和网络类型
private var supportedPaymentNetworks = [PKPaymentNetwork.visa, PKPaymentNetwork.masterCard, PKPaymentNetwork.amex]
private let merchantCapabilities = PKMerchantCapability.RawValue(UInt8(PKMerchantCapability.capability3DS.rawValue) | UInt8(PKMerchantCapability.capabilityEMV.rawValue))
typealias completionBlock = (PKPaymentAuthorizationStatus) -> ()
private var completion: completionBlock?
// 这个方法, 可以当成初始化的方法, 因为9.2 才开始支持 chinaUnionPay,所以要做个判断.
override func initialize() {
if #available(iOS 9.2, *) {
supportedPaymentNetworks.append(PKPaymentNetwork.chinaUnionPay)
}
}
override func handleMessage(method: String, body: NSDictionary, callback: CallbackHandler?) {
switch method {
case "makePaymentRequest": /// 发起支付
if merchantIdentifier == nil {
showAlert(nil, "miss apple merchant identifier")
return
}
payResultCallback = callback
makePaymentRequest(body, callback)
break;
case "completeLastTransaction": // 支付完成的后要调用它告诉结果
if merchantIdentifier == nil {
showAlert(nil, "miss apple merchant identifier")
return
}
completeLastTransaction(body, callback)
break;
default:
break;
}
}
/*
body: {
items: [ // 一定要, 要不会报错
{
label: '3 x Basket Items',
amount: 49.99
}],
shippingMethods: [ // 运输方式
{
identifier: 'NextDay',
label: 'NextDay',
detail: 'Arrives tomorrow by 5pm.',
amount: 3.99
}],
currencyCode: 'HKD',
countryCode: 'HK',
billingAddressRequirement: 'none', // none/all/postcode/email/phone
shippingAddressRequirement: 'none', // none/all/postcode/email/phone
shippingType: 'shipping' // shipping/delivery/store/service
}
*/
private func makePaymentRequest(_ body: NSDictionary, _ callback: CallbackHandler?) {
print("apple pay body:\(body) callback:\(callback)")
guard canMakePayments() else { return }
completion = nil
let request = PKPaymentRequest();
request.supportedNetworks = supportedPaymentNetworks
request.merchantCapabilities = PKMerchantCapability(rawValue: merchantCapabilities)
//request info
if let currencyCode = body.value(forKey: "currencyCode") as? String {
request.currencyCode = currencyCode
}
if let countryCode = body.value(forKey: "countryCode") as? String {
request.countryCode = countryCode
}
if let merchantIdentifier = merchantIdentifier {
request.merchantIdentifier = merchantIdentifier
}
request.requiredBillingAddressFields = billingAddressRequirementFromBody(body)
request.requiredShippingAddressFields = shippingAddressRequirementFromArgumentsBody(body)
if #available(iOS 8.3, *) {
request.shippingType = shippingTypeFromBody(body)
}
request.shippingMethods = shippingMethodsFromBody(body)
request.paymentSummaryItems = itemsFromBody(body)
let authVC = PKPaymentAuthorizationViewController(paymentRequest: request)
authVC?.delegate = self
if let authVC = authVC {
vc?.present(authVC, animated: true, completion: nil)
} else {
// 如果这个错,要看一下是不是参数没传对.
showAlert(nil, "PKPaymentAuthorizationViewController was nil.")
return
}
}
// 判断是否能苹果支付
private func canMakePayments() -> Bool {
var canPayment = false
if PKPaymentAuthorizationViewController.canMakePayments() {
if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_8_0) { // < ios8.0
showAlert(nil, "This device cannot make payments.")
} else if #available(iOS 9.0, *) {
if PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: supportedPaymentNetworks, capabilities: PKMerchantCapability(rawValue: merchantCapabilities)) {
// This device can make payments and has a supported card"
canPayment = true
} else {
showAlert(nil, "This device can make payments but has no supported cards.")
}
} else if #available(iOS 8.0, *) {
if PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: supportedPaymentNetworks) {
// This device can make payments and has a supported card , in ios 8
canPayment = true
} else {
showAlert(nil, "This device can make payments but has no supported cards.")
}
} else {
showAlert(nil, "This device cannot make payments.")
}
} else {
showAlert(nil, "This device cannot make payments.")
}
return canPayment;
}
// Pay the call, pass the result
/*
body: {paymentAuthorizationStatus: success / failure / invalid-billing-address / invalid-shipping-address / invalid-shipping-contact / require-pin/ incorrect-pin/ locked-pin }
*/
private func completeLastTransaction(_ body: NSDictionary, _ callback: CallbackHandler?) {
let paymentAuthorizationStatusString = self.paymentAuthorizationStatusFromBody(body)
completion?(paymentAuthorizationStatusString)
}
/*
{
shippingMethods :{
[{
label:String
amount:DecimalNumber
detail:String?
identifier:String?
}]
}
}
*/
private func shippingMethodsFromBody(_ body: NSDictionary) -> [PKShippingMethod] {
var methods: [PKShippingMethod] = []
if let tempMethods = body.value(forKey: "shippingMethods") as? Array<Dictionary<String, Any>> {
for tempMethod in tempMethods {
print("payment method:\(tempMethod)")
let method = PKShippingMethod()
if let lable = tempMethod["label"] as? String, let amount = tempMethod["amount"], let decimalValue = (amount as AnyObject).decimalValue {
let amountNumber = NSDecimalNumber(decimal: decimalValue)
method.label = lable
method.amount = amountNumber
}
let identifier = tempMethod["identifier"] as? String
let detail = tempMethod["detail"] as? String
method.detail = detail
method.identifier = identifier
methods.append(method)
}
}
return methods;
}
/*
{
items :{
[{
label:String
amount:DecimalNumber
}]
}
}
*/
private func itemsFromBody(_ body: NSDictionary) -> [PKPaymentSummaryItem] {
var items: [PKPaymentSummaryItem] = []
if let tempItems = body.value(forKey: "items") as? Array<Dictionary<String, Any>> {
for item in tempItems {
// print("payment item:\(item)")
if let lable = item["label"] as? String, let amount = item["amount"], let decimalValue = (amount as AnyObject).decimalValue {
let amountNumber = NSDecimalNumber(decimal: decimalValue)
let newItem = PKPaymentSummaryItem(label: lable, amount: amountNumber)
items.append(newItem)
}
}
}
return items
}
// shipping/delivery/store/service
@available(iOS 8.3, *)
private func shippingTypeFromBody(_ body: NSDictionary) -> PKShippingType {
if let shippingType = body.value(forKey: "shippingType") as? String {
if shippingType == "shipping" {
return PKShippingType.shipping
} else if shippingType == "delivery" {
return PKShippingType.delivery
} else if shippingType == "store" {
return PKShippingType.storePickup
} else if shippingType == "service" {
return PKShippingType.servicePickup
}
}
return PKShippingType.shipping
}
/*
// param:none/all/postcode/email/phone
{shippingAddressRequirement:"none"}
*/
private func shippingAddressRequirementFromArgumentsBody(_ body: NSDictionary) -> PKAddressField {
if let shippingAddressRequirement = body.value(forKey: "shippingAddressRequirement") as? String {
if shippingAddressRequirement == "none" {
return PKAddressField.init(rawValue: 0) // none
} else if shippingAddressRequirement == "all" {
return PKAddressField.all
} else if shippingAddressRequirement == "postcode" {
return PKAddressField.postalAddress
} else if shippingAddressRequirement == "name" {
if #available(iOS 8.3, *) {
return PKAddressField.name
}
} else if shippingAddressRequirement == "email" {
return PKAddressField.email
} else if shippingAddressRequirement == "phone" {
return PKAddressField.phone
}
}
return PKAddressField.init(rawValue: 0) // none
}
/*
// param:none/all/postcode/email/phone
{billingAddressRequirement:"none"}
*/
private func billingAddressRequirementFromBody(_ body: NSDictionary) -> PKAddressField {
if let billingAddressRequirement = body.value(forKey: "billingAddressRequirement") as? String {
if billingAddressRequirement == "none" {
return PKAddressField.init(rawValue: 0) // none
} else if billingAddressRequirement == "all" {
return PKAddressField.all
} else if billingAddressRequirement == "postcode" {
return PKAddressField.postalAddress
} else if billingAddressRequirement == "name" {
if #available(iOS 8.3, *) {
return PKAddressField.name
}
} else if billingAddressRequirement == "email" {
return PKAddressField.email
} else if billingAddressRequirement == "phone" {
return PKAddressField.phone
}
}
return PKAddressField.init(rawValue: 0) // none
}
/*
body: {paymentAuthorizationStatus: success / failure / invalid-billing-address / invalid-shipping-address / invalid-shipping-contact / require-pin/ incorrect-pin/ locked-pin }
*/
/// Pay the call, pass the result
private func paymentAuthorizationStatusFromBody(_ body: NSDictionary) -> PKPaymentAuthorizationStatus {
if let paymentAuthorizationStatus = body.value(forKey: "paymentAuthorizationStatus") as? String {
if paymentAuthorizationStatus == "success" {
return PKPaymentAuthorizationStatus.success
} else if paymentAuthorizationStatus == "failure" {
return PKPaymentAuthorizationStatus.failure
} else if paymentAuthorizationStatus == "invalid-billing-address" {
return PKPaymentAuthorizationStatus.invalidBillingPostalAddress
} else if paymentAuthorizationStatus == "invalid-shipping-address" {
return PKPaymentAuthorizationStatus.invalidShippingPostalAddress
} else if paymentAuthorizationStatus == "invalid-shipping-contact" {
return PKPaymentAuthorizationStatus.invalidShippingContact
} else if paymentAuthorizationStatus == "require-pin" {
if #available(iOS 9.2, *) {
return PKPaymentAuthorizationStatus.pinRequired
}
} else if paymentAuthorizationStatus == "incorrect-pin" {
if #available(iOS 9.2, *) {
return PKPaymentAuthorizationStatus.pinIncorrect
}
} else if paymentAuthorizationStatus == "locked-pin" {
if #available(iOS 9.2, *) {
return PKPaymentAuthorizationStatus.pinLockout
}
}
}
return PKPaymentAuthorizationStatus.failure
}
}
/// PKPaymentAuthorizationViewControllerDelegate --------start
extension ApplepayService: PKPaymentAuthorizationViewControllerDelegate {
func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
controller.dismiss(animated: true, completion: nil)
}
// payment result , 这个方法就是用户支付后, 会调用的方法, 通过它获取到用户的资料..
// 基本需要的资料都在这里了,
func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, completion: @escaping (PKPaymentAuthorizationStatus) -> Void) {
// , 要先拿到它, 最后要通过调用它告诉apple 支付结果, 它才会自动dismiss
self.completion = completion
// let paymentData = String(data: payment.token.paymentData, encoding: .utf8);
let response = formatPaymentForApplication(payment)
payResultCallback?.success(response: response)
}
// @available(iOS 11.0, *)
// func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Swift.Void) {
// print("payment:\(payment)")
// }
/*
{
"paymentData": "<BASE64 ENCODED TOKEN WILL APPEAR HERE>",
"transactionIdentifier": "Simulated Identifier",
"paymentMethodDisplayName": "MasterCard 1234",
"paymentMethodNetwork": "MasterCard",
"paymentMethodTypeCard": "credit",
"billingEmailAddress": "",
"billingSupplementarySubLocality": "",
"billingNameFirst": "First",
"billingNameMiddle": "",
"billingNameLast": "NAME",
"billingAddressStreet": "Street 1\n",
"billingAddressCity": "London",
"billingAddressState": "London",
"billingPostalCode": "POST CODE",
"billingCountry": "United Kingdom",
"billingISOCountryCode": "gb",
"shippingEmailAddress": "",
"shippingPhoneNumber": "",
"shippingNameFirst": "First",
"shippingNameMiddle": "",
"shippingNameLast": "Name",
"shippingSupplementarySubLocality": "",
"shippingAddressStreet": "Street Line 1\nStreet Line 2",
"shippingAddressCity": "London",
"shippingAddressState": "London",
"shippingPostalCode": "POST CODE",
"shippingCountry": "United Kingdom",
"shippingISOCountryCode": "gb",
}
*/
private func formatPaymentForApplication(_ payment: PKPayment) -> Dictionary<String, Any> {
// 这里base64 了, 然后再传给server , server 还要decode , 这里看需求, 看你server 想要用怎么给它.
let paymentData = payment.token.paymentData.base64EncodedString()
var response = Dictionary<String, Any>()
response["paymentData"] = paymentData
response["transactionIdentifier"] = payment.token.transactionIdentifier
var typeCard = "error"
if #available(iOS 9.0, *) {
response["paymentMethodDisplayName"] = payment.token.paymentMethod.displayName
response["paymentMethodNetwork"] = payment.token.paymentMethod.network
switch payment.token.paymentMethod.type {
case PKPaymentMethodType.unknown:
typeCard = "unknown"
break;
case PKPaymentMethodType.debit:
typeCard = "debit"
break;
case PKPaymentMethodType.credit:
typeCard = "credit"
break;
case PKPaymentMethodType.prepaid:
typeCard = "prepaid"
break;
case PKPaymentMethodType.store:
typeCard = "store"
break;
}
} else {
typeCard = "error"
}
response["paymentMethodTypeCard"] = typeCard
if #available(iOS 9.0, *), let billingContact = payment.billingContact {
if let emailAddress = billingContact.emailAddress {
response["billingEmailAddress"] = emailAddress
}
if #available(iOS 9.2, *), let supplementarySubLocality = billingContact.supplementarySubLocality {
response["billingSupplementarySubLocality"] = supplementarySubLocality
}
if let name = billingContact.name {
if let givenName = name.givenName {
response["billingNameFirst"] = givenName
}
if let middleName = name.middleName {
response["billingNameMiddle"] = middleName
}
if let familyName = name.familyName {
response["billingNameLast"] = familyName
}
}
if let postalAddress = billingContact.postalAddress {
response["billingAddressStreet"] = postalAddress.street
response["billingAddressCity"] = postalAddress.city
response["billingAddressState"] = postalAddress.state
response["billingPostalCode"] = postalAddress.postalCode
response["billingCountry"] = postalAddress.country
response["billingISOCountryCode"] = postalAddress.isoCountryCode
}
if let shippingContact = payment.shippingContact {
if let emailAddress = shippingContact.emailAddress {
response["shippingEmailAddress"] = emailAddress
}
if let phoneNumber = shippingContact.phoneNumber {
response["shippingPhoneNumber"] = phoneNumber.stringValue
}
if let name = shippingContact.name {
if let givenName = name.givenName {
response["shippingNameFirst"] = givenName
}
if let middleName = name.middleName {
response["shippingNameMiddle"] = middleName
}
if let familyName = name.familyName {
response["shippingNameLast"] = familyName
}
}
if #available(iOS 9.2, *), let supplementarySubLocality = shippingContact.supplementarySubLocality {
response["shippingSupplementarySubLocality"] = supplementarySubLocality
}
if let postalAddress = shippingContact.postalAddress {
response["shippingAddressStreet"] = postalAddress.street
response["shippingAddressCity"] = postalAddress.city
response["shippingAddressState"] = postalAddress.state
response["shippingPostalCode"] = postalAddress.postalCode
response["shippingCountry"] = postalAddress.country
response["shippingISOCountryCode"] = postalAddress.isoCountryCode
}
}
} else if #available(iOS 8.0, *) {
if let shippingAddress = payment.shippingAddress {
if let PersonAddressStreetKey = kABPersonAddressStreetKey as? ABPropertyID, let shippingAddressStreet = ABRecordCopyValue(shippingAddress, PersonAddressStreetKey).takeRetainedValue() as? String {
response["shippingAddressStreet"] = shippingAddressStreet
}
if let PersonAddressCityKey = kABPersonAddressCityKey as? ABPropertyID, let shippingAddressCity = ABRecordCopyValue(shippingAddress, PersonAddressCityKey).takeRetainedValue() as? String {
response["shippingAddressCity"] = shippingAddressCity
}
if let PersonAddressZIPKey = kABPersonAddressZIPKey as? ABPropertyID, let shippingPostalCode = ABRecordCopyValue(shippingAddress, PersonAddressZIPKey).takeRetainedValue() as? String {
response["shippingPostalCode"] = shippingPostalCode
}
if let PersonAddressStateKey = kABPersonAddressStateKey as? ABPropertyID, let shippingAddressState = ABRecordCopyValue(shippingAddress, PersonAddressStateKey).takeRetainedValue() as? String {
response["shippingAddressState"] = shippingAddressState
}
if let PersonAddressCountryCodeKey = kABPersonAddressCountryCodeKey as? ABPropertyID, let shippingCountry = ABRecordCopyValue(shippingAddress, PersonAddressCountryCodeKey).takeRetainedValue() as? String {
response["shippingCountry"] = shippingCountry
}
if let PersonAddressCityKey = kABPersonAddressCityKey as? ABPropertyID, let shippingISOCountryCode = ABRecordCopyValue(shippingAddress, PersonAddressCityKey).takeRetainedValue() as? String {
response["shippingISOCountryCode"] = shippingISOCountryCode
}
if let shippingEmailAddress = ABRecordCopyValue(shippingAddress, kABPersonEmailProperty).takeRetainedValue() as? String {
response["shippingEmailAddress"] = shippingEmailAddress
}
}
}
return response
}
}
/// PKPaymentAuthorizationViewControllerDelegate --------end
end.
20190128 补充: 感觉像是苹果的bug , 如果swift 用以上方法, 在ios 8-9 的系统可能会报passKit.framework 异常, 我是这么解决的, 新建一个 .m 文件, 也就是OC , 然后在里面导入 AddressBook 与 PassKit 就可以了, .m 会参与编译.
@import AddressBook;
#import <PassKit/PassKit.h>
@interface BridgePasskit : NSObject
@end
@implementation BridgePasskit
@end