Developer Guide To Payment Gateway Integration

First of all you need to create a Payment Gateway (KaptioTravel__PaymentGateway__c) record.

Where to create a payment gateway record

KaptioTrave__PaymentGateway__c stores payment gateway settings and authentication parameters. Used when making callouts to a payment service. They can be found under the Payment Gateway tab. Fields on this object are:

  • ApexClass__c – Name of the apex class that implements the KaptioTravel global interface for handling payment requests, response and callbacks.
  • CallbackPage__c – The Visualforce Page that handles the callback once payment attempt has been made.
  • PaymentPage__c – Name of the Visualforce page that displays and processes user inputs (address, credit card, user info).. Leave this field empty if you do not need to customize.
  • PropertiesJSON__c – Properties, authentication and other variables used for payment engine integration. Each payment engine has different authentication and properties, therefore we use a flexible data storage mechanism (i.e JSON).

Payment Settings Setup

Go to the Channel details page -> payment settings -> new
Select visibility, set the type to payment gateway, select Payment Gateway.
Set Require Payer’s Address to true if you want to display the billing address inputs on the payment page.

IPaymentGateway Interface.

For implementation of the new Payment Gateway you need to develop a global payment handler class which should implement the following KaptioTravel.IPaymentGateway methods:

/**
* @author Ragnar
* @date 2016/08/01
* @description Interface (i.e. contract) for Payment Gateway implementation
*/

global interface IPaymentGateway {

 /**
 * @description Returns a HttpRequest for the paymentRequestId
 * @param p_stagingRecord - PaymentRequest__c record
 * @param p_orderInfo - Map<String, Object> of additional payment params
 * @return HttpRequest instance.
 **/
 HttpRequest makeRequest(PaymentRequest__c p_stagingRecord, Map<String, Object> p_orderInfo);
 
 /**
 * @description Returns the redirctURL provided by the payment gateway for so the customer can proceed with his payment.
 * @return String - Redirect page Url.
 **/
 String getRedirectURL();

 /**
 * @description populates PaymentRequest__c record with additional params needed in next steps
 * @param p_paymentRequest - PaymentRequest__c record
 * @return PaymentRequest__c.
 **/
 PaymentRequest__c populatePaymentRequest(PaymentRequest__c p_paymentRequest);

 /**
 * @description Allows the consumer to populate information about the paymnet into the transaction logged in Kaptio using the callbackPage as as source.
 * @param p_payment - Transaction__c record
 * @return Transaction__c - Transaction__c record
 **/
 Transaction__c populatePayment(Transaction__c p_payment);

 /**
 * @description Parse information about the paymnet into and saves it in inner vars.
 * @param p_callbackResponse - response from payment service
 * @return void
 **/
 void parseResponse(String p_callbackResponse);

 /**
 * @description Checks the status of transaction.
 * @param p_callbackResponse - response from payment service
 * @return Boolean - success/failer
 **/
 Boolean isSuccess();

 /**
 * @description Returns Payment error message
 * @return String - error maethod
 **/
 String getErrorMessage();

 /**
 * @description returns the name of payment gateway
 * @return String.
 **/
 String getGatewayName();

}

Payment Handler Class

!!!Important. Payment handler class should have default constructor without parameters.

In example ResponseDto - is a wrapper class for payment service response properties

/**
* @description default constructor, used in payment process
**/
public PaymentHandlerController() {
    response = new ResponseDto();
}

Payment handler methods are used in CustomerPaymentRequestDto and CustomerPaymentResponseDto global classes.

Advice
  • Use KaptioTravel.CustomerPaymentRequestDto global helper class for working with the KaptioTravel__PaymentRequest__c sObject record. This object contains all information about request/ response from the payment service.
  • You can retrieve KaptioTravel__PaymentGateway__c in the payment handler by p_stagingRecord.KaptioTravel__PaymentGateway__c.
  • You can store any information to p_orderInfo. This map will be serialized and saved to KaptioTravel__PaymentRequest__c.
  • The p_orderInfo map contains all information related to the user selection on previous payment steps.(see ex.)
Authentication Request

The MakeRequest method should prepare the HttpRequest instance with required properties for the authentication request to the payment service.

public HttpRequest makeRequest(KaptioTravel__PaymentRequest__c p_stagingRecord, Map<String, Object> p_orderInfo){
 paymentRequest = new KaptioTravel.CustomerPaymentRequestDto(p_stagingRecord);

 itineraryRecord = KtTemplateController.queryItineraryRecord(p_stagingRecord.KaptioTravel__itinerary__c);

 List<KaptioTravel__Transaction__c> itineraryTransactions = [select Id from KaptioTravel__Transaction__c where KaptioTravel__Itinerary__c =: itineraryRecord.Id];

 // Payment is pre-auth compatible if no payment has been taken and itinerary PaymentCaptureType__c field is set to pre-auth value.
 Boolean preAuthCompatible = itineraryTransactions.isEmpty() && itineraryRecord.PaymentCaptureType__c.equals('Pre-Authorise Payment');

 HttpRequest req = new HttpRequest();
 KaptioTravel__PaymentGateway__c paymentGateway = [SELECT Id, KaptioTravel__PropertiesJSON__c FROM KaptioTravel__PaymentGateway__c WHERE Id =: p_stagingRecord.KaptioTravel__PaymentGateway__c];
 PropertiesDto properties = (PropertiesDto)JSON.deserialize(paymentGateway.KaptioTravel__PropertiesJSON__c, PropertiesDto.class);
 PageReference pref = new PageReference('https://'+ properties.merchant_domain +'.altapaysecure.com/merchant/API/createPaymentRequest');
 Map<String, String> paramsMap = new Map<String, String>();

 PageReference callbackSuccess = paymentRequest.getCallbackSuccessURL();
 PageReference callbackFailure = paymentRequest.getCallbackFailureURL();
 Id paymentRequestId = (Id)callbackSuccess.getParameters().get('id');
 Id sobjId = ApexPages.currentPage().getParameters().get('id');

 //check if it's itinerary - redirect to package page due to custom page requires contentId
 if (sobjId.getSobjectType() == KaptioTravel__Itinerary__c.getSobjectType()) {
 callbackSuccess = new PageReference(URL.getSalesforceBaseUrl().toExternalForm() + Page.KaptioTravel__CustomerPaymentResult.getUrl());
 callbackSuccess.getParameters().put('request', paymentRequest.encryptedRequestKey);
 callbackSuccess.getParameters().put('id', paymentRequestId);
 callbackFailure = callbackSuccess;
 } else {
 callbackSuccess.getParameters().put('prId', paymentRequestId);
 callbackSuccess.getParameters().put('id', sobjId);
 callbackFailure.getParameters().put('prId', paymentRequestId);
 callbackFailure.getParameters().put('id', sobjId);
 }

 putParamToRequestMap(paramsMap, 'terminal', properties.terminal_name);
 putParamToRequestMap(paramsMap, 'shop_orderid', itineraryRecord.KaptioTravel__Itinerary_No__c);
 putParamToRequestMap(paramsMap, 'transaction_info[0]', EncodingUtil.base64Encode(Blob.valueOf(itineraryRecord.Id)));
 putParamToRequestMap(paramsMap, 'amount', String.valueOf(p_stagingRecord.KaptioTravel__Amount__c));
 putParamToRequestMap(paramsMap, 'currency', p_stagingRecord.CurrencyIsoCode);
 putParamToRequestMap(paramsMap, 'language', itineraryRecord.KaptioTravel__Channel__r.KaptioTravel__LanguageCode__c);

 String paymentType = preAuthCompatible ? 'payment' : 'paymentAndCapture';
 putParamToRequestMap(paramsMap, 'type', paymentType);

 putParamToRequestMap(paramsMap, 'config[callback_form]', getCallbackFormURL(p_stagingRecord.Id));
 putParamToRequestMap(paramsMap, 'config[callback_ok]', callbackSuccess.getURL() + '&sid='+UserInfo.getSessionId());
 putParamToRequestMap(paramsMap, 'config[callback_fail]', callbackFailure.getURL() + '&sid='+UserInfo.getSessionId());

 //push order items to request if it defined
 if(p_orderInfo.get('orderItems') != NULL) {
 Integer n = 0;
 for(Object orderItemObj : (List<Object>)p_orderInfo.get('orderItems')) {
 Map<String, Object> orderItem = (Map<String, Object>)orderItemObj;

 // pre auth textual handling to show the user that it will be a preauth payment.
 String additionalDescription = preAuthCompatible ? 'Pre-Authorised ' : '';
 orderItem.put('itemName', additionalDescription + orderItem.get('itemName') );

 putParamToRequestMap(paramsMap, 'orderLines[' + n + '][itemId]', p_stagingRecord.Id + String.valueOf(n));
 putParamToRequestMap(paramsMap, 'orderLines[' + n + '][description]', orderItem.get('itemDescription'));
 putParamToRequestMap(paramsMap, 'orderLines[' + n + '][unitPrice]', orderItem.get('amount'));
 putParamToRequestMap(paramsMap, 'orderLines[' + n + '][quantity]', orderItem.get('quantity'));
 n++;
 }
 }

 //push billing information to request from user inputs
 if(p_orderInfo.get('billingInfo') != NULL){
 Map<String, Object> billingInfo = (Map<String, Object>)p_orderInfo.get('billingInfo');
 
 String billingAddress; 
 if (billingInfo.get('address') != null)
 billingAddress = String.valueOf(billingInfo.get('address')).length() > 30 ? String.valueOf(billingInfo.get('address')).left(30) : String.valueOf(billingInfo.get('address'));
 
 putParamToRequestMap(paramsMap, 'customer_info[billing_address]', billingAddress);
 putParamToRequestMap(paramsMap, 'customer_info[billing_firstname]', billingInfo.get('firstname'));
 putParamToRequestMap(paramsMap, 'customer_info[billing_lastname]', billingInfo.get('lastname'));
 putParamToRequestMap(paramsMap, 'customer_info[billing_postal]', billingInfo.get('postalCode'));
 putParamToRequestMap(paramsMap, 'customer_info[billing_city]', billingInfo.get('city'));
 putParamToRequestMap(paramsMap, 'customer_info[billing_country]', billingInfo.get('country'));
 putParamToRequestMap(paramsMap, 'customer_info[email]', billingInfo.get('email'));
 }

 pref.getParameters().putAll(paramsMap);

 Blob headerValue = Blob.valueOf(properties.username + ':' + properties.password);
 String authorizationHeader = 'Basic ' + EncodingUtil.base64Encode(headerValue);

 req.setEndpoint(pref.getURL());
 req.setHeader('Authorization', authorizationHeader);
 req.setMethod('POST');
 system.debug('param:' + paramsMap);
 return req;
}
Authentication Response

After the authentication response is returned from the payment service, the following methods will run:

  1. ParseResponse(String responseBody) — response should be parsed and saved to the local properties (will be used in the next functions)
  2. isSuccess() – returns true/ false based on payment service response
  3. GetErrorMessage() – returns error message from response
  4. GetRedirectURL() – returns url to the payment form of the payment service.
  5. PopulatePaymentRequest(PaymentRequest__c stagingRecord) – Here the PaymentRequest__c default values can be overridden.

After that, the PaymentRequest__c record will be updated (by kaptio inner classes).

Next, the user will be redirected to the payment service payment page. After the user makes a  payment, he will be redirected to the callbackUrl.

Payment Response

We have CustomerPaymentResponseDto.cls as the process response.:

KaptioTravel.CustomerPaymentResponseDto cp_response = new KaptioTravel.CustomerPaymentResponseDto(paymentRequestId, ktkey, ApexPages.currentPage().getURL());
if(cp_response.isSuccess) {
//show success block
//Use created Transaction__c record for output all information related to payment
cp_response.payment.KaptioTravel__Amount__c,
cp_response.payment.KaptioTravel__PaymentInfo__c,
cp_response.payment.KaptioTravel__PayerName__c,
cp_response.payment.KaptioTravel__PayerEmail__c,
....

} else {
//show error message
cp_response.errorMessage
}

CustomerPaymentResponseDto calls the following handler’s methods:

  1. ParseResponse(String responseBody) — response should be parsed and saved to the local properties (will be used in next functions)
  2. isSuccess() – returns true/ false based on the payment service response
  3. GetErrorMessage() – returns error message from the response
  4. PopulatePayment(KaptioTravel__Transaction__c payment) – Payment gateway specific data can be populated
For ex:
public KaptioTravel__Transaction__c populatePayment(KaptioTravel__Transaction__c p_payment) {
p_payment.CardType__c = response.paymentScheme;
p_payment.KaptioTravel__PaymentInfo__c = String.format(
' Paid via WorldPay\n Transaction ID: {0}\n Payment ID: {1}\n \n Credit Card: {3}',
new List<String>{
response.transactionId,
response.paymentId,
response.paymentStatus,
response.maskedCreditCard
});
if(response.surchargeAmount != NULL) {
p_payment.KaptioTravel__Surcharge__c = response.surchargeAmount;
}
return p_payment;
}