Developing a Payment Driver For Helium Rapid
- Jacques Marais
- Charl Viljoen
- Lennie de Villers
Overview
Helium provides a payment framework with standardised functionality to:
- Integrate with third party payment gateways
- Configure DSL apps for payment
- Trigger payments from DSL apps
- Post payment statuses back to DSL apps
- Keep an audit trail of payments and report on account specific payments
Developing Payment Drivers
Development Process
To develop and test any Helium features locally a local Helium instance will be required. The following setup guide can be used as reference: /wiki/spaces/HP/pages/5075618
In general, any change made to the Helium codebase requires a feature branch to be made from the develop branch. Ideally, the branch name should include a relevant Jira ticket number.
Changes can then be made to the feature branch and once implemented a pull request to the develop branch needs to be submitted to the Helium team lead.
Once a feature has been implemented, tested successfully and adopted into Helium, the deployment of the new feature can be scheduled with the Helium team.
To allow beta testing and reduce risks for new deployments the Helium dev team has a fixed time slot and process for deployments:
- First Thursday of every month: Beta deployment to all rc, dev and preprod environments
- Second Thursday of every month: Deployment to all production environments
The implementation should conform the the standards defined here: /wiki/spaces/TPA/pages/5865762
General Payment Workflow
The payment framework supports a general workflow for payment integrations that is as follows:
- Payment request is sent to payment gateway
- A synchronous response is received by Helium. If the response indicates a failure the payment is mark as failed but updating the payment status to "Manual".
- If the payment has not failed the payment gateway posts async responses back to Helium callback URL
- With each async response Helium updates the payment status internally
- Relevant payment status updates are posted back to the DSL app until the payment is completed or fails
For more information on how the above integrates with a Helium Rapid DSL app see the following tutorial documentation: Lesson 12: Payments, Widget Visiblity
Validate Payment Workflow
The payment framework supports an optional validation workflow for payment integrations that is as follows:
- Payment request is sent to payment gateway
- A synchronous response is received by Helium. If the response indicates a failure the payment is mark as failed but updating the payment status to "Manual".
- Helium wait for a validation request on {gatewayName}/validate endpoint. A custom response can be sent back to the payment gateway.
- If the payment has not failed the payment gateway posts async responses back to Helium callback URL
- With each async response Helium updates the payment status internally
- Relevant payment status updates are posted back to the DSL app until the payment is completed or fails
For more information on how the above integrates with a Helium Rapid DSL app see the following tutorial documentation: Lesson 12: Payments, Widget Visiblity
Code Additions
In order to develop a payment driver, additions need to be made to the files, packages and projects in Helium's codebase described in the next section.
The following change sets in Helium's codebase represent previously added payment drivers and can be used as additional reference material:
Configuration Specific
Most base model entities that will be required by a new payment driver is already in Helium in the pay-model-lib-legacy project. In addition to those already available, the following driver specific model additions are required to pay-model-lib-legacy:
com.mezzanine.helium.pay.model.config:
- A driver specific config needs to be added to this package
- It should be split into an interface and implementation
- The interface should extend the existing PaymentConfiguration base interface
- The interface should also be added as a Json subtype to the base PaymentConfiguration
- Any sensitive values such as payment account specific values should make use of crypto types that are built into Helium such as CryptoString.
- Any additional model entities or enums that are required should also be added to this package
- See the existing classes in this package for examples
com.mezzanine.helium.pay.model.result:
- A driver specific async response needs to be added to this package
- It should be split into an interface and implementation
- The interface should extend the existing PaymentInstallmentResult base interface
com.mezzanine.helium.pay.model.visitor:
- Appropriate additions should be made to the PayEntityVisitor and StubPayEntityVisitor classes in this package
Considerations for Configurability
It should be noted that payment configurations can be updated using an API without any downtime to Helium. It's therefore important to keep the following in mind:
- Any account specific details should be configurable and not hard coded in drivers
- If a gateway supports a sandbox integration, a flag should be added to the configuration to indicate whether the config represents a sandbox or non-sandbox integration
- In general, URLs that are integrated with should be configurable
- If a driver for a payment Gateway can be reused for different payment types, the payment type can also be added as a configurable value
- Any other value that might change or that will be differ between applications making use of the same driver should be configurable
Enable validate payment flow
- Set validationRequired to true from your *PaymentConfigure class that extends AbstractPaymentConfiguration
- Implement a factory in PaymentValidationBuilderFactoryFactory for your payment driver. See SafaricomMpesaRestValidationFactory as an example.
- Create your own builder that implements PaymentValidationResultBuilder interface. See SafaricomPaymentValidationResultBuilder as an example.
- if required PaymentValidateResponse class can be extend to return a custom response.
Outbound Driver Specific
In order to add a payment driver the following code additions need to be made to the Helium-ejb project:
com.mezzanine.helium.ea.service.payment.driver:
- A new driver that contains the actual integration logic needs to be added to this package.
- The driver class should extend the existing PaymentDriver class parameterised with the type of configuration applicable to the driver. See previous section for details on implementing configuration classes.
- A private inner class called SingletonContainer that instantiates a single static final instance of the driver class should be added
- A public static method called getInstance that return the above mentioned driver instance should also be added
com.mezzanine.helium.ea.service.payment.strategy:
- Appropriate updates need to be made to the InitiateStrategy class. This will allow the driver to be called from rest of the payment framework.
- Appropriate updated need to be made to the ResultStrategy class. This will allow payment status transitions for the driver to be handled.
- Both of these classes need to implement the existing PaymentStrategy interface.
Gateway Result Specific
In order for async result callbacks from the payment gateway back to Helium to be supported, the following classes in the Helium-war project need to be updated:
- PaymentResultBuilderFactoryFactory
- SupportedPaymentGateways
Manual Payment Status Result Requests
Since Helium 1.48, framework support to manually trigger a payment status check from the DSL is also available. See HE-10562 for details and links to documentation.
As part of this an additional updateStatus method needs to be implemented in the payment driver class.
In addition, relevant updates to PaymentStatusRequestAsyncDelegate needs to be made supported by a relevant ...PaymentStatusRequestResultBuilderFactory implemented in the com.mezzanine.helium.ea.service.payment.strategy.result package of the Helium-ejb project. See MtnMomoPaymentStatusRequestResultBuilderFactory as an example.
Testing Payment Drivers
Unit Tests
As best practice, each payment driver requires a unit / integration test that can be used to test different use cases of payments through the drivers. Tests for payment drivers need to be added to the Helium code base to test package com.mezzanine.helium.ea.service.payment.driver. See existing tests for examples.
Payment Test App
In order to test payments locally the following test payment app can be used:
https://bitbucket.org/mezzanine-helium/helium-payment-app/src/master/
The app can be deployed using the "Administrator" role and in addition to deploying the app locally it has to be configured for payments.
For a quick summary of the different payment configs that Helium requires / exposes see the following document: /wiki/spaces/HP/pages/5074892
The curl calls below demonstrates how this specific app can be configured for payments. The API client credentials set up as part of the Helium Setup Guide can be used for the calls below.
1) Configure the payment config grouping for the app
curl -u 'usr:pwd' \ -H "Content-Type: application/json" \ -X PUT "<base url>/api/v2/payment/client" \ -d '{ "id":"ecb14ecf-9a1f-4876-959c-ab864f9115c4", "name":"My Payment Accounts", "description":"My Payment Accounts" }'
In the payload above the value for id is a randomly generated v4 uuid.
2) Configure the payment routing and DSL payment config for the app
curl -u 'usr:pwd' \ -H "Content-Type: application/json" \ -X PUT "<base url>/api/v2/payment/route" \ -d @route_config.json
See the content of route_config here: route_config.json
In the payload above:
- The value for id is a randomly generated v4 uuid.
- The value for clientId is the unique id of the app.
curl -u 'usr:pwd' \ -H "Content-Type: application/json" \ -X PUT "<base url>/api/v2/payment/configuration/dsl" \ -d '{ "id":"6316a570-5163-42fd-998f-98bef09c6559", "appId":"b1f28c43-1343-4503-91d2-09fb531e20bc", "paymentObjects":{ "Payee":{ "matcherFields":null, "identifierType":"MSISDN", "identifierField":"mobileNumber" }, "Payer":{ "matcherFields":[ "paymentConfigurationUuid" ], "identifierType":"MSISDN", "identifierField":"mobileNumber" } } }'
In the payload above:
- The value for id is a randomly generated v4 uuid.
- The value for appId is the unique id of the app.
3) Configure one or more payment account configurations
curl -u 'usr:pwd' \ -H "Content-Type: application/json" \ -X PUT "<base url>/api/v2/payment/configuration" \ -d '{ "id":"9c2d6182-48ab-43b8-846f-a02f46f1146f", "type":"payConfigurationYourDriver", "name":"Payment account 1", "endpoint":"some.payment.gateway", "userName":"usr", "password":"pwd", "paymentClientId":"b1f28c43-1343-4503-91d2-09fb531e20bc", "validationRequired": false, . . . }'
The payload above is abbreviated and shows only fields that are required for the base PaymentConfiguration class. Any additional fields will be from the driver specific config that has been added.
The for the fields shown above the following applies:
- The value for id is a randomly generated v4 uuid.
- The value id represents the json subtype for your payment config. In code (configuration class for your driver) this will be represented by, for example:
String JSON_TYPE_NAME = PaymentConfiguration.JSON_TYPE_PREF_CONFIGURATION + "MoMo";
- The value for name is a text name / description of this payment account configuration.
- The value for userName is a user name relevant to the account.
- The value for password is a password relevant to the account.
- The value for paymentClientId is the id for the payment configuration grouping that was added above in step 1.
- The value for validationRequired being true/false indicate if validate payment flow should be followed.
Receiving Async Validate Payment Responses
The implementation for the API can be found in the Helium-war project's PaymentResource class and the endpoint exposed by the API is as follows:
<base_url>/api/v2/payment/myDriver/validate
In the above "myDriver" represents the payment gateway name that was added to the SupportedPaymentGateways class. This will in turn be used as input to the PaymentValidationBuilderFactoryFactory class to determine which result builder should be used for the incoming payload.
Receiving Async Responses
Helium already contains a built in API for the purpose of receiving async payment result callbacks from aggregators. The implementation for the API can be found in the Helium-war project's PaymentResource class and the endpoint exposed by the API is as follows:
<base_url>/api/v2/payment/myDriver/result
In the above "myDriver" represents the payment gateway name that was added to the SupportedPaymentGateways class. This will in turn be used as input to the PaymentResultBuilderFactoryFactory class to determine which result builder should be used for the incoming payload.