Skip to content

Commit

Permalink
Support payment failure codes 124, 125, 126, 127, 128 and 134 (#33)
Browse files Browse the repository at this point in the history
* Added support for payment failure 124, 125, 126, 127, 128 and 134

* When invalid json-requests are sent to the application, return a "Invalid JsonObject" Notification response

* Allow application to load request-response pairs when multiple *Requests and *Responses can be found in one directory

* Added tests for payment failure cases 124, 125, 126, 127, 128, 134 and Invalid JsonObject Notification response
  • Loading branch information
Kwok-he-Chu authored May 15, 2024
1 parent a960b6b commit 8253f49
Show file tree
Hide file tree
Showing 42 changed files with 1,094 additions and 275 deletions.
1 change: 1 addition & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
branches: [ main ]
paths-ignore:
- README.md
- '**/README.md'
- '.github/dependabot.yml'
- .dockerignore
- .gitignore
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
branches: [ main ]
paths-ignore:
- README.md
- '**/README.md'
- '.github/dependabot.yml'
- .dockerignore
- .gitignore
Expand All @@ -15,6 +16,7 @@ on:
branches: [ main ]
paths-ignore:
- README.md
- '**/README.md'
- '.github/dependabot.yml'
- .dockerignore
- .gitignore
Expand Down
76 changes: 54 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,51 @@
> [!IMPORTANT]
> This mock application is currently in its alpha-release and is not supporting every request and response. This application is **not** able to reject all invalid requests.
>
> We currently support the following [Terminal API requests](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/):
> - [PaymentRequest](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexopaymentrequest) | [PaymentResponse](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexopaymentresponse) | [PaymentBusyResponse](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexopaymentresponse)
> - [ReversalRequest](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexoreversalrequest) | [ReversalResponse](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexoreversalresponse)
> - [AbortRequest](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexoabortrequest)
> - [TransactionStatusRequest](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexotransactionstatusrequest) | [TransactionStatusResponse](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexotransactionstatusresponse)
> **[!IMPORTANT]** This mock application is currently in its alpha-release and is **not** supporting every terminal-api request and response. This application **cannot** reject all invalid requests.
>
> **Always test** your request and responses on your own physical terminal device first.
# Adyen Mock Terminal-API Application
The Adyen Mock Terminal-API Application is a mock server that handles incoming requests and returns hard-coded responses.
Developers can use this to test their application quickly by sending their requests to the Mock Terminal-API Application (`localhost:3000`) instead of the Adyen servers.
This application is **not** able to reject all invalid requests.
The Adyen Mock Terminal-API Application is a mock server that handles incoming requests and returns hard-coded responses. The application matches the request by looking at the `SaleToPOIRequest.MessageCategory`-field and returns the respective response, see `/public/payloads/...`-folder.

This tool can be used by developers to quickly end-to-end test their application by sending having their application send requests to the Mock Terminal API Application (`http://localhost:3000/sync`) instead of the Adyen servers. You can do this by overriding the `CloudApiEndpoint` on the client (config) of your application.

Currently, the Mock terminal is used to [end-to-end test](https://github.com/adyen-examples/adyen-testing-suite/tree/main/tests/in-person-payments) our In-Person Payments Integration Examples in [**.NET**](https://github.com/adyen-examples/adyen-dotnet-online-payments/tree/main/in-person-payments-example), [**Java**](https://github.com/adyen-examples/adyen-java-spring-online-payments/tree/main/in-person-payments-example) or [**Node.js**](https://github.com/adyen-examples/adyen-node-online-payments/tree/main/in-person-payments-example).
Currently, we use the Mock Terminal-API Application to [end-to-end test](https://github.com/adyen-examples/adyen-testing-suite/tree/main/tests/in-person-payments) our in-person payments integration-examples in [**.NET**](https://github.com/adyen-examples/adyen-dotnet-online-payments/tree/main/in-person-payments-example), [**Java**](https://github.com/adyen-examples/adyen-java-spring-online-payments/tree/main/in-person-payments-example) or [**Node.js**](https://github.com/adyen-examples/adyen-node-online-payments/tree/main/in-person-payments-example).

![Demo Card Mock Terminal-API Application](public/images/demo-card-terminal-api-application.gif)
![Demo Card Mock Terminal-API Application](public/images/demo-card-mock-terminal-api-application.gif)

[![Run this application on Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/adyen-examples/adyen-mock-terminal-api)

## Supported Requests/Responses

### Basics
We currently support the following [Terminal API requests/responses](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/) below.


| Request | Response | Description |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| [PaymentRequest](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexopaymentrequest) | [PaymentResponse](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexopaymentresponse) | A successful payment request. |
| [ReversalRequest](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexoreversalrequest) | [ReversalResponse](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexoreversalresponse) | A successful reversal request. |
| [TransactionStatusRequest](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexotransactionstatusrequest) | [TransactionStatusResponse](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexotransactionstatusresponse) | A successful transaction-status request. |
| | [PaymentBusyResponse](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexopaymentresponse) | Returned when the payment terminal is waiting for pin. |
| [AbortRequest](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/#comadyennexoabortrequest) | | Cancel an in-progress payment. **Note:** Only cancels the payment request. Parameters may slightly differ depending on the terminal. |

### Declined payments
In general, test payments generate the result Approved. To simulate declined payments, you can change the last three digits of the `RequestedAmount` that you specify in the payment request.
- We constructed the mock payloads using a `V400M-` terminal device.
- We used the `Blue-green Adyen point-of-sale test card` (card inserted & pin entered, no tap) to retrieve the responses.

We currently support the following [Payment Refusal Codes](https://docs.adyen.com/point-of-sale/testing-pos-payments/test-card-v1/#testing-declines), see below.


| Amount ending in | Result | Error Condition | Refusal Reason | Message |
|------------------:|--------------|-----------------|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| **124** | Failure | Refusal | 210 Not enough balance | NOT_ENOUGH_BALANCE |
| **125** | Failure | Refusal | 199 Card blocked | BLOCK_CARD |
| **126** | Failure | Refusal | 228 Card expired | CARD_EXPIRED |
| **127** | Failure | Refusal | 214 Declined online | INVALID_AMOUNT |
| **128** | Failure | InvalidCard | 214 Declined online | INVALID_CARD |
| **134** | Failure | WrongPIN | 129 Invalid online PIN | INVALID_PIN - **Remark:** The terminal shows "Incorrect PIN" and then "Enter PIN". Cancel the payment on the terminal to get the failure response. |

When an invalid JSON is provided, an `invalidJsonObjectNotificationResponse` is returned.


## Prerequisites
- Node.js 18+
Expand All @@ -45,7 +72,7 @@ Visit [http://localhost:3000/](http://localhost:3000/) to see the mock Terminal

There are two ways in which you can use the application.

1. We recommend to clone on of our our In-Person Payment Integration examples in [**.NET**](https://github.com/adyen-examples/adyen-dotnet-online-payments/tree/main/in-person-payments-example), [**Java**](https://github.com/adyen-examples/adyen-java-spring-online-payments/tree/main/in-person-payments-example) or [**Node.js**](https://github.com/adyen-examples/adyen-node-online-payments/tree/main/in-person-payments-example).
1. We recommend to clone one of our In-Person Payment Integration examples in [**.NET**](https://github.com/adyen-examples/adyen-dotnet-online-payments/tree/main/in-person-payments-example), [**Java**](https://github.com/adyen-examples/adyen-java-spring-online-payments/tree/main/in-person-payments-example) or [**Node.js**](https://github.com/adyen-examples/adyen-node-online-payments/tree/main/in-person-payments-example).

Once you've cloned the example, you can point the application to use `http://localhost:3000`, this configurable by overriding the `CloudApiEndpoint` URI. Now your application is ready to communicate to the terminal

Expand All @@ -72,23 +99,28 @@ We commit all our new features directly into our GitHub repository. Feel free to
### Example: Add your own mock request/response payload

1. Fork this repository and create a new branch.
2. The example below adds `paymentRequest.json` and `paymentResponse.json` (prefixed by `payment`). The `src/routes/services/payloadService` will automatically add this payload if the JSON is valid.
2. The example below adds `paymentRequest.json` and `paymentResponse.json`. The `src/routes/services/payloadService` will automatically load these files, if it's suffixed with `*Request`/`*Response` **and** if the JSON is valid.
- Create a new folder, in this example we use the existing **{payment}**-folder.
- Add your `Request` to `/public/payloads/**{payment}**/paymentRequest
- Add your `Response` to `/public/payloads/**{payment}**/paymentResponse
- Note: Every `-Request` should have a `-Response`. Except for those that require some kind of (state) logic (f.e. "paymentBusyResponse" triggers when a payment request is in-progress).
3. In `/src/routes/defaultRoutes.js`, find the `/sync`-endpoint and the following code snippet:
- **Note:** Every `*Request` should have a `*Response`, except for those that require some kind of state or logic (f.e: "paymentBusyResponse" triggers when a payment request is in-progress).
- **Note 2:** Keep naming-conventions camelCased and prefixed with its root-folder. Example: if the root-folder is located in `/payloads/example`, we name the jsons accordingly: `exampleRequest.json`/`exampleResponse.json`.
3. In `/src/routes/defaultRoutes.js`, find the `/sync`-endpoint and add the logic needed to map your requests-and-responses.

```js
if (req.body.SaleToPOIRequest.PaymentRequest) {
sendResponse(res, "payment");
if (req.body.SaleToPOIRequest.Request) {
sendOKResponse(res, "payment");
return;
}

if (req.body.SaleToPOIRequest.ExampleRequest) {
sendOKResponse(res, "example");
return;
}
```
3. Open a [Pull Request](https://github.com/adyen-examples/adyen-mock-terminal-api/compare) with your changes.




## License

MIT license. For more information, see the **LICENSE** file.
16 changes: 15 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,25 @@ const handlebars = require('express-handlebars');
const path = require('path');
const defaultRoutes = require('./src/routes/defaultRoutes');
const userInteractionRoutes = require('./src/routes/userInteractionRoutes');
const payloadService = require('./src/services/payloadsService')
const { userInteractionService } = require("./src/services/userInteractionService");

const port = process.env.PORT || 3000;

const app = express();
app.use(express.json());
app.use((err, req, res, next) => {
// Return a invalidJsonObject response when `express.json()` throws an error.
if (err instanceof SyntaxError) {
const invalidJsonObjectResponse = payloadService.getResponseByPrefix("invalidJsonObjectNotification");
userInteractionService.setLastResponse(invalidJsonObjectResponse);
res.status(200).send(invalidJsonObjectResponse);
return;
}

next(err);
});

app.use(express.static(path.join(__dirname, "public")));

app.engine("hbs", handlebars({
Expand All @@ -18,7 +32,7 @@ app.engine("hbs", handlebars({
toUpperCase: function (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
},
isEqual: function(stringA, stringB) {
isEqual: function (stringA, stringB) {
return stringA === stringB;
}
}
Expand Down
Loading

0 comments on commit 8253f49

Please sign in to comment.