Add new Payment Method
This document describes the necessary steps to configure a new payment method in the CoreWallet.
Introduction
Introducing a new payment method to the system means introducing integration to a Payment Service Provider (PSP) that allows for money movement outside of the emoney wallet closed loop. For example, funds can be added to users’ wallet(s), or funds can be withdrawn into a real bank account from users’ wallet(s).
The integration of PSP payment provides functionality fundamentals for internal business processes like payment, purchasing, invoicing etc. Typically the integration is done in CoreWallet based products since it is usually different customers that usually decide to have preferred PSPs (emoney ←→ real-money capable integration systems).
There is, however, also the possibility that integration of such an external system be made in the CoreWallet itself if the payment method is deemed common enough to be usable in all products.
The CoreWallet has a predefined architecture pattern to which a new integration with a PSP “payment method” will abide by. It is discussed below.
Process outline
- Determine an internal payment method name
- Create SQL for PaymentMethodConfig, BusinessContract and BusinessContractRoutes
- Create PaymentMethodSessionStrategy
- Create PaymentGateway
- Configure PaymentGateway / PaymentMethodSessionStrategy in spring-config
- Create service for integration / callbacks / …
- Create API controller for callbacks
1. Determine an internal payment method name
- Business identifier, must be unique
- Ties method, contracts, etc. together
- Will be used to implement specific handling e.g. in the frontend
Once a payment method name has been established, it is customary that a new String constant is defined for it, usually in the <ProjectName>GlobalConstants
(for example GlobalProductConstants.EXAMPLE_PAY
).
Then, in order to be able to make Payments with the new PaymentMethod, one needs to activate it with default merchant category code in the project specific WalletProductConfiguration
class.
Code example (if the HashMap is already created in the project’s Config class, then just the second code line)
// Payment category-codes per payment-method (split by incoming/outgoing)
final Map<String, Integer> mccByPaymentMethodIncoming = new HashMap<>();
mccByPaymentMethodIncoming.put(GlobalProductConstants.EXAMPLE_PAY, WalletConstants.DEFAULT_CATEGORY_CODE);
and then use it to configure for which payment types the new payment method should be working (e.g. Funding)
Code example (if the HashMap is not already created in the project’s Config class)
final Map<PaymentType, Map<String, Integer>> mccByPaymentTypeMethod = new HashMap<>();
mccByPaymentTypeMethod.put(PaymentType.Funding, mccByPaymentMethodIncoming);
setDefaultPaymentCategoryCodeByPaymentMethod(mccByPaymentTypeMethod);
2. Create SQL for PaymentMethodConfig, BusinessContract and BusinessContractRoutes
This assumes there already is an initialized DB where you want to execute the generation script (either local or to be sure, a dev environment).
The following script is an example of initializing the payment method DB with some dummy data
-- Add payment method
INSERT INTO pay_method (name, active, payment_instrument_type, customer_present, cashflow)
VALUES ('<PAYMENT_METHOD_NAME>', TRUE, 'ExternalAccount', TRUE, 'Incoming');
-- Add payment method config
INSERT INTO pay_method_config (currency_code, payment_method_name)
VALUES ('EUR', '<PAYMENT_METHOD_NAME>');
-- Add business contract
INSERT INTO pay_business_contract (comment, subsidiary_id, qualifier_1, type, payment_method_name, qualifier_2, status)
VALUES ('Default <CUSTOMER_IDENTIFIER> EUR business contract', 1, '<CUSTOMER_IDENTIFIER>', 'ACQUIRER_CONTRACT', '<PAYMENT_METHOD_NAME>', 'EUR', 'Active');
--Add business contract route configuration
INSERT INTO pay_business_contract_route (allowed, comment, enabled, business_contract_id, creation_datetime, currency_code, country_code)
SELECT TRUE, '<CUSTOMER_IDENTIFIER> payment method enabled for EUR/DE', TRUE, bc.id, now() AT TIME ZONE 'UTC', bc.qualifier_2, 'DE'
FROM pay_business_contract bc WHERE bc.status = 'Active' AND bc.qualifier_1 = '<CUSTOMER_IDENTIFIER>' AND bc.payment_method_name = '<PAYMENT_METHOD_NAME>' AND bc.qualifier_2 = 'EUR';
Please refer to how to properly have Maven run the script in the DB and keep history using Flyway (in the above linked committed file).
3. Create PaymentMethodSessionStrategy
A new Class needs defining, which extends com.trimplement.wallet.server.payment.impl.session.AbstractSessionPaymentMethodStrategy.
This new class will act as the vehicle through which the new payment method is selectable within the system, as well as have methods to ensure the new payment method is included in the PaymentSession in various contexts. The new class will typically abide by the naming scheme <
PaymentMethodName
>SessionPaymentMethodStrategyImpl.
4. Create PaymentGateway
A new class needs defining, which extends com.trimplement.wallet.server.payment.impl.gateway.AbstractPaymentGatewayAdapter
. This class contains the contract that a PaymentGateway typically needs to fulfill. The new Class will typically abide by the naming scheme <
PaymentMethodName
>Gateway
.
5. Configure PaymentGateway / PaymentMethodSessionStrategy in spring-config
To enable the new class defined at point 3 to work within the system, in the service config spring file of your “impl” project, typically named com.<projectspecificpackagenaming>.server.impl.service.xml
we make use of the DefaultSessionPaymentMethodStrategyProviderImpl
class, which is defined as an extension point in the CoreWallet project and it bears an empty map of strategies. The config file in the product code will then provide real key-values to this map, effectively providing applicable strategies’ implementation classes.
<bean id="payment.impl.ProductSessionPaymentMethodProvider" class="com.trimplement.wallet.server.payment.impl.session.DefaultSessionPaymentMethodStrategyProviderImpl">
<property name="sessionPaymentMethodStrategies">
<map>
<entry key="paymentMethodName" value-ref="<project>.impl.<PaymentMethodName>SessionPaymentMethodStrategy" />
</map>
</property>
</bean>
The sessionPaymentMethodStrategies
map contains all the system known strategy classes, effectively defining which payment methods are “known” in the system. The entry keyName must be the same as defined in the SQL above in the pay_method
table, name
column.
Of course, the Strategy bean itself needs defining so that Spring can access it for the context:
<bean id="<project>.impl.<PaymentMethodName>SessionPaymentMethodStrategy" class="com.<customer>.server.impl.payment.session.<PaymentMethodName>SessionPaymentMethodStrategyImpl" parent="payment.impl.AbstractSessionPaymentMethodStrategy">
</bean>
To enable the class defined at point 4 to work within the system, in the service config spring file of your “server-impl” project, typically named com.<projectspecificnaming>.server.impl.service.xml
we use the ProductPaymentGatewayProvider,
defined as an extension point in CoreWallet with an exentdable list of properties (Gateways). By way of the config file we can inject the Gateway implementations that we want to have “available” in the system.
<bean id="payment.impl.ProductPaymentGatewayProvider" class="com.trimplement.wallet.server.payment.impl.gateway.DefaultPaymentGatewayProviderImpl">
<property name="paymentGateways">
<list>
<ref bean="<projectspecificnaming>.impl.<PaymentMethodName>Gateway" />
</list>
</property>
</bean>
The paymentGateways
list contains all the PaymentGateways “known” in the system.
Of course, the Gateway bean itself needs defining so that Spring can access it for the context:
<bean id="<project>.impl.<PaymentMethodName>Gateway" class="com.<project.specific.naming>.server.impl.payment.gateway.<PaymentMethodName>Gateway">
<property name="<PaymentMethodName>Service" ref="<project>.impl.<PaymentMethodName>Service" />
</bean>
And of course if you defined Service properties for the beans like above, you will need to define beans for the Services themselves in the same (or another referenced) spring config file.
6. Create service for integration / callbacks / …
Within a working live system, the normal workflow is that the UI client initializes a PaymentSession which can be used to effect a payment. A payment method is selected to a payment session by the user of the system (available payment methods are listed by using the afore mentioned PaymentMethodSessionStrategy class). Afterwards, when the user selects one of the available payment methods codeflow is such that the PaymentGateway will call a Service layer class to initiate the flow of methods from the integration capabilities. Keeping in mind that CoreWallet architecture relies on an internal API as well as an external API, we will have an InternalService and an ExternalService for the integration.
The InternalService interface will contain functionality (methods) that map to the capabilities of the PSP (they likely offer functionality like payment, cancelling a payment, refunding a payment etc). It will at a minimum implement the com.trimplement.wallet.server.common.service.BaseService
interface. The ServiceImpl class will implement the InternalService interface.
Example of InternalService interface:
public interface Internal<PaymentMethodName>Service extends com.trimplement.wallet.server.common.service.BaseService {
PaymentGatewayResult createOrder(Params) throws WalletException;
PaymentGatewayResult refundOrder(Params) throws WalletException;
PaymentGatewayResult cancelOrder(Params) throws WalletException;
}
Note that this might also be an asynchronous type integration, which means that the third-party service might provide callbacks to update our system with regard to the status of previously made calls from our system to theirs. For such possibilities the so called ExternalService is the solution. It needs to implement com.trimplement.wallet.server.common.service.ExportedService
, will be defined in the ”api” project and will ultimately be exposed to the external webapp via Spring HttpInvoker.
Example of ExternalService interface
public interface <PaymentMethodName>Service extends com.trimplement.wallet.server.common.service.ExportedService
{
@PreAuthorize(SecurityUtils.ANONYMOUS)
void callback(ApiRequestInfo apiRequestInfo) throws WalletException;
}
The com.trimplement.wallet.server.wallet.impl.WalletBaseServiceImpl
base Service class is a convenience class that the ServiceImpl class should extend so as to have basic CoreWallet services functionality like authentication and security context manipulation, parameter checking, and others.
Example of ServiceImpl:
public class <PaymentMethodName>ServiceImpl extends WalletBaseServiceImpl implements <PaymentMethodName>Service, Internal<PaymentMethodName>Service {
// from external service
void callback(ApiRequestInfo apiRequestInfo) throws WalletException{
//impl
}
// from internal service
PaymentGatewayResult createOrder(Params) throws WalletException{
//impl
}
PaymentGatewayResult refundOrder(Params) throws WalletException{
//impl
}
PaymentGatewayResult cancelOrder(Params) throws WalletException{
//impl
}
}
The implementation of the methods inherited from the InternalService as well as the ExternalService will typically validate parameters passed to the respective method and contain business logic including using an Integration class typically made to interact with the the API of the PSP.
7. Create API controller for callbacks
Continuing from above, the “external-webapp” project will define a Controller class, typically named <
PaymentMethodName
>
APIController
, that will have a method mapped to the callback URL that the third-party service will know to call us back at. The Controller will delegate the call via the ExternalService (ExportedService) interface, also checking the security context, to the <PaymentMethodName>ServiceImpl
class functionality which is defined in the “server-impl” project. It is in this manner that the functionality defined for the callback type will be carried out.
It should be extended from the com.trimplement.wallet.server.wallet.webapp.external.controller.api_1_0_0
.BaseController
class which provides common functionality for the Controller extended classes.
Example of <
PaymentMethodName
>
APIController
:
@RequestMapping(“/myUrlRequestPath”)
public class <PaymentMethodName>ApiController extends BaseController {
@RequestMapping(value="/callback", method=RequestMethod.GET)
@ResponseBody
public String callback(@RequestParam Params) throws WalletException {
// validate params
// call ExternalService for business logic
}
}