PCI 标准是为了最大限度保护持卡人数据的一套标准。要求不少,能够看 PCI标准 站点了解。对于程序猿来讲,要保证的是用户的任何支付信息,都不走本身的服务器,不保存在本身的数据库。javascript
实现符合PCI标准的支付,有两种方式php
加载Authorize.net的托管表单html
使用AcceptJsjava
Authorize.net的托管表单,加载方便,安全性高,可是用户定制程度不高,只能稍微改改表单样式,AcceptJs
能够使用本身设计的表单,调用AcceptJs
作安全性校验和数据发送接收。git
沙盒环境帐号,能够用来在api文档页面直接调试各类接口,也能够在沙盒里面查看各类扣款记录。github
若是项目要上线,请注册生产环境帐号,这里所有使用沙盒环境。web
下载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)
由于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,调试一直报错,但又没有特别的缘由,请注意看是不是顺序问题。
内容 | 测试环境 | 生产环境 |
---|---|---|
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 |
须要请求的API : createCustomerProfileRequest
API的详细文档地址:createCustomerProfileRequestCustomerProfile
详细介绍: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);
须要请求的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);
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.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)
iframe
托管表单发起支付CustomerProfileId
,获取用户填写的PaymentInfo
,用来回填支付表单须要请求的API : getCustomerProfileRequest
API的详细文档地址:getCustomerProfileRequest
$customer = $this->getCustomerProfile($profileId); $billTo = end($customer->getProfile()->getPaymentProfiles())->getBillTo();
由于一个CustomerProfi
对应多个PaymentProfile
,这里获取最后一个PaymentProfile
。
须要请求的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(); }
<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.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 }
(支付完成后的处理我没作,无非就是弹个窗之类的告诉用户支付成功,再处理后台逻辑之类的)
能够看到,这里只能够回填帐单地址、客户电话和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);
托管表单要求你的程序挂载在HTTPS
域名下
还能够经过CustomerProfileId、paymentProfileId发起ARB(Auto Recurring Billing)扣款
须要请求的API : ARBCreateSubscriptionRequest
API的详细文档地址:getHostedPaymentPageRequest
关于APB的详细介绍请看:recurring_billing
关于测试请看:testing_guide
能够填写不一样的 Zip Code 和 Card Code 来模拟不一样的错误返回
(缺)
(缺)
(缺)
缺失的内容请自行参考官方demo。。。。。