苹果支付精要-全-

苹果支付精要(全)

原文:zh.annas-archive.org/md5/1917327bc0a79d874c74521897b70cd8

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

无论您是相对较新的 iOS 应用开发者还是经验丰富的专家,Apple Pay Essentials 都提供了您需要将 Apple Pay 轻松集成到应用支付流程中的技能。本书向您展示如何获取证书,确保支付信息在用户的 iOS 设备、您的支付网关以及参与 Apple Pay 交易的银行之间安全传输。本书还教您如何提供简单一致的用户体验,从而加快从欲望到获取的时间。您将学习如何响应用户对您预配置的支付表单所做的更改,这是用户确认或修改订单和支付详情的地方。本书将引导您了解您的应用与支付网关和订单处理系统之间的交互。最后,本书向您展示如何设计一个简单的订单处理 Web 应用,该应用处理由客户端应用提交的订单和支付。

本书涵盖内容

第一章, Apple Pay 入门,描述了在线支付的一般工作原理,并介绍了 Apple Pay——一种更简单、更安全的在线支付模式。本章还向您展示如何获取 Apple Pay 商户证书,确保只有适当的实体才能访问敏感的支付信息。

第二章,支付请求工作流程,描述了如何创建支付请求,这是一个存储支付处理关键信息(如货币和支付网络要求)以及客户订单详情的对象。它还向您展示如何管理 Apple Pay 用户体验的主要元素:Apple Pay按钮和支付表单。本章还解释了如何从订单处理 Web 应用中获取库存详情,以及如何向用户展示产品信息。

第三章,支付授权工作流程,展示了您的应用应该如何响应用户对支付表单事件所做的更改,例如送货地址变更和用户对支付的授权。

第四章,支付处理工作流程,描述了处理支付过程中涉及的参与者和操作,包括您的订单处理 Web 应用。

第五章, 设计订单管理服务器,描述了订单管理服务器的主要组件,包括其数据结构和客户端 API。

第六章,Apple Pay API 摘要,总结了用于 Apple Pay 交易的 API。

您需要为本书准备的内容

要跟随本书中展示的内容(基于本书的示例代码),您需要 iOS 开发所需的硬件和软件:一台开发 Mac、一台支持 Apple Pay 的 iOS 设备,以及 Xcode 开发工具的最新版本。您需要能够在您的 Mac 上构建应用程序并在 iOS 设备上运行它们,这需要计算机和设备之间的有线连接(以便 Xcode 可以在设备上安装应用程序)。当在 iOS 模拟器上运行示例应用程序时,示例应用程序通过一个针对适当进程的 URI 连接到计算机上运行的订单管理 Web 应用程序。然而,当在 iOS 设备上运行应用程序时,连接必须无线进行。因此,您需要将您的开发 Mac 配置为代理服务器,以便 iOS 设备对订单管理 Web 应用的 HTTP 请求由计算机上的 Web 应用程序解析,而不是发送到更广泛的网络。示例代码中的文档解释了如何使用 SquidMan 代理服务器软件将您的开发 Mac 配置为代理服务器。

本书面向对象

这本书旨在帮助那些想要学习如何将 Apple Pay 集成到他们的 iOS 应用中的人,以便他们的客户可以快速、安全地支付商品和服务。需要具备一定的 iOS API 和 Xcode 开发工具的知识。

术语表

在本书中,你会找到许多不同的文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 处理方式如下所示:“在 ID 字段中输入标识符字符串——例如,merchant.com.company.merchantapp。”

代码块如下设置:

PKContact contact= [PKContact new];
contact.phoneNumber= 
  [CNPhoneNumber phoneNumberWithStringValue: @"678-555-1234"];

当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:

- (void) paymentAuthorizationViewControllerDidFinish:
            (PKPaymentAuthorizationViewController*) controller
{
 [self dismissViewControllerAnimated:true completion:nil];
}

新术语重要词汇将以粗体显示。屏幕上出现的单词,例如在菜单或对话框中,将以如下方式显示:“在iOS 应用下,点击标识符。”

注意

警告或重要提示将以如下框中的形式出现。

小贴士

小技巧和窍门如下所示。

读者反馈

我们欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢或不喜欢的地方。读者反馈对我们来说非常重要,因为它帮助我们开发出您真正能从中获得最大收益的标题。

要向我们发送一般反馈,请简单地发送电子邮件至 <feedback@packtpub.com>,并在邮件主题中提及书籍的标题。

如果你在某个领域有专业知识,并且对撰写或参与一本书籍感兴趣,请参阅我们的作者指南:www.packtpub.com/authors

客户支持

现在您已经是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助您从购买中获得最大收益。

下载示例代码

您可以从www.packtpub.com下载您购买的所有 Packt 出版物的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support,并注册以将文件直接通过电子邮件发送给您。

勘误

尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。

要查看之前提交的勘误表,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在勘误部分。

盗版

在互联网上盗版受版权保护的材料是一个跨所有媒体的持续问题。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现任何形式的非法复制我们的作品,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。

请通过<copyright@packtpub.com>与我们联系,并提供涉嫌盗版材料的链接。

我们感谢您的帮助,以保护我们的作者和我们为您提供有价值内容的能力。

询问

如果您在这本书的任何方面遇到问题,您可以通过<questions@packtpub.com>联系我们,我们将尽力解决问题。

第一章. 开始使用 Apple Pay

Apple Pay 是一种移动支付系统,允许 iPhone 用户使用 Touch ID 支付商品和服务。在每次购买时,用户无需输入或确认支付卡信息(信用卡或借记卡),只需通过触摸 Home 键即可安全地授权支付。重要的是要注意,在 Apple Pay 交易过程中,支付卡信息永远不会离开用户的手机;此信息在设备中安全存储。相反,支付令牌存储了您处理支付所需的所有信息,从授权到结算(即,当用户的资金转移到您的商户银行账户时)。

使用 Apple Pay,您无需在您的服务器上存储客户的支付卡信息。这有助于减少客户在您的应用程序内支付商品时的疑虑;他们相信他们的支付卡信息在他们的设备中是安全的。您的好处是无需处理支付卡信息,至少对于基于 Apple Pay 的交易来说是这样。(当用户的设备不支持 Apple Pay 或用户尚未将支付卡添加到设备时,您可能必须使用常规方式处理支付,这可能涉及捕获和存储支付卡信息。)

虽然您从存储支付卡详情中解放出来,但您仍然需要处理支付,无论是直接处理还是通过支付网关。在两种情况下,您都需要获取 Apple Pay 商户标识符和证书来解密 Apple Pay 与交易支付信息一起创建的支付令牌。要在您的应用程序中使用 Apple Pay,您需要在项目中启用 Apple Pay 功能,这需要 Apple Pay 商户标识符。

本章描述了在线支付的工作原理,在线支付是传统电子汇票捕捉EDC)系统的网络中心版本,用于处理信用卡交易。您还将学习 Apple Pay 支付工作流程的基础,从在用户的设备上显示Apple Pay按钮开始,当 Apple Pay 可用,展示 Apple Pay 支付表单,并在您的服务器上处理交易。

本章将完成以下内容:

  • 提供在线支付流程概述

  • 介绍 Apple Pay 支付工作流程

  • 展示如何创建 Apple Pay 商户标识符和证书

  • 描述如何在 Xcode 中为应用程序启用 Apple Pay 功能

在线支付流程概述

客户通常携带支付卡(借记卡或信用卡)在钱包或手提包中,他们使用这些卡来支付商品和服务。当持卡人用支付卡向商家付款时,商家通常使用支付网关来处理这笔交易。支付网关是一种电子商务服务,它授权基于支付卡的交易。支付网关在处理交易时执行多项任务,但其主要任务是提交交易授权给支付处理器之前对支付卡信息进行加密。支付处理器与发行客户卡的银行(称为发行银行发行者)进行交互,最终批准或拒绝交易。支付处理器可能由支付网关、第三方或商家实现。商家可能会实现一个定制的支付处理器,例如,与定制的库存和订单系统集成。

不管理库存的商家可能只与支付网关打交道。支付网关提供库或框架,应用程序可以链接到这些库。在处理支付时,应用程序将支付令牌传递给库,库处理支付并将结果(授权拒绝)返回给应用程序。网关执行所有必要的任务以授权交易并将支付金额从发卡行转移到商家的收单银行。收单银行(也称为收单行)是接收持卡人付款并将其记入商家银行账户(这是一种用于接收支付卡付款的特殊类型的账户,也称为商家账户)的银行。

需要与定制订单和库存管理系统集成的商家需要对支付处理采取更实际的方法。这正是本书讨论的场景。

首先,让我们谈谈在线支付系统是如何工作的。支付过程分为两个阶段:

  • 授权

  • 结算

在成功的授权中,对客户的卡执行授权保留,以保留为交易提供资金的资金。稍后,商家消耗或结算交易,将资金从客户的卡转移到商家的账户。

以下步骤描述了授权过程:

  1. 客户出示支付卡以支付产品或服务。

  2. 商家加密卡信息并向支付网关发送授权请求。

  3. 然后,支付网关将授权请求转发到支付处理器。

  4. 支付处理器将授权请求转发到适当的支付卡协会(Visa、MasterCard、American Express、Discover 等)。

  5. 卡协会将授权请求转发到发行银行,该银行最终批准或拒绝交易。一些卡协会,如 Discover 和 American Express,也是发行银行。

  6. 发行银行从支付处理器接收授权请求,并将其响应(授权拒绝)发送给支付处理器。然后,发行银行保留一个交易授权或授权保留,将商家、支付卡和批准的金额(资金被保留但未从持卡人账户中扣除)联系起来。

  7. 支付处理器将发行银行的响应转发给支付网关。

  8. 支付网关随后将响应转发给商家,商家再将信息传达给持卡人。

要么立即,要么在一天结束时,商家开始结算过程以接收资金。这个过程与请求支付授权的程序类似;然而,不是授权交易,发行银行将授权保留转换为借记,并准备与收单银行结算交易:

  1. 商家通过支付处理器将其批准的授权提交给其收单银行。

  2. 收单银行向发行银行发出结算请求。

  3. 发行银行向收单银行做出结算支付。

  4. 收款行将批准的金额存入商家的银行账户。

Apple Pay 支付工作流程

如果您开发的应用能够与支付网关交互以处理支付卡,那么您或您的公司是商家,该应用是商家应用

这是支付工作流程的概述:

  1. 显示 Apple Pay 按钮:只有当用户可以执行 Apple Pay 支付时才显示此按钮。

  2. 创建支付请求:此请求包含必要的支付信息和订单详情。

  3. 显示支付单据:此单据显示了用户可以修改的订单信息,例如运费信息。

  4. 响应用户的变化:当用户进行更改时,更新诸如运费和折扣等项目。

  5. 将支付信息提交给支付网关:当用户授权支付请求时,将支付和订单信息提交给适当的系统。

显示 Apple Pay 按钮

当用户到达您的应用中允许用户购买东西的屏幕时,如果用户可以在设备上使用 Apple Pay,应用应显示Apple Pay按钮,以便用户可以轻触按钮,验证购买详情,并通过 Touch ID 授权应用以完成订单并将订单金额记入适当的支付卡。决定用户是否可以使用 Apple Pay 涉及两个步骤:

  • 确定设备是否支持 Apple Pay

  • 确定用户是否已将您支持的支付卡添加到设备中

    小贴士

    您的应用必须在显示Apple Pay按钮之前进行两项检查。如果任一检查失败,应用不得显示Apple Pay按钮。相反,它应该通过购买按钮提供一个传统支付方式(例如获取信用卡号和发货地址)。

创建支付请求

如果用户可以使用 Apple Pay,您的应用将准备一个支付请求。一个支付请求是一个对象,它描述了要收费的项目、您支持的卡关联以及账单和发货信息。

支付请求的主要组成部分是支付摘要项,它向用户描述支付请求。支付摘要项代表交易的一个组成部分,例如小计、折扣、运费、税费和总金额。每个项目都有一个标签,描述了每个金额的含义。最后一项是最重要的,因为它标识了收款人和用户将在下一张支付卡账单中看到的借记金额。因此,这个项目的标签应该是您公司的名称。

除了支付摘要项之外,您的应用还设置了支付请求的属性,描述了您支持哪些卡关联和在线支付协议。您的应用必须至少支持 3D Secure 协议。EMV欧联、万事达卡和维萨卡)协议是可选的。

支付请求还允许您指示用户指定特定的订单详情,例如发货或账单信息。例如,您可能需要电子邮件地址或邮政地址。

如果您的订单系统需要额外的信息,例如订单号,您可以将这些信息包含在支付请求中作为自定义应用程序数据。当用户授权支付时,Apple Pay 会将这些信息的哈希值包含在您收到的支付令牌中。如果您的订单系统稍后需要这些信息,您的应用必须能够单独提供它们。

展示支付单据

一旦您的应用创建了支付请求并且用户点击了Apple Pay按钮,应用就会向用户展示一个支付单据。支付单据(正式名称为支付授权视图控制器)向用户展示支付请求中的支付摘要项以供审查。用户在授权支付之前可以更改订单的某些方面。用户也可能决定不购买商品并取消交易。

响应订单变更和支付授权

您的应用实现了一个支付单据的代理,通过例如,当用户选择不同的发货方式时更新订单的运费和总金额来响应用户的操作。

注意

当用户使用 Touch ID 授权支付请求时,Apple Pay 会与设备的安全元素(在设备上安全存储支付卡详情的芯片,即使是苹果也无法访问的详情)和苹果的服务器交互,以生成一个一次性使用的支付令牌。支付信息描述了支付交易,并包含所有将支付金额记在用户支付卡上所需的信息(但此信息不包含卡号)。

苹果使用您的商户证书在其服务器上加密令牌中的信息。

将支付信息提交给支付网关

当支付单告诉其代表用户已授权支付请求并发送支付信息给用户时,代表调用一个同步方法,该方法将支付信息转发到您的支付网关。当方法返回时,它向代表提供支付请求的结果。如果支付请求被批准,支付单向用户显示确认信息,告知交易已批准,并通知其代表。然后代表关闭支付单并显示一个自定义确认屏幕;这样的屏幕可能显示订单号和一条感谢信息。如果支付请求未被批准,代表必须显示适当的屏幕并要求用户提供另一种支付方式。

在您的应用中启用 Apple Pay

为了您的应用能够使用 Apple Pay,您必须有一个 Apple 商户标识符和商户证书。苹果使用证书来加密支付令牌中的支付信息。您的支付网关(Stripe、Worldpay 等)使用证书来解密支付令牌中的信息。

创建您的应用 Apple Pay 商户标识符

您必须能够访问您的团队成员中心门户和您的支付网关的证书管理设施。

在您的团队成员中心页面通过以下步骤创建您的商户标识符:

  1. 成员中心,点击证书、标识符和配置文件

  2. iOS 应用下,点击标识符

  3. 标识符下,点击商户 ID

  4. 点击继续(如果这是您的第一个商户标识符)或在页面右上角的加号(+)按钮。

  5. 描述字段中为商户标识符输入描述,例如MerchantApp merchant identifier

  6. 在 ID 字段中输入标识符字符串,例如merchant.com.company.merchantapp

  7. 点击继续然后点击注册

  8. 点击完成

通过执行以下操作从您的支付网关请求 Apple Pay 证书:

  1. 在您的支付网关的证书管理页面中创建一个 Apple Pay 证书。

  2. 证书签名请求CSR)文件下载到您的 Mac 上。

现在,按照以下步骤在成员中心创建您的应用商户证书:

  1. 证书、标识符和配置文件页面,在iOS 应用下,在证书下,点击所有

  2. 然后,选择Apple Pay 证书并点击继续

  3. 您想使用哪个商家 ID?下,选择适当的商家标识符并点击继续

  4. 生成您的证书下,点击选择文件

  5. 选择您从支付网关获得的 CSR 文件。

  6. 然后,点击生成,然后点击下载以将您的应用商家证书下载到您的 Mac。

通过以下步骤将您的应用商家证书上传到您的支付网关:

  1. 在您的支付网关的证书管理页面上,上传您从会员中心门户下载的商家证书。

  2. 确认您的商家证书已列在您的支付网关账户中。

在您的 Mac 上安装应用的 Apple Pay 商家证书

双击您之前从会员中心下载的商家证书。此时,密钥链访问将打开并安装证书,包括您的其他证书。

在您的应用 Xcode 项目中启用 Apple Pay

要为您的应用提供访问 Apple Pay 的权限,您需要在 Xcode 项目中启用 Apple Pay 功能。执行以下操作:

  1. 首先,在 Xcode 中打开您的项目。

  2. 选择构建应用的 target 以打开 target 编辑器。

  3. 然后,点击功能

  4. 找到Apple Pay功能,并将相应的开关切换到开启位置。

  5. 在出现的对话框中,选择适当的开发团队,并点击选择。在您的应用 Xcode 项目中启用 Apple Pay

摘要

在本章中,你学习了商家获取基于卡的支付所遵循的在线支付流程。本章介绍了通用的在线支付概念,以描述应用如何使用 Apple Pay 执行类似功能但更加安全。最后,你学习了如何创建 Apple Pay 商家标识符和商家证书,以在你的应用中启用 Apple Pay 支付。

下一章重点介绍支付请求工作流程,其中当设备上可用 Apple Pay 时,您将展示Apple Pay按钮,创建支付请求,并根据该请求展示支付表单。

第二章. 支付请求工作流程

当您的客户决定购买您应用中提供的产品时,您的责任是给用户提供一个顺畅、快速且无痛苦的支付体验。幸运的是,苹果公司通过实施 Apple Pay 承担了这项工作的主要部分。用户无需翻找信用卡和输入送货地址即可完成订单,即使这是他们第一次使用您的应用。通过仅请求执行交易所必需的信息,您为您的客户提供了一种引人入胜的购物体验,这将鼓励他们从您的应用中进行后续购买。

本章描述了您的应用需要执行的操作,以给用户带来愉快的购物体验,在这个体验中,从渴望某物到将此产品送到您家门口的障碍被简化为轻轻触摸一个光滑、圆润的按钮。同时,您减少了或消除了请求和存储客户支付信息的需求,这在计算机系统信息泄露事件显著增加的背景下是一种明智的做法。您可能还会欢迎来自同一客户的显著增加的购买量,这些客户除了在其他应用中拥有更好的购物体验外,还对其支付信息的安全性有信心(因为他们的卡信息没有存储在他们的手机中)。无缝的支付体验将使您的应用在用户“喜爱”和“高度喜爱”的应用列表中名列前茅,并且他们愿意向家人和朋友推荐这些应用。

本章涵盖了以下主题:

  • 提供一个简单库存网络服务的概述,该服务向其客户端提供库存信息

  • 描述了一个产品卡片实现,该卡片显示必要的产品信息,并根据用户设备上 Apple Pay 的可用性显示Apple Pay按钮或常规购买按钮。

  • 演示创建支付请求的过程,并显示带有订单信息的支付单,以便用户授权支付(当 Apple Pay 可用时)

从库存服务获取信息

如果您的应用用户可以通过它购买有形商品,那么应用必须获取有关此类产品可用性的信息。实现这一目标的一种方法是通过网络服务。网络服务是在网络服务器上运行的程序(网络应用或网络进程),客户端(桌面或移动应用)可以连接到它以访问资源,例如库存、产品图片、订单和支付。访问此类资源最网络化的方式是通过使用RESTful API表示状态转移REST)为计算机与其他计算机之间的通信提供了一种方式,类似于人们浏览网页的方式。

当您访问一个网页时,该网页可能会提供一些选项(链接),您可以使用这些选项访问与此页面或您参与的流程(交易)相关的页面。然后,您分析每个链接以决定下一步访问哪个相关页面。RESTful API 为客户端提供了与由 Web 服务器托管资源进行有机交互的方式。客户端无需遵循由 Web 服务指定的严格 API,而是可以请求资源的特定表示(浏览器可能请求其 XHTML 版本,而 iOS 应用可能请求 JavaScript 对象表示法或 JSON)。资源中嵌入了对相关资源或修改资源状态的操作的链接。例如,在创建订单后,Web 服务器可能会提供返回此订单状态、修改它或取消它的操作的链接。这种交互方式被称为超媒体。超媒体指的是分布式系统操作的方式,通过一组小的 HTTP 动词(如POSTPUTGETDELETE)通过其唯一标识符访问资源。这种简单的通信风格为分布式系统在建立专门的通信协议时提供了极大的灵活性。

本书(并可下载)中描述的示例解决方案(iOS 应用和 Web 服务)使用 RESTful API 来提供对库存、订单和支付的访问。通过将应用与其 Web 服务的交互基于简单的 RESTful API,我们可以将注意力集中在将 Apple Pay 集成到您的应用中以及处理 Web 服务器上的支付的主要方面。

在您的应用向客户展示产品信息之前,它必须从您的 Web 服务器获取这些信息。运输信息同样重要,因为您提供的运输方式可能会不时发生变化,应用也应该定期获取这些信息。应用将展示支付单上的可用运输方式,以便用户接受默认的运输方式或更改它。以下部分描述了示例解决方案如何从 Web 服务器获取库存和运输信息到用户的设备。

获取库存信息

示例应用非常简单。在启动时,它从 Web 服务器请求两件事:库存和可用的运输方式。然后,它以列表的形式显示库存,如下面的截图所示:

获取库存信息

为了获取库存信息,应用程序使用 REST API 从服务器请求资源的表示。通过访问http://red:12345/inventory统一资源标识符URI)找到资源。在接收到数据后,应用程序使用NSJSONSerialization类将接收到的数据(以 JSON 编码)转换为NSDictionary实例,这是表格(UITableView实例)的数据源,其行显示库存中每个产品的名称。

此列表显示了由网络服务(用 Node.js 实现)使用的模型,定义相应 iOS 模型的 Objective-C 类,以及将 JSON 转换为产品字典的代码:

// Web service JavaScript
var Product_schema= new Schema({
   name:             String,
   description:      String,
   image_uri:        String,
   quantity_on_hand: Number,
   price:            String
});

// Product.h
@interface Product : NSObject
@property NSString*  name;
@property NSString*  description;
@property NSString*  image_uri;
@property NSUInteger quantity_on_hand;
@property NSString*  price;
@end

// ProductList.m
@interface ProductList ()
@property (nonatomic) NSArray* products;          // array of Product*
@end
@implementation ProductList
- (void) viewDidLoad 
{
   [super viewDidLoad];
   self.title= @"Products";

   RestIO* rest_io= [RestIO sharedRestIO];
   [rest_io getResourceAtURI:@"http://red:12345/inventory"
                  completion:^(NSURLResponse* response, NSData* data) {
      if ([response.MIMEType isEqualToString:@"application/json"])
      {
         NSError* error;
         NSDictionary* json_data= [NSJSONSerialization JSONObjectWithData:data
                                                                  options:0
                                                                    error:&error];
         NSMutableArray* products= [NSMutableArray new];
         for (NSDictionary* json_product in json_data)
         {            
            Product* product=    [Product new];
            product.name=        json_product[@"name"];
            product.description= json_product[@"description"];
            product.image_uri=   json_product[@"image_uri"];
            product.price=       json_product[@"price"];
            {
               NSInteger quantity= [(NSString*)json_product[@"quantity_on_hand"]
                                            integerValue];
               product.quantity_on_hand= quantity > 0? quantity : 0;
            }
            [products addObject:(NSString*)product];
         }
         _products= products.copy;
         dispatch_async(dispatch_get_main_queue(), ^{
            [(UITableView*)self.view reloadData];
         });
      }
   }];
}
...
@end

需要注意的一个重要事项是,产品的价格以字符串形式存储,而不是数字。这是因为使用数字类型进行财务计算不会产生准确的结果。在进行财务计算时,尤其是涉及苹果支付交易的计算,必须使用NSDecimalNumber类的实例,这有助于在十进制数上进行准确的算术运算。NSDecimalNumber类还包含将数字字符串转换为十进制数的方法。

获取运输信息

为了在支付页面上向客户提供可用的运输方式,应用程序需要从服务器获取列表。在示例代码中,应用程序的AppDelegate对象在其application:didFinishLaunchingWithOptions:方法中获取此列表。

此列表显示了ShippingService类实例的 Node.js 和 Objective-C 模型类,以及从服务器获取运输服务的代码:

// Web service JavaScript
var ShippingService_schema= new Schema({
   name:         String,
   description:  String,
   transit_days: Number,
   price:        String
});

// ShippingMethod.h
@interface ShippingService : NSObject
@property NSString*  name;
@property NSString*  detail;
@property NSNumber*  transit_days;
@property NSString*  price;
@end

// AppDelegate.m
#import "AppDelegate.h"
#import "Stripe.h"
#import "RestIO.h"
#import "ShippingMethod.h"

@interface AppDelegate ()
@property (readwrite) NSArray<ShippingMethod*>* shipping_methods;
@end

@implementation AppDelegate
- (BOOL)           application:(UIApplication*) app
 didFinishLaunchingWithOptions:(NSDictionary*)  options
{
   _ApplePay_merchant_identifier= ApplePay_merchant_identifier;
   _ApplePay_supported_networks=  @[PKPaymentNetworkVisa, PKPaymentNetworkAmex, PKPaymentNetworkDiscover, PKPaymentNetworkPrivateLabel];

   [Stripe setDefaultPublishableKey:StripePublishableKey];

   _rest_io_host= @"http://red:12345";

   // get shipping methods from server
   {
      NSString* shipping_methods_uri= [NSString stringWithFormat:@"%@%@",_rest_io_host, @"/shipping_methods"];

      RestIO* rest_io= [RestIO sharedRestIO];
      [rest_io getResourceAtURI:shipping_methods_uri completion:^(NSURLResponse* response, NSData* data) {
         if ([response.MIMEType isEqualToString:@"application/json"])
         {
            NSError* error;
            NSDictionary*   json_data=        [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
            NSMutableArray* shipping_methods= [NSMutableArray array];
            for (NSDictionary* json_shipping_method in json_data)
            {
               ShippingMethod* shipping_method=    [ShippingMethod new];
               {
                  shipping_method.name=         json_shipping_method[@"name"];
                  shipping_method.detail=       json_shipping_method[@"description"];
                  shipping_method.transit_days= [NSNumber numberWithShort:[(NSString*)json_shipping_method[@"transit_days"]
                                                                           integerValue]];
                  shipping_method.price=        json_shipping_method[@"price"];
               }

               [shipping_methods addObject:(NSString*)shipping_method];
            }
            _shipping_methods= shipping_methods.copy;
         }
      }];
   }
   return YES;
}
...
@end

与之前引入的Product类类似,ShippingMethod类使用字符串来存储每种运输方式的价格。这是因为运输方式的价格会被添加到支付请求中其他项目的价格中,并且所有这些添加都必须使用十进制数(NSDecimalNumber实例)来完成。

现在应用程序已经拥有了可用产品的库存和用户可用的运输方式,当用户从产品列表中选择产品时,它可以显示产品信息卡片;当用户点击苹果支付按钮时,它可以显示支付页面。

显示产品卡片

苹果支付的用户体验从苹果支付按钮的出现开始。当您的客户看到这个按钮时,他们会知道他们设备屏幕上显示的产品只需轻触两次即可送到家门口。实际上,根本不需要第三步

产品卡片是一个显示产品信息的屏幕,例如产品的名称、描述和价格,以及当用户的设备上可用Apple Pay按钮时显示的Apple Pay按钮。当Apple Pay不可用(无论是由于客户的设备不支持它,还是因为客户未在该设备上设置 Apple Pay),您的应用不应显示Apple Pay按钮。相反,它应显示添加到购物车按钮或常规购买按钮,并使用传统方式处理支付。

以下部分描述了如何设计产品卡片屏幕以及如何在可用时运行时布局Apple Pay按钮。

展示产品信息

产品卡片定义从应用的主要故事板文件开始,如下面的截图所示:

展示产品信息

产品场景布局构成产品卡片的元素。这主要是用于显示产品图像的一个图像视图,以及三个标签,用于显示产品描述、价格图例和产品价格(使用货币格式化器)。

此列表显示了ProductCard对象的viewDidLoad方法:

// ProductCard.m
- (void) viewDidLoad 
{
   [super viewDidLoad];

   _currency_formatter=             [NSNumberFormatter new];
   _currency_formatter.numberStyle= NSNumberFormatterCurrencyStyle;

   // get last-used shipping method
   _shipping_method_name= [[NSUserDefaults standardUserDefaults] objectForKey:Default_shipping_method_name];

   // define table sections
   {
      short   i= 0;
      sections[  i].cell_row_height=   44; sections[i].cell_type= ProductDescription;
      sections[++i].cell_row_height=  400; sections[i].cell_type= ProductImage;
      sections[++i].cell_row_height=   44; sections[i].cell_type= ProductBuy;
      _section_count= ++i;
   }

   // download product image
   RestIO* rest_io= [RestIO sharedRestIO];
   [rest_io downloadResourceWithURI: _product.image_uri 
                         completion: ^
       (NSURL* destination_url)
       {
          dispatch_async(dispatch_get_main_queue(), ^
             {
                UIImage *image= [UIImage imageWithContentsOfFile:[destination_url path]];
                [((ProductCard_TableView*)self.view) setProductImage:image];
             }
          );
       }
    ];

   // get shipping methods
   {
      AppDelegate*    app_delegate=     [UIApplication sharedApplication].delegate;
      NSMutableArray<PKShippingMethod*>* pk_shipping_methods= [NSMutableArray new];
      for (ShippingMethod* shipping_method in app_delegate.shipping_methods)
      {
         PKShippingMethod* pk_shipping_method=
         [PKShippingMethod 
          summaryItemWithLabel:[@"Shipping " stringByAppendingString:shipping_method.name]
          amount:[NSDecimalNumber decimalNumberWithString:shipping_method.price]];
         pk_shipping_method.identifier= shipping_method.name;
         pk_shipping_method.detail=     shipping_method.detail;
         [pk_shipping_methods addObject:pk_shipping_method];
      }
      _pk_shipping_methods= pk_shipping_methods.copy;
   }
}

viewDidLoad方法实例化了用于购买表格单元格的货币格式化器,该单元格显示产品的价格和Apple Pay按钮。然而,Apple Pay按钮不是在设计时布局的;它是在产品表格显示购买单元格时运行时布局的。此设置将在下一节中解释。

viewDidLoad方法通过获取产品的image_uri属性标识的资源来获取产品的图像。此方法使用 REST API 从服务器下载图像数据。当图像下载完成后,该方法创建一个UIImage对象,并将其分配给产品表格的view属性。

展示 Apple Pay 按钮

如前所述,如果用户无法在他们的设备上使用 Apple Pay,则您的应用不显示Apple Pay按钮非常重要。您的应用使用PKPaymentAuthorizationViewController类的两个方法来确定 Apple Pay 是否可用:canMakePaymentscanMakePaymentsUsingNetworks

此列表显示了示例 iOS 应用如何使用这些方法确定要添加到购买单元格中的购买按钮:Apple Pay按钮,PKPaymentButton类的一个实例,或作为UIButton实例实现的购买按钮:

// ProductCard.m
(ProductCard_BuyCell*) buy_cell
{
   ProductCard_BuyCell* cell= 
      (ProductCard_BuyCell*)[(ProductCard_TableView*)self.view dequeueReusableCellWithIdentifier:@"buy"];

   NSNumber* price_number= [NSNumber numberWithDouble:[_product.price doubleValue]];      
   cell.price_label.text=  [_currency_formatter stringFromNumber:price_number];

   // determine whether ApplePay is available on this device and is configured with the accepted payment networks
   BOOL can_use_ApplePay;
   {
      AppDelegate* app_delegate= [UIApplication sharedApplication].delegate;
      if ((can_use_ApplePay= [PKPaymentAuthorizationViewController canMakePayments]))
         can_use_ApplePay= [PKPaymentAuthorizationViewController
                               canMakePaymentsUsingNetworks:app_delegate.ApplePay_supported_networks];
   }

   // instantiate the purchase button
   UIButton* purchase_button;
   if (can_use_ApplePay)
   // configure a PKPaymentButton (Apple Pay button)
   {
      // create payment button
      purchase_button= [PKPaymentButton buttonWithType:PKPaymentButtonTypePlain 
                                                 style:PKPaymentButtonStyleWhiteOutline];
      purchase_button.tag= ApplePay_button_tag;
   }
   else
   // configure a UIButton
   {
      purchase_button= [UIButton buttonWithType:UIButtonTypeSystem];
      [purchase_button setTitle:@"Buy" forState:UIControlStateNormal];
      purchase_button.tag= Buy_button_tag;
   }

   // define the layout of the purchase button
   ...

   return cell;
}

此过程的结果是一个显示Apple Pay按钮的产品卡片(如果设备上可用 Apple Pay),使用户能够快速满足购买美丽产品的冲动。您的任务是尽可能减少障碍,以促进这种冲动的满足。

展示 Apple Pay 按钮

当用户点击Apple Pay按钮时,您的应用通过创建支付请求来启动 Apple Pay 交易。

创建支付请求

现在用户已经决定购买您的产品,我们的任务是帮助他们以尽可能少的干扰和中断来授权支付请求。用户的注意力,尤其是在 iPhone 这样的设备上,可能是短暂的;在他们确认账单地址的额外一秒钟(即使他们总是使用相同的地址),电话可能会打进来,然后,砰,交易就消失了。您能提供给用户的任何信息都能帮助加快授权过程。Apple Pay 使这一点变得非常方便。

创建支付请求

指定支付详情

支付请求(PKPaymentRequest的一个实例)的基本组件包括:国家代码和货币代码、您的商户标识符和能力,以及您支持的支付网络。您必须在创建的每个支付请求中指定所有这些属性。

国家和货币代码

您通过支付请求的countryCodecurrencyCode属性来指定这些项。指定这些项非常重要,因为它们确定了支付的处理地点和处理方式。国家代码是支付处理所在国家的两位字母代码(参考 ISO 3166,见www.iso.org/iso/country_codes)。货币代码是用于资助交易的货币的三位字母代码(参考 ISO 4217,见www.iso.org/iso/home/standards/currency_codes)。

商户标识符和能力

苹果商户标识符是您从苹果会员中心网站上的“证书、标识符和配置文件”部分获得的标识符(或者您现在就可以获得!)它们是反向 DNS 标识符,以merchant.com开头。商户能力标识了支付协议、3-D Secure 和 EMV(欧付宝、万事达卡和维萨)。3-D Secure 支持是必需的;EMV 支持是可选的。您通过支付请求的merchantIdentifiermerchantCapabilities属性来指定这些组件。

此列表显示了指定支付请求支付详情的代码:

// create payment request
PKPaymentRequest *request= [Stripe paymentRequestWithMerchantIdentifier:ApplePay_merchant_identifier];
request.merchantIdentifier=            ApplePay_merchant_identifier;
request.countryCode=                   @"US";
request.currencyCode=                  @"USD";
request.supportedNetworks=             ApplePay_supported_networks;
request.merchantCapabilities=          PKMerchantCapability3DS;

要求提供运输和账单信息

Apple Pay 提供了你完成交易所需的所有信息。然而,可能存在需要用户在授权支付请求之前存储在 Apple Pay 中但之前无法获取的信息的情况。例如,尽管 Apple Pay 提供了运输和账单信息,但你可能仍然需要在用户授权支付请求之前要求他们输入或确认运输目的地,以便在支付单中计算运输成本。但请记住,由于客户的账单和运输细节很可能在 Apple Pay 中是最新的,因此依赖 Apple Pay 获取这些信息通常是最佳方法。更不用说,如果不要求用户确认此类详细信息,你将使他们快速进入那神奇的授权触摸

要求运输或账单地址

如果你需要用户输入运输或账单信息(因为你的系统与其他需要这些信息的系统接口),你可以使用支付请求的 requiredBillingAddressFieldsrequiredShippingAddressFields 属性来指定这些要求。例如,为了使账单地址成为必填字段,将此属性设置为 PKAddressFieldPostalAddress。如果你需要一个电子邮件地址,将其设置为 PKAddressFieldEmail。最后,为了请求姓名,将属性设置为 PKAddressFieldName。由于这些属性接受的值是位字段常量,你可以将它们聚合起来以要求多个项目。以下列表展示了如何要求运输地址和电子邮件,以及账单电子邮件:

// client_app/merchantapp/ProductCard.m
@interface ProductCard()
@property PKPaymentRequest* payment_request;
...
@end

- (void) process_ApplePay_payment_request
{
   // create payment request
   _payment_request=
      [Stripe paymentRequestWithMerchantIdentifier:...];
   {            
      // require shipping address and email, and billing email
      _payment_request.requiredShippingAddressFields=
         PKAddressFieldPostalAddress | PKAddressFieldEmail;
      _payment_request.requiredBillingAddressFields=
         PKAddressFieldEmail;
   }
   ...
}

指定运输或账单地址

如果你的客户之前已经从你的业务中购买了商品,并且你有他们的账单和运输地址,你可以使用支付请求的 billingAddressshippingAddress 属性在支付单上预先填充这些信息。

以下列表展示了如何使运输和账单信息成为必填字段:

client_app/merchantapp/ProductCard.m
@interface ProductCard()
@property PKPaymentRequest* payment_request;
...
@end

- (void) process_ApplePay_payment_request
{
   ...
   // specify a particular shipping address
   PKContact*              contact= [PKContact new];
   CNMutablePostalAddress* address= [CNMutablePostalAddress new];
   address.street=          @"123 Fern Road";
   address.city=            @"San Jose";
   address.postalCode=      @"95123";
   address.country=         @"USA";
   address.ISOCountryCode=  @"US";
   contact.postalAddress=   [address copy];

   _payment_request.shippingContact= contact;
   ...
}

指定运输方式

如果你向客户发货产品,你可以提供一种或多种运输方式。前面提到的示例 iOS 应用和 Web 服务展示了这样一个系统的基本实现。根据你企业的规模,你可能需要为特定产品或目的地提供运输方式。无论设置有多复杂,你提供的支付单上都应该有一个简单的运输方式列表,用户可以选择对他们最方便的一种。下一章将讨论当用户在支付单中更改运输方式时如何做出反应。目前,你的重点是创建一个运输方式数组(PKShippingMethod 实例),并将其分配给 shippingMethods 支付请求属性。以下列表展示了如何创建这样一个数组以及如何在支付请求中包含它:

// client_app/merchantapp/ProductCard.m
#import "ShippingMethod.h"
...
@property PKPaymentRequest* payment_request;
...
- (void) viewDidLoad 
{
   ...
   // get the ShippingMethod objects in the app delegate,
   // and convert them to PKShippingMethod objects
   AppDelegate* app_delegate=
      [UIApplication sharedApplication].delegate;
   NSMutableArray<PKShippingMethod*>* pk_shipping_methods=
      [NSMutableArray new];
   for (ShippingMethod* shipping_method in
      app_delegate.shipping_methods)
   {
      PKShippingMethod* pk_shipping_method=
         [PKShippingMethod 
             summaryItemWithLabel:
                [@"Shipping " stringByAppendingString:
                   shipping_method.name]
             amount:
                [NSDecimalNumber
                   decimalNumberWithString:
                      shipping_method.price]];
      pk_shipping_method.identifier= shipping_method.name;
      pk_shipping_method.detail=     shipping_method.detail;

      [pk_shipping_methods addObject: pk_shipping_method];
   }
   _pk_shipping_methods= pk_shipping_methods.copy;
}

- (void) process_ApplePay_payment_request
{
   // create payment request
   AppDelegate* app_delegate=
      [UIApplication sharedApplication].delegate;
   _payment_request=
      [Stripe paymentRequestWithMerchantIdentifier:
         app_delegate.ApplePay_merchant_identifier];
  ...      
   // set the shipping methods
   _payment_request.shippingMethods= _pk_shipping_methods;
  ...   
}

如果除了使用运输服务运输商品外,您还支持店内取货或特殊递送方式,例如披萨或家具,那么您可以指定一个替代递送方式的列表。要指定这些运输类型,请使用支付请求的shippingType属性。

指定摘要项

摘要项是支付单中的元素,用于指定用户购买的商品的价格、运费、税费、折扣和总价。每个摘要项都有一个标签,例如SHIPPINGTAX。最后一个项目代表总价,并带有标签,例如PAY ACME。此项目的标签必须能识别您的公司,以便客户可以将购买与他们的支付卡账单相匹配。您通过将PKPaymentSummaryItem实例数组分配给支付请求的paymentSummaryItems属性来指定支付请求的摘要项。每个摘要项都有一个amount属性,表示项目的成本。

此列表显示了如何定义支付请求的摘要项:

// client_app/merchantapp/ProductCard.m
@property Product*           product;
@property PKPaymentRequest*  payment_request;
@property PKShippingMethod*  selected_shipping_method;
...
// tally the summary items' cost and the grand total
- (NSArray<PKPaymentSummaryItem*>*) computeSummaryItems
{
   NSDecimalNumber* product_price=
      [NSDecimalNumber decimalNumberWithString:_product.price];

   NSDecimalNumber* shipping=
      _selected_shipping_method?
         _selected_shipping_method.amount :
         [NSDecimalNumber zero];

   NSDecimalNumber* tax=
      [product_price decimalNumberByMultiplyingBy:
         [NSDecimalNumber decimalNumberWithString:@"0.08"]];

   NSDecimalNumber* total=
      decimal_number_sum(@[product_price, shipping, tax]);

   // specify summary items
   NSMutableArray<PKPaymentSummaryItem*>* summary_items=
   [NSMutableArray arrayWithArray: 
    @[
      [PKPaymentSummaryItem
         summaryItemWithLabel:_product.name amount:product_price],
      [PKPaymentSummaryItem
         summaryItemWithLabel:@"Shipping"   amount:shipping],
      [PKPaymentSummaryItem
         summaryItemWithLabel:@"Tax"        amount:tax],
      [PKPaymentSummaryItem
         summaryItemWithLabel:@"Acme"       amount:total]
      ]];

   return summary_items.copy;
}

- (void) process_ApplePay_payment_request
{
   // create payment request
   _payment_request=
      [Stripe paymentRequestWithMerchantIdentifier:...];

   // compute summary items and assign them to the payment request
   _payment_request.paymentSummaryItems= [self computeSummaryItems];
   ...
}

要指定表示从产品价格中折扣的摘要项,请为摘要项的金额使用负值。当您不知道金额的值,因为该值是作为订单处理的一部分生成时,您可以通过将摘要项的type属性设置为PKPaymentSummaryItemTypePending来向用户表明这一事实。这会在项目的标签中添加适当的说明。

指定与订单相关的自定义信息

如果您的订单系统在发行银行批准支付后需要额外的信息,您可以在支付请求的applicationData属性中指定它。然而,Apple Pay 不会将此信息发送给您。您的应用必须单独将此信息发送到您的系统。

作为处理支付的一部分,您的支付处理器会获取支付请求中applicationData属性设置的值的哈希。您可以使用这个哈希来验证应用单独发送的值与初始值是否相同。

摘要

本章展示了您的应用如何使用 REST API 获取数据以执行其功能,该 API 使用标准的 HTTP 惯用语(如PUTGETUPDATEDELETE等 HTTP 动词)提供灵活的功能。它还向您展示了如何将此数据转换为用户可以采取行动的信息,例如产品名称、描述和图片。特别是,本章讨论了何时向用户展示Apple Pay按钮(只有当用户的设备上可用 Apple Pay 时)。最后,本章向您介绍了创建支付请求的过程,这包括指定支付信息,例如 Apple 商户标识和商户能力、运输和账单信息,以及包含订单总价格并标注您公司名称的摘要项,以便于与支付卡对账单进行轻松验证。

现在用户正在查看付款单,您的应用需要有效地响应用户在运输方式或账单和运输地址上的变化。然而,最终的用户操作是授权支付请求。这是下一章的主题。

第三章。支付授权工作流程

支付表单是 Apple Pay 体验中最重要的面向用户的部分。此外,这也是用户应该花费最少时间的地方。你的应用说服用户购买产品。支付表单是你将用户从欲望引导到获取的地方,尽可能减少点击次数,两个点击是最理想的。

在上一章中,你学习了如何在支付表单中展示信息,包括你可以提供给客户的发货方式列表以及订单的总价。当支付表单打开时,用户可以更改发货类型(例如,从送货到门店自提),发货地址和支付方式(将用于资助交易的支付卡)。对于用户所做的每个更改,你必须更新支付请求的摘要项以反映它。例如,当用户更改发货方式时,你必须更新显示使用新选定的发货方式发送商品价格的摘要项。

本章将展示如何响应支付表单的消息来更新支付请求上的适当信息。它还展示了如何响应用户授权应用提交支付请求以供所选卡的发行银行审批而启动支付处理工作流程。

本章将完成以下工作:

  • 描述支付授权工作流程

  • 展示实现方法(由支付表单代理协议声明)的示例,这些方法响应支付表单消息以更新支付请求中的适当信息

  • 展示如何在用户授权支付请求或通过点击取消按钮取消交易时关闭支付表单

授权工作流程中的参与者和操作

支付授权工作流程是一个用户确认或输入你在支付表单中提供或请求的发货信息,并授权应用提交支付请求以供支付请求中指定的支付卡发行银行审批的过程。用户还可以更改用于资助支付请求的支付卡,在这种情况下,你可能需要更新支付请求的摘要项(例如,为特定卡片添加附加费)。

支付授权工作流程从你的应用展示支付表单(一个PKPaymentAuthorizationViewController对象)开始,并在用户授权支付请求时结束。(用户可以通过在支付表单上点击取消按钮来取消交易。)

本图展示了支付授权工作流程中的参与者和操作:

授权工作流程中的参与者和操作

工作流程包括以下四个步骤:

  1. 应用展示支付表单。

  2. 用户输入或更改您展示或要求的信息(付款单上显示的信息越少,授权越快)。

  3. 应用通过更新支付摘要项来响应付款单的变化。

  4. 用户批准支付请求。

这些是更详细的操作:

  • 支付请求 (PKPaymentRequest): 这是您用来展示付款单的支付请求。

  • 配送类型 (PKShippingType): 这表示客户将如何获得购买的产品。可用的配送类型包括送货(例如,办公用品或披萨)、门店自提(例如,苹果手表或珠宝)和服务自提(例如,当您提供提供家庭取货的运输或配送服务时)。使用门店自提配送类型时,您必须管理配送地址,因为它们不是配送地址,而是取货地址,例如您的门店地址。例如,在这种情况下,您可以设置配送地址为您的门店地址或通过将支付请求的 requiredShippingAddressFields 属性设置为 PKAddressFieldNone 来隐藏配送地址。

  • 支付摘要项配送方式 (PKPaymentSummaryItem, PKShippingMethod): 这表示在订单小计之后显示的摘要项,包括为订单选择的配送方式。

  • 配送方式 (PKShippingMethod 对象): 这表示订单中客户可用的配送方式。如果用户更改了配送地址,此列表可能会发生变化。

  • 付款单 (PKPaymentAuthorizationViewController): 这是应用向用户展示的界面,让用户在授权支付请求之前验证或输入所需的信息。用户将在该界面上授权支付请求。

  • 付款单交互协议 (PKPaymentAuthorizationViewControllerDelegate): 这是应用和付款单之间用来相互通信的 API。

实现一个共享方法来计算摘要项

要响应付款单的代理方法调用,您应该有一个单一的方法,根据配送信息计算支付摘要项。以下是一个此类方法的可能实现:

// client_app/merchantapp/ProductCard.m
@property Product*           product;
@property PKShippingMethod*  selected_shipping_method;
...
// tally the summary items' cost and the grand total
- (NSArray<PKPaymentSummaryItem*>*) computeSummaryItems
{
   NSDecimalNumber* product_price=
      [NSDecimalNumber decimalNumberWithString:_product.price];

   NSDecimalNumber* shipping= 
      _selected_shipping_method?
         _selected_shipping_method.amount :
         [NSDecimalNumber zero];

   NSDecimalNumber* tax=
      [product_price decimalNumberByMultiplyingBy:
         [NSDecimalNumber decimalNumberWithString:@"0.08"]];

   NSDecimalNumber* total=
      decimal_number_sum(@[product_price, shipping, tax]);

   // specify summary items
   NSMutableArray<PKPaymentSummaryItem*>* summary_items=
   [NSMutableArray arrayWithArray: 
    @[
      [PKPaymentSummaryItem
         summaryItemWithLabel: _product.name
                       amount: product_price],
      [PKPaymentSummaryItem
         summaryItemWithLabel: @"Shipping"
                       amount: shipping],
      [PKPaymentSummaryItem
         summaryItemWithLabel: @"Tax"
                       amount: tax],
      [PKPaymentSummaryItem
         summaryItemWithLabel: @"Acme"
                       amount: total]
      ]];

   return summary_items.copy;
}

使用此方法,您可以响应付款单的不同代理方法调用,并有一个一致的算法来计算支付摘要项。

响应用户与付款单的交互

在确定支付授权工作流程中的主要参与者和操作以及一个计算支付摘要项的单个方法后,我们准备深入了解您对用户更改配送信息所引发的付款单消息的响应。

注意,支付表的正确功能对于支付授权工作流程至关重要;特别是,支付表必须一致地调用其代理方法,以便您能够正确判断这些方法何时被调用,这是由于用户与支付表的交互引起的。如果您使用 iOS 模拟器测试代码,并注意到支付表的代理方法没有被调用,请退出并重新启动模拟器应用。

用户更改运输信息

当用户更改运输地址或联系人时,您的支付表代理会收到paymentAuthorizationViewController:didSelectShippingContact:completion:消息,表明用户已更改订单的运输信息。

新的运输详情被封装在一个联系人(一个PKContact实例)中。这个contact对象只包含您在支付请求的requiredShippingAddressFields属性中请求的运输信息。contact对象的属性有nameemailAddressphoneNumberpostalAddress。所以,如果您通过将requiredShippingAddressFields支付请求属性设置为PKAddressFieldPostalAddress|PKAddressFieldName来仅请求邮政地址和姓名,支付表将确保在调用此delegate方法时,联系人的namepostalAddress属性被填充。这是此方法的实现:

// client_app/merchantapp/ProductCard.m
- (void)
paymentAuthorizationViewController:
   (PKPaymentAuthorizationViewController* _Nonnull)     controller
didSelectShippingContact:
   (PKContact* _Nonnull)                          shipping_contact
completion:
   (void (^ _Nonnull) 
      (PKPaymentAuthorizationStatus             status,
       NSArray<PKShippingMethod*>* _Nonnull     shipping_methods,
       NSArray<PKPaymentSummaryItem*>* _Nonnull summary_items)
      )                                                 completion
{
   // validate address in shipping_contact parameter.
   // if the address is not valid, set the status argument of
   // the completion block to an appropriate value, such as
   // PKPaymentAuthorizationStatusInvalidShippingPostalAddress.

   completion(
      PKPaymentAuthorizationStatusSuccess,
      _pk_shipping_methods,
      [self computeSummaryItems]
   );
}

此方法所做的只是通过方法的完成块向支付表提供成功状态、标准运输方法和计算出的支付摘要项。在您的应用中,您可能需要根据完成块中contact参数包含的信息调整返回的运输方法。

用户更改运输方法

当用户更改运输方式时,支付表会调用其代理的paymentAuthorizationViewController:didSelectShippingMethod:completion:方法。类似于运输信息的更改,您根据用户选择的运输方式重新计算支付摘要项。在completion块中,提供成功授权状态和重新计算后的支付摘要项,如下例所示:

// client_app/merchantapp/ProductCard.m
@property PKShippingMethod*  selected_shipping_method;
@property NSString*          shipping_method_name;
...
- (void)
paymentAuthorizationViewController:
   (PKPaymentAuthorizationViewController* _Nonnull)    controller
didSelectShippingMethod:
   (PKShippingMethod* _Nonnull)                   shipping_method
completion:
   (void (^ _Nonnull)
      (PKPaymentAuthorizationStatus             status,
       NSArray<PKPaymentSummaryItem*>* _Nonnull summary_items)
      )                                                completion
{   
   _selected_shipping_method= shipping_method;
   _shipping_method_name=     shipping_method.identifier;

   completion(
      PKPaymentAuthorizationStatusSuccess,
      [self computeSummaryItems]
   );
}

此方法所做的只是存储所选运输方法的索引,重新计算支付摘要项数组,并通过方法的完成块调用完成块,将PKPaymentAuthorizationStatusSuccess作为status参数,以及新的支付摘要项数组作为summary_items参数。

用户授权支付请求

当用户授权支付请求时,支付表单在其代理上调用paymentAuthorizationViewController:didAuthorizePayment:completion:方法。在这个方法中,你处理从支付表单获得的支付(PKPayment实例),这个过程将在下一章中描述。如果支付处理成功,你将使用PKPaymentAuthorizationStatusSuccess作为参数调用completion块。如果你的验证检查确定支付请求包含错误信息,你可以返回指定此信息的值,例如:

  • PKPaymentAuthorizationStatusInvalidBillingPostalAddress

  • PKPaymentAuthorizationStatusInvalidShippingPostalAddress

  • PKPaymentAuthorizationStatusInvalidShippingContact

否则,对于一般性失败,包括发行银行未批准交易的情况,你可以返回PKPaymentAuthorizationStatusFailure

这里是一个payment sheet:payment request, authorizing方法的示例实现:

// client_app/merchantapp/ProductCard.m
- (void)
paymentAuthorizationViewController:
   (PKPaymentAuthorizationViewController*)      controller
didAuthorizePayment:
   (PKPayment*)                                 payment_info
completion:
   (void (^)(PKPaymentAuthorizationStatus))     payment_completion
{
   [self process_ApplePay_payment_with_Stripe: payment_info
                                   completion: payment_completion
   ];
}

这里是支付表单的示例:

用户授权支付请求

无论支付是否成功处理或用户取消了交易,当它在其代理上调用paymentAuthorizationViewControllerDidFinish:方法时,你有责任关闭支付表单。以下是如何做到这一点的示例:

// client_app/merchantapp/ProductCard.m
- (void)
paymentAuthorizationViewControllerDidFinish:
   (PKPaymentAuthorizationViewController*)    controller
{
   [self dismissViewControllerAnimated:YES completion:nil];
}

除了关闭支付表单之外,根据需要,你可以执行其他恢复状态的操作。

摘要

在本章中,你学习了你的应用程序支付授权工作流程中的参与者如何协同工作,以帮助用户提供你处理支付和订单(如果支付卡发行银行批准支付)所需的信息。本章展示了如何准备带有基本支付信息的支付请求,例如你支持的支付网络。它描述了如何响应用户在支付表单中对订单所做的更改。最后,本章展示了在用户授权或取消支付请求后如何关闭支付表单。

下一章将描述支付处理工作流程,这是你将处理在此处获得的信息,通过你的支付网关处理支付并在你的订单处理 Web 应用程序中履行客户订单的地方。我们将考虑获取用户支付卡发行银行批准交易所需的支付信息。

第四章:支付处理工作流程

在用户授权支付请求后,用户应用支付网关订单处理网络应用团队协作,安全地将支付信息传递给发卡行,将资金从用户的账户转移到收单银行,并通知用户交易状态(即是否被批准或拒绝)。

支付处理工作流程由三个阶段组成:

  1. 预处理阶段:在这个阶段,应用从支付网关获取扣费令牌,并将订单信息(包括扣费令牌)发送到订单处理服务器

  2. 处理阶段:在这个阶段,订单处理网络应用(在您的服务器上运行)通过支付网关对用户的卡进行扣费,如果扣费成功,则更新订单和库存数据,并将交易状态发送给用户应用

  3. 后处理阶段:在这个阶段,用户应用通知用户交易状态并关闭支付表单

本章将完成以下内容:

  • 介绍支付处理工作流程中的演员和操作

  • 描述支付处理工作流程的每个阶段以及每个阶段内采取的步骤

    注意

    由于支付网关 API 通常在模拟器应用中运行不适当,您必须在实际的 iOS 设备上使用您的开发环境测试支付处理工作流程。

处理工作流程中的演员和操作

支付处理工作流程 是将苹果支付生成的支付信息(从支付请求和用户在支付表单中输入的信息)传输到您的支付网关和发卡行的过程,以便对卡进行扣费并使支付资金在您的收单银行中可用。

工作流程从支付表单调用 paymentAuthorizationViewController:didAuthorizePayment:completion: 委托方法开始,向用户应用提供一般订单信息(如运输和账单信息)以及包含加密支付数据的支付令牌。

此图展示了支付处理工作流程中的演员、操作和数据:

处理工作流程中的演员和操作

这些是工作流程中的一部分操作和数据:

  • 支付授权:支付表单告知应用用户已授权支付

  • 支付令牌:应用向支付网关提供支付令牌,支付网关返回一个扣费令牌

  • 订单信息和扣费令牌:应用将订单信息和扣费令牌发送到订单处理网络应用

  • 扣费卡:网络应用通过支付网关对卡进行扣费

  • 批准或拒绝:支付网关告知网络应用支付是否被批准或拒绝

  • ** 交易结果和订单元数据 **:Web 应用向用户应用提供交易结果和订单信息,如订单号

  • ** 交易结果 **:应用告知支付表单支付交易的结果——即是否被批准或拒绝

  • ** 支付表单完成 **:支付表单告知应用交易已完成

  • ** dismiss **:应用将支付表单关闭

预处理阶段

预处理 阶段,用户应用使用支付表单生成的支付信息(以 PKPayment 对象的形式)作为用户授权的结果。应用将此信息发送到支付网关,并获取一个收费令牌(此项目可能根据支付网关的不同名称进行标识,例如注册 ID;订单处理 Web 应用使用收费令牌向用户的卡收费)。然后,应用将收费令牌与订单的相关信息(如 账单信息配送信息配送方式 等)打包,并将其发送到服务器上的订单处理 Web 应用。

以下三个部分描述了 预处理 阶段的步骤。

商户应用接收支付令牌

当用户授权支付请求时,支付表单调用 paymentAuthorizationViewController:didAuthorizePayment:completion: 代理方法(这是 PKPaymentAuthorizationViewControllerDelegate 协议的一部分)。此方法提供支付信息(一个 PKPayment 对象)和一个完成块。

这些是 PKPayment 对象的组成部分:

  • ** payment token ** (PKPaymentToken):当用户授权支付请求时生成

  • ** 账单联系人 **:这是账单联系人信息,包括电子邮件地址(NSString)、姓名(NSPersonNameComponents)、电话号码(CNPhoneNumber)和邮政地址(CNPostalAddress

  • ** 配送联系人 **:这是配送联系人信息(与账单联系人具有相同的组成部分)

  • ** 配送方式 ** (PKShippingMethod):这是用户选择的配送方式

这些是 PKPaymentToken 对象的组成部分:

  • ** 支付数据 **(一个加密的 NSData 对象):这是支付网关和发卡行用来向用户收费的数据

  • ** 支付方式 ** (PKPaymentMethod):这标识了用于交易的卡的类型,例如借记卡、信用卡、预付卡或商店卡

  • ** 交易标识符 ** (NSString):这是支付交易的友好标识符

下面列出了 paymentAuthorizationViewController:didAuthorizePayment:completion: 方法的示例实现,该实现是示例商户应用的一部分:

// client_app/merchantapp/ProductCard.m
- (void)
paymentAuthorizationViewController:
   (PKPaymentAuthorizationViewController*)      controller
didAuthorizePayment:
   (PKPayment*)                                 payment_info
completion:
   (void (^)(PKPaymentAuthorizationStatus))     payment_completion
{
 [self process_ApplePay_payment_with_Stripe:payment_info
 completion:payment_completion];
}

该方法调用process_ApplePay_payment_with_Stripe:completion:以开始处理支付。此方法针对特定的支付网关定制。payment_info参数包含支付令牌和支付请求中请求的运费和账单信息。payment_completion参数定义了一个块,告诉支付表单交易已处理。

商家应用从支付网关接收收费令牌

在应用获取支付信息(一个PKPayment对象)后,它使用支付网关 API 来获取一个收费令牌,该令牌由订单处理 Web 应用用来对卡进行收费。(一些支付网关提供原生 iOS API 来发行收费令牌;而其他则要求应用向他们的支付服务器发送 HTTP 请求。)一些支付网关的 API 在支付令牌(PKPaymentToken)上操作,而另一些则要求使用PKPayment对象。

在示例商家应用中,ProductCard类的process_ApplePay_payment_with_Stripe:completion:方法调用由 Stripe 支付网关提供的原生 iOS API,该 API 在PKPayment对象上操作,返回一个收费令牌(一个STPToken对象)。其他支付网关也有类似的本地或 Web API,提供相同的功能。

这是示例商家应用中的process_ApplePay_payment_with_Stripe:completion:方法:

// client_app/merchantapp/ProductCard.m
- (void)
process_ApplePay_payment_with_Stripe:
   (PKPayment*)                                 payment_info 
completion:
   (void (^)(PKPaymentAuthorizationStatus))     payment_completion
{   
 [[STPAPIClient sharedClient]
 createTokenWithPayment: payment_info
 completion: ^
   (STPToken* charge_token, NSError* error)
   {
      if (error)
      {
         NSLog(@"error creating STPToken object");
         payment_completion(PKPaymentAuthorizationStatusFailure);
      }
      else
 [self backend_process_payment_info: payment_info
 gateway: @"stripe"
 charge_token: charge_token
 completion: payment_completion];
   }];
}

如果创建收费令牌的方法(STPAPIClient类的createTokenWithPayment:completion:)报告错误,此方法将使用PKPaymentAuthorizationStatusFailure参数调用支付完成块,这实际上结束了交易。否则,它调用下一节中描述的backend_process_payment_info:gateway:charge_token:completion:方法。

商家应用将订单信息发送到订单处理系统

在从支付表单获取支付信息(PKPayment)和从支付网关获取收费令牌后,应用将收费令牌和其他订单处理 Web 应用所需的信息打包,并通过 HTTP 请求将其发送到您的服务器。

在示例商家应用中,ProductCard类的backend_process_payment_info:gateway:charge_token:completion:方法将所需信息打包成一个 JSON 对象(有效载荷),并通过 HTTP POST请求发送到http://red:12345/payment唯一资源标识符(URI)到订单处理 Web 应用。请查看以下代码:

// client_app/merchantapp/ProductCard.m
- (void)
backend_process_payment_info:
   (PKPayment*)                                  payment_info
gateway:
   (NSString*)                                   gateway
charge_token:
   (id)                                          charge_token
completion:
   (void (^)(PKPaymentAuthorizationStatus))      payment_completion
{
   RestIO* rest_io= [RestIO sharedRestIO];
   {
      AppDelegate* app_delegate=
         [UIApplication sharedApplication].delegate;
      NSString* payment_charge_uri=
         [NSString stringWithFormat:@"%@%@",
            app_delegate.rest_io_host,
            @"/payment"];

      NSNumber* total_in_cents= (NSNumber*)
      [_payment_request
            .paymentSummaryItems
             [_payment_request.paymentSummaryItems.count - 1]
            .amount
          decimalNumberByMultiplyingBy:
             [NSDecimalNumber decimalNumberWithString: @"100"]
      ];
      NSString* currency= _payment_request.currencyCode;

      NSString* contact_name=
         [payment_info.shippingContact.name.givenName
             stringByAppendingString:
                [@" " stringByAppendingString:
                   payment_info.shippingContact.name.familyName]];

      // collect info required by order processing webapp
 NSDictionary* order_info_package_dictionary= @
 {
 @"gateway"     : gateway,
 @"source"      : ((STPToken*)charge_token).tokenId,
 @"amount"      : total_in_cents,
 @"currency"    : currency,
 @"description" : 
 _payment_request.paymentSummaryItems[0].label,
 @"shipping_contact"     : contact_name,
 @"shipping_email"       :
 payment_info.shippingContact.emailAddress,
 @"shipping_street"      :
 payment_info.shippingContact.postalAddress.street,
 @"shipping_city"        :
 payment_info.shippingContact.postalAddress.city,
 @"shipping_state"       :
 payment_info.shippingContact.postalAddress.state,
 @"shipping_zip"         :
 payment_info.shippingContact.postalAddress.postalCode,
 @"shipping_method_name" :
 payment_info.shippingMethod.identifier
 };

      NSData* order_info_package_json;
      {
 NSError* error;
 order_info_package_json=
 [NSJSONSerialization
 dataWithJSONObject: order_info_package_dictionary
 options: NSJSONWritingPrettyPrinted 
 error: &error];
         NSAssert(!error, @"error converting %@ to JSON", error);
      }

      // send order information to order processing web app
 [rest_io postResourceAtURI: payment_charge_uri
 body: order_info_package_json
 completion: ^
 (NSURLResponse* response, NSData* data)
      {
         if (((NSHTTPURLResponse*)response).statusCode == 200)
         {
            NSDictionary* result;
            {
               NSError* error;
               result=
                  [NSJSONSerialization
                     JSONObjectWithData: data
                                options: 0
                                  error: &error];
               if (error)
                  [NSException
                      raise: @"JSONDeserializationException"
                     format: @"error deserializing JSON"];
            }
            NSString* status= (NSString*)result[@"status"];
            payment_completion(
               [status isEqual: @"succeeded"]?
                  PKPaymentAuthorizationStatusSuccess :
                  PKPaymentAuthorizationStatusFailure
            );
         }
         else
            payment_completion(
               PKPaymentAuthorizationStatusFailure);
      }];
   }
}

payment_info参数是支付表单在paymentAuthorizationViewController:didAuthorizePayment:completion:代理方法中提供的PKPayment对象。gateway参数标识应用用于获取充电令牌的支付网关;这可能对使用多个支付网关但只有一个订单处理 Web 应用的商户应用很有用。charge_token参数是从支付网关 API 获得的充电令牌。payment_completion参数与paymentAuthorizationViewController:didAuthorizePayment:completion:代理方法中提供的相同完成块。

在订单处理 Web 应用中,/payment请求的处理程序接收一个包含所需信息的 JSON 对象(在backend_process_payment_info:方法中的order_info_package_json变量中存储),例如示例项目 Web 应用中的信息是支付网关的名称、支付处理器要求为支付卡充电的信息(支付令牌、支付金额和支付货币),以及运输详情。

处理阶段

在支付处理工作流程的处理阶段,订单处理 Web 应用为用户的卡片充电,更新订单和库存数据,并将交易的批准或拒绝状态作为 HTTP 响应返回给用户设备上的用户应用。此 Web 应用使用支付网关的服务器端 API 与其通信。

在示例订单处理 Web 应用(一个 Node.js Web 应用)中,用户应用的 HTTP 请求由中间件处理,如下所示:

// server_app/red.js
// payment middleware
server.post('/payment', function(request, response, next)
{
 // 1\. parse request
   var order_info_package= JSON.parse(request.body);

   // process charge token
   if (order_info_package.gateway == 'stripe')
   {
 // 2\. charge payment card
      var charge= stripe.charges.create
      (
         {
            amount      : order_info_package.amount,
            currency    : order_info_package.currency,
            source      : order_info_package.source,
            description : 
               'charge for ' + order_info_package.description
         }, 
         function(error, charge)
         {
            var transaction_info= 
            {
               id     : charge.id,
               status : charge.status
            }

            if (error)
               console.log('there's an error creating a charge: '
                  + error);
            else
            {
 // 3.a update inventory
               // ...

 // 3.b create order
               var order= models.Order(
               {
                  date                 : new Date(),
                  description          :
                     order_info_package.description,
                  shipping_email       :
                     order_info_package.shipping_email,
                  shipping_street      :
                     order_info_package.shipping_street,
                  shipping_city        :
                     order_info_package.shipping_city,
                  shipping_state       :
                     order_info_package.shipping_state,
                  shipping_zip         :
                     order_info_package.shipping_zip,
                  shipping_method_name :
                     order_info_package.shipping_method_name,
                  total_price          :
                     order_info_package.amount,
                  stripe_charge_id     : charge.id
               });
               order.save();

               transaction_info.order_id= order._id;
            }

 // 4\. send transaction result to customer's device 
            response.send(transaction_info);
         }
      );
   }
   next();
});

此中间件分为四个步骤:

  1. 解析请求的 JSON 内容。

  2. 使用支付网关提供的 API 为卡片充电;在这种情况下,charge函数由Stripe JavaScript模块提供。

  3. 如果交易被批准,创建订单并更新所订购产品的库存数量。

  4. 将交易结果(包括如果支付被批准则包括订单号)发送到用户的设备。

后处理阶段

在支付处理工作流程的后处理阶段,应用分析订单处理 Web 应用对应用在预处理阶段发出的 HTTP 请求的响应。一般来说,响应指示发行银行是否批准或拒绝了支付。响应还可能包括订单号、订单状态和其他您认为对用户有用的详细信息;应用可能显示包含此信息的自定义确认表单。最后,应用关闭支付表单。

以下三个部分描述了后处理阶段的步骤。

商户应用从订单处理 Web 应用接收交易状态

示例用户应用在backend_process_payment_info:gateway:charge_token:completion:方法中的块中接收订单处理 Web 应用的响应。块的参数是一个NSURLResponse对象和一个NSData对象。处理响应和返回数据的代码在此处突出显示:

// client_app/merchantapp/ProductCard.m
- (void)
backend_process_payment_info:
   (PKPayment*)                                  payment_info
gateway:
   (NSString*)                                   gateway
charge_token:
   (id)                                          charge_token
completion:
   (void (^)(PKPaymentAuthorizationStatus))      payment_completion
{
   RestIO* rest_io= [RestIO sharedRestIO];
   {
      ...      
      // send order information to order processing web app
      [rest_io postResourceAtURI: payment_charge_uri
                            body: order_info_package_json
                      completion: ^
 (NSURLResponse* response, NSData* data)
 {
 if (((NSHTTPURLResponse*)response).statusCode == 200)
 {
 NSDictionary* result;
 {
 NSError* error;
 result=
 [NSJSONSerialization
 JSONObjectWithData: data
 options: 0
 error: &error];
               if (error)
                  [NSException
                      raise: @"JSONDeserializationException"
                     format: @"error deserializing JSON"];
            }
            NSString* status= (NSString*)result[@"status"];
            payment_completion(
               [status isEqual: @"succeeded"]?
                  PKPaymentAuthorizationStatusSuccess :
                  PKPaymentAuthorizationStatusFailure
            );
         }
         else
            payment_completion(
               PKPaymentAuthorizationStatusFailure);
      }];
   }
}

如果 HTTP 响应代码是200,则表示在生成响应时没有服务器端问题。在这种情况下,该方法将构成返回内容的 JSON 转换为NSDictionary对象(result),其中包含多个条目描述交易(有关详细信息,请参阅处理阶段部分)。该方法特别关注一个条目:"status"键。

商户应用将交易状态传达给用户

在示例商户应用从订单处理 Web 应用接收响应(作为 HTTP 响应有效载荷中的 JSON 对象)之后,它通过检查返回数据来确定交易是否被批准或拒绝。然后,它调用payment_completion块来通知支付表格交易状态,以便表格可以将此信息传达给用户。此列表中的突出显示代码执行此过程:

// client_app/merchantapp/ProductCard.m
- (void)
backend_process_payment_info:
   (PKPayment*)                                  payment_info
gateway:
   (NSString*)                                   gateway
charge_token:
   (id)                                          charge_token
completion:
   (void (^)(PKPaymentAuthorizationStatus))      payment_completion
{
   RestIO* rest_io= [RestIO sharedRestIO];
   {
      ...      
      // send order information to order processing web app
      [rest_io postResourceAtURI: payment_charge_uri
                            body: order_info_package_json
                      completion: ^
      (NSURLResponse* response, NSData* data)
      {
         if (((NSHTTPURLResponse*)response).statusCode == 200)
         {
            NSDictionary* result;
            {
               NSError* error;
               result=
                  [NSJSONSerialization
                     JSONObjectWithData: data
                                options: 0
                                  error: &error];
               if (error)
                  [NSException
                      raise: @"JSONDeserializationException"
                     format: @"error deserializing JSON"];
            }
 NSString* status= (NSString*)result[@"status"];
 payment_completion(
 [status isEqual: @"succeeded"]?
 PKPaymentAuthorizationStatusSuccess :
 PKPaymentAuthorizationStatusFailure
 );
 }
 else
 payment_completion(
 PKPaymentAuthorizationStatusFailure);
      }];
   }
}

如果返回数据中"status"键的值是"succeeded",则表示支付已被发行银行批准,资金将在不久的将来在收购银行中可用。在这种情况下,该方法调用支付表格提供的完成块(payment_completion)以指示支付已批准,并将PKPaymentAuthorizationStatusSuccess作为参数。如果 HTTP 响应代码不是200或返回数据中"status"键的值不是"succeeded",则该方法将使用PKPaymentAuthorizationStatusFailure作为参数调用完成块。

使用PKPaymentAuthorizationStatusSuccess调用payment_completion块会导致支付表格显示勾选标记,表示支付已被批准,如下面的截图所示:

商户应用传达交易状态给用户

您可以显示一个额外的表格,提供有关交易的更多信息,例如订单号和预计交货日期。

商户应用关闭支付表格

当支付表格完成时(无论是由于交易被批准或拒绝,还是由于用户取消了交易),它将调用paymentAuthorizationViewControllerDidFinish:代理方法。在这里,您可以执行任何必要的清理或应用状态更新。

这是示例商户应用实现代理方法的方式:

// client_app/merchantapp/ProductCard.m
- (void) paymentAuthorizationViewControllerDidFinish:
            (PKPaymentAuthorizationViewController*)
                                                 controller
{
   [self dismissViewControllerAnimated:YES completion:nil];
}

摘要

在本章中,您已经学习了苹果支付(Apple Pay)的支付处理过程。本章详细描述了支付处理工作流程的每个阶段,并确定了构成每个阶段的步骤。特别是,处理阶段是所有组件连接起来,安全地为用户的卡片充值并在您的收单银行中使资金可用的地方。

本章的解释基于本书附带的示例项目。该项目将在下一章中进行描述。

第五章:设计订单管理服务器

如果你有一个向客户提供实体商品的业务,拥有一个可靠的订单管理服务器非常重要。根据客户的需求,你可能需要从运行在 Web 浏览器、Web 服务器或移动设备上的客户端应用提供对服务器的访问。本书重点关注运行在 iPhone 和 iPad 设备上的客户端应用,以及在模拟器应用中。

通常,基于客户端/服务器的订单捕获和订单处理系统具有以下组件:

  • 服务器:运行订单处理 Web 应用、数据库管理系统和其他服务器端进程。Web 应用实现了基于 HTTP 的 API 客户端,用于请求和提交信息。通过这个 API,Web 应用向客户端提供产品信息,并处理客户端提交的支付和订单信息。

  • 客户端:运行客户端应用。客户端应用调用订单处理 Web 应用提供的 API,以请求和提交信息到 Web 应用。

为了本书的目的,一个订单管理 Web 应用是一个响应 HTTP 请求的过程。在本书的示例项目中,这个过程向客户端提供库存和运输方式信息,并处理客户端提交给它的支付信息(包括关于订购的项目、运输详情等信息)。

客户端应用通过 HTTP 请求与服务器交互。在你的开发环境中,订单管理 Web 应用运行在你的开发计算机上,而客户端应用则运行在支持 Apple Pay 的模拟器或 iOS 设备上。

本章描述以下内容:

  • 订单管理服务器的特点

  • 其数据库结构和客户端 API

配置订单管理服务器 Web 应用

一个Web 服务器是一个存储信息、修改信息并向客户端提供访问权限的计算机。这些客户端可以请求信息或要求服务器以特定方式处理数据,这可能导致服务器上存储的数据发生变化,客户端接收到的数据表示也会发生变化。实际上,任何计算机都可以作为 Web 服务器。Web 应用是在 Web 服务器上运行并通过对客户端的 HTTP 请求提供服务的过程。在生产环境中,Web 应用运行在专门配置的 Web 服务器上,这些服务器提供冗余、复制和其他功能,以确保稳健性、高性能和安全,以及其他特性。然而,你不需要一个完整的 Web 服务器来运行 Web 应用。你可以在你的开发计算机上运行 Web 应用。

一个订单管理服务器是一个运行在可供客户端访问的计算机上的 Web 应用。在你的开发环境中(由你的计算机、开发工具集和 iOS 设备组成),服务器 Web 应用运行在你的计算机上,而客户端应用则运行在你的计算机上的模拟器或 iOS 设备上。

注意

服务器和客户端的硬件和软件配置众多。本章描述了在单个 Mac 上使用模拟器应用和可以发送 HTTP 请求到该计算机的 iOS 设备进行开发的配置。此配置不适用于测试或部署。当您准备在更真实的配置中测试或部署应用程序时,请咨询部署工程师。

要测试支持 Apple Pay 的客户端应用程序的所有组件(例如本书的示例应用程序、商家应用程序),您必须在 iOS 设备上运行客户端应用程序。通常,客户端支付网关软件在模拟器中运行效果不佳。

客户端应用程序可以直接访问服务器网络应用程序(即,如果两个进程都在同一台计算机上运行,例如当客户端在 iOS 模拟器中运行时),通过运行在 iOS 设备上的客户端的内部网络(在子网内),或通过使用可通过公共互联网访问的 Web 服务器(无论是用于大规模测试还是生产)通过互联网访问。示例项目使用 Node.js 实现服务器网络应用程序。Node.js 是用于开发服务器端应用程序的环境。

定义订单管理数据结构

订单管理系统至少有表示可供销售的产品(库存)和订单的数据结构。为了支持 Apple Pay 工作流程,系统还应有一个表示支持的运输方式的结构。示例项目使用 MongoDB 数据库存储这些数据,并使用 Node.js 的 Mongoose 数据建模模块。MongoDB 是一种基于文档的数据库系统。Mongoose 使得从模式创建文档和访问这些文档中的数据变得更加容易。

示例订单管理系统数据库有三个集合:ProductShippingMethodOrder。集合在关系型数据库系统中类似于表。

Product 集合存储有关出售的每个产品的信息。下表显示了集合中产品记录可用的字段:

字段名称 描述
name 这是产品的名称
description 这是一个描述产品的短语
image_uri 这是一个标识产品图像的 URI
quantity_on_hand 这是库存中产品的单位数量
price 这是每个产品单位的销售价格

ShippingMethod 集合存储有关可用运输方式的信息。下表显示了运输记录的字段:

字段名称 描述
name 这是运输方式的名称
description 这是一个表示运输持续时间的天数
transit_days 这是一个表示运输持续时间的天数
price 这是运输方式的费用

Order 集合存储了订单管理系统处理的每个订单的信息。以下是订单记录的字段:

字段名称 描述
date 这是订单处理的日期时间组
description 这是销售产品的名称(每个订单一个产品)
shipping_contact 这是被标识为运输联系人的姓名
shipping_email 这是运输联系人的电子邮件地址
shipping_street 这是运输地址的街道
shipping_city 这是运输地址的城市
shipping_state 这是运输地址的状态
shipping_zip 这是运输地址的邮政编码
shipping_method_name 这是用于订单的运输方式名称

这是定义三个集合结构的 Node.js 代码:

// server_app/lib/inventory_models.js
var mongoose= require('mongoose');
var Schema=   mongoose.Schema;

var Product_schema= new Schema({
   name:             String,
   description:      String,
   image_uri:        String,
   quantity_on_hand: Number,
   price:            String
});

var ShippingMethod_schema= new Schema({
   name:         String,
   description:  String,
   transit_days: Number,
   price:        String
});

var Order_schema= new Schema({
   date                 : String,
   description          : String,
   shipping_contact     : String,
   shipping_email       : String,
   shipping_street      : String,
   shipping_city        : String,
   shipping_state       : String,
   shipping_zip         : String,
   shipping_method_name : String,
   total_price          : String,
   stripe_charge_id     : String
});

exports.Product=
   mongoose.model('Product',        Product_schema);
exports.ShippingMethod=
   mongoose.model('ShippingMethod', ShippingMethod_schema);
exports.Order=
   mongoose.model('Order',          Order_schema);

向客户端提供库存信息

订单管理系统实现了三个 REST API(在 Node.js 术语中称为中间件)以向客户端提供库存和运输方式信息。具体如下:

  • /shipping_methods: 这将返回支持的运输方式列表

  • /inventory: 这将返回产品目录

  • /product_image/<image_name>: 这将返回用于向用户展示产品的图片

当客户端应用程序启动时,它会请求系统支持的运输方式列表(在 AppDelegate.m 中的 application:didFinishLaunchingWithOptions: 方法)。在产品列表屏幕上显示可用的产品列表之前,应用程序会请求产品目录(在 ProductList.m 中的 viewDidLoad 方法)。当应用程序即将显示特定产品的卡片(在用户在产品列表屏幕中选择产品后),它会从服务器请求产品的图片(在 ProductCard.m 中的 viewDidLoad 方法)。

注意

对于开发,示例应用程序(商家应用程序)使用的 URI 指定了进程名称(但不是实际的 Web 地址)和端口号,以访问订单管理系统 Web 应用程序(例如,http://red:12345/inventory)。当您将应用程序部署到生产环境(即现实世界)时,您必须使用配置适当的真实 Web 服务器来运行您的 Web 应用程序,并使用指向公共 Web 地址的 URI,例如 http://red.com/inventory

这是实现本节前面介绍的中间件的 Node.js 代码:

// server_app/red.js

// load required modules
var models=    require('./lib/inventory_models.js');
var assert=    require('assert');
var mongoose=  require('mongoose');
var restify=   require('restify');
var stripe=    require('stripe')('<my_key>')
var server=    restify.createServer();
server.use(restify.bodyParser());

// specify the data models
Product=         models.Product;
ShippingMethod=  models.ShippingMethod;
Order=           models.Order;

// connect to the red MongoDB database
mongoose.connect('mongodb://localhost/red');

protocol=  'http://';
hostname=  'red';
port=      12345;
base_uri=  protocol + hostname + ':' + port;
console.log(base_uri);

// initialize Product collection, if needed
Product.find(function(error, _products)
{
   if (_products.length == 0)
   {
      console.log('initializing product collection');
      models.Product(
      {
         name:             'Clock', 
         description:      'Wooden clock', 
         quantity_on_hand: 10,
         price:            '50.00',
         image_uri:        base_uri + '/product_image/clock.jpeg'
      }).save();
      ...
   }
});

// load Product collection into products
var products= new Array();
Product.find(function(error, _products)
{
   products= _products;
});

// initialize ShippingMethods collection, if needed
ShippingMethod.find(function(error, _shipping_methods)
{
   if (_shipping_methods.length == 0)
   {
      console.log('initializing shipping-method collection');
      models.ShippingMethod({
         name:         'Free',
         description:  'Delivers in seven days',
         transit_days: 7,
         price:        '0.00'
      }).save();
      ...
   }
})

// load ShippingMethod collection into shipping_methods
var shipping_methods= new Array();
ShippingMethod.find(function(error, _shipping_methods)
{
   shipping_methods= _shipping_methods;
});

// ** start middleware (client API) **
//   /product_image/<name> API:
//     provides product image from
//     <project_dir>/public/product_image/<name>
server.get(/\/product_image\/?.*/,
   restify.serveStatic( { directory: '../public' }));

//   /inventory API:
//     provides current inventory from Product document
server.get('/inventory', function(request, response, next)
{
   response.send(products);
   next();
});

//   /shippng_methods API:
//     provides shipping methods from ShippingMethod document
server.get('/shipping_methods', function(request, response, next)
{
   response.send(shipping_methods);
   next();
});
...
// ** end middleware (client API) **

// listen for HTTP requests
server.listen(12345);

处理来自客户的订单

示例订单管理系统服务器 Web 应用程序通过单个函数 payment(在第四章中描述,支付处理工作流程)处理订单,客户端通过向 http://red:12345/payment URI 发送 HTTP POST 请求来执行此函数。

首先,该函数尝试对客户卡片进行收费。如果收费成功,该函数将使用客户端提供的订单信息将一条记录添加到Order集合中。完成后,该函数将有关支付交易和新的订单(如果已创建)的信息返回给客户端。

有关payment函数实现的详细信息,请参阅第四章中的流程阶段部分,支付处理工作流程

实现安全通信

前几节中描述的配置仅适用于开发。当部署一个用于真实客户使用的订单管理系统 Web 应用时,你需要做几件事情。你的客户使用的客户端应用也必须是安全的。

首先,客户端应用在 URI 中不应使用进程名称和端口号,而应使用基于 Web 地址的 URI,例如red.com。你还应配置一台计算机作为你的公开可访问的 Web 服务器,该服务器运行你的服务器进程。根据预期的流量量,你可能需要配置你的 Web 服务器同时运行多个服务器 Web 应用的实例来处理来自多个客户端的并发请求。

其次,你必须确保服务器和客户端之间传输的数据是安全的。通过确保数据传输的安全性,你确保只有授权实体才能接收数据,数据在传输过程中不会被修改,并且数据不能被第三方读取。实现这一目标的一种方法是用HTTPSHTTP 安全)协议而不是 HTTP 来在服务器和客户端之间传输消息和数据。

你可以在苹果的开发者库中了解如何向客户端应用添加 HTTPS 支持。有关在 Web 服务器中实现 HTTPS 的信息,请参阅相应的文档。

摘要

本章已描述了一个简单 Web 应用的设计和实现,该应用作为订单管理系统,为客户端应用提供库存数据,并处理在 iOS 模拟器应用、iPhone 或 iPad 设备上运行的客户端应用提交的订单,这些设备支持 Apple Pay。本章还提到了在 Web 服务器和客户端应用中需要实施的临界安全措施,以确保它们之间的安全通信。下一章将提供有关客户端应用使用的 Apple Pay 主 API 的基本信息。

第六章。Apple Pay API 概述

您在三个阶段处理 Apple Pay 交易:

  1. 准备:创建一个支付请求,并用支付信息和用户购买的产品详情填充它。

  2. 支付表单交互:通过 PKPaymentAuthorizationViewControllerDelegate 协议的方法响应用户与支付表单的交互(例如更改送货地址或所需的运输方式)。

  3. 支付处理:通过您的支付网关提供的 API 和对您的订单处理 Web 应用的 HTTP 请求,在用户的设备和您的服务器上处理 Apple Pay 交易。

早期章节展示了您用于与 Apple Pay 交易中的主要参与者交互的工作流程。本章提供了有关 Apple Pay API 的类、方法和属性的有用信息,这些信息是您为了有效使用它而必须熟悉的。然而,当您需要更详细的信息时,您应该查阅 Apple 开发者文档。

在本章中,您将了解涉及支付交易的 Apple Pay 主要类,例如 PKPaymentReqestPKPaymentSummaryItem 类。您还将了解如何通过 PKPaymentAuthorizationViewControllerDelegate 协议响应用支付表单事件。最后,本章将描述 Apple Pay API 用于处理地址的附加类,例如 PKContact 类。

本章涵盖了以下主题:

  • 涉及支付交易的 Apple Pay 主要类,例如 PKPaymentReqestPKPaymentSummaryItem

  • 通过 PKPaymentAuthorizationViewControllerDelegate 协议对支付表单事件进行响应

  • Apple Pay API 使用的其他类来工作

主要类

本节描述了您用于处理 Apple Pay 交易的 Apple Pay 主要类。这些类包括:

  • PKPaymentButton: 您使用这个类来显示一个支付按钮,用户通过这个按钮开始 Apple Pay 交易。

  • PKPaymentRequest: 这个类代表一个支付请求,用户在支付表单中授权或取消该请求。

  • PKPaymentSummaryItem: 这个类的实例代表支付表单中的摘要项(例如小计、运费和总计)。

  • PKPaymentMethod: 这个类提供了访问 Apple Pay 交易中使用的支付卡信息的方式。

  • PKShippingMethod: 这个类的实例代表您支持的运输方式。

  • PKPaymentAuthorizationViewController: 这个类用于确定用户是否可以在设备上使用 Apple Pay。它还用于在屏幕上显示支付表单。

  • PKPayment: 这个类的实例存储 Apple Pay 交易的支付信息。

  • PKPaymentToken: 这个类的实例包含加密的支付信息,准备提交给支付处理器以对用户的支付卡进行收费。

PKPaymentButton 类

PKPaymentButton 类为用户提供了一个点击以启动 Apple Pay 交易的按钮。您应该仅使用 Apple Pay 按钮来启动 Apple Pay 交易。

在显示 Apple Pay 按钮之前,您必须通过调用 PKPaymentAuthorizationViewController 类的 canMakePayments 方法来确保设备支持 Apple Pay。您还应该调用同一类的 canMakePaymentsUsingNetworks:canMakePaymentsUsingNetworks:capabilities: 来确保用户的支付卡支持您所需的支付网络和支付处理器功能。有关详细信息,请参阅本节后面的 PKPaymentAuthorizationViewController

如果设备不支持 Apple Pay,或者用户添加到设备上的支付卡与您所需的支付网络和支付处理器功能不兼容,请使用带有标题如 Buy 的标准按钮来启动标准支付交易。

创建按钮

使用此方法创建一个 Apple Pay 按钮。仅使用 Apple Pay 按钮进行 Apple Pay 交易。

+buttonWithType:style

此方法提供了一个 Apple Pay 按钮。

这里是参数:

  • type (PKPaymentButtonType): 这指定了按钮的标题。选项有普通(Pay)、购买(Buy with Pay)和设置(Set up Pay)。要了解更多关于使用最后一个选项的信息,请参阅 Apple 开发者库中的 Apple Pay Programming Guide

  • style (PKPaymentButtonStyle): 这指定了按钮的外观。使用它来确保按钮从背景中突出。选项有白色、白色轮廓和黑色。

PKPaymentRequest

PKPaymentRequest 类代表对特定商品的支付请求。这是您在 Apple Pay 交易中使用的主体类。您可以在每个 Apple Pay 交易中使用此类的单个实例。支付请求包含有关请求支付的商业信息、支付处理的国家、使用的货币、请求支付的项目的价格(包括运费、税费等),以及支持的支付网络(如 Amex、Discover 和 Visa)。

当用户在支付表中授权支付请求时,您将获得一个 PKPayment 对象,您需要将其提交给您的支付网关以获取一个 PKPaymentToken 对象(或替代品),然后您可以使用它来在您的订单处理系统中对用户的支付卡进行收费。

支付处理信息

这些必需的属性指定了您的支付网关用于处理支付的信息。这些应该是您在支付请求上设置的第一件事。

countryCode (NSString*)

这是一个表示支付处理国家(ISO 3166)的两个字母代码,例如 "US"

此代码将支付请求的 countryCode 属性设置为 US,如下所示:

_payment_request.countryCode=@"US";

currencyCode (NSString*)

这是用于处理支付的货币的三位代码(ISO 4217),例如 "USD"

merchantCapabilities (PKMerchantCapability)

这些是您的支付网关支持的支付处理协议。3-D Secure 协议是必需的。其他可用的协议包括 EMV(欧联、万事达卡、维萨卡)、信用卡和借记卡。

这些常量标识了苹果支付支持的以下支付处理能力:

  • PKMerchantCapability3DS

  • PKMerchantCapabilityEMV

  • PKMerchantCapabilityCredit

  • PKMerchantCapabilityDebit

以下代码将支付请求的 merchantCapabilities 属性设置为 3D Secure 和 EMV:

_payment_request.merchantCapabilities= 
  PKMerchantCapability3DS | PKMerchantCapabilityEMV;

merchantIdentifier (NSString*)

这是一个存储在您项目 Entitlements 文件中的商户标识符。

supportedNetworks (NSArray<NSString>)

这包括您支持的支付网络(或关联)。可用的选项有发现卡、万事达卡、维萨卡和私人标签(商店卡)。

这些常量标识了苹果支付支持的以下支付网络:

  • PKPaymentNetworkAmex

  • PKPaymentNetworkDiscover

  • PKPaymentNetworkMasterCard

  • PKPaymentNetworkPrivateLabel

  • PKPaymentNetworkVisa

例如,此代码将支付请求的 supportedNetworks 属性设置为美国运通和万事达卡:

_payment_request.supportedNetworks= 
  PKPaymentNetworkAmex | PKPaymentNetworkMasterCard;

支付摘要项目

摘要项目标识并定价客户购买的产品(小计)和相关项目,如税费、运费和总计。

paymentSummaryItems (NSArray<PKPaymentSummaryItem>)

这是一个摘要项目数组,指定每个项目的标签和价格。此外,您可以将项目标识为 待定(价格尚未设置,但在服务完成后将确定,例如出租车行程)或 最终。有关详细信息,请参阅 PKSummaryItem 类 部分。

必要的地址字段

这些属性指定了您需要在支付表中输入的账单和发货地址的字段,以便它们在用户授权支付请求时在支付表提供的 PKPayment 对象中可用。

requiredBillingAddressFields (PKAddressField), requiredShippingAddressFields (PKAddressField)

如果您需要在发货信息中包含姓名、电子邮件和邮政地址,可以使用类似以下代码:

payment_request.requiredShippingAddressFields= 
    PKAddressFieldName | PKAddressFieldEmail | PKAddressFieldPostalAddress;

这些是地址字段指定常量:

  • PKAddressFieldNone(不需要任何字段)

  • PKAddressFieldPostalAddress

  • PKAddressFieldPhone

  • PKAddressFieldEmail

  • PKAddressFieldName

  • PKAddressFieldAll

账单和发货联系人

这些属性指定了账单和发货联系信息。您可以在显示支付表之前设置它们(然而,这并不推荐,因为用户最有可能拥有最新的联系信息)。用户也可以通过在支付表中选择或指定不同的联系人来更改它们。

billingContact (PKContact), shippingContact (PKContact)

这些是 PKContact 对象的组成部分:

  • emailAddress (NSString*)

  • name (NSPersonNameComponents*)

  • phoneNumber (CNPhoneNumber*)

  • postalAddress (CNPostalAddress*)

运输方式

运输方式是你支持的运输选项(承运人或速度)。

shippingMethods (NSArray<PKShippingMethod>)

此属性包含客户可用的运输方式集合。

每个PKShippingMethod对象都有以下属性:

  • detail (NSString*): 这向用户描述了运输方式,例如 "四天内送达"

  • identifier (NSString*): 这在应用中标识运输方式。当用户选择一种运输方式时,使用此属性来识别所选择的方法。

运输类型

这指定了产品运输的方式,例如是否由商家发货,或者由客户从商家的店铺取货。

shippingType (PKShippingType)

这些是可用的运输类型:

  • PKShippingTypeShipping (默认): 商家使用客户选择的运输方式将产品运送给客户。

  • PKShippingTypeDelivery: 商家(使用车辆)将产品运送给客户。这用于食品、杂货、家具或不能运输的特殊商品。

  • PKShippingTypeStorePickup: 客户从商家的店铺取货。在这种情况下,将shippingContact设置为你的店铺地址或通过将requiredShippingAddressFields设置为PKAddressFieldNone来隐藏它从支付单中。请记住,以可靠的方式与客户确认取货地点。

  • PKShippingTypeServicePickup: 商家从shippingContact中指定的地址取货(例如,当客户支付要求送货时)。

应用数据

此属性存储了一个NSData对象的哈希值。

applicationData (NSData*)

当设置时,此属性包含指定数据的哈希值(然而,它不包含数据的值)。如果你的支付网关支持它,从支付网关 API 获取的PKPaymentToken对象的paymentData属性设置为当你设置applicationData时生成的相同哈希值。你可以将此哈希值作为发送支付信息到你的订单处理服务器的一部分发送。另外,你可以将实际数据发送到你的服务器。然后你可以在服务器上计算哈希值,并将两个哈希值进行比较,以确保数据在传输过程中未被更改。

PKPaymentSummaryItem 类

PKPaymentSummaryItem 类代表客户支付的项,例如代表产品价格、税费、运费等的总计。你可以将一个或多个项标识为“挂起”(价格必须在未来的某个时间才能确定)或“最终”。支付单不会显示“挂起”项的价格;因此,你应该将这些项的价格设置为零。当服务(如出租车行程)完成时,你可以计算实际价格并在交易完成后向客户报告。

创建摘要项

这些方法创建摘要项。

+summaryItemWithLabel:amount:

这创建了一个带有标签和金额的“最终”摘要项。

+summaryItemWithLabel:amount:type:

这创建了一个带有标签和金额的“挂起”或“最终”摘要项。

以下代码创建了一个“挂起”摘要项:

PKPaymentSummaryItem* trip= 
[PKPaymentSummaryItem 
      summaryItemWithLabel: @"Trip"
                                 amount: [NSDecimalNumber zero] 
                                      type: PKPaymentSummaryItemTypePending];

摘要项组件

这些属性访问摘要项的组件。

标签(NSString*)

这是摘要项的标签。

金额(NSDecimalNumber*)

这是摘要项的价格(如果项目是“最终”,则显示在支付单上)。

挂起或最终

这个属性决定了摘要项的类型。

类型(PKPaymentSummaryItemType)

这指定了项目是“挂起”还是“最终”。

这是 type 属性的可能值:

  • PKPaymentSummaryItemTypeFinal

  • PKPaymentSummaryItemTypePending

PKPaymentMethod

PKPaymentMethod 类提供了访问在苹果支付交易中使用的支付卡信息的权限。当用户在支付单中选择支付卡时,支付单会调用其代理的 paymentAuthorizationViewController:didSelectPaymentMethod:completion: 方法。

卡名称

这个属性标识了 payment 方法的支付卡。

displayName(NSString*)

这标识了支付卡给用户。

卡类型

这个属性标识了支付卡的类型。

类型(PKPaymentMethodType)

这标识了使用的支付卡类型(Amex、Visa 等)。旧卡可能没有这项信息;在这种情况下,此属性的值为 PKPaymentMethodTypeUnknown

这些常量标识了苹果支付支持的卡片类型:

  • PKPaymentMethodTypeUnknown

  • PKPaymentMethodTypeDebit

  • PKPaymentMethodTypeCredit

  • PKPaymentMethodTypePrepaid

  • PKPaymentMethodTypeStore

支付网络

这个属性标识了支付网络。

网络(NSString*)

这标识了卡的支付网络(或协会)。

这些常量标识了苹果支付支持的支付网络:

  • PKPaymentNetworkAmex

  • PKPaymentNetworkDiscover

  • PKPaymentNetworkMasterCard

  • PKPaymentNetworkPrivateLabel(这是为商店卡)

  • PKPaymentNetworkVisa

支付密码

这个属性与支付密码一起使用。

paymentPass(PKPaymentPass*)

你可以用这个,例如,来支持定制的品牌支付卡。查看苹果的文档以获取更多详细信息。

PKShippingMethod

PKShippingMethod类代表您为向客户交付商品所支持的配送方式。

用户友好的描述

此属性向用户提供有关配送方式的信息。

详细信息(NSString*)

这向用户描述配送方式,例如:“四天内送达”。

应用级标识符

此属性标识了应用内的配送方式。

标识符(NSString*)

当用户更改配送方式时,使用此属性来识别所选的配送方式。

PKPaymentAuthorizationViewController

PKPaymentAuthorizationViewController类有两个功能:

  • 确定用户是否可以使用 Apple Pay 进行支付

  • 向用户展示付款单

此类通过PKPaymentAuthorizationViewControllerDelegate协议的方法与其代理(您实现)进行通信。

确定 Apple Pay 支持

以下方法确定设备是否支持 Apple Pay,以及用户添加的支付卡是否支持特定的支付网络和支付处理功能。

+canMakePayments

这表示设备是否支持 Apple Pay。

例如,要确定设备是否支持 Apple Pay,可以使用类似以下代码:

BOOL can_use_ApplePay= [PKPaymentAuthorizationViewController canMakePayments];

+canMakePaymentsUsingNetworks:

这表示用户是否可以通过您支持的网路使用 Apple Pay。如果用户尚未将支付卡添加到设备,则此方法返回NO

在调用canMakePayments之后,您可以调用此方法。

例如,要仅接受 Visa 或 Amex 的支付卡,可以使用类似以下代码:

BOOL can_use_ApplePay_with_Amex_and_Visa= 
  [PKPaymentAuthorizationViewController
           canMakePaymentsUsingNetworks:, @[PKPaymentNetworkVisa, PKPaymentNetworkAmex];

这些常量标识了 Apple Pay 支持的支付网络:

  • PKPaymentNetworkAmex

  • PKPaymentNetworkDiscover

  • PKPaymentNetworkMasterCard

  • PKPaymentNetworkPrivateLabel(这是为商店卡)

  • PKPaymentNetworkVisa

+canMakePaymentsUsingNetworks:capabilities:

这表示用户是否可以通过您支持的支付网络和支付处理能力(如 3D Secure 和 EMV)在设备上使用 Apple Pay。如果用户尚未将兼容的支付卡添加到设备,则此方法返回NO

在调用canMakePayments之后,您可以调用此方法。

例如,要仅接受支持 3D Secure 和 EMV 协议的 Visa 或 Amex 支付卡,可以使用此代码:

BOOL can_use_ApplePay_with_Amex_and_Visa_debit_card= 
  [PKPaymentAuthorizationViewController
        canMakePaymentsUsingNetworks: @[PKPaymentNetworkVisa, PKPaymentNetworkAmex]
                        capabilities: PKMerchantCapability3DS | PKMerchantCapabilityEMV];

这些常量标识了 Apple Pay 支持的支付处理功能:

  • PKMerchantCapability3DS

  • PKMerchantCapabilityEMV

  • PKMerchantCapabilityCredit

  • PKMerchantCapabilityDebit

初始化和展示

以下方法准备并展示付款单。

-initWithPaymentRequest:

此方法使用支付请求初始化一个分配的PKPaymentAuthorizationViewController对象。

-initWithPaymentRequest:

如果用户可以使用 Apple Pay,则此方法使用提供的PKPaymentRequest对象初始化一个支付授权视图控制器(付款单)。

你可以在配置好支付请求后调用这个方法。

支付表单代理

这个属性标识了支付表单报告支付请求更改的对象:

代理 id

这是采用PKPaymentAuthorizationViewControllerDelegate协议以响应支付表单用户事件的对象。

PKPayment

PKPayment类表示用户授权的支付。当用户在支付表单中授权支付请求时(通过PKPaymentAuthorizationViewControllerDelegate协议中的paymentAuthorizationViewController:didAuthorizePayment:completion:方法),你会得到一个PKPayment对象。PKPayment对象包含了支付网关和发行和收购银行处理支付、向用户收费并将资金存入你账户所需的支付信息。这同样是为了你的订单处理 Web 应用程序在发行银行授权交易后启动服务或向客户发货。

支付信息

这个属性包含了关于支付的信息,它标识了使用的支付卡、参与交易的银行以及其他细节:

token (PKPaymentToken*)

这是支付网关、发行银行和收购银行使用的加密支付信息。

账单和发货联系人

这个属性包含了在初始化支付表单时标记为“必需”的账单和发货信息:

billingContact (PKContact*)

这包含了交易中的账单联系信息。只有支付请求中requiredBillingAddressFields属性中标识的字段会被填充。如果没有请求任何字段,这个属性将是nil

shippingContact (PKContact*)

这是交易中的发货联系人信息。只有支付请求中requiredShippingAddressFields属性中标识的字段会被填充。如果没有请求任何字段,这个属性将是nil

shippingMethod

这标识了用户从支付请求中shippingMethods属性列出的发货方式中选择的方法。

PKPayment

一个PKPaymentToken对象包含了支付网关和银行使用的加密支付信息,用于资助支付所使用的支付卡类型(借记卡、信用卡或私人标签),以及交易的标识符。

注意,一些支付网关 API 在其工作流程中不使用这个类。相反,它们提供自己的支付令牌类。使用 Stripe 的示例项目使用STPToken类。查看ProductCard类中的process_ApplePay_payment_with_Stripe:completion:方法(ProductCard.m)以获取详细信息。

加密支付信息

这个属性仅由授权的实体解密,例如你的支付网关。

paymentData (NSData*)

这是交易的加密支付信息。

支付方法和交易标识符

这些属性提供了有关支付令牌的信息。

paymentMethod (PKPaymentMethod*)

这是用于交易的支付方法。

transactionIdentifier (NSString*)

这是您的支付网关分配的交易标识符。

支付表单用户事件协议

本节描述了支付表单用于与其代理通信的协议。

PKPaymentAuthorizationViewControllerDelegate 协议

PKPaymentAuthorizationViewControllerDelegate 协议指定了支付表单用于将其事件传达给其代理(您可以实现以响应这些方法的调用)的方法集。以下各节确定了用户可以在支付表单中执行的操作,以及当用户执行这些操作时调用的代理方法。

用户选择支付卡

用户可以在支付表中切换支付卡(即,如果用户在设备上的 Apple Pay 中添加了多个支付卡)。

-paymentAuthorizationViewController:didSelectPaymentMethod: completion:

当用户选择支付卡时,会调用此方法。如果支付卡会影响支付请求的摘要项(例如,如果您提供使用商店卡的折扣),则必须计算支付请求的摘要项(这些是PKPaymentSummaryItem对象数组)。在任何情况下,您都必须将摘要项数组作为此方法完成处理程序的参数提供。

用户选择收货地址

用户可以从之前输入的地址列表中选择一个收货地址,或者在支付表单中输入一个新的地址。

-paymentAuthorizationViewController:didSelectShippingContact: completion:

用户指定收货地址后,会调用此方法。支付表单在PKContact* contact参数中提供收货地址。此收货地址已匿名化(即,不包括个人信息)。在美国,这些信息仅包括城市、州和邮政编码。

如果您的运输方法根据运输地址而变化,在此方法中,您应创建一个包含适用于新运输地址的运输方法的数组。您还应该使用重新计算的运输方法数组中的第一个运输方法重新计算支付请求的摘要项数组以计算运输费用(支付表单选择运输方法数组中的第一个运输方法)。

此方法的完成块接受三个参数:statusshippingMethodssummaryItems

  • status:这表示收货地址是否有效。如果用户指定的收货地址正确,则将此参数设置为PKPaymentAuthorizationStatusSuccess。否则,将其值设置为PKPaymentAuthorizationStatusInvalidShippingPostalAddress

  • shippingMethods:这是重新计算的运输方法数组。

  • summaryItems:这是重新计算的摘要项数组。

用户选择一个配送方式

用户可以从您支持的配送方式列表中选择配送方式(这存储在支付请求的shippingMethods属性中)。

-paymentAuthorizationViewController:didSelectShippingMethod: completion:

当用户选择配送方式时,会调用此方法。如果用户选择的配送方式会影响支付请求的摘要项(例如,如果配送方式有不同的价格),则必须重新计算支付请求的摘要项(PKPaymentSummaryItem对象数组)。

此方法的完成块接受两个参数:statussummaryItems

  • status:这表示配送方式和配送地址(在调用paymentAuthorizationViewController:didSelectShippingContact:completion:代理方法时指定)是否有效。如果用户指定的配送地址正确,则将此参数设置为PKPaymentAuthorizationStatusSuccess。否则,将其值设置为PKPaymentAuthorizationStatusInvalidShippingPostalAddress

  • summaryItems:这是重新计算的摘要项数组。

用户授权支付请求

用户在支付单中确认或更改信息后,用户可以授权支付请求以进行 Apple Pay 交易。

-paymentAuthorizationViewControllerWillAuthorizePayment:

在用户验证支付单之后但在调用paymentAuthorizationViewController:didAuthorizePayment:completion:之前调用此方法。使用此方法进行授权准备。

-paymentAuthorizationViewController:didAuthorizePayment: completion:

在用户授权支付请求之后调用此方法。这是您使用支付网关处理支付的地方。完成后,您必须使用状态参数的适当值调用完成处理程序块((void (^)(PKPaymentAuthorizationStatus status)) completion)。

这是完成处理程序status参数的可能值:

  • PKPaymentAuthorizationStatusSuccess:这表示支付卡的发行银行成功授权支付

  • PKPaymentAuthorizationStatusFailure:这表示由于支付卡的发行银行授权失败而失败

  • PKPaymentAuthorizationStatusInvalidBillingPostalAddress:这表示由于无效的账单地址而失败

  • PKPaymentAuthorizationStatusInvalidShippingPostalAddress:这表示由于无效的配送地址而失败

  • PKPaymentAuthorizationStatusInvalidShippingContact:这表示由于无效的配送联系人而失败

支付单完成

用户授权支付请求并在应用处理 Apple Pay 交易后,或者用户取消支付单时,支付单完成。

-paymentAuthorizationViewControllerDidFinish:

当交易完成或用户取消支付表单时调用。您必须关闭支付表单并执行任何其他必要的更新。

辅助类

本节描述了您必须使用的一些附加类,以使用 Apple Pay API。这些类包括:

  • PKContact: 这用于表示联系信息(姓名、电话号码和电子邮件)和地址

  • CNPhoneNumber: 这用于表示电话号码

  • CNPostalAddress: 这用于表示地址

PKContact 类

PKContact类包含表示账单或配送地址组成部分的属性。支付表单在用户选择配送地址时(paymentAuthorizationViewController: didSelectShippingContact:completion:)向其代理提供此类的实例,并在用户授权支付请求时(paymentAuthorizationViewController:didAuthorizePayment:completion:)在PKPayment对象中提供。

联系地址组成部分

这些属性包含了PKPaymentRequest对象中requiredBillingAddressFieldsrequiredShippingAddressFields属性请求的信息。对应未请求地址字段的属性值为nil

emailAddress (NSString*)

这是联系人的电子邮件地址。

name (NSPersonNameComponents*)

这是联系人的名字。

phoneNumber (CNPhoneNumber*)

这是联系人的电话号码。

postalAddress (CNPostalAddress*)

这是联系人的邮政地址。

NSPersonNameComponents 类

NSPersonNameComponents类包含表示个人姓名组成部分的属性。

个人姓名组成部分

这些属性表示个人姓名的组成部分。

namePrefix (NSString*)

这是在人的名字前使用的称呼(或头衔),例如 "Dr.""Ms.""Mr."

givenName (NSString*)

这是人的给定名(或名)。

middleName (NSString*)

这是人的第二个名字。

familyName (NSString*)

这是人的姓氏(通常与兄弟姐妹共享)。

nameSuffix (NSString*)

这些是添加到个人全名中的字母,用于提供有关个人的额外信息,例如 "Jr.""Ph.D."

nickname (NSString)

这是用于人的熟悉或幽默的名字。

phoneticRepresentation (NSPersonNameComponents*)

如果指定了,这是本类中除phoneticRepresentation之外其他每个属性的音标表示。

CNPhoneNumber 类

CNPhoneNumber类代表PKContact对象的电话号码组件。

创建电话号码

当创建PKContact对象时,要设置其phoneNumber属性,请使用此方法创建存储在属性中的CNPhoneNumber值。

例如,此代码设置联系人的电话号码:

PKContact contact= [PKContact new];
contact.phoneNumber= 
  [CNPhoneNumber phoneNumberWithStringValue: @"678-555-1234"];

+phoneNumberWithStringValue:

这会创建一个带有提供文本的电话号码组件。

电话号码字符串

此属性包含表示联系人电话号码的字符串。

stringValue (NSString*)

这是表示电话号码的文本。

CNPostalAddress 类

CNPostalAddress 类表示 PKContact 对象的邮政地址组件。

邮政地址组件

这些属性表示地址的组成部分。

street (NSString*)

这是联系人的街道地址。

city (NSString*)

这是联系人所在的城市。

state (NSString*)

这是联系人所在的状态。

postalCode (NSString*)

这是联系人所在地的邮政(或邮政编码)代码。

country (NSString*)

这是联系人所在的国家。

ISOCountryCode (NSString*)

这是国家属性的 ISO 3166 国家代码。

摘要

本章描述了您必须使用以处理 Apple Pay 支付的大多数类。它还描述了支付表单用于将用户操作传达给其代理的协议,以便您可以适当地做出响应。最后,本章列出了几个您必须与之合作的附加类,例如访问地址组件,如人的姓名、电话号码和邮政地址。

这标志着 Apple Pay Essentials 的结束,旨在帮助你向应用程序添加 Apple Pay 支持。本书介绍了在线支付的世界,描述了支付协会、支付网关和银行如何共同工作,将客户的支付卡资金转移到商家的账户。Apple Pay 通过增加便利性、隐私性和安全性来改进这一模型。

你学习了如何在客户端应用程序中使用 REST API 与提供所需信息的服务器网络应用程序(例如销售产品的详细信息和支持的运输方式)进行通信,以及处理来自应用程序的支付和订单。

本书提供了一个示例工作流程,用于向客户显示产品信息,并在适当的时候显示 Apple Pay 按钮。你学习了如何创建支付请求并将其添加到支付和订单信息中,以及展示主要的 Apple Pay 用户界面和支付表单。本书展示了如何响应用户在支付表单中做出的更改,例如选择不同的支付卡、运输地址或运输方式。

你学习了如何在用户授权支付请求后处理应用程序和后端服务器中的支付和订单信息。本书还展示了如何在交易完成后处理支付表单。

最后,本书描述了订单管理系统的关键元素和流程,使用 Node.js 处理订单和支付,以及 Mongoose 模块与基于文档的数据库进行交互。

通过这些知识,你现在能够为你的应用程序用户提供基于 Apple Pay 的支付便利性和安全性。

posted @ 2025-10-27 08:52  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报