How to implement Authorize.NET Hosted Payments iFrame & Laravel - php

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.

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("changethis#later.com");
$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...

Related

Getting the XML from a response using the Authorize.Net PHP SDK

I have written some functions using Authorize.Net's PHP SDK's that look like the following:
public function getCustomerProfiles() {
$customerProfiles = array();
// Before we can get customer profiles, we need to get a list of all customer id's.
$customerIdListRequest = new AnetAPI\GetCustomerProfileIdsRequest();
$customerIdListRequest->setMerchantAuthentication(self::getMerchantAuth(Config::LOGIN_ID, Config::TRANSACTION_KEY));
$customerIdListController = new AnetController\GetCustomerProfileIdsController($customerIdListRequest);
$customerIdListResponse = $customerIdListController->executeWithApiResponse(\net\authorize\api\constants\ANetEnvironment::SANDBOX);
if(($customerIdListResponse != null) && ($customerIdListResponse->getMessages()->getResultCode() == "Ok")) {
// TODO: Investigate warning about no method named getIds().
foreach( $customerIdListResponse->getIds() as $id ) {
// Now we can get each customer profile.
$request = new AnetAPI\GetCustomerProfileRequest();
$request->setMerchantAuthentication(self::getMerchantAuth(Config::LOGIN_ID, Config::TRANSACTION_KEY));
$request->setCustomerProfileId($id);
$controller = new AnetController\GetCustomerProfileController($request);
$response = $controller->executeWithApiResponse(\net\authorize\api\constants\ANetEnvironment::SANDBOX);
if(($response != null) && ($response->getMessages()->getResultCode() == "Ok")) {
// TODO: Investigate warning about no method named getProfile()
// Add it to the array.
array_push($customerProfiles, $response->getProfile()->xml);
} else {
throw new \Exception($response->getMessages()->getMessage());
}
}
} else {
throw new \Exception($customerIdListResponse->getMessages()->getMessage());
}
return $customerProfiles;
}
Currently, I'm just returning an array of objects. I'd prefer to get the raw XML response. Is this functionality available via Authorize.Net's PHP SDK? Or am I better of using something like Guzzle and making the request manually?
Looking at the source code I think it would be simple enough.
Look the execute method that is invoked by executeWithApiResponse there. See xmlResponse? Just need to store that as a class property (and add a public getter), or maybe tweak the function to take an extra argument telling it to return the raw response. Could hack it, or better yet, extend that ApiOperationBase class (note the interface IApiOperation gives you a outline to follow).
Seeing that serializer also...
$this->apiResponse = $this->serializer->deserialize( $xmlResponse, $this->apiResponseType , 'xml');
Could maybe do something more elegant with that. But not as clear as path I first described.

How to make a wrapper to call functions in different classes?

I'm trying to solve a problem in ajax which is coming out from the moment that my client asked me to not use any framework for web applications.
I have always used CodeIgniter and I never had any problem with ajax requests, especially when I had to call a method simply perform this call:
var postUrl = GlobalVariables.baseUrl + 'application/controllers/user.php/ajax_check_login';
//http://localhost/App_Name/application/controllers/user.php/ajax_check_login <-postUrl content
var postData =
{
'username': $('#username').val(),
'password': $('#password').val()
};
$.post(postUrl, postData, function(response)
{
// do stuff...
});
How you can see from the code above what I want to do is call a method within the controller user.php whose name is ajax_check_login.
What I have done so far to achieve the desired result is to make this code:
$allowed_functions = array('ajax_check_login');
$ru = $_SERVER['REQUEST_URI']
$func = preg_replace('/.*\//', '', $ru);
if (isset($func) && in_array($func, $allowed_functions)) {
$user = new User();
$user->$func();
}
if you want to see the complete structure of a class click here.
The problem is that this code should be placed inside each controller,
and you have to set all the methods offered, sometimes the function available reaching fifty, leads to discard this solution...
What I want to know is: how can I make a wrapper, a class that allows me to invoke a method of the controller from url and execute it?
Before all this work was done by CodeIgniter. So now I have to write my own class that allows me to access the controls easily and recall methods in different classes.
All classes that have to respond to ajax request reside in application/controllers / ... folder. In the controllers folder I have 20 controllers.
You can add ajax.php:
<?php
preg_match_all('/([^\/.]*)\.php\/([^\/]*)$/', $_SERVER['REQUEST_URI'], $matches);
$class = $matches[1][0];
$func = $matches[2][0];
$allowed_classes = array('user','account','foo');
if (isset($class) && isset($func) && in_array($class, $allowed_classes)) {
require_once "application/controllers/" . $class. ".php";
// here you could do some security checks about the requested function
// if not, then all the public functions will be possible to call
// for example if you don't want to allow any function to be called
// you can add a static function to each class:
// static function getAllowedFunctions() {return array('func1','func2');}
// and use it the same way you had checked it in the question
$obj = new $class();
$obj->$func();
// or if need to pass $_POST:
// call_user_func(array($obj, $func, $_POST));
}
and in javascript post to:
var postUrl = GlobalVariables.baseUrl + 'application/controllers/ajax.php/user.php/ajax_check_login';
If you have apache, then you might be able to do it even without adding ajax.php by adding this to .htaccess in the controller directory:
RewriteEngine On
RewriteBase /baseUrl.../application/controllers/
RewriteRule ^([^\.]*\.php)/[^/]*$ ajax.php?file=$1&func=$2
Of course you need your real baseUrl there. And change the 1st 3 lines in the php to:
$class = $_GET['class'];
$func = $_GET['func'];

How to output in realtime when number is change?

I have this code for output :
$tot_clicks6 = $db->FetchArray($db->Query("SELECT SUM(visits) AS sum_visits FROM surf"));
and
<?=$tot_clicks6['sum_visits']?>
is display total of number.
Your question represents a common misconception with PHP. The block of code
<?=$tot_clicks6['sum_visits']?>
Is only that code in your server. As the page is loaded, it gets served as HTML as whatever the value of that variable is. For example,
6
In order to update your page in real time, you need to use AJAX.
See this question
Get variable from PHP file using JQuery/AJAX
Or you can use a real time framework. I work for Realtime.co and we do just that.
You can get a free license at www.realtime.co, get the PHP API at http://www.xrtml.org/downloads_62.html#pubsub:php and use the following code for the page that should broadcast the information (your administration page, for example). Note: this code is the same you can find at Github for the ORTC example (https://github.com/RTWWorld/pubsub-examples/tree/master/PHP) adapted for your needs.
<?php
error_reporting(E_ALL);
session_start();
require('./ortc.php');
/* -------------------- */
/* REPLACE THESE VALUES */
/* -------------------- */
$URL = 'http://ortc-developers.realtime.co/server/2.1';
$AK = 'YOUR_APPLICATION_KEY';// your realtime.co application key
$PK = 'YOUR_APPLICATION_PRIVATE_KEY';// your realtime.co private key
$TK = 'YOUR_AUTHENTICATION_TOKEN';// token: could be randomly generated in the session
$CH = 'MyChannel'; //channel
$ttl = 180;
$isAuthRequired = false;
$result = false;
/* -------------------- */
/* END */
/* -------------------- */
// ORTC auth
// on a live usage we would already have the auth token authorized and stored in a php session
// Since a developer appkey does not require authentication the following code is optional
if( ! array_key_exists('ortc_token', $_SESSION) ){
$_SESSION['ortc_token'] = $TK;
}
$rt = new Realtime( $URL, $AK, $PK, $TK );
// Your query
$tot_clicks6 = $db->FetchArray($db->Query("SELECT SUM(visits) AS sum_visits FROM surf"));
if($isAuthRequired){
$result = $rt->auth(
array(
$CH => 'w'
),
$ttl
);//post authentication permissions. w -> write; r -> read
echo 'authentication status '.( $result ? 'success' : 'failed' ).'<br/>';
}
if($result || !$isAuthRequired){
$result = $rt->send($CH, tot_clicks6['sum_visits'], $response);
echo ' send status '.( $result ? 'success' : 'failed' ).'<br/>';
}
?>
On the receiver page, you'll need to receive the data, using JavaScript and display it. For this example I'm just alerting the user with the data.
<!doctype html>
<html>
<head>
</head>
<body>
<script src="http://code.xrtml.org/xrtml-3.2.0.js"></script>
<script>
var appkey = 'YOUR_APPLICATION_KEY';
var url = 'http://ortc-developers.realtime.co/server/2.1';
var authToken = 'YOUR_AUTHENTICATION_TOKEN';
var channel = 'MyChannel';
xRTML.load(function(){
xRTML.Config.debug = true;
xRTML.ConnectionManager.create({
id: 'myConn',
appkey: appkey,
authToken: authToken,
url: url,
channels: [
{name: channel}
]
}).bind({
message: function(e) {
alert(e);
}
});
});
</script>
</body>
</html>
With this code you won't need to use AJAX or anything like that. You'll be able to push your data to browsers instead.
Hope it helps!

How to make Behat wait for an AJAX call?

Scenario: Modify and save an incomplete change to a Campaign
Given I click on the Campaign section folder
And I press Save in the selected Campaign
Then I should see an error balloon informing the changes cannot be saved
Point is that this 'error balloon' in the final step is a ajax call which will then bring a green or red balloon according to the success of the operation. Currently what I do is after
'And I press Save...' I will do a sleep(3) to give it time for this balloon to show up. This doesn't seem very smart coz you are wasting time and also because some times it can take more or less time for this call to be processed.
How do you guys make your behat tests wait for Ajax do be done instead of just putting the beasts to sleep?
thank you very much for any feedback!
This is done by waiting for your outstanding ajax calls to hit 0. jQuery.active will check just that for you.
In your FeatureContext.php, you can do something like;
public function iShouldSeeAnErrorBalloon($title)
{
$time = 5000; // time should be in milliseconds
$this->getSession()->wait($time, '(0 === jQuery.active)');
// asserts below
}
And do make sure you use a Mink Driver that runs javascript and ajax (the default does not).
I do it by waiting for the DOM to change as a result of the Ajax Call. I made a subclass of DocumentElement, calling it AsyncDocumentElement and overriding the findAll method:
public function findAll($selector, $locator, $waitms=5000)
{
$xpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($selector, $locator);
// add parent xpath before element selector
if (0 === strpos($xpath, '/')) {
$xpath = $this->getXpath().$xpath;
} else {
$xpath = $this->getXpath().'/'.$xpath;
}
$page = $this->getSession()->getPage();
// my code to wait until the xpath expression provides an element
if ($waitms && !($this->getSession()->getDriver() instanceof \Behat\Symfony2Extension\Driver\KernelDriver)) {
$templ = 'document.evaluate("%s", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null ).snapshotLength > 0;';
$waitJs = sprintf($templ, $xpath);
$this->getSession()->wait($waitms, $waitJs);
}
return $this->getSession()->getDriver()->find($xpath);
}
Then in \Behat\Mink\Session I changed the constructor to use that class.
public function __construct(DriverInterface $driver, SelectorsHandler $selectorsHandler = null)
{
$driver->setSession($this);
if (null === $selectorsHandler) {
$selectorsHandler = new SelectorsHandler();
}
$this->driver = $driver;
$this->page = new AsyncDocumentElement($this);
$this->selectorsHandler = $selectorsHandler;
}
Once I did this, I found my AngularJS tests were working. So far, I've only tested in Firefox.
In case you are using Prototypejs (e.g Magento), the equivalent code is:
public function iShouldSeeAnErrorBalloon($title)
{
$this->getSession()->wait($duration, '(0 === Ajax.activeRequestCount)');
// asserts below
}

AS2: load class variables with sendandload

I'm using Actionscript 2.0 in combination with PHP, now I can make a call to my PHP file and receive data but apparently I have to use that data immediately, I cannot use it to fill my class variables.
This is what I want :
class user {
var lastname:String;
function user(in_ID:Number){
var ontvang:LoadVars = new LoadVars();
var zend:LoadVars = new LoadVars();
zend.ID = in_ID;
zend.sendAndLoad("http://localhost/Services/getUser.php", ontvang, "POST");
ontvang.onLoad = function(success:Boolean) {
if (success) {
lastname = ontvang.lastname;
} else {
lastname = 'error';
}
};
}
}
I've found out that this is a big issue in AS2, I found this post to work around it if you're loading XML data but I can't seem to get it to work with LoadVars :
http://www.actionscript.org/forums/showthread.php3?t=144046
Any help would be appreciated ..
When your onLoad handler is called, it is being called as if it were a member function of the LoadVars instance, and not your user instance.
There are several ways around this, one is to use Delegate.create() to create a function which will work as intended, for example:
import mx.utils.Delegate;
class user {
var lastname:String;
var ontvang:LoadVars;
function user(in_ID:Number){
ontvang = new LoadVars();
var zend:LoadVars = new LoadVars();
zend.ID = in_ID;
ontvang.onLoad = Delegate.create(this, onLoad);
zend.sendAndLoad("http://localhost/Services/getUser.php", ontvang, "POST");
};
}
function onLoad(success:Boolean) : Void
{
if (success) {
lastname = ontvang.lastname;
} else {
lastname = 'error';
}
}
}
Don't forget that the load is asynchronous - when you create one of your user objects, the member variables won't be immediately available. What you may need to do is let your user object be capable of signaling its readiness much like LoadVars does, (e.g. with a callback function provided by the caller) so that your app is driven by by these asynchronous events.

Categories