Westpac Payment Gateway


// this is the default javascript for a screen layout that will appear in the screen layout javascript editor
//
// this file can be found at templates/scripts/dialer/defaultScreenLayout.js

/**
 * Implements an interface to westpac's Payway CC system
 * 
 * You can find a list of test cards here:
 * https://www.payway.com.au/docs/rest.html#test-card-numbers
 * 
 * We use the 'payway.js' which essentially injects an iframe with cc UI
 * into our screen layout.
 * 
 * We then call into an Action Script SendPaywayPayment to to do the actual
 * transaction.
 * 
 * The action script uses this api:
 * https://www.payway.com.au/docs/rest.html#payway-rest-api
 * 
 */




$.njDialerPlugin.getPage('${NjWizardPageName}').onPageNext = function(page,defaultNextPage) {
	
	return $.njDialerPlugin.getPage("Finished Appointment");

};

 

$.njDialerPlugin.getPage('${NjWizardPageName}').onPagePrev = function(page,defaultPrevPage) {
	// do something here
    debugger;
	// return page.getPage('Name of desired page');

	// to stay on the current page: return page;
	return defaultPrevPage;

};

 
$.njDialerPlugin.loadjscssfile = function(filename, filetype, callback)
{
    
    jQuery.getScript(filename, callback);
};



/*
 * this method will be invoked when the lead is displayed for previewing
 */
$.njDialerPlugin.onPreview = function() {
	// lead loaded in preview mode

   debugger;
   $.njDialerPlugin.loadCCFrame();
};


$.njDialerPlugin.loadCCFrame = function()
{
	// lead connected
	$.njDialerPlugin.loadjscssfile("https://api.payway.com.au/rest/v1/payway.js", "js", $.njDialerPlugin.createFrame) //dynamically load and add this .js file
}




$.njDialerPlugin.validAmount = function() {
	
	debugger;
	
	var valid = false;
	
    var amount = $.njDialerPlugin.getFieldValue("CreditCardAmount");
    
    if (amount != undefined && amount.length > 1)
    {
        // is this a currency value
        valid = /^\d{0,4}(\.\d{0,2})?$/.test(amount);
    }
    
    return valid;
    
};

// Callback once script gets loaded.
$.njDialerPlugin.createFrame = function(script, textStatus, jqXHR)
{
    debugger;
    // If the form isn't already loaded then load it.
    var frames = jQuery.find("#payway-credit-card-iframe0");
    if (frames.length == 0)
    {
        // Get our submit button
        var submit = document.getElementById('payway-cc-submit');
        
       submit.disabled = true;
        
        // Wire the click handler
        $("#payway-cc-submit").on('click', $.njDialerPlugin.submitPayment);
        
        var testKey = 'XXXX';
        var productionKey='YYYYY';
        
        var apiKey = productionKey;
       
        payway.createCreditCardFrame({
            publishableApiKey: apiKey,
            onValid: function() {  submit.disabled = !$.njDialerPlugin.validAmount(); },
            onInvalid: function() { submit.disabled = true; },
            tokenMode: "callback"
        }, $.njDialerPlugin.ccFrameReady);
        
        // wire the submit button

     }
}



$.njDialerPlugin.ccFrameReady = function(err, frame) {
	
	if (err != null)
	{
	    alert(err.message);
	}
	else
	{
	    $.njDialerPlugin.ccFrame = frame;
	}
};

$.njDialerPlugin.submitPayment = function(event) 
{
    // stop the form submission
    debugger;
    event.preventDefault();

    // Fire the actoin to get the token and the callback will submit the payment.
    $.njDialerPlugin.ccFrame.getToken($.njDialerPlugin.receiveTokenAndSubmitPayment);

};


 
/**
 * Callback to called by the above call to ccFrame.getToken
 * 
 * The data object contains the singlueUseTokenId and the 'amount'.'
 */
$.njDialerPlugin.receiveTokenAndSubmitPayment = function(err, data) {
	
	if (err != null)
	{
	    alert(err.message);
	}
	else
	{
	    debugger;
        //var amount = $("#ccAmount").value;

        // Pass in the single use token. The amount should be in the CreditCardAmount field already.
        $.njDialerPlugin.setFieldValueByName("paywaySingleUseTokenID", data.singleUseTokenId);
        
        // alert("Amount is: " + $.njDialerPlugin.getFieldValue("CreditCardAmount") );
        
        //$.njDialerPlugin.setFieldValueByName("CreditCardAmount", data.amount);
        
        $("#progress").text('Credit Card has been submitted please wait...');
        
               $.njDialerPlugin.setFieldValueByName("CreditCardTransactionId", "");
                $.njDialerPlugin.setFieldValueByName("CreditCardReceiptNumber", "");
                $.njDialerPlugin.setFieldValueByName("CreditCardResponseCode", "");
                $.njDialerPlugin.setFieldValueByName("CreditCardStatus", "Processing...");

         

      	$.njDialerPlugin.callActionScript('SendPaywayPayment', data.singleUseTokenId,  
          	function(result)
            {
        	    
        	    try
        	    {
        	    var allValid = true;
        	    var details = JSON.parse(result); // deserialise it.
        	    
        	    // alert("details:" + details)
        	    debugger;
                
                $.njDialerPlugin.setFieldValueByName("CreditCardTransactionId", details.transactionId);
                $.njDialerPlugin.setFieldValueByName("CreditCardReceiptNumber", details.receiptNumber);
                $.njDialerPlugin.setFieldValueByName("CreditCardResponseCode", details.responseCode);
                $.njDialerPlugin.setFieldValueByName("CreditCardStatus", details.status);

            
    
        	    
        	    // alert(details.responseCode + ":" + details.status + " " + details.responseText);
        	    }
        	    catch(e)
        	    {
        	        alert("something bad in callback" + e);
        	    }
        	    
        	    
        	});
	}
};



$.njDialerPlugin.paymentFailed = function(xhr, status, error)
{
    alert("failed:" + xhr.responseText);
}



$.njDialerPlugin.paymentSuccess = function(data)
{
     $.njDialerPlugin.setFieldValueByName("paywaySingleUseTokenID", data.singleUseTokenId);
    
    alert("You must click 'Next' to transition to the Final Page for the appointment to be applied");
    alert("success:" + data);
}


/*
 * this method is called before saving.
 * also for the contact hub when, hanging up and also when the 'Verify' button is clicked.
 * 
 * if you return false the save or hangup will fail
 * 
 * you should display any messages to the user so they know how to resolve the problem...
 * for example:
 *  alert('The comments field may not be blank');
 * 
 */
$.njDialerPlugin.onVerify = function(disposition) {
	// validate lead and return true if ok to save
	return true;

};

$.njDialerPlugin.onSave = function(disposition) {
	// lead is being saved, an opportunity to notify external services

};

/*
 * this method will be called when the call is hung up
 */
$.njDialerPlugin.onHangup = function() {
	// do something here

};


$.njDialerPlugin.getPage('${NjWizardPageName}').onPageEntry = function(page) {
	// do something here
	
	 
    $.njDialerPlugin.loadCCFrame();

};


Payway Action Script


package creditcardpayment;

import au.com.noojee.actionscript.api.v1.BaseScreenLayoutAction;
import au.com.noojee.actionscript.api.JSCallback;
import au.com.noojee.actionscript.api.v1.ScreenLayoutAction;

import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class SendPaywayPayment  extends BaseScreenLayoutAction
{
	static private String TEST_API_KEY = "TTTT";
	static private String TEST_MERCHANT_ID = "TEST";

	static private String PRODUCTION_API_KEY = "PPPP";
	static private String PRODUCTION_MERCHANT_ID = "XXXXX";

	static private boolean productionMode = true;

	static private String REST_END_POINT_URL = "https://api.payway.com.au/rest/v1/transactions";

	// @Override
	public void exec(String singleUseTokenID, Map<String, String> fieldValues, JSCallback jsCallback) throws Exception 
	{
		logger.warn(singleUseTokenID);

		//String singleUseTokenID = fieldValues.get("paywaySingleUseTokenID");
		String amount = fieldValues.get("CreditCardAmount");

        logger.error("Submitting CC Payment: token=" + singleUseTokenID + " amount:" + amount);
		PaymentTransaction.Response result = submitPayment(singleUseTokenID, amount, fieldValues.get("njLeadId"));
		//cancelPayment(singleUseTokenID, amount, fieldValues.get("njLeadId"));

        logger.error("Payment response: " + result);

        String jsonEntity = new Gson().toJson(result);
		// jsCallback.call(result);

		logger.error("Calling jsoncallback with: " + jsonEntity);
		jsCallback.call(jsonEntity);
	}

  	public PaymentTransaction.Response submitPayment(String singleUseTokenID, String amount, String leadId)
			throws PaymentTransactionException, IOException, HTTPException {

		Map<String,String> args = new HashMap<>();

		args.put("singleUseTokenId", singleUseTokenID);
		args.put("customerNumber", leadId);
		args.put("transactionType", "payment");
		args.put("principalAmount", amount);
		args.put("currency", "aud");
		args.put("orderNumber", leadId);
		args.put("merchantId",  (productionMode == true ? PRODUCTION_MERCHANT_ID : TEST_MERCHANT_ID));

		logger.error("args" + Arrays.toString(args.entrySet().toArray()));

		Map<String, String> headers = getAuthHeaders();

		HTTPConnector connector = new HTTPConnector();
		URL url = new URL(REST_END_POINT_URL);

		logger.error("URL:" + url);
		//PaymentTransaction transaction = new PaymentTransaction(singleUseTokenID, amount);
		// PaymentTransaction.Response 
		HTTPResponse httpResponse = connector.push(HTTPMethod.POST, url, connector.buildBody(args)
				, headers,  PaymentTransaction.Response.class);

		logger.error("Raw response: " + httpResponse);

		//String jsonEntity = new Gson().toJson(response);

		// return response.getResponseBody();

		return httpResponse.getResponse(PaymentTransaction.Response.class);

	}

	private Map<String, String> getAuthHeaders() {

		Map<String,String> headers = new HashMap<>();

		String apiKey = (productionMode == true ? PRODUCTION_API_KEY : TEST_API_KEY);

		String encodedKey = javax.xml.bind.DatatypeConverter.printBase64Binary(apiKey.getBytes());
		headers.put("Authorization", "Basic " + encodedKey);

		return headers;
	}

	public PaymentTransaction.Response cancelPayment(String originalTransactionId, String amount, String leadId)
			throws PaymentTransactionException, IOException, HTTPException {

		Map<String,String> args = new HashMap<>();

		args.put("transactionType", "refund");
		args.put("principalAmount", amount);
		args.put("orderNumber", leadId);
		args.put("parentTransactionId", originalTransactionId);

		Map<String, String> headers = getAuthHeaders();

		HTTPConnector connector = new HTTPConnector();
		URL url = new URL(REST_END_POINT_URL);
		HTTPResponse httpResponse = connector.push(HTTPMethod.POST, url, connector.buildBody(args)
				, headers,  PaymentTransaction.Response.class);

		return httpResponse.getResponse(PaymentTransaction.Response.class);

	}

	class PaymentTransactionErrors {
		String message;
		Link[] links;

		String getMessage() {
			return message;
		}

		@Override
		public String toString() {
			return "PaymentTransactionErrors [message=" + message + ", links=" + Arrays.toString(links) + "]";
		}

	}

	static class PaymentTransaction {
		String singleUseTokenID;
		String principalAmount;

		PaymentTransaction(String singleUseTokenID, String principalAmount) {
			this.singleUseTokenID = singleUseTokenID;
			this.principalAmount = principalAmount;
		}

		static class Response {
            String transactionId;
            String receiptNumber;
            String status; // approved
            String responseCode;
            String responseText;
            String customerName;
            String principleAmount;
            String surchargeAmount;
            String paymentAmount;
            String transactionDateTime;
            String settlementDate;

		}

	}

	public PaymentTransactionErrors getError(String body) {
		logger.error("Body" + body);
		Gson gson = new GsonBuilder().create();
		PaymentTransactionErrors errors = gson.fromJson(body, PaymentTransactionErrors.class);
		return errors;
	}

    static class Link {

    	String rel;
    	String href;
    	@Override
    	public String toString() {
    		return "Link [rel=" + rel + ", href=" + href + "]";
    	}
    }
}