Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support payment failure codes 124, 125, 126, 127, 128 and 134 #33

Merged
merged 15 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading