使用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。。。。。

相關文章
相關標籤/搜索