Skip to content

Commit

Permalink
Merge pull request #1 from iZettle/first-release
Browse files Browse the repository at this point in the history
First release of example-integration
  • Loading branch information
skye-pp authored Oct 21, 2021
2 parents fd7a629 + 9a09ffc commit 1e53654
Show file tree
Hide file tree
Showing 102 changed files with 16,012 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
### macOS ###
*.DS_Store
.AppleDouble
.LSOverride

### Intellij ###
.idea/*
.run/*

### misc ###
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.cert
server/.cert
web/.cert
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Zettle Example Integration

- [Introduction](#introduction)
- [Structure](#structure)
- [Instructions](#instructions)
- [Dependencies](#dependencies)
- [Environment variables](#environment-variables)
- [Docker](#running-via-docker)
- [Certificates](#certificates)

## Introduction

This is an example project to help integrators get started, and understand the basics of Zettle’s public API.

You'll be able to clone this repository down and follow instructions to start web and backend components, so that you can perform a basic function on your account. For example, you can "Log in with Zettle", which will create a session allowing you to make requests to Zettle's other APIs such as Purchases or Merchant reports.

This code is intended to be used as reference material rather than being copied verbatim.

## Structure

This repository contains everything integrators need to run the project in one place, and is split into three sections:

* Top level: scripts and Docker-related files to make running everything simple
* `web`: everything relating to the React frontend, to start OAuth flows and display content to the user.
* `server`: everything related to the backend service, which makes calls to Zettle APIs and manages user sessions.

The first version is designed to be run on macOS (we assume the presence of `brew`), but could be extended to run on other operating systems too, if there's demand.

## Instructions
### Dependencies
Prior to starting the project locally you'll need to install dependencies for the server and the web app. Instructions on doing this are:

- [Web app readme](./web/README.md#installing-dependencies)
- [Server readme](./server/README.md#dependencies)

If you encounter difficulties running the project via Docker, ensure the above steps are completed first.

### Environment variables
There are some settings that need to be populated in each project so that the code can use your [developer.zettle.com](https://developer.zettle.com) credentials.

You should create a Public API Application here to get these values https://developer.zettle.com/applications/create/public

#### Web
These are set in `/web/.env`. An example set of values is provided at `/web/.env.example`

| Environment Variable| Value | Notes |
|----|-----|----|
| HTTPS | Boolean | Required to be true to use Zettle auth |
| SSL_CRT_FILE | File path string | For development server use |
| SSL_KEY_FILE | File path string | For development server use |
| DISABLE_ESLINT_PLUGIN | Boolean | Set to false to enforce lint/format rules |
| REACT_APP_SERVER_URL | URL:PORT string | Change this if you run the server at a different address |
| REACT_APP_ZETTLE_OAUTH_URL | https://oauth.zettle.com | |
| REACT_APP_ZETTLE_OAUTH_CLIENT_ID | UUID string | The client ID within your public app |

#### Server
These are set in `/server/.env`. An example set of values is provided at `/server/.env.example`

| Environment Variable| Value | Notes |
|----|-----|----|
| ZETTLE_OAUTH_BASE_URL | https://oauth.zettle.com | |
| ZETTLE_OAUTH_CLIENT_ID | UUID string | The client ID within your public app |
| ZETTLE_OAUTH_CLIENT_SECRET | String |The client secret within your public app |

### Running via Docker
To start both the server and web frontend you can execute the `./docker-run.sh` script in the root of this repository.
This will create docker images for both components of the example integration and then run them. You can verify that
the apps are running in the docker dashboard following the completion of the script.

## Certificates
The project uses a tool called `mkcert` to install self-signed certificates to use in both the server and web projects. It's automatically installed during the `docker-run.sh` process, and marks these certificates as valid in your browser (tested by us with Firefox).

If you want to run either of the projects independently, you'll need run `create-certificate.sh` once to generate the required certificates first.
29 changes: 29 additions & 0 deletions create-certificate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euxo pipefail

KEY=.cert/localhost.key
CERT=.cert/localhost.pem
KEYSTORE=server/.cert/keystore.jks

if [[ -f "$KEY" && -f "$CERT" && -f "$KEYSTORE" && -f "web/$CERT" ]]; then
echo "Key and cert exist and will not be recreated";
exit 0;
fi

echo "Generating new TLS certificate for localhost"

brew list mkcert || (brew install mkcert && echo "mkcert will ask for permission to install a new certificate authority on your system")
mkcert -install
mkdir -p .cert
mkcert -key-file $KEY -cert-file $CERT "localhost"

mkdir -p web/.cert
cp $KEY web/.cert/localhost.key
cp $CERT web/.cert/localhost.pem

echo "Converting generated certificate for use in server project"

mkdir -p server/.cert

openssl pkcs12 -export -in "$CERT" -inkey "$KEY" -out server/.cert/keystore.p12 -name "testCert" -password "pass:example-password"
keytool -importkeystore -noprompt -srckeystore server/.cert/keystore.p12 -srcstoretype pkcs12 -srcstorepass "example-password" -destkeystore server/.cert/keystore.jks -deststorepass "example-password" -destkeypass "example-password"
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: "3"
services:
web:
image: example-integration-web:test
ports:
- "3000:443"
env_file:
- web/.env
server:
image: example-integration-server:test
ports:
- "8001:8001"
env_file:
- server/.env
12 changes: 12 additions & 0 deletions docker-run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euxo pipefail

(test -f server/.env && test -f web/.env) || (echo "Please populate .env files as described in README.md" && exit 1)

./create-certificate.sh

cd web && ./build-image.sh
cd ../server && ./build-image.sh

cd ../
docker compose up -d
3 changes: 3 additions & 0 deletions server/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ZETTLE_OAUTH_BASE_URL=https://oauth.zettle.com
ZETTLE_OAUTH_CLIENT_ID=
ZETTLE_OAUTH_CLIENT_SECRET=
6 changes: 6 additions & 0 deletions server/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf

5 changes: 5 additions & 0 deletions server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.gradle
.idea
build
*.jks
*.p12
Empty file added server/.gitkeep
Empty file.
10 changes: 10 additions & 0 deletions server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM openjdk:16.0.2-slim
EXPOSE 8001
RUN mkdir /app
WORKDIR /app
ARG jar
ARG keystore
ADD $jar server.jar
RUN mkdir .cert
ADD $keystore .cert/keystore.jks
CMD java -jar server.jar
55 changes: 55 additions & 0 deletions server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Example Integration - Server

## Dependencies

You'll need a copy of JDK 16 to compile the server:

* Install [SDKMAN](https://sdkman.io/) and open a new terminal
* Install OpenJDK 16: `sdk install java 16.0.2-open`
* Verify that it's selected: `sdk current java` should say `Using java version 16.0.2-open`

You must also have registered a public Zettle application, and noted its client ID and client secret: https://developer.zettle.com/
The client ID and client secret are provided to the backend service as environment variables: `ZETTLE_OAUTH_CLIENT_ID` and `ZETTLE_OAUTH_CLIENT_SECRET`

## Running the server

* Open `server` folder with IntelliJ IDEA (Community or Ultimate)
* Run `Server` target to start the server on port 8001
* You must also replace the `ZETTLE_OAUTH_CLIENT_ID` and `ZETTLE_OAUTH_CLIENT_SECRET` environment variables as noted above

Sessions are stored in memory, so you'll be logged out if you restart the backend service.

## Docker

* Build an image: `./build-image.sh` will produce an image with the tag `example-integration-server:test`
* Run the image: `docker run -it -e ZETTLE_OAUTH_BASE_URL="https://oauth.zettle.com" -e ZETTLE_OAUTH_CLIENT_ID="replace with oauth client id" -e ZETTLE_OAUTH_CLIENT_SECRET="replace with oauth client secret" -p 8001:8001 example-integration:test`

## Terminal

* Running server: `./gradlew run`
* Running tests: `./gradlew test`
* Linting: `./gradlew ktlintCheck`
* Formatting: `./gradlew ktlintFormat`

## Example output

Using [httpie](https://httpie.io/):

```
→ http localhost:8001
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 13
Content-Type: text/plain; charset=UTF-8
Hello, world!
```

Running the Docker image:

```
→ docker run -it -e ZETTLE_OAUTH_BASE_URL="https://oauth.zettle.com" -e ZETTLE_OAUTH_CLIENT_ID="replace with oauth client id" -e ZETTLE_OAUTH_CLIENT_SECRET="replace with oauth client secret" -p 8001:8001 example-integration:test
[main] INFO ktor.application - Autoreload is disabled because the development mode is off.
[main] INFO ktor.application - Responding at http://0.0.0.0:8001
[main] INFO ktor.application - Application started in 0.066 seconds.
```
48 changes: 48 additions & 0 deletions server/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
kotlin("jvm") version "1.5.21"
kotlin("plugin.serialization") version "1.5.21"
id("org.jlleitschuh.gradle.ktlint") version "10.1.0"
id("com.github.johnrengelman.shadow") version "7.0.0"
application
}

repositories {
mavenCentral()
}

dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation(platform("io.ktor:ktor-bom:1.6.2"))
implementation("io.ktor:ktor-server-core")
implementation("io.ktor:ktor-server-netty")
implementation("io.ktor:ktor-serialization")
implementation("io.ktor:ktor-auth")
implementation("io.ktor:ktor-server-sessions")
implementation("io.ktor:ktor-network-tls-certificates")
implementation("org.slf4j:slf4j-simple:1.7.32")
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))
implementation("com.squareup.okhttp3:okhttp")

testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
testImplementation("io.ktor:ktor-server-test-host") {
// Prevents the inclusion of multiple SLF4J implementations - we already use slf4j-simple above
exclude(group = "ch.qos.logback")
}
}

application {
mainClass.set("server.MainKt")
}

tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "16"
}

tasks.withType<ShadowJar> {
archiveBaseName.set("server")
}
8 changes: 8 additions & 0 deletions server/app/src/main/kotlin/server/Logger.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package server

import org.slf4j.Logger
import org.slf4j.LoggerFactory

inline fun <reified T> logger(): Logger {
return LoggerFactory.getLogger(T::class.java)
}
Loading

0 comments on commit 1e53654

Please sign in to comment.