Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement Authorize.NET Hosted Payments iFrame & Laravel

I found the official documentation and the github example provided by Authorize.NET to be a terribly confusing mess of stuff you don't need. This post is a summary of the last few hours work hoping it may help others.

This guide assumes you don't want the receipt page and you want to automatically move the user forward on successful payment.

like image 364
Joel Avatar asked Apr 29 '17 21:04

Joel


People also ask

Can the hosted form provide transaction details to the iframe?

Therefore, the hosted form cannot directly provide transaction details to the page that hosts the iframe. However, by hosting on your domain a small JavaScript-powered page called an iframe communicator, you can pass some details between the payment form and the merchant site.

How do I accept hosted payment forms?

The Accept Hosted process starts with a getHostedPaymentPageRequest API call. The response contains a form token that you can use in a subsequent request to display the hosted payment form. Using the form token ensures that the payment form request comes from you and that the transaction details remain unchanged by the customer or a third party.

How do I integrate the payment form with my website?

Integrate the form using an embedded frame, in which case the payment form appears within your website. See the sample application on GitHub to explore different ways to display the hosted form.

How do I integrate a hosted form with my website?

Integrate the form using iframes, in which case the form pops up in a lightbox overlaid on your website. Integrate the form using an embedded frame, in which case the payment form appears within your website. See the sample application on GitHub to explore different ways to display the hosted form.


1 Answers

The site back-end is Laravel (PHP) but there's little in here that's Laravel specific.

First thing to do is add the Authorize.NET SDK:

composer require authorizenet/authorizenet

I then setup a hosted payments repository that accepts the order but you could do this however you like, this returns the hosted form token to a controller ready to pass to the view.

<?php

namespace ShopApp\Repositories;
use ShopApp\Models\Order;
use ShopApp\Repositories\Contracts\hostedPaymentRepositoryContract;
use Illuminate\Support\Facades\Config;
use net\authorize\api\contract\v1\MerchantAuthenticationType;
use net\authorize\api\contract\v1\TransactionRequestType;
use net\authorize\api\controller\GetHostedPaymentPageController;
use net\authorize\api\contract\v1\GetHostedPaymentPageRequest;
use net\authorize\api\contract\v1\SettingType;
use net\authorize\api\constants\ANetEnvironment;
use net\authorize\api\contract\v1\CustomerAddressType;
use ShopApp\Models\Address;

/**
 * Class hostedPaymentRepository
 * @package ShopApp\Repositories
 * @todo - Implement methods to talk to Authorize.NET and show form.
 */

class hostedPaymentRepository implements hostedPaymentRepositoryContract
{

    public $response; //what did we get back?
    public $paymentFormToken;

    public function getHostedFormToken(Order $order){

        $payment_amount = null;

        foreach($order->items as $order_item){

            $payment_amount += $order_item->price;

        }

        $billing_address = Address::findOrFail($order->billing_address_id);

        // Common setup for API credentials
        $merchantAuthentication = new MerchantAuthenticationType();
        $merchantAuthentication->setName(Config::get('yoursite.payment_providers.authorize_dot_net.MERCHANT_LOGIN_ID'));
        $merchantAuthentication->setTransactionKey(Config::get('yoursite.payment_providers.authorize_dot_net.MERCHANT_TRANSACTION_KEY'));

        //create a transaction
        $transactionRequestType = new TransactionRequestType();
        $transactionRequestType->setTransactionType("authCaptureTransaction");
        $transactionRequestType->setAmount($payment_amount);

        // Create the Bill To info
        $billto = new CustomerAddressType();
        $billto->setFirstName($order->billing_first_name);
        $billto->setLastName($order->billing_last_name);
        $billto->setAddress($billing_address->address_1);
        $billto->setCity($billing_address->city);
        $billto->setState($billing_address->state);
        $billto->setZip($billing_address->zip);
        $billto->setCountry($billing_address->country);

        if(!is_null($order->phone)){

            $billto->setPhoneNumber($order->phone);

        }

        //@todo - implement user stuff and get email
        //$billto->setEmail("[email protected]");

        $transactionRequestType->setBillTo($billto);

        // Set Hosted Form options
        $setting1 = new SettingType();
        $setting1->setSettingName("hostedPaymentButtonOptions");
        $setting1->setSettingValue("{\"text\": \"Pay Now\"}");

        $setting2 = new SettingType();
        $setting2->setSettingName("hostedPaymentOrderOptions");
        $setting2->setSettingValue("{\"show\": false}");

        $setting3 = new SettingType();
        $setting3->setSettingName("hostedPaymentReturnOptions");
        $setting3->setSettingValue("{\"showReceipt\" : false }");

        $setting4 = new SettingType();
        $setting4->setSettingName("hostedPaymentIFrameCommunicatorUrl");
        $setting4->setSettingValue("{\"url\": \"https://yoursite.local/checkout/payment/response\"}");


        // Build transaction request
        $request = new GetHostedPaymentPageRequest();
        $request->setMerchantAuthentication($merchantAuthentication);
        $request->setTransactionRequest($transactionRequestType);

        $request->addToHostedPaymentSettings($setting1);
        $request->addToHostedPaymentSettings($setting2);
        $request->addToHostedPaymentSettings($setting3);
        $request->addToHostedPaymentSettings($setting4);

        //execute request
        $controller = new GetHostedPaymentPageController($request);
        $response = $controller->executeWithApiResponse(ANetEnvironment::SANDBOX);

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

        return $response->getToken();
    }

}

Note that I have set showReceipt to false and I have offered another setting called hostedPaymentIFrameCommunicatorUrl. Do not forget the hostedPaymentIFrameCommunicatorUrl otherwise you will get the reciept page regardless of setting showReceipt to false.

The hostedPaymentIFrameCommunicatorUrl MUST be on the same domain as your payment page and on the same port.

Then in your view you need to add the iFrame (mine just sits in the page, i didn't bother with the lightbox:

<div id="iframe_holder" class="center-block" style="width:90%;max-width: 1000px" data-mediator="payment-form-loader">
    <iframe id="load_payment" class="embed-responsive-item" name="load_payment" width="750" height="900" frameborder="0" scrolling="no">
    </iframe>

    <form id="send_hptoken" action="https://test.authorize.net/payment/payment" method="post" target="load_payment">
        <input type="hidden" name="token" value="{{ $hosted_payment_form_token }}" />
    </form>

</div>

In the same view you need to load at least the following javascript (im using jQuery and have only half implemented the transactResponse method, but you get the idea):

$(document).ready(function(){

    window.CommunicationHandler = {};

    function parseQueryString(str) {
        var vars = [];
        var arr = str.split('&');
        var pair;
        for (var i = 0; i < arr.length; i++) {
            pair = arr[i].split('=');
            vars[pair[0]] = unescape(pair[1]);
        }
        return vars;
    }

    window.CommunicationHandler.onReceiveCommunication = function (argument) {

        console.log('communication handler enter');

        var params = parseQueryString(argument.qstr)

        switch(params['action']){
            case "resizeWindow"     :

                console.log('resize'); break;

            case "successfulSave"   :

                console.log('save'); break;

            case "cancel"           :

                console.log('cancel'); break;

            case "transactResponse" :

                sessionStorage.removeItem("HPTokenTime");

                console.log('transaction complete');

                var transResponse = JSON.parse(params['response']);

                window.location.href = '/checkout/complete';

        }
    }

    //send the token
    $('#send_hptoken').submit();


});

The above code adds a function to handle the message that comes back from the iFrame Communicator (next step) and also submits the payment form token to get and load the actual payment form.

Next we need to setup an 'iframe communicator' This is basically just a way for Authorize.NET to get around the same-domain origin policy

To do this, create a new route and a view that just returns a simple HTML page (or a blade template, but it should have no content other than the scripts).

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>IFrame Communicator</title>
    <script type="text/javascript">

    function callParentFunction(str) {

        if (str && str.length > 0 && window.parent.parent
            && window.parent.parent.CommunicationHandler && window.parent.parent.CommunicationHandler.onReceiveCommunication) {

            var referrer = document.referrer;
            window.parent.parent.CommunicationHandler.onReceiveCommunication({qstr : str , parent : referrer});

        }

    }

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

    }

</script>
</head>
<body></body>
</html>

The key part here is the window.parent.parent

Authorize.NET grabs your hostedPaymentIFrameCommunicatorUrl that you gave it in the beginning and embeds this within another iFrame, inside their own payment iFrame. Which is why it's window.parent.parent.

Your hostedPaymentIFrameCommunicatorUrl script then can pass the payment response to your payment page and you can edit the functions above to do what you like after that.

Hope that helps someone.

Authorize.NET is seriously lacking examples and their docs are lightweight at best. The 'catch-all' example that has you sifting through loads of code you don't need is just plain lazy.

Their API doc isn't bad though, they just need decent examples...

like image 132
Joel Avatar answered Nov 01 '22 12:11

Joel