Skip to content

Commit

Permalink
Feat/cloud terminal api (#211)
Browse files Browse the repository at this point in the history
* Add assets

* Add views

* Add js code

* Add config

* Add README

* Add In-Person Payments section

* Add in-person-payments build

* Remove unused

* Manage webhook events

* Update README

* Update README

* Add example of ADYEN_POS_POI_ID

* Minor edit

* Move events in Webhook Setup

* Correct access to field

* Remove unused

* Add SaleData with saleTransactionId

* Remove unused

* Init object with empty field

* Code formatting

* Initialise TerminalAPICloud client

* Refactor single index.js in multiple components

* Add in-person-payments-example

* Remove empty

* Check ADYEN_POS_POI_ID is defined

* Use optional chaining to prevent access to undefined variable/field

* Correct field names

* Remove unnecessary quotes from field name

* Log error message
  • Loading branch information
gcatanese authored Oct 17, 2023
1 parent 9a5ba79 commit bfb38bc
Show file tree
Hide file tree
Showing 28 changed files with 6,382 additions and 6 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,23 @@ jobs:
cache-dependency-path: 'giving-example'
- name: Build giving-example
run: npm install

build-in-person-payments:

runs-on: ubuntu-latest
defaults:
run:
working-directory: 'in-person-payments-example'
strategy:
matrix:
node-version: [18.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: 'in-person-payments-example'
- name: Build in-person-payments-example
run: npm install
4 changes: 4 additions & 0 deletions .gitpod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ tasks:
echo "Build subscription-example application"
cd subscription-example && npm install
;;
"in-person-payments-example")
echo "Build in-person-payments-example application"
cd in-person-payments-example && npm install
;;
*)
echo "Build default checkout-example application instead because '$path' is not defined ..."
cd checkout-example && npm install
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Get started by navigating to one of the supported demos below.
| [`Checkout Example`](checkout-example) | E-commerce checkout flow with different payment methods. | [See below](#checkout-example) |
| [`Advanced Checkout Example`](checkout-example-advanced) | E-commerce checkout flow with different payment methods, using the 3 steps flow. | [See below](#advanced-checkout-example) |
| [`Authorisation Adjustment Example`](authorisation-adjustment-example) | Pre-authorise a payment, adjust the authorised amount, capture or reverse the payment. | [See below](#authorisation-adjustment-example) |
| [`In-person Payments Example`](in-person-payments-example) | In-person payments using a POS terminal and the terminal-api/sync endpoint. | [See below](#in-person-payments-example) |
| [`Gift Card Example`](giftcard-example) | Gift Cards checkout flow using partial orders. | [See below](#gift-card-example) |
| [`Pay by Link Example`](paybylink-example) | Pay by Link flow. | [See below](#pay-by-link-example) |
| [`Subscription Example`](subscription-example) | Subscription flow using Adyen tokenization. | [See below](#subscription-example) |
Expand Down Expand Up @@ -48,6 +49,15 @@ The [`authorisation adjustment example`](authorisation-adjustment-example) repos

![Authorisation Adjustment Card Demo](authorisation-adjustment-example/public/images/cardauthorisationadjustment.gif)

## [In-person Payments Example](in-person-payments-example)
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/adyen-examples/adyen-node-online-payments/tree/main/in-person-payments-example)

[First time with Gitpod?](https://github.com/adyen-examples/.github/blob/main/pages/gitpod-get-started.md)

The [in-person payments example](in-person-payments-example) features an in-person payment [cloud terminal API](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/cloud/) integration. Within this demo app, you can make in-person payments using a terminal, initiate reversals (refunds) and check transaction statuses.

![Card In-person Payments Demo](in-person-payments-example/public/images/cardinpersonpayments.gif)

## [Gift Card Example](giftcard-example)

[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/adyen-examples/adyen-node-online-payments/tree/main/giftcard-example)
Expand Down
4 changes: 4 additions & 0 deletions in-person-payments-example/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env
.github
.idea
node_modules
12 changes: 12 additions & 0 deletions in-person-payments-example/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM node:16-alpine

WORKDIR /app

COPY package*.json ./

RUN npm install

# Copy the source files into the image
COPY . .

CMD [ "npm", "start" ]
93 changes: 93 additions & 0 deletions in-person-payments-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Adyen [In-person Payment Demo](https://docs.adyen.com/point-of-sale/) Integration Demo

## Run demo in one-click

[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/adyen-examples/adyen-node-online-payments/tree/main/in-person-payments-example)
 [First time with Gitpod?](https://github.com/adyen-examples/.github/blob/main/pages/gitpod-get-started.md)

## Description
This demo shows developers how to use the Adyen [Cloud Terminal API](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/cloud/) `/terminal-api/sync` to make requests to your connected terminal.

The following implementations are included:
- [Payment requests](https://docs.adyen.com/point-of-sale/basic-tapi-integration/make-a-payment/)
- [Referenced refund requests](https://docs.adyen.com/point-of-sale/basic-tapi-integration/refund-payment/referenced/)
- [Cancel/abort requests](https://docs.adyen.com/point-of-sale/basic-tapi-integration/cancel-a-transaction/)
- [Transaction status requests](https://docs.adyen.com/point-of-sale/basic-tapi-integration/verify-transaction-status/)

There are typically two ways to integrate in-person payments: local or cloud communications.
To find out which solution (or hybrid) suits your needs, visit the following [documentation page](https://docs.adyen.com/point-of-sale/design-your-integration/choose-your-architecture/#choosing-between-cloud-and-local).

You can find the [Terminal API documentation](https://docs.adyen.com/point-of-sale/design-your-integration/terminal-api/terminal-api-reference/) here.

This demo integrates the Adyen's API Library for Node.js ([GitHub](https://github.com/Adyen/adyen-node-api-library) | [Docs](https://docs.adyen.com/development-resources/libraries#javascript)).

![In-person Payments Demo](public/images/cardinpersonpayments.gif)

## Prerequisites
- A [terminal device](https://docs.adyen.com/point-of-sale/user-manuals/) and a [test card](https://docs.adyen.com/point-of-sale/testing-pos-payments/) from Adyen
- [Adyen API Credentials](https://docs.adyen.com/development-resources/api-credentials/)
- Node.js 18+

## 1. Installation

```
git clone https://github.com/adyen-examples/adyen-node-api-library.git
```

## 2. Set the environment variables
* [API key](https://docs.adyen.com/user-management/how-to-get-the-api-key)
* [Client Key](https://docs.adyen.com/user-management/client-side-authentication)
* [Merchant Account](https://docs.adyen.com/account/account-structure)
* [HMAC Key](https://docs.adyen.com/development-resources/webhooks/verify-hmac-signatures)
* `ADYEN_POS_POI_ID`: the unique ID of your payment terminal for the NEXO Sale to POI protocol.
- **Format:** `[device model]-[serial number]` **Example:** `V400m-123456789`

Create a `./.env` file with all required configuration

```
PORT=8080
ADYEN_API_KEY="your_API_key_here"
ADYEN_MERCHANT_ACCOUNT="your_merchant_account_here"
ADYEN_CLIENT_KEY="your_client_key_here"
ADYEN_HMAC_KEY="your_hmac_key_here"
ADYEN_POS_POI_ID=="your_pos_poi_id"
```

## 3. Run the application

```
cd in-person-payments-example
npm install
npm run dev
```

Visit [http://localhost:8080/](http://localhost:8080/) and perform payments and refunds

# Webhooks

Webhooks deliver asynchronous notifications about the payment status and other events that are important to receive and process.
You can find more information about webhooks in [this blog post](https://www.adyen.com/knowledge-hub/consuming-webhooks).

### Webhook setup

In the Customer Area under the `Developers → Webhooks` section, [create](https://docs.adyen.com/development-resources/webhooks/#set-up-webhooks-in-your-customer-area) a new `Standard webhook`.

A good practice is to set up basic authentication, copy the generated HMAC Key and set it as an environment variable. The application will use this to verify the [HMAC signatures](https://docs.adyen.com/development-resources/webhooks/verify-hmac-signatures/).

Make sure the webhook is **enabled**, so it can receive notifications.

The following webhooks `events` should be enabled:
* **AUTHORISATION**
* **CANCEL_OR_REFUND**
* **REFUND_FAILED**
* **REFUNDED_REVERSED**


### Expose an endpoint

This demo provides a simple webhook implementation exposed at `/api/webhooks/notifications` that shows you how to receive, validate and consume the webhook payload.

### Test your webhook

To make sure that the Adyen platform can reach your application, we have written a [Webhooks Testing Guide](https://github.com/adyen-examples/.github/blob/main/pages/webhooks-testing.md)
that explores several options on how you can easily achieve this (e.g. running on localhost or cloud).
44 changes: 44 additions & 0 deletions in-person-payments-example/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Initialise TerminalCloudAPI client
//

const dotenv = require("dotenv");
const { Client, Config, TerminalCloudAPI } = require("@adyen/api-library");

// enables environment variables by
// parsing the .env file and assigning it to process.env
dotenv.config({
path: "./.env",
});

var terminalCloudAPIClient;

const getClient = () => {
return terminalCloudAPIClient;
}

function client_init() {
if(process.env.ADYEN_API_KEY === undefined) {
throw new Error("ADYEN_API_KEY undefined");
}

if(process.env.ADYEN_HMAC_KEY === undefined) {
throw new Error("ADYEN_HMAC_KEY undefined");
}

if(process.env.ADYEN_POS_POI_ID === undefined) {
throw new Error("ADYEN_POS_POI_ID undefined");
}

const config = new Config({
apiKey: process.env.ADYEN_API_KEY,
environment: "TEST" // change to LIVE for production
});
const client = new Client({ config });

terminalCloudAPIClient = new TerminalCloudAPI(client);
}

client_init();

module.exports = { getClient }
47 changes: 47 additions & 0 deletions in-person-payments-example/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const express = require("express");
const path = require("path");
const hbs = require("express-handlebars");
const morgan = require("morgan");

const webhookRoute = require('./routes/webhookRoute')
const apiRoute = require('./routes/apiRoute')
const webRoute = require('./routes/webRoute')

// Unique ID for the system where you send this request from
global.POS_SALE_ID = "SALE_ID_POS_42";


// init app
const app = express();
// setup request logging
app.use(morgan("dev"));
// Parse JSON bodies
app.use(express.json());
// Parse URL-encoded bodies
app.use(express.urlencoded({ extended: true }));
// Serve client from build folder
app.use(express.static(path.join(__dirname, "/public")));


app.engine(
"handlebars",
hbs.engine({
defaultLayout: "main",
layoutsDir: __dirname + "/views/layouts",
helpers: require("./util/helpers"),
})
);

app.set("view engine", "handlebars");

// define routes
app.use("/api/webhooks/notifications", webhookRoute);
app.use("/api", apiRoute);
app.use("/", webRoute);

function getPort() {
return process.env.PORT || 8080;
}

// Start server
app.listen(getPort(), () => console.log(`Server started -> http://localhost:${getPort()}`));
Loading

0 comments on commit bfb38bc

Please sign in to comment.