Skip to content

PSP Module integration process

Afifa Bakiz edited this page Nov 27, 2019 · 13 revisions

PSP Modules are separate libraries that contain specific implementations required to facilitate flow with various PSPs.

If you want to integrate a new PSP into the Stash! SDK, the following steps should be followed:

Creating a Gradle module

You should create a new gradle module with the name following format psp-integration where psp is the actual name of the PSP you are integrating. We are using Kotlin DSL for our gradle files. You should also add a dependency to the core Stash! SDK project

implementation(project(Modules.stash))

You can use template-integration module to jump-start your module development.

Managing dependency injection

We use Dagger 2 to provide dependency injection in the core library, and depending on your needs you might need to use this dependency graph in your module. One use case would be to provide SEPA registration, as you would need to call Stash! SDK backend from your module, but with specific BillingData fields populated, that might be PSP dependent. To do this you should provide a companion object that implements IntegrationCompanion interface inside your concrete Integration class. Here is an example of what that companion object should look like.

companion object : IntegrationCompanion {
    var integration: BsPayoneIntegration? = null

    override fun create(): IntegrationInitialization {
        return object : IntegrationInitialization {
            override fun initializedOrNull(): Integration? {
                return integration
            }

            override fun initialize(stashComponent: StashComponent, url: String): Integration {
                if (integration == null) {
                    if (url.isEmpty()) {
                        integration = BsPayoneIntegration(stashComponent)
                    } else {
                        integration = BsPayoneIntegration(stashComponent, url)
                    }
                }
                return integration as Integration
            }
        }
    }
}

The reason for the singleton like approach in the method initializedOrNull is that building a core dependency graph depends on having all integrations provided, while the integrations depend on the dependency graph to start initialization. To work around this circular dependency and still provide an easy to use API to 3rd party developers, we use initializedOrNull. URL parameter is provided for easier testing.

Once you have provided the IntegrationCompanion you should initialize your module in the init block of your concrete Integration implementation, as in the following example:

init {
        bsPayoneIntegrationComponent = DaggerBsPayoneIntegrationComponent.builder()
                .stashComponent(stashComponent)
                .bsPayoneModule(BsPayoneModule(url))
                .build()

        bsPayoneIntegrationComponent.inject(this)
    }

Handling payment method registration requests

Payment method registration requests are modeled with a RegistrationRequest class provided by core library. This class contains StandardizedData and AdditionalRegistrationData. Standardized data represents the values we are certain each payment method shares no matter what PSP is being used, while AdditionalRegistrationData contains values that are PSP specific. Combining values provided by user (such as Credit card number, expiry data, IBAN account, Billing data) and data provided by the Stash! SDK backend (such as hashed requests, or other forms of credentials used to identify with the PSP) you can create a proper request that your particular PSP can consume and provide a payment alias which can then be sent to the merchant backend to be used as a payment method.

Here is the overview of the credit card flow:

Core

Here is an example of registration request handling:

override fun handleRegistrationRequest(registrationRequest: RegistrationRequest): Single<String> {
    val standardizedData = registrationRequest.standardizedData
    val additionalData = registrationRequest.additionalData
    return when (standardizedData) {
        is CreditCardRegistrationRequest -> {
            bsPayoneHandler.registerCreditCard(
                    registrationRequest.standardizedData.aliasId,
                    BsPayoneRegistrationRequest.fromMap(additionalData.extraData),
                    standardizedData.creditCardData
            )
        }
        is SepaRegistrationRequest -> {
            bsPayoneHandler.registerSepa(
                    registrationRequest.standardizedData.aliasId,
                    standardizedData.sepaData,
                    standardizedData.billingData
            )
        }
        else -> {
            throw RuntimeException("Unsupported payment method")
        }
    }
}

In the example above you can see that we currently support CreditCard and SEPA requests for this particular PSP. Calls to bsPayoneHandler are concrete implementations for this particular PSP.