导读:Pay的应用内支付提供了一种全新的在线支付形式,如果将Pay的应用内支付特点与App本身的产品形态相结合,将极大提升用户的在线支付体验。作为Pay中国首批支持应用内支付的App之一,在接入Pay时已经与产品功能进行了深度融合。基于此,本文就Pay技术接入的关键点,包括可用性、服务器解密、交易处理等进行深入体验分享。
Pay已经在中国大陆正式上线,但大多数人关注的重点还是其线下支付体验。对于我们应用开发者来说,Pay的应用内支付为我们提供了一种全新的线上支付形式。如果将Pay的应用内支付的特点与App本身的产品形态结合起来,可以大大提升用户的线上支付体验。
Pay 与现有支付方式的比较
在国内开发包含在线支付功能的应用时,目前可选的方式是接入第三方支付平台,比如支付宝或者微信支付。这些支付方式在接入方式上类似,就是在App中引入对应平台的SDK,整理好支付信息后,调用对应第三方平台的SDK完成支付。不同平台的SDK对支付请求的处理方式不同,一般来说,完成支付的方式有两种:调出对应App或者打开网页。比如微信就只支持打开微信App进行支付。
与现有的第三方支付平台相比,Pay具有以下优势:
系统级支持,支付流程无需跳转第三方APP;支付流程可获取用户信息,如手机号、收货地址等。
Pay应用内支付的接入方式与微信等第三方平台不同,作为iOS系统原生支持的功能,Pay的相关功能均包含在此系统中,无需引入第三方SDK即可集成。
深度支付整合
以我们的产品为例,作为国内首批支持应用内支付的应用之一,我们在接入Pay时,就将其与产品功能进行了深度融合。除了Pay拥有最短的支付路径,我们认为它最大的优势还在于Pay提供了用户维护的系统级个人信息。基于这些特点,结合我们现有的用户体系和支付体系,应用内支付体验得到了极大的提升。
与Pay集成后的功能:
最突出的一点是第一点:未登录用户也能直接购买商品。对于目前的电商应用来说,用户只有登录应用后才能购买商品。之所以能做到这一点,是因为只要我们能拿到用户的手机号,就能与我们的用户系统进行对接,完成购买流程。正是利用了Pay可以将手机号提供给应用的特性,才实现了让未登录用户直接通过Pay购买商品的功能(如图1所示)。
图1 未登录情况下购买商品的截图,可以看到联系信息栏支付技术接入点
首先我们来看一下Pay应用内支付序列图(如图2所示)。
图2 支付时序图
从序列图中可以清楚的看到,应用内支付流程分为以下几个步骤:
App展示并提示用户进行支付验证(指纹和PIN码);用户支付验证完成后,iOS系统与服务器交互处理支付信息(主要是加密);App收到系统回调,然后将回调中包含的支付信息传送到自己的服务器;服务器收到App发送的信息后,进行解密,将需要的信息组织成银联消息,然后调用银联扣款接口完成扣款;服务器将支付结果通知App,App更新相应UI。
从以上步骤我们可以看出,虽然实际支付时间很短,但是从App显示到支付成功的过程还是很复杂的,实际开发中需要注意的地方也比较多,下面就来详细讲解一下。
薪酬可用性判断
如果APP显示支付按钮,需要先判断支付功能是否可用,这个实现需要结合中间类中的两个类函数来完成判断,分别是:
class func canMakePayments() -> Bool
和
class func canMakePaymentsUsingNetworks(supportedNetworks: [String]) -> Bool
第一个函数返回值代表当前设备是否支持Pay,用于判断设备硬件,目前只有6、6 Plus、6s、6s Plus设备支持Pay。
第二个功能是判断是否支持指定的支付网络,Pay 国内的支付网络是银联,所以需要传入这个值。
同时需要注意API版本的可用性,上面两个函数是iOS 8才支持的API,而这个支付网络从iOS 9.2才支持,所以最终判断Pay是否支持的代码如下:
if #available(iOS 9.2, *) { if PKPaymentAuthorizationViewController.canMakePayments() && PKPaymentAuthorizationViewController.canMakePaymentsUsingNetworks([PKPaymentNetworkChinaUnionPay]) { // TODO: 支持 Apple Pay } }
注意:一定要在项目设置中打开Pay选项,否则真机调试时肯定不支持上述代码判断结果。

展示
也就是说,Pay支付的UI有很多可以自定义的地方,要进行自定义,需要对类的初始化参数进行相应的设置,参数是一个类型的对象(如图3所示)。
图 3 iOS 付费结构
首先初始化一个对象,并设置所需的内容:
let request = PKPaymentRequest() request.merchantIdentifier = "merchant.xxxxx” request.merchantCapabilities = [.Capability3DS, .CapabilityEMV, .CapabilityCredit, .CapabilityDebit] request.countryCode = "CN" request.currencyCode = "CNY" request.supportedNetworks = [PKPaymentNetworkChinaUnionPay]
需要和苹果开发者后台的配置对应,上面代码中有一个必填属性没有设置,该属性表示 中展示的价格表,是一个 类型的数组,数组至少要有一个元素,最后一个元素包含的价格为实际需要支付的价格。如果需要显示手机号或者收货地址,可以相应设置该属性。
设置完成后可以通过以下代码调用:
let vc = PKPaymentAuthorizationViewController(paymentRequest: request) vc.delegate = self presentViewController(vc, animated: true, completion: nil)
特别说明一下这个UI:这个UI的view优先级相当高,甚至会盖住背景,所以App在处理这个UI的时候需要注意。当它出现和消失的时候,对应App相当于进入了后台,又切换回了前台,所以系统通知和里面的回调函数也会做出响应。
服务器解密过程
用户输入指纹和PIN码进行验证后,会调用回调函数func(_, : : ),其中参数type为,包含两类信息,第一类是明文的用户信息,对应初始化设置,第二类是加密后的支付信息,有一个属性type为,该属性就是加密后的支付信息,内容是一个JSON二进制流,解析之后结构如下:
{ data = "xxxxx"; header = { publicKeyHash = "xxxxx"; transactionId = bce2f62f92cb1f5b16696f1ea3b1b375cbfe8fe45c9132ae381c77ed45202455; wrappedKey = "xxxx"; }; signature = "xxx"; version = "RSA_v1"; }
使用字段对消息进行验证,防止信息被篡改。然后取出.的内容,这是一个使用非对称加密算法加密的对称密钥。使用苹果开发者后台配置的私钥进行解密得到这个对称密钥。然后使用这个对称密钥解密data中包含的加密数据,得到返回的支付信息。这个支付信息是经过加密的,包含了用户的支付卡号,PIN码等信息。理论上只有银联才能解析出真正的内容,它的内容对于我们商户来说是加密的。服务器需要将解密后的信息整理成银联需要的消息内容,然后调用银联的扣款接口,完成扣款。
银联交易状态确认
在调用银联的扣款接口之后,保证扣款结果是否成功是很重要的,只有准确的知道扣款结果,才能正确的设置你平台的用户订单状态。银联的扣款接口是这样设计的,调用接口之后返回既有同步的,也有异步的,如果同步结果成功返回,只代表银联成功接收并开始处理扣款请求,并不代表扣款成功。扣款是否成功是以异步的形式通知的。扣款不成功的原因有很多,比如卡被冻结、PIN码错误、余额不足等等。
我们的做法是,调用扣费接口之后,如果3秒内没有收到异步回调,就利用同步结果返回的银联流水号,开始轮询银联的另一个交易状态接口,确保拿到交易结果。
处理交易失败
如果交易失败,App 需要做相应的 UI 处理。根据交互需求,以下失败情况无法退出。其他情况需要退出,App 负责通知用户。
显示结果由回调函数 func(_, : : ) 中的参数控制,参数是一个枚举,第一个参数类型为 ,打开枚举的定义可以看到,上面的情况已经被操作系统定义好了,对应的有:
UI展示细节:调用后,如果传入的值为 或 ,则显示相应结果后自动消失。如果传入的值为以上三个PIN码值,则不会消失,用户可以继续选择银行卡然后进行指纹验证等支付操作。
交易逆转
在显示到消失这段时间内的任意时刻,用户都可以通过点击右上角的“取消”按钮来手动取消支付,这就涉及到交易取消的问题。交易取消是由服务端完成的,所以App要做的第一件事就是将用户的取消事件通知给服务端。由于整个支付流程有很多步骤,一般来说,服务端在取消交易时会面临以下三种情况:
第一种情况比较好处理,只要服务器不再调用银联扣款接口即可。第二种和第三种情况处理方式相同,只不过第二种情况是在得到扣款结果后再处理。如果是后两种情况,则调用银联取消交易接口,取消之前的交易,并将扣除的用户款项退还给用户。
注意:由于部分银行不支持取消交易,因此调用取消交易接口后需要对返回结果进行判断,如果不支持,则需要调用退款接口进行退款。
总结
Pay 应用内支付在技术上并不难,但涉及的细节较多,处理各种情况需要花费不少心思。如果你对定制化 Pay 应用内支付的需求不是很强,很多第三方支付平台已经提供了自己封装好的 SDK 供开发者使用,比如银联的 CUP SDK。这些 SDK 屏蔽了支付流程的很多细节,可以很方便的接入到 App 中。但如果你需要接入原生 Pay 来实现良好的应用内支付体验,那么本文可以为你提供一些帮助。
关于作者:陈成芳(@科技iOS项目负责人,凡本应用架构主力开发者,专注于iOS应用架构、动画、新技术的使用与实践。
责任编辑:唐小银(@唐门教主),欢迎技术投稿、委托投稿、文章修改,请发邮件至