- This payment gateway has three main components:
- MerchantSimulator
- Gateway
- BankSimulator
- This API mocks a merchant and has been created just to test the API client library built using IHttpClientFactory and Refit - https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-2.2
- The MerchantSimulator API sends all requests to the Gateway API.
- A correlation id is also auto-generated and propagated till the BankSimulator API. This uses the CorrelationId package - https://github.com/stevejgordon/CorrelationId
- The correlation id can also be passed by setting the CorrelationId header in the HTTP request.
- The Gateway API hosts all the logic necessary to process a payment.
- Its endpoints are secure and Authentication is done using a secret key - https://joonasw.net/view/creating-auth-scheme-in-aspnet-core-2
- Requests are then forwarded to any one acquiring bank.
- Decision on routing is based on pre-defined settings in the database tables.
- Autofac Keyed Services is used to select appropriate processor.
- Payment details are stored in a PostgreSQL database and can be queried.
- The newly created resource link is available in create payment response headers.
- Card numbers are encrypted using AES and masked when returned by the API.
- Validation at API and Service layers are done using FluentValidation.
- Credit Card validation is done using CreditCardValidator library - https://github.com/gustavofrizzo/CreditCardValidator
- App metrics have been included and available at 'http://localhost:5000/metrics'.
- All logs go to Seq via Serilog. Seq can be accessed at 'http://localhost:5003'.
- The BankSimulator API mocks an acquirer and sends a successful or failed response based on the amount being passed (1000 for failed, the rest will be successful).
- The CorrelationId flows from MerchantSimulator to Gateway to BankSimulator and is logged on Seq and stored in PostgreSQL.
- The database used is PostgreSQL and is created upon launch of the docker containers using migration scripts and Evolve - https://github.com/lecaillon/Evolve.
- Dapper is used as ORM to communicate with the DB and is setup in a Repository pattern to promote de-coupling.
- The database has the following tables:
- Merchants - stores all merchant details along with the secret key to use in API authentication.
- Acquirers - stores acquirer details, mainly the URL of the acquirer API.
- MerchantAcquirers - links merchants to acquirers.
- AcquirerResponseCodeMappings - maps response code details from the acquirer to that of the gateway.
- Payments - stores all the payment details.
- ASP.NET Core 2.2
- PostgreSQL
- Dapper
- Evolve
- Docker
- AutoMapper
- Autofac
- Swagger
- App Metrics
- CorrelationId
- Serilog
- Seq
- IHttpClientFactory
- Refit
- FluentValidation
- xUnit
- The Gateway has two main functionality that it provides:
- Create a payment
- Retrieve details of a past payment
- Swagger UI can be accessed at
http://localhost:5000/swagger/index.html
. - Download the following Postman collection -
- The entire system has been containerised and runs on Docker. Bring everything up using
docker-compose up
. Wait some time until all containers are up and running. - Select the Create Payment request and hit the Send button.
- A successful response should be back with the payment details.
{
"acquirerPaymentId": "6d1ab701-96ef-4dfc-869a-0383892c68e5",
"paymentId": "82bce548-3de0-424e-9051-135d60d9c3ae",
"trackId": "Track ID 1",
"amount": 1100,
"currency": "USD",
"card": {
"number": "495308****3607",
"expiryMonth": 10,
"expiryYear": 2023,
"name": "Test User"
},
"responseCode": "10000",
"status": "Succeeded",
"acquirerStatus": "Succeeded",
"acquirerResponseCode": "200"
}
- A failed response can also be obtained by sending 1000 as the amount in the request.
- Copy the paymentId GUID and select the Get Payment request.
- Overwrite the paymentId in the URL and hit Send.
- The same response as the previous one should be obtained.
- Notice the Authorization key in both request headers. This is the Merchant secret key and is found in the Merchants table in the database.
- It is the key used to identify the merchant. If an invalid key, a 401 response will be obtained.
- The Postman collection also contains two more requests that can be used to hit the MerchantSimulator API.
- Authentication in the MerchantSimulator is handled by the API client library.
- For the sake of this test, the routing process has been kept very straight-forward. Card scheme and currency are not taken into consideration when routing and all requests are sent to the BankSimulator.
- A complete payment is processed with one single request to the Gateway.
- Validations have been kept to a bare-minimum.
- The Database is also a very simplistic one and does not include a lot of requirements from a proper payment gateway.
- The MerchantSimulator and BankSimulator APIs have been kept very simple in terms of design.
- The Gateway API is designed in a 3-layer approach - API, Service and Data layers. It has also been implemented in several projects to allow reusability.
- A common folder exists with common projects that are re-used across all components.
- Obviously, this is a very naive, quickly built PoC of a Payment Gateway and is far from a production system.
- Add more validations and risk checks.
- A better routing system with cascading to increase success rate.
- Perform benchmarking and performance tests to identify hot paths and improve response times.
- Caching can be considered for get payment details responses and keeping merchant and acquirer details in-memory.
- Add health checks and prepare for an orchestration system like Kubernetes to properly handle a microservices setup.
- Break the main gateway into smaller services that fits better in a microservices pattern. They can then be developed, maintained and deployed separately.
- Generate asynchronous events to keep all services in sync to changes in the entire system - Use technologies like RabbitMQ, Kafka or EventStore for this.
- Communication between microservices can be done by asynchronous events, HTTP or gRPC.
- Add more unit tests to test all the logic.
- Add integration tests to test the whole application flow.