Payment Gateway
Assumptions
- API is located under an application load balancer (ALB) decrypting HTTPS traffic. Hence this API is HTTP. When deployed, we should setup
ASP.NET core
to use forwarded headers - Bank could be slow to process requests
- Payment should be made to a
MerchantId
. This id should not be passed through the request but determined based on theApiKey
passed to the API
How to run the API
Open the solution with Visual Studio 2019
and start the debug.
- Default project should be set to
Checkout.PaymentGateway.Api
- Default port is
50947
. It can be changed by modifying thelaunchSettings.json
file under theCheckout.PaymentGateway.Api
project - Swagger Doc: http://localhost:50947/docs
You can use the Postman file PaymentGateway.postman_collection.json
included in the repository:
-
Run
1- [POST] /payments
-
Copy the content of the
Location
header
-
Open
2- [GET] /jobs/:id
and replace/jobs/XXX
with the string you have copied -
Run the query until the job is marked as completed
-
Copy the EntityId
-
Open
3- [GET] /payments/:id
and replace theXXX
with the EntityId you have copied -
Run the query
Technologies used
- ASP.NET Core 3.1
- Mediatr to decouple controllers from queries/commands handling
- FluentValidation to validate requests through a pipeline built on top of a Mediatr
- Masstransit to send commands (
CQRS
) or publish events raised while interacting with our domain - Alba + an homemade framework for integration testing
- xUnit for unit testing
- Serilog for structured logs
Spirit & Coding style
- Use DDD to design and organize business logic
- Make the API as consistent as possible by returning standardized errors (=same structure) or results
- When creating ValueObject, return a
Result
instead of returningnull
or throwing exceptions - Always return
Result
in handler. Those are translated by controllers to404-NotFound
,404-BadRequest
based on their respective type ValueObject
must be built with the associated staticCreate
method- Endpoints are organized by
features
Flow
Since we cannot predict how fast the bank will return a response, we need to make the [POST] /payments
endpoint asynchronous.
Our current flow is:
- Send a payment request to
[POST] /payments
endpoint - Once the request validated, a
MakePayment
command is sent usingMassTransit
. In parallel, a job representing the current operation is created - Eventually the
[POST] /payments
endpoint returns202-Accepted
+ aLocation
header containing the url to query the job details - Calling the
[GET] /jobs/:id
endpoint returns the job status (Created
,Failed
orCompleted
), an error (if failed) and theEntityId
(if completed) - Once the
EntityId
(=PaymentId) determined, call the[GET] /payments/:id
endpoint to retrieve the payment details
Extra miles
Authentication & Security
- I used an
AuthenticationHandler
to validate theApiKey
passed through theAuthorization
header (formatbearer %APiKeys%
). Each ApiKey is then associated to claims (MerchantId
for example) - Exceptions are caught by the
ErrorHandlingMiddleware
- Model binding errors (=missing required parameters) are caught by the
ModelBindingValidatorFilter
Logs
I used Serilog for his structured logs
support.
ContextLoggingFilter
logs all incoming requests and associated responses (=result)UseSerilogRequestLogging
(middleware) logs the response time
One common scenario is to push these logs to AWS CloudWatch
. Since all logs are JSON objects, we can query our logs with the CW Logs
query syntax.
Example: { $.Elapsed > 10 }
CI
The JSON files under the ci
folder defines the CI pipeline I usually use in AWS:
- Starts when Github triggers
CodePipeline
through aWebhook
- Builds the code within a docker container and pushes the new docker image to an ECR repository
- Adds the
Staging
tag to this image - Starts the staging deployment by creating a new task definition and forcing a new deployment in the associated service
- Requires a manual approval (SNS notification)
- Adds the
Production
tag to this image - Starts the production deployment by creating a new task definition and forcing a new deployment in the associated service
Note: all scripts are processed by a PWS script replacing variables (%XXX%) and calling AWS CLI
commands. Another option could be to use a CloudFormation
template.
Note 2: Another PWS script is in charge of running all tests, merging the code to the master branch and eventually creating a new GitHub
release.
Docker
Docker images are built using the dockerfile
and buildspec.yml
files provided in this repository.
builspec.yml
is anAWS CodeBuild
file containing all commands to build our code. In our case, it runs thedocker build
command and push the created image to an ECR repositoryDockerfile
contains all the steps required to build a new image
Improvements
- Supports 3D Secure
- Improves CVV validation. Indeed most cards come with a 3 digits CVV. However AMEX come with a 4 digits.
- Supports more payment methods
- Persists data in a NoSQL/traditional database. Currently we use persist data in memory
- Deploy API and associated infrastructure elements with CloudFormation template