Ethan's Blog

记录和思考

PHP 开发详解:PayPal Payment Data Transfer (PDT)

最近手头的一个项目需要整合 PayPal 的支付功能,其实就是添加一个立即付款的充值功能,对于使用 PayPal 来接受付款非常简单,但是如果付款后需要对结果进行判断与后续操作,例如将部分有用数据写入数据库或者如果是数字商品的话,根据付款结果立即提供给用户下载链接,这样的使用场景需要在支付成功后将交易数据传输回来,再进一步分析处理和进行后续操作。PayPal 提供了一个名为 “Payment Data Transfer(PDT)” 的东西来实现此功能。

PayPal 的开发文档指出:

Payment Data Transfer (PDT) is a secure method to retrieve the details about a PayPal transaction so that you can display them to your customer. It is used in combination with PayPal Payments Standard, so that after a customer returns to your website after paying on the PayPal site, they can instantly view a confirmation message with the details of the transaction.

我想根据网上部分教程以及官方开发文档将这个功能实现,结果发现网上大部分的代码都比较老,然后可用性已经不够,而且很多的地方都说得非常模糊。于是我想根据 PayPal 的开发文档来写,然后在 PayPal 的开发者网站上找了很多,简直是越看越晕,完全无法想象,PayPal 作为一个使用范围最广的支付工具,它的开发文档不管是版本组织还是逻辑结构简直就像翔一般的。。。

不管如何,即使资料不多,官方文档乱得令人发指,但是项目还是要做,经过这几天的探索,参考了不少资料,在众多版本的 PayPal 开发文档中大海捞针,终于还是基本搞通了 PayPal Payment Data Transfer 的工作流程,下面我根据自己的理解和摸索过程,尽可能详细清楚地记录整个过程。如果你有任何关于此文的建议,或者能使得文章更清晰明了的看法,欢迎留言交流。

一、Payment Data Transfer 的工作流程和原理

如果商家在 PayPal 立即付款的按钮中设置了 ReturnURL,当顾客付款后:

  • PayPal 会以 GET 的方式在 ReturnURL 中附加传递此次支付的相关交易标记和参数。
  • 商家的 ReturnURL 的页面包括一个 HTML 的 post 表单,此外页面还会将 ReturnURL 中的参数解析出来,其中最重要的是获取 transaction ID。
  • 然后 ReturnURL 的页面会将包含 transaction ID 和商家的唯一 Payments Data Transfer token 以 post 形式发送给 PayPal。
  • PayPal 将根据接收到的信息进行验证,如果通过验证,则返回 SUCCESS,以及后面一行一行跟着 “键名 = 键值” 的交易信息,验证失败则返回 FAIL。
  • 最后商家的 ReturnURL 页面将这些 PayPal 返回来的信息进行解译和展示出来,或者根据不同的结果进行后续操作。

完整的工作流如下图所示:

PHP 开发详解:PayPal Payment Data Transfer (PDT)

二、申请 PayPal 开发者账号

鉴于 PayPal 真实账户付款的手续费比较高,在正式开发前,非常推荐你申请一个 PayPal 开发者账号,这样在后面测试自己的程序将会方便很多,还有最重要的一点是,申请开发者账号之后,能够在开发者平台继续申请测试用的商家账号和普通账号,这样在后面的 sandbox 测试中能够无限次的将完整流程测试下来而不必支付任何手续费。

  1. 访问 https://developer.paypal.com/,使用已有的账号登录或者注册新的账户。
  2. 依次点击【applications】【sandbox accounts】【create accounts】,根据需求建立模拟的商家账号和普通用户账号。

PHP 开发详解:PayPal Payment Data Transfer (PDT)

三、为商家账号启用 PDT 功能

为了使用 PayPal 的 Payment Data Transfer,必须启用商家账户的实时数据传输,并获取商家账号唯一的 PDT identity token。

根据官方文档,启用 PDT 的步骤为:

  1. 访问 https://www.sandbox.paypal.com/,使用之前开发者平台创建的商家账户登录,如果默认的界面为英文的话,可以在右侧将语言先切换为中文。
  2. 鼠标滑动到【用户信息】处点击【更多选项】,然后找到【网站付款习惯设定】,进入设置页面。
  3. 将【网站付款的自动返回】选项设置为开启,并填入返回 URL,此处要注意,返回 URL 必须为公网能够直接访问的网址,不能写入 localhost 的地址。
  4. 将【付款数据传输(可选)】选项设置为开启。

点击保存,然后页面会提示你的唯一身份标记( PDT identity token),记下来备用。

PHP 开发详解:PayPal Payment Data Transfer (PDT)

四、建立一个 “立即付款” 按钮

在 PayPal 上建立一个 “立即付款” 按钮非常简单,可以简单可视化地通过几步构建出来。Payment Data Transfer 的应用主要是商家工具的标准版。根据下面步骤来建立一个 “立即付款” 按钮:

  1. 以管理员账号登录 https://www.sandbox.paypal.com/,点击【用户信息】【更多选项】【我保存的按钮】,里面会有三个自动建立的示例按钮,我们选择第一个【立即购买示例】,在后面的【操作】下选择【编辑代码】。在这里我们之所以不自己去商户工具中建立是因为 sandbox 中的商户工具下建立按钮的链接有问题,每次点击建立都会自动退出账户。
  2. 在【第 1 步:选择按钮类型并输入付款明细】中键入必须的物品名称和价格。
  3. 在【第 3 步:自定义高级功能(可选)】中设置当客户完成结账时,将其转至的 URL。这里注意,此次设置的 URL 可以覆盖掉之前在开启 PDT 时设置的 URL。
  4. 点击【创建按钮】,然后将生成的 HTML 代码粘贴到网页相应位置即可。

整个按钮建立的过程还是非常简单的,我们将获取的代码放入网页相应位置就能看到 “立即付款” 的按钮了。用户点击此按钮之后即可进行付款操作。

五、获取相关交易标记和参数

用户付款后,PayPal 在转至设定 URL 的时候会在链接后附加特定的交易标记和参数,于是最终的 URL 像下面这样:

http://www.returnurl.com/user/paypal?tx=9R752804UK2014041&st=Pending&amt=1%2e00&cc=USD&cm=&item_number=

我们可以通过 GET 方式从 URL 中获取参数,URL 中的参数其中最重要的是 Transaction ID,tx:

if(isset($_GET['tx']))
{
  $tx = $_GET['tx'];
  // 其他操作
}

而其他的参数我们不需要也不重视,原因在于其他的参数都是不可信的。我们知道回调的参数都直接显示在 URL 中,那么用户可以轻易在浏览器的地址栏将数据更改,所以除开 Transaction ID,其他数据不可信也不需要解析。

那么,我们如何获取真实可信的交易信息呢?下面就介绍给大家,以 post 方式提交 Transaction ID 和商家唯一的身份标志去向 PayPal 请求交易信息。

六、向 PayPal 提交表单请求交易数据

为了获取真实可信的交易信息,我们需要使用 post 方式向 PayPal 提交 Transaction ID 和 PDT Identity Token,PHP 中提交数据的方式很多,这里使用 cURL 函数进行提交请求数据:

// Init cURL
$request = curl_init();

// Set request options
curl_setopt_array($request, array
(
  CURLOPT_URL => 'https://www.sandbox.paypal.com/cgi-bin/webscr',
  CURLOPT_POST => TRUE,
  CURLOPT_POSTFIELDS => http_build_query(array
    (
      'cmd' => '_notify-synch',
      'tx' => $tx,
      'at' => $your_pdt_identity_token,
    )),
  CURLOPT_RETURNTRANSFER => TRUE,
  CURLOPT_HEADER => FALSE,
  // CURLOPT_SSL_VERIFYPEER => TRUE,
  // CURLOPT_CAINFO => 'cacert.pem',
));

// Execute request and get response and status code
$response = curl_exec($request);
$status   = curl_getinfo($request, CURLINFO_HTTP_CODE);

// Close connection
curl_close($request);

利用上面的代码请求回来的数据包括两个,一个是反映请求响应的 HTTP 状态码 $status,另一个是返回的数据 $response。$response 是一个如下所示的字符组:

SUCCESS
first_name=Jane+Doe
last_name=Smith
payment_status=Completed
payer_email=janedoesmith%40hotmail.com
payment_gross=3.99
mc_currency=USD
custom=For+the+purchase+of+the+rare+book+Green+Eggs+%26+Ham

第一行表示请求信息的成功与否,后面每一行表示特定信息的键名和键值。

七、检查并格式化返回信息

首先为了验证网络请求是否成功以及确保信息成功取回,我们要检查返回的 HTTP 状态码 $status 是否为 200,此外,需要检测返回的字符组的第一行是否为 SUCCESS。具体的检测代码如下:

if($status == 200 AND strpos($response, 'SUCCESS') === 0)
{
    // Further processing
}
else
{
    // Log the error, ignore it, whatever
}

判断了取回信息成功后,我们需要的商品交易信息都已经在 $response 中了,但是此时的 $response 还不是格式友好可用的信息,我们需要进一步处理与优化信息的组织形式:

// Remove SUCCESS part (7 characters long)
$response = substr($response, 7);

// URL decode
$response = urldecode($response);

// Turn into associative array
preg_match_all('/^([^=\s]++)=(.*+)/m', $response, $m, PREG_PATTERN_ORDER);
$response = array_combine($m[1], $m[2]);

// Fix character encoding if different from UTF-8 (in my case)
if(isset($response['charset']) AND strtoupper($response['charset']) !== 'UTF-8')
{
  foreach($response as $key => &$value)
  {
    $value = mb_convert_encoding($value, 'UTF-8', $response['charset']);
  }
  $response['charset_original'] = $response['charset'];
  $response['charset'] = 'UTF-8';
}

// Sort on keys for readability (handy when debugging)
ksort($response);

至此,$response 已经以关联数组的信息组织好,这样我们获得了真实可信的交易信息,后续处理就能根据需求灵活进行了。

八、在正式生产环境中使用 PayPal Payment Data Transfer

前文讲述了如何在 sandbox 中使用 PayPal Payment Data Transfer,当所有的测试和部署在 sandbox 中都通过后,我们就可以在正式的生产环境中使用 PayPal Payment Data Transfer 了。在正式的生产环境中使用 PayPal Payment Data Transfer 和我们前文所述方法步骤完全一致,只是 PayPal 账户以及请求网址需要替换为 PayPal 的真实账户和正式交易的请求网址 https://www.paypal.com。

九、示例代码

前文已经详细介绍了 PayPal Payment Data Transfer 的 PHP 开发步骤,中间夹带了部分重要代码片段,由于前面的代码比较分散,为方便大家参考,我写了一份示例代码,你可以 访问这里 来获取代码。

至此,PayPal Payment Data Transfer 的 PHP 开发详解已经介绍完毕,希望大家看过有所收获。如果你在阅读过程中发现错误或者对文章有任何观点欢迎留言讨论。最后,此篇文章的形成离不开我在探索过程中所参阅的各种网络资料,对它们的作者表示感谢!

相关文章: