Paypal 支付提供了两种风格的 API 接口(如下图),一种是 NVP/SOAP 风格的经典接口,另一种是比较新的、 RESTful 风格的接口。NVP 风格目前已经被视为遗留接口,官方推荐使用 RESTful 接口访问。但目前网络上流传的资料还是以 NVP 居多,介绍 REST 接口的比较罕见。

本文是对目前使用 Paypal 支付接口的一个总结,也供有类似需求的朋友参考。

文档首页

注册和访问

要使用 Paypal 接口,首先你应该有一个 Paypal 账户,并登录 开发者后台。需要说明的是:

  • 如果你在大陆访问速度很慢,请考虑 Cross over the wall;
  • 为了保证安全,Paypal 后台会话时间很短,如果你离开一会,再回去的话可能就会发现用户已经注销或者页面不正常了。这种情况下请重新点击右上角的 Login to dashboard再次登录。

接口文档

一般来说,我们需要了解的 Paypal 文档包括这几部分:

Docs Paypal 文档的总目录,包括多种接口和业务的访问链接; APIs 描述 API 接口的使用细节;

上述链接可以直接从 Dashboard 的顶部链接访问。

文档分类

从 API 部分我们可以看到,Paypal 接口支持相当广泛的内容,我们这里只介绍 Payment(支付) API 相关部分。所有接口的访问方法都是类似的,一旦你掌握了其中之一,其他的完全可以举一反三。

API 接口

API 文档中各个接口的具体使用方法值得仔细阅读。页面中间部分是接口参数和返回内容的说明,右边则列出了示例代码,如果你有 curl 工具并且已经得到访问接口的 Access Token(后面代码部分会说到如何获取 Access Token),你甚至可以把示例代码直接拷到终端窗口去测试。

Github 包含各种语言的 Paypal SDK 源码和下载资源。我们这里使用 C#,因此你可以在 Paypal .Net SDK 页面找到 SDK 的下载链接。如果你使用其他编程语言的话,可以从上述主页找到相应语言的访问地址。(Paypal 接口使用简单的 RESTful 风格,所以纯手工构造也不是特别麻烦。但为了简化工作和避免潜在的 bug,我还是推荐你在条件运行的情况下尽可能使用官方 SDK)。

Paypal on Github

Paypal .Net SDK

创建 REST App

为了调用 REST 接口,首先我们需要创建一个 REST App。

回到 Dashboard 主页,在左边导航栏确保 My Apps & Credentials 是选中的。然后在右边内容向下查找 REST API Apps,点击其中的 Create App。

REST App

创建 App 要填写的内容很少。你要填写一个唯一的 App 名称(建议使用纯英文字母),并关联一个用户帐号(如果想精细化管理,可以去 Accounts 页面创建用户帐号) 。

创建 REST App

当 REST App 创建完毕后,浏览器会跳转到其详情页面,其中有一些重要的内容需要关注。

  • 首先看到右上角有 Sandbox/Live 的字样。当一个 App 创建后,默认是在沙箱模式(Sandbox)下,这时所有的支付请求并不会真正从你的信用卡上扣钱,而是从一个模拟的余额中扣减(你可以在账户信息页面看到这个余额)。当然这是为了方便你测试程序的。当测试正常以后,你可以切换到 Live 模式,这时候可就是真金白银的交易了,所以测试一定要仔细啊。

  • Sandbox account 就是你的支付用户,我们后面支付阶段会用到。

  • 下面的配置信息包含 Client ID 和 Client Secret,其中 Secret 默认是隐藏的,点击才能查看。这两个值是非常重要的配置,调用接口的时候必须包含,所以请找个安全的地方记录下来,但千万不要放到公开的地方!特别是不要无意中把它们签入到源代码仓库。另外,Sandbox 和 Live 环境的 Cilent ID/Secret 是分开的,当你切换环境以后别忘了配置也要跟着修改。

REST App 属性

接下来还有一个 WebHook 的配置,什么是 WebHook?就是当有特定的事件发生(比如用户付款完成)时,Paypal 会向你的网站发送一条 HTTP 请求,告诉你这个事件发生了。当然,为了使用 WebHook,你的网站必须有公共网络地址(最好是域名),部署在局域网是不行的。

Web Hook

对于 WebHook 还有一个很有意思的页面(如下图)。当你的网站部署好以后,你可以在这里要求 Paypal 模拟发送一条信息,以验证你的 WebHook 是不是正常工作了。当然,这里生成的信息是随机生成的(比如支付单号在你的系统里可能根本没有),所以你处理 Web Hook 的时候要做好异常处理,不要让网站因为随机的数据而跨掉。

Web Hook 模拟器

Paypal 支付流程

好了,上述配置工具准备就绪,我们可以准备编写代码了。不过别忙,首先我们还是搞清楚 Paypal 的支付流程是什么样的:

  • 当用户开始支付时,你需要向 Paypal 发送一个请求(Paypal Create),告诉 Paypal 支付要开始了。这个请求中比较重要的内容包括支付商品(要支付的目标)名称、支付金额、货币代码(USD/RMB)等。另外,还有两个地址要指定:return url(支付成功后跳转返回的地址)和 cancel url(用户取消支付的情况下返回的地址)。你也可以把两个地址设置为相同的,但是后续处理就要做好区分。
  • Paypal 接受到你的请求后,如果请求数据没有问题,会返回给你一个支付地址的页面,你需要引导用户的浏览器转向这个页面;
  • 支付页面如下图所示。用户要完成支付需要首先登录 Paypal。注意在沙箱环境下,只能用你在 REST App 中关联的用户来完成支付;在 Live 环境下所有 Paypal 用户都可以支付。
  • 用户在这里可以选择 Continue(继续支付)或 Cancel(取消支付)。如果用户选择取消,则页面会跳转回你在上一步指定的 Cancel url;如果选 Continue,则返回 return url 页面。
  • 如果页面来到你指定的 return url,说明用户已经同意支付了(但此时支付还没有真正发生)。这个页面会带有 paymentIdPayerID 两个参数(注意大小写!)你需要用这两个参数向 Paypal 发送一个 Execute 请求,如果请求成功,就代表支付动作完成了。另外,你配置的 WebHook 也会接受到一条事件(一般不是马上收到,而是要晚几秒钟)。

支付界面

示例代码

明白了这个过程,我们接下来看实际的代码。为了方便阅读且避免泄漏业务内容,我删掉了一些错误处理和业务相关的代码,你在开发的时候当然应该仔细处理错误。

要调用 Paypal 接口首先要实现用户认证和授权。Paypal REST 接口使用 OAuth 协议,如果你使用 SDK 的话,这部分已经为你封装好了,你只需要传入 Client ID/Client Secret:

var clientId = "...";
var clientSecret = "...";
var credential = new OAuthTokenCredential(clientId, clientSecret);
var accessToken = credential.GetAccessToken();
apiContext = new APIContext(accessToken);
return apiContext;

接下来是调用 Paypal Create 的部分。这部分构造数据的结构很复杂,但实际上内容并不多,我就不详细写出了,你参考 API 文档的示例代码 即可。需要注意的是应答数据的 approval_url,你需要根据这个地址向浏览器返回一个跳转(302)。

然后是 Paypal Execute 部分,这一步很简单:

var payerId = Request.QueryString["PayerID"];
var paymentId = Request.QueryString["paymentId"];
var paymentExecution = new PaymentExecution()
{
payer_id = payerId
};
var paymentIn = Payment.Get(apiContext, paymentId);
var paymentOut = paymentIn.Execute(apiContext, paymentExecution);

最后,作为补充,我也列出 WebHook 的一般性处理代码:

Request.InputStream.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(Request.InputStream, Encoding.UTF8))
{
string json = await reader.ReadToEndAsync();
dynamic jsonBody = JObject.Parse(json);
string webhookId = jsonBody.id;
var whe = WebhookEvent.Get(apiContext, webhookId);
switch (whe.event_type)
{
case "PAYMENT.CAPTURE.COMPLETED":
...
case "PAYMENT.CAPTURE.DENIED":
...
}
}

其中 WebHook 事件类型的分类及具体说明可以参考 API 文档。

参考资源