使用Authorize.net的SDK实现符合PCI标准的支付流程

PCI 标准是为了最大限度保护持卡人数据的一套标准。要求不少,能够看 PCI标准 站点了解。对于程序猿来讲,要保证的是用户的任何支付信息,都不走本身的服务器,不保存在本身的数据库。javascript

实现符合PCI标准的支付,有两种方式php

  • 加载Authorize.net的托管表单html

  • 使用AcceptJsjava

Authorize.net的托管表单,加载方便,安全性高,可是用户定制程度不高,只能稍微改改表单样式,AcceptJs能够使用本身设计的表单,调用AcceptJs作安全性校验和数据发送接收。git

一. 前期准备工做

1.1 注册一个沙盒环境帐号 (必须)

沙盒环境帐号,能够用来在api文档页面直接调试各类接口,也能够在沙盒里面查看各类扣款记录。github

若是项目要上线,请注册生产环境帐号,这里所有使用沙盒环境。web

1.2 下载Authorize.net SDK (非必须)

下载SDK到项目。数据库

cd /your_php_project_path
composer require authorizenet/authorizenet

再在项目中引入便可(如何引入能够看上面地址的介绍,这里再也不重复)。json

该项目的GITHUB地址:AuthorizeNet/sdk-php 能够在上面搜索、提出你的issuesapi

使用SDK的php案列:AuthorizeNet/sample-code-php

Authorizenet官方实现的一个符合PCI标准的案列AuthorizeNet/accept-sample-app (这个没有使用SDK)

1.3 不使用Authorize.net SDK (非必须)

由于Authorize.net SDK 要求 php: >=5.5 , 因此只能本身封装api请求了,具体如何封装我的自便,但要说明的一点是,Authorize.net 的api,若是选择的是json格式:

header("Content-type:text/json;charset=utf-8");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->authorizeUrl);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_COOKIESESSION, true);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, urldecode($data));
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
// curl_setopt($curl, CURLOPT_HTTPHEADER,     array('Content-Type: text/plain')); //xml request
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/json'));
$result    = curl_exec($curl);
$curlErrno = curl_errno($curl);
$curlError = curl_error($curl);
curl_close($curl);

返回的数据也是JSON格式,but。。。。,这个返回的json数据,是没法用

json_decode($result,true)

来解析的,须要

json_decode(substr($result, 3), true);

来解析。究其缘由,应该是它返回的数据带了BOM头,详细请移步 json-decode-returns-null

XML格式我没有去写代码测试,各位有兴趣能够自行测试,也能够在沙盒环境直接测试。

有个直接扣款的API,其中的ORDER参数要有顺序,要有顺序,要有顺序,若是遇到一些API,调试一直报错,但又没有特别的缘由,请注意看是不是顺序问题。

1.4 各类环境地址

内容 测试环境 生产环境
api请求地址 apitest url api url
Accept.js Accept jstest url Accept js url
请求支付表单 test payment/payment accept payment/payment
Manage Profiles Manage Profiles Manage Profiles
Add Payment Profile Add Payment Profile Add Payment Profile
Add Shipping Profile Add Shipping Profile Add Shipping Profile
Edit Payment Profile Edit Payment Profile Edit Payment Profile
Edit Shipping Profile Edit Shipping Profile Edit Shipping Profile

二. iframe 加载托管表单方式发起支付

1. 加载iframe托管表单建立用户的payment Info。

1.1 为用户申请建立CustomerProfileID

须要请求的API : createCustomerProfileRequest
API的详细文档地址:createCustomerProfileRequest
CustomerProfile详细介绍:customer_profiles

该API能够在建立CustomerProfileId 的同时,也建立PaymentProfileId 。可是PaymentProfileId须要的参数都是涉及到用户敏感信息的,按照PCI标准,是不容许商户收集,因此须要使用Authorize.net的托管表单来建立。
因此这一步只简单的传递几个参数便可,使用SDK建立代码:

$customerProfile = new AnetAPI\CustomerProfileType();
$customerProfile->setDescription("Customer 2 Test PHP");
$customerProfile->setMerchantCustomerId('11211');
$customerProfile->setEmail($post['email']);
$request = new AnetAPI\CreateCustomerProfileRequest();
$request->setMerchantAuthentication($this->merchantAuthentication);
$request->setProfile($customerProfile);
$controller = new AnetController\CreateCustomerProfileController($request);
$response = $controller->executeWithApiResponse(\net\authorize\api\constants\ANetEnvironment::SANDBOX);

1.2 为添加PaymentInfo托管表单申请token

须要请求的API : getHostedProfilePageRequest
API的详细文档地址:getHostedProfilePageRequest

用上一步建立的CustomerProfileId $profileId = $response->getCustomerProfileId(); 来获取token

$setting = new AnetAPI\SettingType();
$setting->setSettingName("hostedProfileIFrameCommunicatorUrl");
$url = \Yii::$app->urlManager->createAbsoluteUrl(['authorizenet/special']);
$setting->setSettingValue($url);
$request = new AnetAPI\GetHostedProfilePageRequest();
$request->setMerchantAuthentication($this->merchantAuthentication);
$request->setCustomerProfileId($profileId);
$request->addToHostedProfileSettings($setting);
$controller = new AnetController\GetHostedProfilePageController($request);
$response = $controller->executeWithApiResponse(
\net\authorize\api\constants\ANetEnvironment::SANDBOX);

1.3 视图页面iframe使用token加载托管表单

<form method="post" action="https://test.authorize.net/customer/addPayment" target="add_payment">
    <input type="hidden" name="token" value="<?php echo $token;?>"/>
    <input id='submit' type="submit" value="添加支付信息"/>
</form>
<iframe id="add_payment" class="embed-responsive-item panel" name="add_payment" width="100%" height="650px" frameborder="0" scrolling="no">
</iframe>

此时该iframe里面尚未任何东西,须要提交这个form表单才能加载托管表单,这里给一个函数让他页面加载的时候自动提交以加载托管表单。

var button = document.getElementById('submit');
button.click();

1.4 捕获响应并处理

咱们回到 1.2 申请表单这里,这个API支持设置托管表单的不少属性,比较有用的有 :

hostedProfileReturnUrl : 设置托管会话结束(用户点击SAVE)返回给用户的页面 (这里省略)
hostedProfileIFrameCommunicatorUrl : 用来接受、处理Authorize.net响应的页面

上面设置的hostedProfileIFrameCommunicatorUrl的页面为authorizenet/special

function callParentFunction(str) {
    var referrer = document.referrer;
    var s = {qstr : str , parent : referrer};
    if(referrer == 'https://test.authorize.net/customer/addPayment'){
        switch(str){
            case 'action=successfulSave' :
                window.parent.parent.location.href="https://www.basic.com/authorizenet/payment";
                break;
        }
    }
}

function receiveMessage(event) {
    if (event && event.data) {
        callParentFunction(event.data);
    }
}

if (window.addEventListener) {
    window.addEventListener("message", receiveMessage, false);
} else if (window.attachEvent) {
    window.attachEvent("onmessage", receiveMessage);
}

if (window.location.hash && window.location.hash.length > 1) {
    callParentFunction(window.location.hash.substring(1));
}

这里设置成功保存paymentInfo 信息到Authorize.net以后就跳转到 payment 页面支付。
action有不一样的状态,能够根据action做相应的处理。
resizeWindow : 托管表单加载
successfulSave : 表单成功保存(CustomerProfile)
cancel : 用户点击取消按钮
transactResponse :支付成功(payment)

2. 加载iframe托管表单发起支付

1.1 经过上面的CustomerProfileId,获取用户填写的PaymentInfo,用来回填支付表单

须要请求的API : getCustomerProfileRequest
API的详细文档地址:getCustomerProfileRequest

$customer = $this->getCustomerProfile($profileId);
$billTo = end($customer->getProfile()->getPaymentProfiles())->getBillTo();

由于一个CustomerProfi对应多个PaymentProfile ,这里获取最后一个PaymentProfile

1.2 为添加Payment托管表单申请token

须要请求的API : getHostedPaymentPageRequest
API的详细文档地址:getHostedPaymentPageRequest
请求该URL,能够指定加载表单的样式等各类参数,具体参考:Accept Hosted feature details page

$transactionRequestType = new AnetAPI\TransactionRequestType();
$transactionRequestType->setTransactionType("authCaptureTransaction");
$transactionRequestType->setAmount("12.23");
$customer = $this->getCustomerProfile(\Yii::$app->session->get('profileId'));
$billTo = end($customer->getProfile()->getPaymentProfiles())->getBillTo();

$transactionRequestType->setBillTo($billTo);//回填帐单地址
$customer = new AnetAPI\CustomerDataType();
$customer->setEmail(\Yii::$app->session->get('email'));
$customer->setId(\Yii::$app->session->get('user_id'));
$transactionRequestType->setCustomer($customer);

$request = new AnetAPI\GetHostedPaymentPageRequest();
$request->setMerchantAuthentication($this->merchantAuthentication);
$request->setTransactionRequest($transactionRequestType);
$setting3 = new AnetAPI\SettingType();
$setting3->setSettingName("hostedPaymentReturnOptions");
$setting3->setSettingValue("{\"url\": \"https://www.basic.com/index.php?r=authorizenet/receipt\", \"cancelUrl\": \"https://www.basic.com/index.php?r=authorizenet/cancel\", \"showReceipt\": false}");
$request->addToHostedPaymentSettings($setting3);

//设置托管表单显示email,且必填 (由于form表单没有禁止修改email参数,因此能够设置email但不显示在表单中,以防修改)
$setting4 = new AnetAPI\SettingType();
$setting4->setSettingName('hostedPaymentCustomerOptions');
$setting4->setSettingValue("{\"showEmail\": true, \"requiredEmail\":true}");
$request->addToHostedPaymentSettings($setting4);

$setting6 = new AnetAPI\SettingType();
$setting6->setSettingName('hostedPaymentIFrameCommunicatorUrl');
$url = \Yii::$app->urlManager->createAbsoluteUrl(['authorizenet/special']);
$setting6->setSettingValue("{\"url\": \"".$url."\"}");
$request->addToHostedPaymentSettings($setting6);
$controller = new AnetController\GetHostedPaymentPageController($request);
$response = $controller->executeWithApiResponse( \net\authorize\api\constants\ANetEnvironment::SANDBOX);

if (($response != null) && ($response->getMessages()->getResultCode() == "Ok") ) {
   return $response->getToken();
}

1.3 视图页面iframe使用token加载托管表单

<body onload="func()">
<form id="send_hptoken" action="https://test.authorize.net/payment/payment" method="post" target="load_payment" >
    <input type="hidden" name="token" value="<?php echo $token ?>" />
    <button type="submit" id="submit">我要支付</button>
</form>

<iframe id="load_payment" class="embed-responsive-item" name="load_payment" width="100%" height="650px" frameborder="0" scrolling="no">
</iframe>
</body>
<script type="application/javascript">
    function func(){
        var button = document.getElementById('submit');
        button.click();
    }
</script>

1.4 捕获响应并处理。

同 二.1.14 一致,能够设置为同一个页面,经过referrer来判断是完善支付信息表单的响应,仍是支付表单的响应
如:

if(referrer == 'https://test.authorize.net/customer/addPayment'){
    //your code
}else if(referrer == 'https://test.authorize.net/payment/payment'){
    //your code
}else if(other){
    //your code
}

3. 最终效果图

注册-完善支付-支付 流程

(支付完成后的处理我没作,无非就是弹个窗之类的告诉用户支付成功,再处理后台逻辑之类的)

能够看到,这里只能够回填帐单地址、客户电话和email之类的信息。信用卡、信用卡过时时间、信用卡安全码等都没法回填,须要用户再次输入,用户体验很是很差。
因此支付这一步咱们能够不用托管表单,使用经过CustomerProfileID发起支付的API来完成

须要请求的API : createTransactionRequest
API的详细文档地址:createTransactionRequest

$paymentprofileid = $this->getCustomerProfile($profileid);
$profileToCharge = new AnetAPI\CustomerProfilePaymentType();
$profileToCharge->setCustomerProfileId($profileid);
$paymentProfile = new AnetAPI\PaymentProfileType();
$paymentProfile->setPaymentProfileId($paymentprofileid);
$profileToCharge->setPaymentProfile($paymentProfile);

$transactionRequestType = new AnetAPI\TransactionRequestType();
$transactionRequestType->setTransactionType( "authCaptureTransaction");
$transactionRequestType->setAmount(5);
$transactionRequestType->setProfile($profileToCharge);

$request = new AnetAPI\CreateTransactionRequest();
$request->setMerchantAuthentication($this->merchantAuthentication);
$request->setTransactionRequest( $transactionRequestType);
$controller = new AnetController\CreateTransactionController($request);
$response = $controller->executeWithApiResponse( \net\authorize\api\constants\ANetEnvironment::SANDBOX);

4. 结尾补充

托管表单要求你的程序挂载在HTTPS域名下

还能够经过CustomerProfileId、paymentProfileId发起ARB(Auto Recurring Billing)扣款
须要请求的API : ARBCreateSubscriptionRequest
API的详细文档地址:getHostedPaymentPageRequest
关于APB的详细介绍请看:recurring_billing

关于测试请看:testing_guide
能够填写不一样的 Zip Code 和 Card Code 来模拟不一样的错误返回

三. AccceptJs方式发起支付

(缺)

1. 加载AccpectJS

(缺)

2. 巴拉巴拉

(缺)

缺失的内容请自行参考官方demo。。。。。

相关文章
相关标签/搜索