commit eb7a28bbf646d89571417925ede7e98737939dad Author: jillingk <93914435+jillingk@users.noreply.github.com> Date: Wed May 17 16:37:57 2023 +0200 start diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0132d44 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# Change these settings to your own preference +indent_style = space +indent_size = 4 + +[*.{ts,tsx,js,jsx,json,css,scss,yml,html}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5330eae --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,24 @@ +name: Java CI with Gradle + +on: + push: + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' +# cache: gradle + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build --debug diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fba4d84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ +env.tmp diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..a96de4e --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,15 @@ +tasks: +- init: ./gradlew bootJar + command: ./gradlew bootRun + +# exposed ports +ports: +- port: 8080 + onOpen: open-preview + +vscode: + extensions: + - redhat.java + - vscjava.vscode-java-debug + - vscjava.vscode-java-test + - pivotal.vscode-spring-boot diff --git a/.vagrant/bundler/global.sol b/.vagrant/bundler/global.sol new file mode 100644 index 0000000..e118b50 --- /dev/null +++ b/.vagrant/bundler/global.sol @@ -0,0 +1 @@ +{"dependencies":[["racc",["~> 1.4"]],["nokogiri",[">= 0"]],["vagrant-parallels",["= 2.3.1"]]],"checksum":"306cc775b2e3e42f6683a775882841f765b492aba52a9466599f24190c03f6c4","vagrant_version":"2.3.4"} \ No newline at end of file diff --git a/.vagrant/machines/default/parallels/action_provision b/.vagrant/machines/default/parallels/action_provision new file mode 100644 index 0000000..c495752 --- /dev/null +++ b/.vagrant/machines/default/parallels/action_provision @@ -0,0 +1 @@ +1.5:8c33c12b-0476-4308-a4eb-0a0e3f6e7c04 \ No newline at end of file diff --git a/.vagrant/machines/default/parallels/action_set_name b/.vagrant/machines/default/parallels/action_set_name new file mode 100644 index 0000000..477554c --- /dev/null +++ b/.vagrant/machines/default/parallels/action_set_name @@ -0,0 +1 @@ +1684332805 \ No newline at end of file diff --git a/.vagrant/machines/default/parallels/box_meta b/.vagrant/machines/default/parallels/box_meta new file mode 100644 index 0000000..a12e2a9 --- /dev/null +++ b/.vagrant/machines/default/parallels/box_meta @@ -0,0 +1 @@ +{"name":"jeffnoxon/ubuntu-20.04-arm64","version":"1.0.1","provider":"parallels","directory":"boxes/jeffnoxon-VAGRANTSLASH-ubuntu-20.04-arm64/1.0.1/parallels"} \ No newline at end of file diff --git a/.vagrant/machines/default/parallels/creator_uid b/.vagrant/machines/default/parallels/creator_uid new file mode 100644 index 0000000..99f9f07 --- /dev/null +++ b/.vagrant/machines/default/parallels/creator_uid @@ -0,0 +1 @@ +502 \ No newline at end of file diff --git a/.vagrant/machines/default/parallels/id b/.vagrant/machines/default/parallels/id new file mode 100644 index 0000000..2cc8175 --- /dev/null +++ b/.vagrant/machines/default/parallels/id @@ -0,0 +1 @@ +8c33c12b-0476-4308-a4eb-0a0e3f6e7c04 \ No newline at end of file diff --git a/.vagrant/machines/default/parallels/index_uuid b/.vagrant/machines/default/parallels/index_uuid new file mode 100644 index 0000000..0916b11 --- /dev/null +++ b/.vagrant/machines/default/parallels/index_uuid @@ -0,0 +1 @@ +f8191b6c631844249c51d3efa644d6e5 \ No newline at end of file diff --git a/.vagrant/machines/default/parallels/private_key b/.vagrant/machines/default/parallels/private_key new file mode 100644 index 0000000..518dc98 --- /dev/null +++ b/.vagrant/machines/default/parallels/private_key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAuC1nYsYLVfRM8SqObjVwqawSmMVWdauwghkDpiw1DmCfKG2C +OkGuMOkCfEpiP86IpJEf1gZbVoBncKg3u5NKHF44BcaMPAunCde6sf68fUs8RHA3 +QyaOwmRC8rdpzXAeVycoV1gxqN4t/wwRuK73/4YVNmVxYbD6TXh1YGKjRpZ8qV6b +WDLaX0PanhAFdfXvKEViZi+Scm/9m/bBxgJVcCxV30KRQGFqT6QWHgIRoDHhFVSN +ISPFsd0yOnNH4UQDAtBUiKmAroXvYubdpot5JqmlnsHVE8SXSEIwR0Ma6PLLYBnJ +0V0KURu4JXMKu+Bc7FL18BrwhXz7ejz6tx586QIDAQABAoIBAEjt+bVcFJsJrPoG +TvYHBUC6mXaTFcRsou0xOIexk6TIz90zV9D3tyE48zoCPltjzinUKZYXWE2tdMhg +ZAlLrpoDGQ34bAlO1+sG2K+o3jyC8S9sfAWEHB2PlA8GX8fwsKL0J35GtwtWR0Jq +Jg5KgntVPcsXakkzyjmpDFmJVXwaaIn0DAxjdbxRtczyaqztBf/uzmZC0gAR1go+ +d/eAMnVEIaKkQjPGzWo5y6u4SDDf3Ec1bFw0o+tKuQmRLC111/kHHSZUm1yVE2Nt +E1a6nb1KqUof/gx6IU59HyMcyFb2cdGNzJdpIQ8JAMsN0kwGxT4wD57qy8bnBbgS +DTLTFQECgYEA6NmZ1JySpE43o8BneqJcK75CtTZxfjW3UFpRZvd1Wk1ocJ85cmzq +PwKZHcSwGFL+bY+jfQK9r5/1utnSJ6AHOl8lM1k4AKaTrJSJcSYK8/a+nF9Db6Al +cdyDcpa6dWVkghTGY3ra0ASTE+C3ROmlqFLjNvigZ1vraglUdt0iLokCgYEAyn0B +7YbTHzZP9E/SJ8+MmDoHvmPTgLD4GyOXx1vaCPEeuJ/QQG9UFA8wVrrN2KhXQj9F +KqiYD02rF0ClVLWU//jR8hYuoUsIOuO/Q8MysVKtcd2h94Oo9DCP77srNAXDQJVs +dGw2wf8sfy0uJqexHoR9m5oQtbbsK3BMhWvVQ2ECgYBoXjh8Ot3jtYds5226YxHL +LZF65fdC6dLLul/oNhysqCua51N5fWYVbhWxaRrBHFocQ5i1DMh28szSvpVa23jB +6imjdWcLq55faYidZb7dEQjo0C5K105RVdS5ROQ4gITnZWZy6nsvaA2AiGW9BrkQ +1G4xkolbKrE/KwVn7cBHeQKBgDo7LvW/AzIDrYSl+9rlDhqs/ZMcJDPrML6lQr3b +RV2y8dHIa9xL59MI9y7J8+6tosJzbZGyAGDORjiep60vtxTR2paTGH/cExbUbimv +lgF2tFh9k1mZ7FmlKY+YtPj1+0u0bQciI7Ewj/xW4uHM8dvD4FeCO/H2E/7+AZOR +CjihAoGBAJiPAtv19G9R6i/rhnZMYD/vbc4enNPyit3SIBiZ5kXG+VxJ4opeQWqc +Khk2LcwG+VdPoIchhM5RJYPU+14v19XL1X/bGEWXJ+jmY5vNVErYkT2QBhZvb0Md +jFTCAMpgJawlQ6lGcl3JXoo0cX0LRtvs50Ap61oSra9nFpY9eT1R +-----END RSA PRIVATE KEY----- diff --git a/.vagrant/machines/default/parallels/synced_folders b/.vagrant/machines/default/parallels/synced_folders new file mode 100644 index 0000000..0104569 --- /dev/null +++ b/.vagrant/machines/default/parallels/synced_folders @@ -0,0 +1 @@ +{"parallels":{"/home/vagrant/adyen-java-spring":{"disabled":false,"guestpath":"/home/vagrant/adyen-java-spring","hostpath":"/Users/jilling/Downloads/adyen-java-spring-online-payments-8984812cc775f66ebb8e2b8a3e14794cabe57a0e","__vagrantfile":true}}} \ No newline at end of file diff --git a/.vagrant/machines/default/parallels/vagrant_cwd b/.vagrant/machines/default/parallels/vagrant_cwd new file mode 100644 index 0000000..b87aa6c --- /dev/null +++ b/.vagrant/machines/default/parallels/vagrant_cwd @@ -0,0 +1 @@ +/Users/jilling/Downloads/adyen-java-spring-online-payments-8984812cc775f66ebb8e2b8a3e14794cabe57a0e \ No newline at end of file diff --git a/.vagrant/rgloader/loader.rb b/.vagrant/rgloader/loader.rb new file mode 100644 index 0000000..c3c05b0 --- /dev/null +++ b/.vagrant/rgloader/loader.rb @@ -0,0 +1,9 @@ +# This file loads the proper rgloader/loader.rb file that comes packaged +# with Vagrant so that encoded files can properly run with Vagrant. + +if ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] + require File.expand_path( + "rgloader/loader", ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"]) +else + raise "Encoded files can't be read outside of the Vagrant installer." +end diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ce59e0c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Adyen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ea1025 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# Adyen [online payment](https://docs.adyen.com/checkout) integration demos + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/adyen-examples/adyen-java-spring-online-payments) + + +This repository includes examples of PCI-compliant UI integrations for online payments with Adyen. Within this demo app, you'll find a simplified version of an e-commerce website, complete with commented code to highlight key features and concepts of Adyen's API. Check out the underlying code to see how you can integrate Adyen to give your shoppers the option to pay with their preferred payment methods, all in a seamless checkout experience. + +![Card checkout demo](src/main/resources/static/images/cardcheckout.gif) + +## Supported Integrations + +**Java + Spring Boot + Thymeleaf** demos of the following client-side integrations are currently available in this repository: + +- [Drop-in](https://docs.adyen.com/checkout/drop-in-web) +- [Component](https://docs.adyen.com/checkout/components-web) + - ACH + - Alipay + - Card (3DS2) + - Dotpay + - giropay + - iDEAL + - Klarna (Pay now, Pay later, Slice it) + - SOFORT + - PayPal + +The Demo leverages Adyen's API Library for Java ([GitHub](https://github.com/Adyen/adyen-java-api-library) | [Docs](https://docs.adyen.com/development-resources/libraries#java)). + +## Requirements + +- Java 11 +- Network access to maven central + +## Installation + +1. Clone this repo: + +``` +git clone https://github.com/adyen-examples/adyen-java-spring-online-payments.git +``` + +## Usage + +1. Set environment variables for your [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) - Remember to add `http://localhost:8080` as an origin for client key, and merchant account name: + +```shell +export ADYEN_API_KEY=yourAdyenApiKey +export ADYEN_MERCHANT_ACCOUNT=yourAdyenMerchantAccount +export ADYEN_CLIENT_KEY=yourAdyenClientKey +``` + +On Windows CMD you can use below commands instead + +```shell +set ADYEN_API_KEY=yourAdyenApiKey +set ADYEN_MERCHANT_ACCOUNT=yourAdyenMerchantAccount +set ADYEN_CLIENT_KEY=yourAdyenClientKey +``` + +2. Start the server: + +``` +./gradlew bootRun +``` + +3. Visit [http://localhost:8080/](http://localhost:8080/) to select an integration type. + +To try out integrations with test card numbers and payment method details, see [Test card numbers](https://docs.adyen.com/development-resources/test-cards/test-card-numbers). + +## Contributing + +We commit all our new features directly into our GitHub repository. Feel free to request or suggest new features or code changes yourself as well! + +## License + +MIT license. For more information, see the **LICENSE** file in the root directory. diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..d563010 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,11 @@ +Vagrant.configure("2") do |config| + config.vm.box = "jeffnoxon/ubuntu-20.04-arm64" + config.vm.provider :parallels do |v| + v.memory = "4096" + v.cpus = 4 + v.update_guest_tools = true + end + config.vm.synced_folder '.', '/home/vagrant/adyen-java-spring', disabled: false + config.vm.network :forwarded_port, guest:3000, host: 3000 + config.vm.network :forwarded_port, guest:8080, host: 8080 +end \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..12d6b08 --- /dev/null +++ b/build.gradle @@ -0,0 +1,28 @@ +plugins { + id 'org.springframework.boot' version '2.3.5.RELEASE' + id 'io.spring.dependency-management' version '1.0.10.RELEASE' + id 'java' +} + +group = 'com.adyen' +version = '0.0.1-SNAPSHOT' +sourceCompatibility = '11' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' + implementation 'com.adyen:adyen-java-api-library:20.0.0' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + testImplementation('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' + } +} + +test { + useJUnitPlatform() +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..12d38de --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..41b9c89 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'online-payments' diff --git a/src/main/java/com/adyen/checkout/Config.java b/src/main/java/com/adyen/checkout/Config.java new file mode 100644 index 0000000..c6651f9 --- /dev/null +++ b/src/main/java/com/adyen/checkout/Config.java @@ -0,0 +1,12 @@ +package com.adyen.checkout; + +import org.springframework.context.annotation.Bean; + +import nz.net.ultraq.thymeleaf.LayoutDialect; + +public class Config { + @Bean + public LayoutDialect layoutDialect() { + return new LayoutDialect(); + } +} diff --git a/src/main/java/com/adyen/checkout/OnlinePaymentsApplication.java b/src/main/java/com/adyen/checkout/OnlinePaymentsApplication.java new file mode 100644 index 0000000..94e14cc --- /dev/null +++ b/src/main/java/com/adyen/checkout/OnlinePaymentsApplication.java @@ -0,0 +1,20 @@ +package com.adyen.checkout; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class OnlinePaymentsApplication { + private static final Logger log = LoggerFactory.getLogger(OnlinePaymentsApplication.class); + + public static void main(String[] args) { + SpringApplication.run(OnlinePaymentsApplication.class, args); + log.info("\n----------------------------------------------------------\n\t" + + "Application is running! Access URLs:\n\t" + + "Local: \t\thttp://localhost:8080\n\t" + + "\n----------------------------------------------------------"); + } + +} diff --git a/src/main/java/com/adyen/checkout/api/CheckoutResource.java b/src/main/java/com/adyen/checkout/api/CheckoutResource.java new file mode 100644 index 0000000..c291a70 --- /dev/null +++ b/src/main/java/com/adyen/checkout/api/CheckoutResource.java @@ -0,0 +1,191 @@ +package com.adyen.checkout.api; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.view.RedirectView; +import com.adyen.Client; +import com.adyen.enums.Environment; +import com.adyen.model.checkout.*; +import com.adyen.service.Checkout; +import com.adyen.service.exception.ApiException; + +/** + * REST controller for using Adyen checkout API + */ +@RestController +@RequestMapping("/api") +public class CheckoutResource { + private final Logger log = LoggerFactory.getLogger(CheckoutResource.class); + + @Value("${ADYEN_MERCHANT_ACCOUNT}") + private String merchantAccount; + + private final Checkout checkout; + + public CheckoutResource(@Value("${ADYEN_API_KEY}") String apiKey) { + var client = new Client(apiKey, Environment.TEST); + this.checkout = new Checkout(client); + } + + /** + * {@code POST /getPaymentMethods} : Get valid payment methods. + * + * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the paymentMethods response. + * @throws IOException from Adyen API. + * @throws ApiException from Adyen API. + */ + @PostMapping("/getPaymentMethods") + public ResponseEntity paymentMethods() throws IOException, ApiException { + var paymentMethodsRequest = new PaymentMethodsRequest(); + paymentMethodsRequest.setMerchantAccount(merchantAccount); + paymentMethodsRequest.setChannel(PaymentMethodsRequest.ChannelEnum.WEB); + + log.info("REST request to get Adyen payment methods {}", paymentMethodsRequest); + var response = checkout.paymentMethods(paymentMethodsRequest); + return ResponseEntity.ok() + .body(response); + } + + /** + * {@code POST /initiatePayment} : Make a payment. + * + * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the paymentMethods response. + * @throws IOException from Adyen API. + * @throws ApiException from Adyen API. + */ + @PostMapping("/initiatePayment") + public ResponseEntity payments(@RequestBody PaymentsRequest body, HttpServletRequest request) throws IOException, ApiException { + var paymentRequest = new PaymentsRequest(); + paymentRequest.setMerchantAccount(merchantAccount); // required + paymentRequest.setChannel(PaymentsRequest.ChannelEnum.WEB); // required + + var amount = new Amount() + .currency(findCurrency(body.getPaymentMethod().getType())) + .value(1000L); // value is 10€ in minor units + paymentRequest.setAmount(amount); + + var orderRef = UUID.randomUUID().toString(); + paymentRequest.setReference(orderRef); // required + // required for 3ds2 redirect flow + paymentRequest.setReturnUrl("http://localhost:8080/api/handleShopperRedirect?orderRef=" + orderRef); + + // required for 3ds2 native flow + paymentRequest.setAdditionalData(Collections.singletonMap("allow3DS2", "true")); + // required for 3ds2 native flow + paymentRequest.setOrigin("http://localhost:8080"); + // required for 3ds2 + paymentRequest.setBrowserInfo(body.getBrowserInfo()); + // required by some issuers for 3ds2 + paymentRequest.setShopperIP(request.getRemoteAddr()); + + paymentRequest.setPaymentMethod(body.getPaymentMethod()); + + var type = body.getPaymentMethod().getType(); + // required for Klarna + if (type.contains("klarna")) { + paymentRequest.setCountryCode("DE"); + paymentRequest.setShopperReference("1234"); + paymentRequest.setShopperEmail("youremail@email.com"); + paymentRequest.setShopperLocale("en_US"); + var lineItems = new ArrayList(); + lineItems.add( + new LineItem().quantity(1L).amountExcludingTax(331L).taxPercentage(2100L).description("Sunglasses").id("Item 1").taxAmount(69L).amountIncludingTax(400L) + ); + lineItems.add( + new LineItem().quantity(2L).amountExcludingTax(248L).taxPercentage(2100L).description("Headphones").id("Item 2").taxAmount(52L).amountIncludingTax(300L) + ); + paymentRequest.setLineItems(lineItems); + } else if (type.contains("paypal")) { + paymentRequest.setCountryCode("US"); + } + + log.info("REST request to make Adyen payment {}", paymentRequest); + var response = checkout.payments(paymentRequest); + return ResponseEntity.ok() + .body(response); + } + + /** + * {@code POST /submitAdditionalDetails} : Make a payment. + * + * @return the {@link ResponseEntity} with status {@code 200 (Ok)} and with body the paymentMethods response. + * @throws IOException from Adyen API. + * @throws ApiException from Adyen API. + */ + @PostMapping("/submitAdditionalDetails") + public ResponseEntity payments(@RequestBody PaymentsDetailsRequest detailsRequest) throws IOException, ApiException { + log.info("REST request to make Adyen payment details {}", detailsRequest); + var response = checkout.paymentsDetails(detailsRequest); + return ResponseEntity.ok() + .body(response); + } + + /** + * {@code GET /handleShopperRedirect} : Handle redirect during payment. + * + * @return the {@link RedirectView} with status {@code 302} + * @throws IOException from Adyen API. + * @throws ApiException from Adyen API. + */ + @GetMapping("/handleShopperRedirect") + public RedirectView redirect(@RequestParam(required = false) String payload, @RequestParam(required = false) String redirectResult, @RequestParam String orderRef) throws IOException, ApiException { + var detailsRequest = new PaymentsDetailsRequest(); + if (redirectResult != null && !redirectResult.isEmpty()) { + detailsRequest.setDetails(Collections.singletonMap("redirectResult", redirectResult)); + } else if (payload != null && !payload.isEmpty()) { + detailsRequest.setDetails(Collections.singletonMap("payload", payload)); + } + + return getRedirectView(detailsRequest); + } + + private RedirectView getRedirectView(final PaymentsDetailsRequest detailsRequest) throws ApiException, IOException { + log.info("REST request to handle payment redirect {}", detailsRequest); + var response = checkout.paymentsDetails(detailsRequest); + var redirectURL = "/result/"; + switch (response.getResultCode()) { + case AUTHORISED: + redirectURL += "success"; + break; + case PENDING: + case RECEIVED: + redirectURL += "pending"; + break; + case REFUSED: + redirectURL += "failed"; + break; + default: + redirectURL += "error"; + break; + } + return new RedirectView(redirectURL + "?reason=" + response.getResultCode()); + } + + /* ################# UTILS ###################### */ + private String findCurrency(String type) { + switch (type) { + case "paypal": + case "ach": + return "USD"; + case "wechatpayqr": + case "alipay": + return "CNY"; + case "dotpay": + return "PLN"; + case "boletobancario": + case "boletobancario_santander": + return "BRL"; + default: + return "EUR"; + } + } + /* ################# end UTILS ###################### */ +} diff --git a/src/main/java/com/adyen/checkout/web/CheckoutController.java b/src/main/java/com/adyen/checkout/web/CheckoutController.java new file mode 100644 index 0000000..57a5563 --- /dev/null +++ b/src/main/java/com/adyen/checkout/web/CheckoutController.java @@ -0,0 +1,38 @@ +package com.adyen.checkout.web; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +public class CheckoutController { + @Value("${ADYEN_CLIENT_KEY}") + private String clientKey; + + @GetMapping("/") + public String index() { + return "index"; + } + + @GetMapping("/preview") + public String preview(@RequestParam String type, Model model) { + model.addAttribute("type", type); + return "preview"; + } + + @GetMapping("/checkout") + public String checkout(@RequestParam String type, Model model) { + model.addAttribute("type", type); + model.addAttribute("clientKey", clientKey); + return "checkout"; + } + + @GetMapping("/result/{type}") + public String result(@PathVariable String type, Model model) { + model.addAttribute("type", type); + return "result"; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..9fc68d4 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.jackson.default-property-inclusion=non_null diff --git a/src/main/resources/static/adyenImplementation.js b/src/main/resources/static/adyenImplementation.js new file mode 100644 index 0000000..a898842 --- /dev/null +++ b/src/main/resources/static/adyenImplementation.js @@ -0,0 +1,125 @@ +const clientKey = document.getElementById("clientKey").innerHTML; +const type = document.getElementById("type").innerHTML; + +async function initCheckout() { + try { + const paymentMethodsResponse = await callServer("/api/getPaymentMethods"); + const configuration = { + paymentMethodsResponse: filterUnimplemented(paymentMethodsResponse), + clientKey, + locale: "en_US", + environment: "test", + showPayButton: true, + paymentMethodsConfiguration: { + ideal: { + showImage: true, + }, + card: { + hasHolderName: true, + holderNameRequired: true, + name: "Credit or debit card", + amount: { + value: 1000, + currency: "EUR", + }, + }, + paypal: { + amount: { + value: 1000, + currency: "USD", + }, + environment: "test", // Change this to "live" when you're ready to accept live PayPal payments + countryCode: "US", // Only needed for test. This will be automatically retrieved when you are in production. + onCancel: (data, component) => { + component.setStatus('ready'); + }, + } + }, + onSubmit: (state, component) => { + if (state.isValid) { + handleSubmission(state, component, "/api/initiatePayment"); + } + }, + onAdditionalDetails: (state, component) => { + handleSubmission(state, component, "/api/submitAdditionalDetails"); + }, + }; + // `spring.jackson.default-property-inclusion=non_null` needs to set in + // src/main/resources/application.properties to avoid NPE here + const checkout = new AdyenCheckout(configuration); + checkout.create(type).mount(document.getElementById("payment")); + } catch (error) { + console.error(error); + alert("Error occurred. Look at console for details"); + } +} + +function filterUnimplemented(pm) { + pm.paymentMethods = pm.paymentMethods.filter((it) => + [ + "scheme", + "ideal", + "dotpay", + "giropay", + // "sepadirectdebit", + "directEbanking", + "ach", + "alipay", + "klarna_paynow", + "klarna", + "klarna_account", + "paypal", + ].includes(it.type) + ); + return pm; +} + +// Event handlers called when the shopper selects the pay button, +// or when additional information is required to complete the payment +async function handleSubmission(state, component, url) { + try { + const res = await callServer(url, state.data); + handleServerResponse(res, component); + } catch (error) { + console.error(error); + alert("Error occurred. Look at console for details"); + } +} + +// Calls your server endpoints +async function callServer(url, data) { + const res = await fetch(url, { + method: "POST", + body: data ? JSON.stringify(data) : "", + headers: { + "Content-Type": "application/json", + }, + }); + + return await res.json(); +} + +// Handles responses sent from your server to the client +function handleServerResponse(res, component) { + if (res.action) { + component.handleAction(res.action); + } else { + switch (res.resultCode) { + case "Authorised": + window.location.href = "/result/success"; + break; + case "Pending": + case "Received": + window.location.href = "/result/pending"; + break; + case "Refused": + window.location.href = "/result/failed"; + break; + default: + window.location.href = "/result/error"; + break; + } + } +} + +initCheckout(); diff --git a/src/main/resources/static/css/application.css b/src/main/resources/static/css/application.css new file mode 100644 index 0000000..0d6fda3 --- /dev/null +++ b/src/main/resources/static/css/application.css @@ -0,0 +1,413 @@ +/* General page body */ + +html, +body { + width: 100%; + margin: 0; + font-family: "Fakt", sans-serif, Helvetica, Arial; +} + +*, +:after, +:before { + box-sizing: border-box; +} + +a, +u { + text-decoration: none; +} + +a:hover { + text-decoration: none; +} + +.hidden { + display: none; +} + +#header { + background: #fff; + border-bottom: 1px solid #e6e9eb; + height: 44px; + left: 0; + margin-bottom: 24px; + padding: 14px 26px; + position: fixed; + text-align: center; + top: 0; + width: 100%; + z-index: 2; + box-sizing: border-box; +} + +/* Buttons */ + +.button { + background: #00112c; + border: 0; + border-radius: 6px; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: 1em; + font-weight: 500; + margin: 0; + padding: 15px; + text-align: center; + transition: background 0.3s ease-out, box-shadow 0.3s ease-out; + width: 100%; +} + +.button:hover { + background: #1c3045; + box-shadow: 0 3px 4px rgba(0, 15, 45, 0.2); +} + +.button:active { + background: #3a4a5c; +} + +.button:disabled { + background: #e6e9eb; + box-shadow: none; + cursor: not-allowed; + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; +} + +/* end General page body */ + +/* Index page */ + +.main-container { + margin: auto; + max-width: 1048px; + padding: 0 16px; + display: flex; + flex-direction: column; +} + +.integration-list { + display: flex; + margin-top: 6%; + max-width: 1048px; + flex-wrap: wrap; + justify-content: space-between; + list-style: none; + margin: 0; + padding: 0; +} + +@media (min-width: 768px) { + .integration-list { + margin-left: -8px; + margin-bottom: -8px; + margin-right: -8px; + } +} + +@media (min-width: 1024px) { + .integration-list { + margin-left: -16px; + margin-bottom: -16px; + margin-right: -16px; + } +} + +.integration-list-item { + background: #f7f8f9; + border-radius: 6px; + flex: 1 1 0; + margin: 4px; + min-width: 40%; + position: relative; + display: flex; + justify-content: center; + align-items: center; + border: 1px solid #f7f8f9; +} + +.integration-list-item:hover { + box-shadow: 0 16px 24px 0 #e5eaef; + text-decoration: none; + border: 1px solid #06f; +} + +@media (min-width: 768px) { + .integration-list-item { + margin-left: 16px; + margin-bottom: 16px; + margin-right: 16px; + margin-top: 16px; + min-width: 25%; + } +} + +.integration-list-item-link { + padding: 20px; + width: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +@media (min-width: 768px) { + .integration-list-item-link { + padding-left: 28px; + padding-bottom: 28px; + padding-right: 28px; + padding-top: 28px; + } +} + +.integration-list-item-title { + margin: 0; + text-align: center; + color: #00112c; + font-size: 1em; + font-weight: 700; + margin: 10px 0 0; +} + +@media (min-width: 768px) { + .integration-list-item-title { + font-size: 24px; + margin-left: 0; + margin-bottom: 6px; + margin-right: 0; + } +} + +.integration-list-item-subtitle { + color: #00112c; + font-size: 0.67em; + font-weight: 700; + margin: 10px 0 0; +} + +@media (min-width: 768px) { + .integration-list-item-subtitle { + font-size: 16px; + margin-left: 0; + margin-bottom: 6px; + margin-right: 0; + } +} + +.title-container { + display: flex; + flex-direction: column; + align-items: center; +} + +.info { + margin-top: 10%; + color: #00112c; +} + +/* end Index page */ + +/* Cart preview page */ + +.shopping-cart { + float: right; +} +@media (min-width: 768px) { + .shopping-cart { + padding-left: 0; + padding-bottom: 0; + padding-right: 0; + padding-top: 3px; + } +} +.shopping-cart-link { + display: inline-block; + position: relative; +} +.order-summary-list { + border-top: 1px solid #e6e9eb; +} +.order-summary-list-list-item { + border-bottom: 1px solid #e6e9eb; + display: flex; + height: 97px; +} +.order-summary-list-list-item-image { + height: 64px; + margin: 16px; + width: 64px; +} +.order-summary-list-list-item-title { + font-weight: 700; + margin: auto auto auto 0; +} +.order-summary-list-list-item-price { + color: #687282; + margin: auto 16px; + text-align: right; + width: 80px; +} +@media (min-width: 768px) { + .order-summary-list-list-item-price { + margin-left: 24px; + margin-bottom: auto; + margin-right: 24px; + margin-top: auto; + } +} +.order-summary-list-list-item-remove-product { + background: none; + border: 0; + cursor: pointer; + height: 25px; + margin: auto 0; + padding: 0; + width: 25px; +} +.cart { + text-align: center; + margin-top: 80px; +} +.cart-footer { + font-weight: 700; + margin-top: 17px; + text-align: right; +} +@media (min-width: 768px) { + .cart-footer { + margin-top: 24px; + } +} +.cart-footer .button { + margin-top: 30px; + width: 100%; +} +@media (min-width: 768px) { + .cart-footer .button { + margin-top: 0; + width: 288px; + } +} +.cart-footer-amount { + margin-left: 16px; + margin-right: 24px; +} +.whole-preview { + margin: auto; + max-width: 1110px; + padding: 0 16px; +} +@media (min-width: 1440px) { + .whole-preview { + padding-left: 0; + padding-bottom: 0; + padding-right: 0; + padding-top: 0; + } +} + +/* end of Cart preview page */ + +/* Payment page */ + +#payment-page { + display: flex; + flex-direction: column; + align-items: center; +} + +#payment-page .container { + margin-top: 100px; + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; + max-width: 1110px; + padding-left: 8px; + padding-right: 8px; +} + +.checkout-component { + background: #f7f8f9; + border: 1px solid #e6e9eb; + border-radius: 12px; + margin: 8px 0; +} + +/* Adyen Components */ +.payment { + width: 100%; + padding-top: 0px !important; + padding-left: 20px; + padding-right: 20px; +} + +@media screen and (max-width: 1076px) { + #payment-page .container { + display: flex; + flex-direction: column; + align-items: center; + } + + .payment { + align-self: center; + } +} + +.payment-container { + display: flex; + justify-content: center; + background: #f7f8f9; + border: 1px solid #e6e9eb; + border-radius: 12px; + padding-top: 18px; + padding-bottom: 18px; + width: 100%; + height: 100%; +} + +/* end Payments page */ + + +/* Status page */ + +.status-container { + height: 100vh; + display: flex; + align-items: center; + justify-content: center; +} + +.status { + margin: 100px 0 126px; + text-align: center; +} + +.status .status-image { + display: block; + height: 100px; + margin: 16px auto 0; +} + +.status .status-image-thank-you { + height: 66px; +} + +.status .status-message { + margin: 8px 0 24px; +} + +.status .button { + max-width: 236px; +} + +@media (min-width: 768px) { + .status .button { + max-width: 200px; + } +} + +/* end Status page */ diff --git a/src/main/resources/static/images/.keep b/src/main/resources/static/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/static/images/cardcheckout.gif b/src/main/resources/static/images/cardcheckout.gif new file mode 100644 index 0000000..dc3e612 Binary files /dev/null and b/src/main/resources/static/images/cardcheckout.gif differ diff --git a/src/main/resources/static/images/error.svg b/src/main/resources/static/images/error.svg new file mode 100644 index 0000000..4db9773 --- /dev/null +++ b/src/main/resources/static/images/error.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/static/images/failed.svg b/src/main/resources/static/images/failed.svg new file mode 100644 index 0000000..4db9773 --- /dev/null +++ b/src/main/resources/static/images/failed.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/main/resources/static/images/headphones.png b/src/main/resources/static/images/headphones.png new file mode 100644 index 0000000..12d1cf4 Binary files /dev/null and b/src/main/resources/static/images/headphones.png differ diff --git a/src/main/resources/static/images/mystore-logo.svg b/src/main/resources/static/images/mystore-logo.svg new file mode 100644 index 0000000..0418b24 --- /dev/null +++ b/src/main/resources/static/images/mystore-logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/main/resources/static/images/pending.svg b/src/main/resources/static/images/pending.svg new file mode 100644 index 0000000..465c4b4 --- /dev/null +++ b/src/main/resources/static/images/pending.svg @@ -0,0 +1,24 @@ + + + + Icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/static/images/success.svg b/src/main/resources/static/images/success.svg new file mode 100644 index 0000000..465c4b4 --- /dev/null +++ b/src/main/resources/static/images/success.svg @@ -0,0 +1,24 @@ + + + + Icon + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/static/images/sunglasses.png b/src/main/resources/static/images/sunglasses.png new file mode 100644 index 0000000..1f28053 Binary files /dev/null and b/src/main/resources/static/images/sunglasses.png differ diff --git a/src/main/resources/static/images/thank-you.svg b/src/main/resources/static/images/thank-you.svg new file mode 100644 index 0000000..587d3b5 --- /dev/null +++ b/src/main/resources/static/images/thank-you.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/main/resources/templates/checkout.html b/src/main/resources/templates/checkout.html new file mode 100644 index 0000000..03e6907 --- /dev/null +++ b/src/main/resources/templates/checkout.html @@ -0,0 +1,24 @@ + + + Checkout + + +
+ + + + +
+ + +
+
+
+
+ + +
+ diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 0000000..d573b4f --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,118 @@ + + + Select type + + +
+
+
+

Select a demo

+

Click to view an interactive example of a PCI-compliant UI integration for online payments.

+

+ Make sure the payment method you want to use are enabled for your account. + Refer the documentation + to add missing + payment methods. +

+

To learn more about client-side integration solutions, check out Online + payments.

+
+ +
+
+ + diff --git a/src/main/resources/templates/layout.html b/src/main/resources/templates/layout.html new file mode 100644 index 0000000..e884a38 --- /dev/null +++ b/src/main/resources/templates/layout.html @@ -0,0 +1,44 @@ + + + + + Checkout Demo + + + + + + + + + + + + + + + + +
+ +
+ + diff --git a/src/main/resources/templates/preview.html b/src/main/resources/templates/preview.html new file mode 100644 index 0000000..fd85f75 --- /dev/null +++ b/src/main/resources/templates/preview.html @@ -0,0 +1,34 @@ + + + Preview + + +
+
+

Cart

+
+
    +
  • + +

    Sunglasses

    +

    5.00

    +
  • +
  • + +

    Headphones

    +

    5.00

    +
  • +
+
+ +
+
+ diff --git a/src/main/resources/templates/result.html b/src/main/resources/templates/result.html new file mode 100644 index 0000000..8e145b1 --- /dev/null +++ b/src/main/resources/templates/result.html @@ -0,0 +1,26 @@ + + + + + +
+
+ + + +

+ Your order has been successfully placed. + Your order has been received! Payment completion pending. + The payment was refused. Please try a different payment method or card. + + Error! Please review response in console and refer to + Response handling. + +

+ Return Home +
+
+ diff --git a/src/test/java/com/adyen/checkout/OnlinePaymentsApplicationTests.java b/src/test/java/com/adyen/checkout/OnlinePaymentsApplicationTests.java new file mode 100644 index 0000000..2be4488 --- /dev/null +++ b/src/test/java/com/adyen/checkout/OnlinePaymentsApplicationTests.java @@ -0,0 +1,30 @@ +package com.adyen.checkout; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.event.annotation.BeforeTestClass; + +@SpringBootTest +class OnlinePaymentsApplicationTests { + + @BeforeAll + public static void onceExecutedBeforeAll() { + System.setProperty("ADYEN_API_KEY", "testKey"); + System.setProperty("ADYEN_MERCHANT_ACCOUNT", "testAccount"); + System.setProperty("ADYEN_CLIENT_KEY", "testKey"); + } + + @AfterAll + public static void onceExecutedAfterAll(){ + System.clearProperty("ADYEN_API_KEY"); + System.clearProperty("ADYEN_MERCHANT_ACCOUNT"); + System.clearProperty("ADYEN_CLIENT_KEY"); + } + + @Test + void contextLoads() { + } + +}