Using SDK of Authorize.net to realize payment process conforming to PCI standard

Posted by jynmeyer on Wed, 05 Jun 2019 23:19:03 +0200

The PCI standard is a set of standards designed to maximize the protection of cardholder data. There are many requirements, you can see. PCI standard Site understanding. There are many requirements. For program apes, it is necessary to ensure that any payment information of users does not go to their own servers and do not save in their own database.

There are two ways to achieve PCI-compliant payment

  • Load the hosted form of Authorize.net

  • Using AcceptJs

Authorize.net hosted form, easy to load, high security, but the user customization level is not high, can only slightly change the form style, AcceptJs can use their own design form, call AcceptJs for security verification and data transmission and reception.

I. Preparatory Work

1.1 Register a Sandbox Environment Account (Must)

Sandbox environment Account, which can be used in api document page Direct debugging of various interfaces, you can also see various debit records in the sandbox.

If the project is going to be online, please register. production environment Account number, all use sandbox environment here.

1.2 Download Authorize.net SDK (not required)

download SDK To the project.

cd /your_php_project_path
composer require authorizenet/authorizenet

Then introduce it into the project (how to introduce it can see the introduction of the address above, which is not repeated here).

GITHUB address of the project: AuthorizeNet/sdk-php You can search and submit your questions on it.

The php column using SDK: AuthorizeNet/sample-code-php

Authorizenet's official implementation of a PCI-compliant case AuthorizeNet/accept-sample-app (This does not use SDK)

1.3 Do not use Authorize.net SDK (not required)

Because the Authorize.net SDK requires php: >= 5.5, it can only encapsulate api requests by itself. How to encapsulate personal convenience is a point to be explained. If the api of Authorize.net is in json format:

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);

The data returned is also in JSON format, but... This returned JSON data is unavailable

json_decode($result,true)

To analyse, need

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

To analyze. The reason for this is that the data it returns has a BOM header. Please move on. json-decode-returns-null

I haven't written code tests for XML format. You are interested in self-testing or direct testing in sandbox environments.

1.4 Environmental Addresses

content testing environment production environment
api request address apitest url api url
Accept.js Accept jstest url Accept js url
Request for payment form 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

Ii. iframe loads the hosted form to initiate payment

1. Load the iframe managed form to create payment Info for the user.

1.1 Create Customer ProfileID for user application

Required API: createCustomer ProfileRequest
Detailed Document Address of API: createCustomerProfileRequest
Customer Profile details: customer_profiles

This API can create both Customer ProfileId and Payment ProfileId. However, PaymentProfileId requires parameters that involve user-sensitive information. According to PCI standards, merchants are not allowed to collect, so it needs to use Authorize.net hosted form to create.
So this step simply passes a few parameters, using SDK to create code:

tomerProfile = 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 Apply for token to add PaymentInfo Managed Form

Required API: getHostedProfilePageRequest
Detailed Document Address of API: getHostedProfilePageRequest

Get token with Customer ProfileId $profile Id = $response - > getCustomer ProfileId (); created in the previous step

$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 View page iframe uses token to load managed forms

<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="Adding Payment Information"/>
</form>
<iframe id="add_payment" class="embed-responsive-item panel" name="add_payment" width="100%" height="650px" frameborder="0" scrolling="no">
</iframe>

At this time, there is nothing in the iframe. You need to submit this form to load the hosted form. Here is a function to automatically submit the hosted form when the page is loaded.

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

1.4 Capturing Response and Processing

Let's go back to the 1.2 application form. This API supports setting many attributes of the managed form. It's useful to have:

Hosted Profile Return Url: Set the end of the managed session (user clicks SAVE) to return to the user's page (omitted here)
Hosted Profile IFrame Communicator Url: Page used to receive and process Authorize.net responses

The hosted Profile IFrame Communicator Url page set above is 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));
}

This setup successfully saves paymentInfo information to Authorize.net and then jumps to the paymentPayment page.
Action has different states and can be processed according to action.
resizeWindow: Managed form loading
Successful Save: Customer Profile
cancel: The user clicks the cancel button
transactResponse: payment Success

2. Loading iframe managed form to initiate payment

1.1 Through Customer ProfileId above, get the Payment Info filled out by the user to fill in the payment form.

Required API: getCustomer ProfileRequest
Detailed Document Address of API: getCustomerProfileRequest

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

Because a Customer Profi corresponds to multiple Payment Profiles, the last Payment Profile is obtained here.

1.2 Apply for token to add Payment Managed Form

Required API: getHostedPaymentPageRequest
Detailed Document Address of API: getHostedPaymentPageRequest
Requesting this URL, you can specify various parameters such as the style of loading the form, for specific reference: 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);//Fill in the billing address
$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);

//Set the managed form to display email and fill it in (because the form form does not prohibit modifying email parameters, you can set email but not display it in the form in case of modification)
$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 View page iframe uses token to load managed forms

<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">I want to pay</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 Capture response and process.

Consistent with II.1.14, you can set it to the same page and use referrer to determine whether the response to the payment information form is perfect or the response to the payment form.
Such as:

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. Final effect

(I didn't do the processing after the payment was completed. I just played a window and told the user that the payment was successful, and then processed the background logic and so on.)

As you can see, you can only fill in billing addresses, customer calls and email s. Credit cards, expiration time of credit cards, credit card security codes and so on can not be filled back, requiring users to re-enter, user experience is very bad.
So we can do this step without hosting the form, using the API that initiates payment through Customer ProfileID.

API Required: Create Transaction Request
Detailed Document Address of 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. Ending Supplement

Hosting forms require your program to be mounted under the HTTPS domain name

ARB(Auto Recurring Billing) deductions can also be initiated through Customer ProfileId and paymentProfileId
Required API: ARBCreateSubscription Request
Detailed Document Address of API: getHostedPaymentPageRequest
For a detailed description of APB, see: recurring_billing

For testing, see: testing_guide
You can fill in different Zip Code s and CardCodes to simulate different error returns

III. AccceptJs Initiation of Payment

(missing)

1. Loading AccpectJS

(missing)

2. Barabara

(missing)

Please refer to the official demo for the missing content.

Topics: PHP curl SDK JSON