一、代码实现之前先配置证书和Merchant ID :
具体操作看:http://blog.csdn.net/songchunmin_/article/details/51543356
demo地址:https://github.com/songchunmin/PayDemo
二、具体实现
/* 判定用户是否能够支付,在创建支付请求之前,要首先通过调用PKPaymentAuthorizationViewController 类里的 canMakePaymentsUsingNetworks:方法来判断用户是否能够使用你提供的支付网络进行支付。 如果要判断用户的硬件是否支持Apple Pay或者是否因为家长控制而不能支付,请使用canMakePayments 方法。 如果用户不能进行支付,那就不要显示支付按钮,相应的应该退回到其它支付方式。 */ if([PKPaymentAuthorizationViewController canMakePayments]) { NSLog(@"Woo! Can make payments!"); PKPaymentRequest *request = [[PKPaymentRequest alloc] init]; // PKPaymentSummaryItem *widget1 = [PKPaymentSummaryItem summaryItemWithLabel:@"娃哈哈" amount:[NSDecimalNumber decimalNumberWithString:@"0.01"] type:PKPaymentSummaryItemTypeFinal]; PKPaymentSummaryItem *widget2 = [PKPaymentSummaryItem summaryItemWithLabel:@"鲜牛奶" amount:[NSDecimalNumber decimalNumberWithString:@"0.01"]]; PKPaymentSummaryItem *total = [PKPaymentSummaryItem summaryItemWithLabel:@"Grand Total" amount:[NSDecimalNumber decimalNumberWithString:@"0.02"]]; //数组中,最后的对象是总价。 request.paymentSummaryItems = @[widget1, widget2, total]; //国家--一定要填写正确,如果不知道的话,随便输入,控制台会列举所有的出来, request.countryCode = @"CN"; //货币单位需要使用- 人民币 request.currencyCode = @"CNY"; //Wallet所绑定的卡的类型, 银联记得加上,我记得在配置证书那里选项,是否支持中国境内(大概意思),这里不加PKPaymentNetworkChinaUnionPay直接Crash,估计和这个有关。。。 request.supportedNetworks = @[PKPaymentNetworkAmex, PKPaymentNetworkMasterCard, PKPaymentNetworkVisa,PKPaymentNetworkChinaUnionPay]; request.merchantIdentifier = @"merchant.com.scm.PayDemo"; //通过指定merchantCapabilities属性来指定你支持的支付处理标准,3DS支付方式是必须支持的,EMV方式是可选的。 request.merchantCapabilities = PKMerchantCapabilityEMV; //设置后,如果用户之前没有填写过,那么会要求用户必须填写才能够使用Apple Pay // request.requiredShippingAddressFields = PKAddressFieldPostalAddress | PKAddressFieldPhone | PKAddressFieldEmail | PKAddressFieldName; //显示支付信息的控制器 self.paymentPane = [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest:request]; self.paymentPane.delegate = self; [self presentViewController:_paymentPane animated:TRUE completion:nil]; } else { NSLog(@"This device cannot make payments"); } } /** * 用户发送付款请求后会调用该方法。在这个方法中发送相关的支付信息到你的服务器,最后通过服务器来处理。如果服务期处理成功, * 那么需要调用 completion 的block 并且传入 PKPaymentAuthorizationStatusSuccess 的标记即可。 * 如果服务器处理不成功,那么传一个其他的标记就可以了 */ - (void)paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller didAuthorizePayment:(PKPayment *)payment completion:(void (^)(PKPaymentAuthorizationStatus status))completion { NSLog(@"Payment was authorized: %@", payment); // do an async call to the server to complete the payment. // See PKPayment class reference for object parameters that can be passed BOOL asyncSuccessful = FALSE; // When the async call is done, send the callback. // Available cases are: // PKPaymentAuthorizationStatusSuccess, // Merchant auth'd (or expects to auth) the transaction successfully. // PKPaymentAuthorizationStatusFailure, // Merchant failed to auth the transaction. // // PKPaymentAuthorizationStatusInvalidBillingPostalAddress, // Merchant refuses service to this billing address. // PKPaymentAuthorizationStatusInvalidShippingPostalAddress, // Merchant refuses service to this shipping address. // PKPaymentAuthorizationStatusInvalidShippingContact // Supplied contact information is insufficient. if(asyncSuccessful) { completion(PKPaymentAuthorizationStatusSuccess); // do something to let the user know the status NSLog(@"Payment was successful"); // [Crittercism endTransaction:@"checkout"]; } else { completion(PKPaymentAuthorizationStatusFailure); // do something to let the user know the status NSLog(@"Payment was unsuccessful"); // [Crittercism failTransaction:@"checkout"]; }
三、注意事项(参考别人的,原博客:http://www.open-open.com/lib/view/open1422324034345.html)
创建支付请求
支付请求是PKPaymentRequest类的实例,它的组成部分包括一个用来表示将要购买的项目的摘要,一个可用的配送方式列表,一个表示用户需要提供的配送信息的描述,以及一些商家和支付平台的信息。
判定用户是否能够支付
在创建支付请求之前,要首先通过调用PKPaymentAuthorizationViewController 类里的canMakePaymentsUsingNetworks:方法来判断用户是否能够使用你提供的支付网络进行支付。如果要判断用户的硬件是否支持Apple Pay或者是否因为家长控制而不能支付,请使用canMakePayments 方法。
如果用户不能进行支付,那就不要显示支付按钮,相应的应该退回到其它支付方式。
支付请求包含货币和地区信息
所有的汇总金额应该使用同一种货币,货币的信息可使用PKPaymentRequest类的currencyCode属性进行指定。像"USD"这样,使用3个字符格式的ISO货币编码。
一个支付请求里的国家代码表示了这次购买发生的国家或者将要在这个国家处理这次支付。像"US"这样,使用2个字符格式的ISO国家编码。
在支付请求里指定的商用ID必须匹配应用中指定的商用ID列表之一。
request.currencyCode = @"USD"; request.countryCode = @"US"; request.merchantIdentifier = @"merchant.com.example";
支付请求包含一个支付摘要项目的列表
支付摘要项目,属于PKPaymentSummaryItem
类,描述了支付请求的不同部分。在一个支付请求里不要使用太多的摘要项目---典型的项目像比如小计金额、折扣信息、配送信息、含税信息以及总计金额等。如果你想要提供更详细的支付项目列表,可以在你应用的其它地方提供。
每一个摘要项目会有一个标签和数额,就像在代码列表3-1中显示的那样。标签文本是一个用户可阅读的摘要项目描述信息,数额是相对应的支付数额。在一个支付请求中所有的数额都要使用在这个请求中指定的货币。对于折扣或优惠券,则需要把数额设成负数。
Listing 3-1创建支付项目
// 12.75 subtotal NSDecimalNumber *subtotalAmount = [NSDecimalNumber decimalNumberWithMantissa:1275 exponent:-2 isNegative:NO]; self.subtotal = [PKPaymentSummaryItem summaryItemWithLabel:@"Subtotal" amount:subtotalAmount]; // 2.00 discount NSDecimalNumber *discountAmount = [NSDecimalNumber decimalNumberWithMantissa:200 exponent:-2 isNegative:YES]; self.discount = [PKPaymentSummaryItem summaryItemWithLabel:@"Discount" amount:discountAmount];
注意
这里使用NSDecimalNumber类来存储摘要项目的数额,它是一个以10为底数的数值。可以使用指定尾数和指数的方式(像代码中那样)来创建这个类的实例,也可以通过指定字符串和locale来实例化,字符串指定了相应的数值。这里总是使用以10为底数的数值来做财务计算--例如当需要计算5%折扣掉的金额时。
尽管有时使用其它的计数方法更方便,但是像float或者Double这样的IEEE浮点数类型是不适合作财务计算的,这些数据类型使用的是以2为底数的数值表示方法,这就表示有一些十进制数值不能准确得被表示--例如0.42必须以0.41999这样的循环小数来近似表示,而这种近似表示常常会造成财务计算的错误结果。
在这个摘要项目列表中的最后一个是总计金额。这个金额是通过把所有其它金额相加而得到。总计的显示方法和其它的摘要项目不同:应该使用你公司的名称做为其标签,使用所有其它项目的金额总和做为金额。使用paymentSummaryItems 属性将这些摘要项目加入支付请求。
// 10.75 grand total NSDecimalNumber *totalAmount = [NSDecimalNumber zero]; totalAmount = [totalAmount decimalNumberByAdding:subtotalAmount]; totalAmount = [totalAmount decimalNumberByAdding:discountAmount]; self.total = [PKPaymentSummaryItem summaryItemWithLabel:@"My Company Name" amount:totalAmount]; self.summaryItems = @[self.subtotal, self.discount, self.total]; request.paymentSummaryItems = self.summaryItems;
配送方式是一种特殊的摘要项目
对于每一种可用的配送方式创建一个PKShippingMethod的实例。就像其它支付摘要项目一样,配送方式包含用户易于辨别的标签,比如"标准配送"或者"第二天配送",还有一个金额来表示配送费用。与其它摘要项目不同的是,配送方式还有一个detail属性--像"7月29日到达"或者"24小时之内配送"等--可以用来解释各个配送方式之间的区别。
使用identifier属性来在代理方法中区分不同的配送方式,这个属性只会在你的应用内使用--框架看不到这个属性,并且它也不会出现在UI中。在创建配送方式时为其分配一个独一无二的标识符。为了方便调试,可使用文本缩写,比如"discount",
"standard", 或者 "next-day".
有一些配送方式在某些地区可能不适用,或者有不同的价格,你可以在用户选择配送地址或配送方式的代理方法时更新这些信息,就像Your
Delegate Updates Shipping Methods and Costs描述的一样。
指定你支持的支付方式
通过在supportedNetworks属性中填入字符串常量数组来指定你支持的支付网络。通过指定merchantCapabilities属性来指定你支持的支付处理标准,3DS支付方式是必须支持的,EMV方式是可选的。
商家支持的支付处理标准使用标识位来进行组合,像下面这样:
request.supportedNetworks = @[PKPaymentNetworkAmex, PKPaymentNetworkMasterCard, PKPaymentNetworkVisa]; // Supports 3DS only request.merchantCapabilities = PKMerchantCapability3DS; // Supports both 3DS and EMV request.merchantCapabilities = PKMerchantCapability3DS | PKMerchantCapabilityEMV;
指示所需配送信息和账单信息
通过填充 requiredBillingAddressFields 和 requiredShippingAddressFields属性来指定所需账单信息和配送地址信息。当你显示一个视图控制器时,它会提示用户输入所需内容。这些字段常量可以像下面这样进行组合来设置这些属性:
request.requiredBillingAddressFields = PKAddressFieldEmail; request.requiredBillingAddressFields = PKAddressFieldEmail | PKAddressFieldPostalAddress;
如果你已经有了用户的账单和配送信息,可以直接在支付请求中使用它们。但是尽管Apple Pay默认使用了这些信息,用户仍然可以在授权支付的过程中修改这些信息。
ABRecordRef record = ABPersonCreate(); CFErrorRef error; BOOL success; success = ABRecordSetValue(record, kABPersonFirstNameProperty, @"John", &error); if (!success) { /* ... handle error ... */ } success = ABRecordSetValue(record, kABPersonLastNameProperty, @"Appleseed", &error); if (!success) { /* ... handle error ... */ } ABMultiValueRef shippingAddress = ABMultiValueCreateMutable(kABMultiDictionaryPropertyType); NSDictionary *addressDictionary = @{ (NSString *) kABPersonAddressStreetKey: @"1234 Laurel Street", (NSString *) kABPersonAddressCityKey: @"Atlanta", (NSString *) kABPersonAddressStateKey: @"GA", (NSString *) kABPersonAddressZIPKey: @"30303" }; ABMultiValueAddValueAndLabel(shippingAddress, (__bridge CFDictionaryRef) addressDictionary, kABOtherLabel, nil); success = ABRecordSetValue(record, kABPersonAddressProperty, shippingAddress, &error); if (!success) { /* ... handle error ... */ } request.shippingAddress = record; CFRelease(shippingAddress); CFRelease(record);
存储额外信息
使用applicationData属性来存储一些在你的应用中关于这次支付请求的唯一标识信息,比如一个购物车的标识符。在用户授权支付之后,这个属性的哈希值会出现在这次支付的token中。
part 4 授权支付
支付授权过程是由支付授权view controller和它的代理协作完成的。支付授权view controller做了两件事情:它让用户选择支付请求所必需的账单和配送信息,还有让用户最终授权同意这次支付。当用户和view controller交互时,代理方法就会被调用,这样你的应用就可以不断地更新显示的信息--例如在配送地址更改后更新配送费用。用户最终授权支付请求之后代理方法同样也会被调用。
注意:在实现这些方法时注意,这些方法可能会被多次调用,而它们被调用的顺序取决于用户的行为的顺序。
在所有这个授权过程中被调用的代理方法中,都会有一个completion block被做为参数之一传入,支付授权view controller会在一个代理方法执行完毕(通过调用completion块)后再调用另一个代理方法。唯一的例外是paymentAuthorizationViewControllerDidFinish:方法:它不包含completion
block,所以它可以在任何时候被调用。
这个completion block有一个传入参数,基于现有的可用信息,你可以通过这个参数并指定这次交易的状态。如果这次交易没有任何问题,传入PKPaymentAuthorizationStatusSuccess,否则,你要传入一个识别问题的值。
通过在PKPaymentAuthorizationViewController类的构造方法中传入一个支付请求来对它进行实例化,然后给这个视图控制器设置一个代理,就可以把它展示给用户了。
PKPaymentAuthorizationViewController *viewController = [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest:request]; if (!viewController) { /* ... Handle error ... */ } viewController.delegate = self; [self presentViewController:viewController animated:YES completion:nil];
当用户与这个视图控制器进行交互时,它的代理方法会被调用。
通过代理更新配送方式和费用
当用户提供配送信息之后,授权view controller 会调用paymentAuthorizationViewController:didSelectShippingAddress:completion:和paymentAuthorizationViewController:didSelectShippingMethod:completion:这两个代理方法。在这两个方法中根据最新信息来更新支付请求。
- (void) paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller didSelectShippingAddress:(ABRecordRef)address completion:(void (^)(PKPaymentAuthorizationStatus, NSArray *, NSArray *))completion { self.selectedShippingAddress = address; [self updateShippingCost]; NSArray *shippingMethods = [self shippingMethodsForAddress:address]; completion(PKPaymentAuthorizationStatusSuccess, shippingMethods, self.summaryItems); } - (void) paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller didSelectShippingMethod:(PKShippingMethod *)shippingMethod completion:(void (^)(PKPaymentAuthorizationStatus, NSArray *))completion { self.selectedShippingMethod = shippingMethod; [self updateShippingCost]; completion(PKPaymentAuthorizationStatusSuccess, self.summaryItems); }
当支付被授权后,支付token会被创建
当用户最终授权了一个支付请求,框架会通过与苹果服务器和嵌入在设备中的一个安全模块进行通信,生成一个支付token。然后你在paymentAuthorizationViewController:didAuthorizePayment:completion:方法中将这个token和其它一些你需要用来处理这次购买的信息--例如配送地址和购物车标识--发送给你的服务器。这个过程是这样的:
- 框架发送支付请求给安全模块,只有安全模块可以访问存储在设备上的标记化的卡信息。
- 安全模块把特定的卡和商家等支付数据加密,以保证只有苹果可以读取,然后发送给框架。框架会将这些数据发送给苹果。
- 苹果服务器再次加密这些支付数据,以保证只有商家可以读取。然后服务器对它进行签名,生成支付token,然后发送给设备。
- 框架调用相应的代理方法并传入这个token,然后你的代理方法传送token给你的服务器。
至于你的服务器采取的行为要取决于你是自己处理这次支付或者你是和其它支付平台合作来进行支付处理。不管怎样,你的服务器处理这个订单然后传送一个状态信息给设备,代理方法会把这个状态信息传送给completion块,像在“Processing
a Payment”中讨论过的。
- (void) paymentAuthorizationViewController:(PKPaymentAuthorizationViewController *)controller didAuthorizePayment:(PKPayment *)payment completion:(void (^)(PKPaymentAuthorizationStatus))completion { NSError *error; ABMultiValueRef addressMultiValue = ABRecordCopyValue(payment.billingAddress, kABPersonAddressProperty); NSDictionary *addressDictionary = (__bridge_transfer NSDictionary *) ABMultiValueCopyValueAtIndex(addressMultiValue, 0); NSData *json = [NSJSONSerialization dataWithJSONObject:addressDictionary options:NSJSONWritingPrettyPrinted error: &error]; // ... Send payment token, shipping and billing address, and order information to your server ... PKPaymentAuthorizationStatus status; // From your server completion(status); }
在代理方法中释放授权View Controller
在框架显示交易状态之后,授权View Controller会调用代理paymentAuthorizationViewControllerDidFinish:的方法。在这个方法的实现中,先释放授权页面控制器再显示你自己的订单确认页面。
- (void) paymentAuthorizationViewControllerDidFinish:(PKPaymentAuthorizationViewController *)controller { [controller dismissViewControllerAnimated:YES completion:nil]; }
Part 5 支付处理
处理一个支付请求涉及以下几个步骤:
- 把支付信息,以及支付流程+所需的其他信息,一起发送给你的服务器。
- 验证支付数据的哈希表和签名
- 为加密过的支付数据解码
- 向支付处理系统提交支付数据
- 向订单追踪系统提交订单
处理支付请求时,你有两个选择;你既可以利用支付平台处理支付请求,也可以自己实现支付请求处理流程。一个常用的支付平台可以完成上述大部分操作。
读取,验证,以及处理支付信息需要有一定的相关密码知识,例如计算SHA-1哈希表,读取和验证PKCS#7签名,执行Elliptic Curve Diffie-Hellman密匙交换。如果没有一定的密码学背景,你可以考虑使用第三方支付平台来完成这些操作。
关于支持Apple Pay支付平台的更多信息,请参考developer.apple.com/apple-pay/
处理支付请求所用的信息拥有一种嵌套式的数据结构,如下图。支付令牌是PKPaymentToken类的实例。其paymentData属性值是一个JSON词典,它的头文件信息可以用来验证和加密支付数据。加密过的数据信息包括支付金额、持卡人姓名,以及一些其他指定的支付处理协议。
关于支付数据结构格式的详细信息,请参看:Payment
Token Format Reference.
Figure 5-1支付数据结构