We need to dynamically support multiple payment methods (credit, paypal, google pay, etc...).
We can change the gateway we use to implement a certain payment method.
For example:
PayPal payment method can be implemented today by using Stripe SDK, and tomorrow by using PayPal SDK.
This lead me to the strategy pattern, where I can just implement each payment method by any gateway sdk.
Example: :
class PayPalModel : IPaymentModel
class PayPalGateway(val someSdk: SDK) : IPaymentService<PayPalModel> {
override suspend fun authorizeFunds(model: PayPalModel) {
// someSdk can be: stripe sdk, paypal sdk, ...
}
}
and then simply inject the services, and call "authorizeFunds" :
val gateways =
listOf(
CreditGateway(),
PayPalGateway()
)
val api = PaymentAPI(gateways)
api.authorizeFunds(PayPalModel(...)
and then the "api" will internally be locating the correct service first :
private suspend fun findService(model: IPaymentModel): IPaymentService<IPaymentModel>? {
for (service in paymentServices) {
if (service.appliesTo(model)) {
return service as IPaymentService<IPaymentModel>
}
}
return null
}
However, for all our supported payment methods, upon placing an order the Client only sends the server two fields :
- The selected payment method type
- The auth id/token they received from the Stripe/Square/.. SDK
So in this case, it seems that the various payment models (PayPalModel, CreditModel,..) are redundant, because they will always contain the same data.
But, if we omit OR generalize this model, we will lose the type-safe distinction between all types of Gateways implementation.
For example, imagine:
class PayPalGateway : IPaymentService { // omiting the model
override suspend fun authorizeFunds(model: PaymentRequest) {
/// stripe sdk, paypal sdk, ...
}
}
class CreditGateway : IPaymentService<PaymentRequest> { // generalizing the model
override suspend fun authorizeFunds(model: PaymentRequest) {
/// stripe sdk, paypal sdk, ...
}
}
So except for the class naming, there is no real way to safely distinguish the services in order to use the correct one. the function "findService" from above would be broken/useless.
What would be the wise choice to make sure that when the client sends me the method type + token/id, I pick the correct payment service?
According to my humble experience in fintech, this case is usually solved by using enums. As simple as it is, I would extend the model with the property, representing the payment method/service.
I suppose, that client provides you with the information about their payment method/service. Since that, you can simply check the value and choose the correct gateway.
In my opinion, this approach is more robust than iterating over available services.