This commit is contained in:
jillingk
2023-05-17 16:37:57 +02:00
commit eb7a28bbf6
47 changed files with 1710 additions and 0 deletions

23
.editorconfig Normal file
View File

@@ -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

24
.github/workflows/build.yml vendored Normal file
View File

@@ -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

35
.gitignore vendored Normal file
View File

@@ -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

15
.gitpod.yml Normal file
View File

@@ -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

View File

@@ -0,0 +1 @@
{"dependencies":[["racc",["~> 1.4"]],["nokogiri",[">= 0"]],["vagrant-parallels",["= 2.3.1"]]],"checksum":"306cc775b2e3e42f6683a775882841f765b492aba52a9466599f24190c03f6c4","vagrant_version":"2.3.4"}

View File

@@ -0,0 +1 @@
1.5:8c33c12b-0476-4308-a4eb-0a0e3f6e7c04

View File

@@ -0,0 +1 @@
1684332805

View File

@@ -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"}

View File

@@ -0,0 +1 @@
502

View File

@@ -0,0 +1 @@
8c33c12b-0476-4308-a4eb-0a0e3f6e7c04

View File

@@ -0,0 +1 @@
f8191b6c631844249c51d3efa644d6e5

View File

@@ -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-----

View File

@@ -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}}}

View File

@@ -0,0 +1 @@
/Users/jilling/Downloads/adyen-java-spring-online-payments-8984812cc775f66ebb8e2b8a3e14794cabe57a0e

View File

@@ -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

21
LICENSE Normal file
View File

@@ -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.

75
README.md Normal file
View File

@@ -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.

11
Vagrantfile vendored Normal file
View File

@@ -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

28
build.gradle Normal file
View File

@@ -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()
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -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

185
gradlew vendored Executable file
View File

@@ -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" "$@"

89
gradlew.bat vendored Normal file
View File

@@ -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

1
settings.gradle Normal file
View File

@@ -0,0 +1 @@
rootProject.name = 'online-payments'

View File

@@ -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();
}
}

View File

@@ -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----------------------------------------------------------");
}
}

View File

@@ -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<PaymentMethodsResponse> 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<PaymentsResponse> 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<LineItem>();
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<PaymentsDetailsResponse> 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 ###################### */
}

View File

@@ -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";
}
}

View File

@@ -0,0 +1 @@
spring.jackson.default-property-inclusion=non_null

View File

@@ -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();

View File

@@ -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 */

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

View File

@@ -0,0 +1,4 @@
<svg width="88" height="88" viewBox="0 0 88 88" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 44C2 20.9046 20.9046 2 44 2C67.0954 2 86 20.9046 86 44C86 67.0954 67.0954 86 44 86C20.9046 86 2 67.0954 2 44Z" stroke="#D10244" stroke-width="4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.5 41.0339L55.8744 28.7009C56.6554 27.9225 57.9218 27.9225 58.7028 28.7009C59.4839 29.4793 59.4839 30.7414 58.7028 31.5199L46.3284 43.8529L58.7028 56.1858C59.4838 56.9643 59.4838 58.2264 58.7028 59.0048C57.9218 59.7832 56.6554 59.7832 55.8744 59.0048L43.5 46.6718L31.1257 59.0048C30.3446 59.7832 29.0783 59.7832 28.2972 59.0048C27.5162 58.2264 27.5162 56.9643 28.2972 56.1858L40.6716 43.8529L28.2972 31.5199C27.5162 30.7414 27.5162 29.4793 28.2972 28.7009C29.0783 27.9225 30.3446 27.9225 31.1256 28.7009L43.5 41.0339Z" fill="#D10244"/>
</svg>

After

Width:  |  Height:  |  Size: 850 B

View File

@@ -0,0 +1,4 @@
<svg width="88" height="88" viewBox="0 0 88 88" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 44C2 20.9046 20.9046 2 44 2C67.0954 2 86 20.9046 86 44C86 67.0954 67.0954 86 44 86C20.9046 86 2 67.0954 2 44Z" stroke="#D10244" stroke-width="4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.5 41.0339L55.8744 28.7009C56.6554 27.9225 57.9218 27.9225 58.7028 28.7009C59.4839 29.4793 59.4839 30.7414 58.7028 31.5199L46.3284 43.8529L58.7028 56.1858C59.4838 56.9643 59.4838 58.2264 58.7028 59.0048C57.9218 59.7832 56.6554 59.7832 55.8744 59.0048L43.5 46.6718L31.1257 59.0048C30.3446 59.7832 29.0783 59.7832 28.2972 59.0048C27.5162 58.2264 27.5162 56.9643 28.2972 56.1858L40.6716 43.8529L28.2972 31.5199C27.5162 30.7414 27.5162 29.4793 28.2972 28.7009C29.0783 27.9225 30.3446 27.9225 31.1256 28.7009L43.5 41.0339Z" fill="#D10244"/>
</svg>

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 83 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.3 (57544) - http://www.bohemiancoding.com/sketch -->
<title>Icon</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M67.7975,34.25 C68.3735,34.25 68.948375,34.469375 69.38825,34.90925 C70.266875,35.787875 70.266875,37.21325 69.38825,38.091875 L43.136375,64.342625 C42.917,64.562 42.629,64.67225 42.341,64.67225 C42.053,64.67225 41.765,64.562 41.545625,64.342625 L30.40925,53.20625 C29.5295,52.3265 29.5295,50.903375 30.40925,50.023625 C30.848,49.584875 31.424,49.364375 32,49.364375 C32.574875,49.364375 33.152,49.584875 33.59075,50.023625 L42.341,58.775 L66.20675,34.90925 C66.646625,34.469375 67.2215,34.25 67.7975,34.25" id="path-1"></path>
</defs>
<g id="Layouts" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Chanel---04---Checkout" transform="translate(-669.000000, -275.000000)">
<g id="Group-5" transform="translate(524.000000, 100.000000)">
<g id="Group-4" transform="translate(115.000000, 175.000000)">
<g id="Icon" transform="translate(30.000000, 0.000000)">
<path d="M50,4 C24.709139,4 4,24.709139 4,50 C4,75.290861 24.709139,96 50,96 C75.290861,96 96,75.290861 96,50 C96,24.709139 75.290861,4 50,4 Z M50,0 C77.5,0 100,22.5 100,50 C100,77.5 77.5,100 50,100 C22.5,100 0,77.5 0,50 C0,22.5 22.5,0 50,0 Z" id="Path" fill="#0ABF53" fill-rule="nonzero"></path>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Mask" fill="#0ABF53" xlink:href="#path-1"></use>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.3 (57544) - http://www.bohemiancoding.com/sketch -->
<title>Icon</title>
<desc>Created with Sketch.</desc>
<defs>
<path d="M67.7975,34.25 C68.3735,34.25 68.948375,34.469375 69.38825,34.90925 C70.266875,35.787875 70.266875,37.21325 69.38825,38.091875 L43.136375,64.342625 C42.917,64.562 42.629,64.67225 42.341,64.67225 C42.053,64.67225 41.765,64.562 41.545625,64.342625 L30.40925,53.20625 C29.5295,52.3265 29.5295,50.903375 30.40925,50.023625 C30.848,49.584875 31.424,49.364375 32,49.364375 C32.574875,49.364375 33.152,49.584875 33.59075,50.023625 L42.341,58.775 L66.20675,34.90925 C66.646625,34.469375 67.2215,34.25 67.7975,34.25" id="path-1"></path>
</defs>
<g id="Layouts" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Chanel---04---Checkout" transform="translate(-669.000000, -275.000000)">
<g id="Group-5" transform="translate(524.000000, 100.000000)">
<g id="Group-4" transform="translate(115.000000, 175.000000)">
<g id="Icon" transform="translate(30.000000, 0.000000)">
<path d="M50,4 C24.709139,4 4,24.709139 4,50 C4,75.290861 24.709139,96 50,96 C75.290861,96 96,75.290861 96,50 C96,24.709139 75.290861,4 50,4 Z M50,0 C77.5,0 100,22.5 100,50 C100,77.5 77.5,100 50,100 C22.5,100 0,77.5 0,50 C0,22.5 22.5,0 50,0 Z" id="Path" fill="#0ABF53" fill-rule="nonzero"></path>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Mask" fill="#0ABF53" xlink:href="#path-1"></use>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -0,0 +1,24 @@
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout}">
<head>
<title>Checkout</title>
</head>
<body>
<div layout:fragment="content" id="payment-page">
<!-- Hidden divs with data passed from the Express server -->
<div id="clientKey" class="hidden" th:text="${clientKey}"></div>
<div id="type" class="hidden" th:text="${type}"></div>
<div class="container">
<!-- The Checkout integration type will be rendered below.-->
<!-- Drop-in includes styling out-of-the-box, so no additional styling is needed. -->
<div class="payment-container">
<div id="payment" class="payment"></div>
</div>
</div>
<!-- Adyen Component client code -->
<script type="text/javascript" src="/adyenImplementation.js"></script>
</div>
</body>

View File

@@ -0,0 +1,118 @@
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout}">
<head>
<title>Select type</title>
</head>
<body>
<div layout:fragment="content">
<div class='main-container'>
<div class="info">
<h1>Select a demo</h1>
<p>Click to view an interactive example of a PCI-compliant UI integration for online payments.</p>
<p>
Make sure the payment method you want to use are enabled for your account.
Refer <a href="https://docs.adyen.com/payment-methods#add-payment-methods-to-your-account">the documentation</a>
to add missing
payment methods.
</p>
<p>To learn more about client-side integration solutions, check out <a href="https://docs.adyen.com/checkout">Online
payments.</a></p>
</div>
<ul class="integration-list">
<li class="integration-list-item">
<a href="/preview?type=dropin" class="integration-list-item-link">
<div class="title-container">
<p class="integration-list-item-title">Drop-in</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a href="/preview?type=card" class="integration-list-item-link">
<div class="title-container">
<p class="integration-list-item-title">Card</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a href="/preview?type=ideal" class="integration-list-item-link">
<div class="title-container">
<p class="integration-list-item-title">iDEAL</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a href="/preview?type=dotpay" class="integration-list-item-link">
<div class="title-container">
<p class="integration-list-item-title">Dotpay</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a href="/preview?type=giropay" class="integration-list-item-link">
<div class="title-container">
<p class="integration-list-item-title">giropay</p>
</div>
</a>
</li>
<!-- <li class="integration-list-item">-->
<!-- <a href="/preview?type=sepadirectdebit" class="integration-list-item-link">-->
<!-- <div class="title-container">-->
<!-- <p class="integration-list-item-title">SEPA Direct Debit</p>-->
<!-- </div>-->
<!-- </a>-->
<!-- </li>-->
<li class="integration-list-item">
<a href="/preview?type=directEbanking" class="integration-list-item-link">
<div class="title-container">
<p class="integration-list-item-title">SOFORT</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a href="/preview?type=ach" class="integration-list-item-link">
<div class="title-container">
<p class="integration-list-item-title">ACH</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a href="/preview?type=paypal" class="integration-list-item-link">
<div class="title-container">
<p class="integration-list-item-title">PayPal</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a href="/preview?type=alipay" class="integration-list-item-link">
<div class="title-container">
<p class="integration-list-item-title">Alipay</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a href="/preview?type=klarna_paynow" class="integration-list-item-link">
<div class="title-container">
<p class="integration-list-item-title">Klarna - Pay now</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a href="/preview?type=klarna" class="integration-list-item-link">
<div class="title-container">
<p class="integration-list-item-title">Klarna - Pay later</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a href="/preview?type=klarna_account" class="integration-list-item-link">
<div class="title-container">
<p class="integration-list-item-title">Klarna - Slice it</p>
</div>
</a>
</li>
</ul>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html
lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
>
<head>
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">
Checkout Demo
</title>
<meta charset="UTF-8" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
crossorigin="anonymous"
/>
<script src="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/4.1.0/adyen.js"
integrity="sha384-3tEepwhhMcyxgIbL3HBe3I59BpSMNyKoNrbKWARYH1tJ7K7K6NdTDqOltKlwiVsH"
crossorigin="anonymous"></script>
<link rel="stylesheet"
href="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/4.1.0/adyen.css"
integrity="sha384-+CPzBNZVkBXu4uXDECnVuVQ24Kl8vWrR61UzuuuUj5IBEP//BQ0G0KDNfz2iPcvJ"
crossorigin="anonymous">
<link rel="stylesheet" href="/css/application.css" />
</head>
<body>
<header id="header">
<a href="/">
<img src="/images/mystore-logo.svg" alt="" />
</a>
</header>
<div class="container">
<th:block layout:fragment="content" />
</div>
</body>
</html>

View File

@@ -0,0 +1,34 @@
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{layout}">
<head>
<title>Preview</title>
</head>
<body>
<main layout:fragment="content" class="preview-page">
<section class="cart">
<h2>Cart</h2>
<div class="order-summary">
<ul class="order-summary-list">
<li class="order-summary-list-list-item">
<img src="/images/sunglasses.png" class="order-summary-list-list-item-image" alt="">
<p class="order-summary-list-list-item-title">Sunglasses</p>
<p class="order-summary-list-list-item-price">5.00</p>
</li>
<li class="order-summary-list-list-item">
<img src="/images/headphones.png" class="order-summary-list-list-item-image" alt="">
<p class="order-summary-list-list-item-title">Headphones</p>
<p class="order-summary-list-list-item-price">5.00</p>
</li>
</ul>
</div>
<div class="cart-footer"><span class="cart-footer-label">Total:</span><span
class="cart-footer-amount">10.00</span>
<a th:href="@{/checkout(type=${type})}">
<p class="button">Continue to checkout</p>
</a>
</div>
</section>
</main>
</body>

View File

@@ -0,0 +1,26 @@
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layout}">
<head>
<title th:text="${type}"></title>
</head>
<body>
<div layout:fragment="content" class="status-container">
<div class="status">
<img th:src="${'/images/' + type + '.svg'}" class="status-image" alt="">
<img th:if="${type == 'success' || type == 'pending'}" src="/images/thank-you.svg" class="status-image" alt="">
<p class="status-message">
<span th:if="${type == 'success' }">Your order has been successfully placed.</span>
<span th:if="${type == 'pending' }">Your order has been received! Payment completion pending.</span>
<span th:if="${type == 'failed' }">The payment was refused. Please try a different payment method or card.</span>
<span th:if="${type == 'error' }">
Error! Please review response in console and refer to
<a href="https://docs.adyen.com/development-resources/response-handling">Response handling.</a>
</span>
</p>
<a class="button" href="/">Return Home</a>
</div>
</div>
</body>

View File

@@ -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() {
}
}