initial commit

This commit is contained in:
Tyler Douglas
2020-04-03 17:59:27 -07:00
commit a866a55137
39 changed files with 1686 additions and 0 deletions

2
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
# Default ignored files
/workspace.xml

6
.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_STRING" value="Didea.case.sensitive.fs=true" />
</component>
</project>

18
.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>
</project>

20
.idea/jarRepositories.xml generated Normal file
View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
</component>
</project>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

124
.idea/uiDesigner.xml generated Normal file
View File

@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

23
build.gradle Normal file
View File

@@ -0,0 +1,23 @@
plugins {
id 'application'
}
version "0.1"
sourceCompatibility = 1.8
application {
mainClassName = 'controller.Main'
}
repositories {
mavenCentral()
}
dependencies {
implementation "com.sparkjava:spark-core:2.8.0"
implementation "org.slf4j:slf4j-simple:1.7.25"
implementation "com.sparkjava:spark-template-jinjava:2.7.1"
implementation 'org.apache.httpcomponents:httpclient:4.5.11'
implementation 'com.adyen:adyen-java-api-library:4.0.1'
}

1
settings.gradle Normal file
View File

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

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

BIN
src/main/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,64 @@
package controller;
import com.adyen.model.PaymentResult;
import com.adyen.model.checkout.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import model.PaymentMethodDetailsDeserializer;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
public class FrontendParser {
// Deserialize payment information passed from frontend. Requires TypeAdapter for PaymentMethodDetails interface
public static PaymentsRequest parsePayment(String body) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(PaymentMethodDetails.class, new PaymentMethodDetailsDeserializer());
Gson gson = builder.create();
PaymentsRequest paymentsRequest = gson.fromJson(body, PaymentsRequest.class);
return paymentsRequest;
}
// Deserialize PaymentDetails generated by component
public static PaymentsDetailsRequest parseDetails(String body) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(PaymentMethodDetails.class, new PaymentMethodDetailsDeserializer());
Gson gson = builder.create();
PaymentsDetailsRequest paymentDetailsRequest = gson.fromJson(body, PaymentsDetailsRequest.class);
return paymentDetailsRequest;
}
// Format response being passed back to frontend. Only leave resultCode and action. Don't need to pass back
// The rest of the information
public static PaymentsResponse formatResponseForFrontend(PaymentsResponse unformattedResponse) throws IOException {
PaymentsResponse.ResultCodeEnum resultCode = unformattedResponse.getResultCode();
if (resultCode != null) {
PaymentsResponse newPaymentsResponse = new PaymentsResponse();
newPaymentsResponse.setResultCode(resultCode);
CheckoutPaymentsAction action = unformattedResponse.getAction();
if (action != null) {
newPaymentsResponse.setAction(action);
}
return newPaymentsResponse;
} else {
throw new IOException();
}
}
// Doesn't handle nested objects right now. Will see if neccessary
public static List<NameValuePair> parseQueryParams(String queryString) {
List<NameValuePair> params = URLEncodedUtils.parse(queryString, Charset.forName("UTF-8"));
return params;
}
}

View File

@@ -0,0 +1,183 @@
package controller;
import com.adyen.model.checkout.PaymentDetails;
import com.adyen.model.checkout.PaymentsDetailsRequest;
import com.adyen.model.checkout.PaymentsRequest;
import com.adyen.model.checkout.PaymentsResponse;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closeables;
import com.google.common.net.MediaType;
import org.apache.http.NameValuePair;
import view.RenderUtil;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import model.PaymentMethods;
import model.Payments;
import model.PaymentsDetails;
import static spark.Spark.*;
import spark.Response;
public class Main {
private static final File FAVICON_PATH = new File("src/main/resources/static/img/favicon.ico");
public static String merchantAccount = "";
public static String apiKey = "";
public static String originKey = "";
public static String paymentMethodsUrl = "";
public static String paymentsUrl = "";
public static String paymentsDetailsUrl = "";
public static void main(String[] args) {
port(8080);
staticFiles.location("/static");
initalizeConstants();
// Routes
get("/", (req, res) -> {
Map<String, Object> context = new HashMap<>();
return RenderUtil.render(context, "templates/home.html");
});
get("/cart/:integration", (req, res) -> {
String integrationType = req.params(":integration");
Map<String, Object> context = new HashMap<>();
context.put("integrationType", "/checkout/" + integrationType);
return RenderUtil.render(context, "templates/cart.html");
});
get("/checkout/:integration", (req, res) -> {
String integrationType = req.params(":integration");
Map<String, Object> context = new HashMap<>();
context.put("paymentMethods", PaymentMethods.getPaymentMethods());
context.put("originKey", originKey);
context.put("integrationType", integrationType);
return RenderUtil.render(context, "templates/component.html");
});
post("/api/getPaymentMethods", (req, res) -> {
String paymentMethods = PaymentMethods.getPaymentMethods();
return paymentMethods;
});
post("/api/initiatePayment", (req, res) -> {
PaymentsRequest request = FrontendParser.parsePayment(req.body());
String response = Payments.makePayment(request);
return response;
});
post("/api/submitAdditionalDetails", (req, res) -> {
PaymentsDetailsRequest details = FrontendParser.parseDetails(req.body());
String response = PaymentsDetails.getPaymentsDetails(details);
return response;
});
get("/api/handleShopperRedirect", (req, res) -> {
System.out.println("GET result\n" + req.body());
res.redirect("/error"); //TODO: Evaluate contents of res at this point to handle redirect
return res;
});
post("/api/handleShopperRedirect", (req, res) -> {
System.out.println("POST result\n" + req.body() + "\n");
// Triggers when POST contains query params. Triggers on call back from issuer after 3DS2 challenge w/ MD & PaRes
if (req.body().contains("&")) {
List<NameValuePair> params = FrontendParser.parseQueryParams(req.body());
System.out.println(params.toString());
String md = params.get(0).getValue();
String paRes = params.get(1).getValue();
Map<String, Object> context = new HashMap<>();
context.put("MD", md);
context.put("PaRes", paRes);
return RenderUtil.render(context, "templates/fetch-payment-data.html"); // Get paymentData from localStorage
} else {
PaymentsDetailsRequest pdr = FrontendParser.parseDetails(req.body());
PaymentsResponse paymentResult = PaymentsDetails.getPaymentsDetailsObject(pdr);
PaymentsResponse.ResultCodeEnum result = paymentResult.getResultCode();
switch (result) {
case AUTHORISED:
res.redirect("/success");
case RECEIVED: case PENDING:
res.redirect("/pending");
default:
res.redirect("/failed");
}
return res;
}
});
get("/success", (req, res) -> {
Map<String, Object> context = new HashMap<>();
return RenderUtil.render(context, "templates/checkout-success.html");
});
get("/failed", (req, res) -> {
Map<String, Object> context = new HashMap<>();
return RenderUtil.render(context, "templates/checkout-failed.html");
});
get("/pending", (req, res) -> {
Map<String, Object> context = new HashMap<>();
return RenderUtil.render(context, "templates/checkout-success.html");
});
get("/error", (req, res) -> {
Map<String, Object> context = new HashMap<>();
return RenderUtil.render(context, "templates/checkout-failed.html");
});
get("/favicon.ico", (req, res) -> {
return getFavicon(res);
});
}
private static void initalizeConstants() {
merchantAccount = "TylerDouglas";
apiKey = "AQEyhmfxL4jIYhVBw0m/n3Q5qf3VaY9UCJ1+XWZe9W27jmlZiiSaWGoa4mOFeQne5hiuhQsQwV1bDb7kfNy1WIxIIkxgBw==-hJYG90gqLYPclLs6We+q8CUtAsa+KgXr/iWftd+rrCM=-89EKHpWfW8ABmGF3";
originKey = "pub.v2.8115499067697722.aHR0cDovL2xvY2FsaG9zdDo4MDgw.I4ixvXum4JGOjgI0Nd3YQ49P4AWvIncxMv41suCoW1Y";
paymentMethodsUrl = "https://checkout-test.adyen.com/v52/paymentMethods";
paymentsUrl = "https://checkout-test.adyen.com/v52/payments";
paymentsDetailsUrl = "https://checkout-test.adyen.com/v52/payments/details";
}
private static Object getFavicon(Response res) {
try {
InputStream in = null;
OutputStream out;
try {
in = new BufferedInputStream(new FileInputStream(FAVICON_PATH));
out = new BufferedOutputStream(res.raw().getOutputStream());
res.raw().setContentType(MediaType.ICO.toString());
ByteStreams.copy(in, out);
out.flush();
return "";
} finally {
Closeables.close(in, true);
}
} catch (FileNotFoundException ex) {
res.status(404);
return ex.getMessage();
} catch (IOException ex) {
res.status(500);
return ex.getMessage();
}
}
}

View File

@@ -0,0 +1,19 @@
package model;
import com.adyen.model.checkout.DefaultPaymentMethodDetails;
import com.adyen.model.checkout.PaymentMethodDetails;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import java.lang.reflect.Type;
public class PaymentMethodDetailsDeserializer implements JsonDeserializer<PaymentMethodDetails> {
@Override
public PaymentMethodDetails deserialize(JsonElement jsonElement, Type typeOfT,
JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
return jsonDeserializationContext.deserialize(jsonElement, DefaultPaymentMethodDetails.class);
}
}

View File

@@ -0,0 +1,43 @@
package model;
import com.adyen.Client;
import com.adyen.enums.Environment;
import com.adyen.model.Amount;
import com.adyen.model.checkout.PaymentMethodDetails;
import com.adyen.model.checkout.PaymentMethodsRequest;
import com.adyen.model.checkout.PaymentMethodsResponse;
import com.adyen.service.Checkout;
import com.adyen.service.exception.ApiException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import controller.Main;
import java.io.IOException;
public class PaymentMethods {
public static String getPaymentMethods() {
Client client = new Client(Main.apiKey, Environment.TEST);
Checkout checkout = new Checkout(client);
PaymentMethodsRequest paymentMethodsRequest = new PaymentMethodsRequest();
paymentMethodsRequest.setMerchantAccount(Main.merchantAccount);
paymentMethodsRequest.setCountryCode("NL");
Amount amount = new Amount();
amount.setCurrency("EUR");
amount.setValue(1000L);
paymentMethodsRequest.setAmount(amount);
paymentMethodsRequest.setChannel(PaymentMethodsRequest.ChannelEnum.WEB);
System.out.println("/paymentMethods context:\n" + paymentMethodsRequest.toString());
try {
PaymentMethodsResponse response = checkout.paymentMethods(paymentMethodsRequest);
Gson gson = new GsonBuilder().create();
String paymentMethodsResponseStringified = gson.toJson(response);
System.out.println("/paymentMethods response:\n" + paymentMethodsResponseStringified);
return paymentMethodsResponseStringified;
} catch (ApiException | IOException e) {
return e.toString();
}
}
}

View File

@@ -0,0 +1,48 @@
package model;
import com.adyen.Client;
import com.adyen.enums.Environment;
import com.adyen.model.Address;
import com.adyen.model.Amount;
import com.adyen.model.checkout.PaymentsRequest;
import com.adyen.model.checkout.PaymentsResponse;
import com.adyen.service.Checkout;
import com.adyen.service.exception.ApiException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import controller.FrontendParser;
import controller.Main;
import java.io.IOException;
public class Payments {
public static String makePayment(PaymentsRequest paymentsRequest) {
Client client = new Client(Main.apiKey, Environment.TEST);
Checkout checkout = new Checkout(client);
paymentsRequest.setMerchantAccount(Main.merchantAccount);
paymentsRequest.setCountryCode("NL");
Amount amount = new Amount();
amount.setCurrency("EUR");
amount.setValue(1000L);
paymentsRequest.setAmount(amount);
paymentsRequest.setReference("Test Reference");
paymentsRequest.setReturnUrl("http://localhost:8080/api/handleShopperRedirect");
paymentsRequest.setChannel(PaymentsRequest.ChannelEnum.WEB);
System.out.println("/paymentMethods response:\n" + paymentsRequest.toString());
try {
PaymentsResponse response = checkout.payments(paymentsRequest);
PaymentsResponse formattedResponse = FrontendParser.formatResponseForFrontend(response);
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.create();
String paymentsResponse = gson.toJson(formattedResponse);
System.out.println("/payments response:\n" + paymentsResponse);
return paymentsResponse;
} catch (ApiException | IOException e) {
return e.toString();
}
}
}

View File

@@ -0,0 +1,51 @@
package model;
import com.adyen.Client;
import com.adyen.enums.Environment;
import com.adyen.model.checkout.PaymentsDetailsRequest;
import com.adyen.model.checkout.PaymentsResponse;
import com.adyen.service.Checkout;
import com.adyen.service.exception.ApiException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import controller.Main;
import java.io.IOException;
import java.util.HashMap;
public class PaymentsDetails {
public static String getPaymentsDetails(PaymentsDetailsRequest paymentsDetailsRequest) {
PaymentsResponse paymentsDetailsResponse = makePaymentDetailsRequest(paymentsDetailsRequest);
Gson gson = new GsonBuilder().create();
return gson.toJson(paymentsDetailsResponse);
}
public static PaymentsResponse getPaymentsDetailsObject(PaymentsDetailsRequest paymentsDetailsRequest) {
return makePaymentDetailsRequest(paymentsDetailsRequest);
}
private static PaymentsResponse makePaymentDetailsRequest(PaymentsDetailsRequest paymentsDetailsRequest) {
Client client = new Client(Main.apiKey, Environment.TEST);
Checkout checkout = new Checkout(client);
System.out.println("/paymentsDetails request:" + paymentsDetailsRequest.toString());
PaymentsResponse paymentsDetailsResponse = null;
try {
paymentsDetailsResponse = checkout.paymentsDetails(paymentsDetailsRequest);
} catch (ApiException | IOException e) {
e.printStackTrace();
} finally {
if (paymentsDetailsResponse != null) {
System.out.println("paymentsDetails response:\n" + paymentsDetailsResponse.toString());
}
}
return paymentsDetailsResponse;
}
}

View File

@@ -0,0 +1,41 @@
package view;
import com.google.common.io.Files;
import com.hubspot.jinjava.interpret.JinjavaInterpreter;
import com.hubspot.jinjava.loader.ResourceLocator;
import com.hubspot.jinjava.loader.ResourceNotFoundException;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
public class CustomResourceLocator implements ResourceLocator {
public static final String jinjaRoot = "src/main/resources/templates/";
private File baseDir;
public CustomResourceLocator() {
this.baseDir = new File(".");
}
private File resolveFileName(String name) {
File f = new File(name);
if (f.isAbsolute()) {
return f;
}
return new File(baseDir, name);
}
@Override
public String getString(String name, Charset encoding, JinjavaInterpreter interpreter) throws IOException {
File file = resolveFileName(jinjaRoot + name);
if (!file.exists() || !file.isFile()) {
throw new ResourceNotFoundException("Couldn't find resource: " + file);
}
return Files.toString(file, encoding);
}
}

View File

@@ -0,0 +1,18 @@
package view;
import java.util.Map;
import com.hubspot.jinjava.JinjavaConfig;
import spark.ModelAndView;
import spark.template.jinjava.JinjavaEngine;
public class RenderUtil {
public static String render(Map<String, Object> model, String templatePath) {
JinjavaConfig config = new JinjavaConfig();
CustomResourceLocator customResourceLocator = new CustomResourceLocator();
return new JinjavaEngine(config, customResourceLocator).render(new ModelAndView(model, templatePath));
}
}

BIN
src/main/resources/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,527 @@
/* 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;
max-width: 610px;
}
}
.payment-container {
display: flex;
justify-content: center;
background: #f7f8f9;
border: 1px solid #e6e9eb;
border-radius: 12px;
padding-top: 18px;
padding-bottom: 18px;
width: 100%;
max-width: 450px;
height: 100%;
}
@media screen and (max-width: 1076px) {
.payment-container {
align-self: center;
max-width: 610px;
}
}
/* end Payments page */
/* Dropin page */
#dropin {
width: 100%;
max-width: 450px;
}
@media screen and (max-width: 1076px) {
#dropin {
width: 100%;
max-width: 610px;
}
}
/* end Dropin page */
/* Customer billing information form */
.address {
background: #f7f8f9;
border: 1px solid #e6e9eb;
border-radius: 12px;
}
.billing-header {
padding: 1em 1em 0px 1em;
}
.address-form {
padding: 1em;
border-radius: 3px;
}
.address-form * {
box-sizing: border-box;
}
.address-form input {
border: 1px solid #ccc;
border-radius: 6px;
font-size: 0.8em;
padding: 0.5em;
}
.address-form input[type="text"] {
width: 100%;
height: 40px;
}
.address-label {
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.1s ease-out;
white-space: nowrap;
font-size: 0.81em;
font-weight: 400;
line-height: 13px;
padding-bottom: 5px;
}
.address-input {
display: inline-grid;
width: 100%;
}
.address-input:first-child {
padding-right: 4px;
}
.address-input:last-child {
padding-left: 4px;
}
.address-input.full-width {
padding-left: 0px;
padding-right: 0px;
}
.address-line {
padding: 4px;
display: flex;
flex-direction: row;
}
.address-line4 div {
width: 33.3%;
}
.customer-form {
width: 100%;
max-width: 610px;
margin-bottom: 16px;
}
@media screen and (max-width: 1076px) {
.customer-form {
align-self: center;
max-width: 610px;
}
}
/* end Customer billing information 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;
}
}
.title-status {
margin-top: 10%;
text-align: center;
}
/* end Status page */

BIN
src/main/resources/static/img/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.9333 2H11.7333H9.06667V1C9.06667 0.4 8.64 0 8 0C7.36 0 6.93333 0.4 6.93333 1V2H4.26667H0.999998C0.359998 2 0 2.4 0 3C0 3.6 0.359998 4 0.999998 4H2V14C2 15.1 2.82667 16 4 16H12C13.3067 16 14 15.1 14 14V4H14.9333C15.5733 4 16 3.6 16 3C16 2.4 15.5733 2 14.9333 2ZM4 4H7V14H4V4ZM9 14H12V4H9V14Z" fill="#687282"/>
</svg>

After

Width:  |  Height:  |  Size: 465 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: 1.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 146 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 83 KiB

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="#0ABF53" stroke-width="4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M59.6617 30.1399C60.1686 30.1399 60.6745 30.3329 61.0616 30.72C61.8348 31.4932 61.8348 32.7476 61.0616 33.5207L37.9599 56.6214C37.7669 56.8145 37.5135 56.9115 37.26 56.9115C37.0066 56.9115 36.7531 56.8145 36.5601 56.6214L26.7601 46.8214C25.9859 46.0472 25.9859 44.7949 26.7601 44.0207C27.1462 43.6346 27.6531 43.4405 28.1599 43.4405C28.6658 43.4405 29.1737 43.6346 29.5598 44.0207L37.26 51.7219L58.2619 30.72C58.649 30.3329 59.1549 30.1399 59.6617 30.1399Z" fill="#0ABF53"/>
</svg>

After

Width:  |  Height:  |  Size: 786 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 110 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -0,0 +1,174 @@
// Structure credit card payment request
const structureRequest = (data) => {
const paymentRequest = {
paymentMethod: data.paymentMethod
};
if (data.paymentMethod.type === "scheme") {
paymentRequest['billingAddress'] = data.billingAddress;
paymentRequest['browserInfo'] = data.browserInfo;
}
console.log(paymentRequest);
return paymentRequest;
};
// Parse payment response and directing shopper to correct place
const handleFinalState = (resultCode) => {
if (resultCode === 'Authorised') {
window.location.href = "http://localhost:8080/success";
} else if (resultCode === 'Pending') {
window.location.href = "http://localhost:8080/pending";
} else if (resultCode === 'Error') {
window.location.href = "http://localhost:8080/error";
} else {
window.location.href = "http://localhost:8080/failed";
}
};
/*
* Dropin and Component event handlers
*
* And Create Adyen checkout method
*/
const onSubmit = (state, component) => {
fetch(`/api/initiatePayment`, {
method: 'POST',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: JSON.stringify(structureRequest(state.data))
}).then(response => response.json())
.then(response => {
if (response.action) {
if (response.resultCode === 'RedirectShopper') {
localStorage.setItem('redirectPaymentData', response.action.paymentData);
}
adyenComponent.handleAction(response.action);
} else {
handleFinalState(response.resultCode);
}
})
.catch(error => {
console.log(error);
window.location.href = "http://localhost:8080/failed";
});
};
const onAdditionalDetails = (state, component) => {
console.log("On additionalDetails triggered");
fetch(`/api/submitAdditionalDetails`, {
method: 'POST',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: JSON.stringify(state.data)
}).then(response => response.json())
.then(response => {
if (response.action) {
adyenComponent.handleAction(response.action);
} else {
handleFinalState(response.resultCode);
}
})
.catch(error => {
throw Error(error);
});
};
const onError = (error) => {
console.log(error);
};
// Create Adyen checkout instance and initilize component
const createAdyenCheckout = () => {
const paymentMethods = JSON.parse(document.getElementById('payment-methods').innerHTML);
const originKey = document.getElementById('origin-key').innerHTML;
// Placeholder values
const translations = {
// "en-US": {
// "creditCard.numberField.title": "Custom Card Name",
// }
};
const paymentMethodsConfiguration = {
// applepay: { // Example required configuration for Apple Pay
// configuration: {
// merchantName: 'Adyen Test merchant', // Name to be displayed on the form
// merchantIdentifier: 'adyen.test.merchant' // Your Apple merchant identifier as described in https://developer.apple.com/documentation/apple_pay_on_the_web/applepayrequest/2951611-merchantidentifier
// },
// onValidateMerchant: (resolve, reject, validationURL) => {
// // Call the validation endpoint with validationURL.
// // Call resolve(MERCHANTSESSION) or reject() to complete merchant validation.
// }
// },
// paywithgoogle: { // Example required configuration for Google Pay
// environment: "TEST", // Change this to PRODUCTION when you're ready to accept live Google Pay payments
// configuration: {
// gatewayMerchantId: "TylerDouglas", // Your Adyen merchant or company account name. Remove this field in TEST.
// merchantIdentifier: "12345678910111213141" // Required for PRODUCTION. Remove this field in TEST. Your Google Merchant ID as described in https://developers.google.com/pay/api/web/guides/test-and-deploy/deploy-production-environment#obtain-your-merchantID
// }
// },
card: { // Example optional configuration for Cards
// hideCVC: false, // Change this to true to hide the CVC field for stored cards. false is default
// placeholders: { # Change placeholder text for the following fields
// encryptedCardNumber: "",
// encryptedSecurityCode: ""
// },
// billingAddressRequired: true,
// hasHolderName: true,
// holderNameRequired: true,
enableStoreDetails: true,
name: 'Credit or debit card'
},
ach: { // Default ACH user information
holderName: 'Ach User',
data: {
billingAddress: {
street: 'Infinite Loop',
postalCode: '95014',
city: 'Cupertino',
houseNumberOrName: '1',
country: 'US',
stateOrProvince: 'CA'
}
}
}
};
const configObj = {
paymentMethodsConfiguration: paymentMethodsConfiguration,
showPayButton: true,
locale: "en_US",
environment: "test",
originKey: originKey,
paymentMethodsResponse: paymentMethods,
translations: translations,
onSubmit: onSubmit,
onAdditionalDetails: onAdditionalDetails,
onError: onError
};
return new AdyenCheckout(configObj);
};
const integrationType = document.getElementById('integration-type').innerHTML;
// Adjust style for Dropin
if (integrationType === 'dropin') {
document.getElementById('component').style.padding = '0em';
document.getElementsByClassName('checkout-component')[0].style.border = 'none';
}
const checkout = createAdyenCheckout();
const adyenComponent = checkout.create(integrationType).mount("#component");

View File

@@ -0,0 +1,31 @@
{% extends "layout.html" %}
{% block content %}
<main 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="/img/sunglasses.svg"
class="order-summary__list__list-item__image"/>
<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="/img/headphones.svg"
class="order-summary__list__list-item__image"/>
<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 href="{{ integrationType }}">
<p class="button">Continue to checkout</p>
</a>
</section>
</main>
{% endblock %}

View File

@@ -0,0 +1,13 @@
{% extends "layout.html" %}
{% block content %}
<title>Confirmation</title>
<div class="status-container">
<div class="status">
<img src="/img/failure.svg" class="status-image"/>
<p class="status-message">Your order has failed. Please try again.</p>
<a class="button" href="/">Try Again</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends "layout.html" %}
{% block content %}
<title>Confirmation</title>
<div class="status-container">
<div class="status">
<img src="/img/success.svg" class="status-image"/>
<img src="/img/thank-you.svg" class="status-image-thank-you"/>
<p class="status-message">Your order has been successfully placed.</p>
<a class="button" href="/">Return Home</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% extends "layout.html" %}
{% block content %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/3.6.0/adyen.css">
<div id="payment-page">
<div class="container">
<div class="checkout-component payment-container" style="width: 100%; max-width: 100%;">
<div id="component" class="payment">
<!-- Component will be rendered here -->
</div>
</div>
</div>
</div>
<script src="https://checkoutshopper-live.adyen.com/checkoutshopper/sdk/3.6.0/adyen.js"></script>
<script id="payment-methods" type="application/json">{{ paymentMethods }}</script>
<script id="origin-key" type="application/json">{{ originKey }}</script>
<script id="integration-type" type="application/json">{{ integrationType }}</script>
<script src="/js/adyen_implementations.js"></script>
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends "layout.html" %}
{% block title %}Page Not Found{% endblock %}
{% block content %}
<p/>
<p/>
<title>Error</title>
<div class="status-container">
<div class="status">
<p class="status-message">Error!</p>
<p class="status-message">Review response in console and refer to <a
href="https://docs.adyen.com/development-resources/response-handling">Response handling.</a></p>
<a class="button" href="/">Return Home</a>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,37 @@
{% extends "layout.html" %}
{% block content %}
<h2 class="title-status">Working on it!</h2>
<script>
const structurePaymentRequest = () => {
const paymentData = localStorage.getItem('redirectPaymentData');
let values_array = {
"MD": "{{ MD }}",
"PaRes": "{{ PaRes }}"
};
const values = {
paymentData: paymentData,
details: values_array
};
return values;
};
console.log("fetching request");
fetch('/api/handleShopperRedirect', {
method: 'POST',
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: JSON.stringify(structurePaymentRequest())
}).then(response => {
localStorage.removeItem("redirectPaymentData");
window.location.href = response.url;
});
</script>
{% endblock %}

View File

@@ -0,0 +1,95 @@
{% extends "layout.html" %}
{% block 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 class="integration-list-item-link" href="cart/dropin">
<div class="title-container">
<p class="integration-list-item-title">Drop-in</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="cart/card">
<div class="title-container">
<p class="integration-list-item-title">Card</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="cart/ideal">
<div class="title-container">
<p class="integration-list-item-title">iDEAL</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="cart/klarna">
<div class="title-container">
<p class="integration-list-item-title">Klarna</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="cart/directEBanking">
<div class="title-container">
<p class="integration-list-item-title">Sofort</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="cart/alipay">
<div class="title-container">
<p class="integration-list-item-title">Alipay</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="cart/boletobancario">
<div class="title-container">
<p class="integration-list-item-title">Boleto</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="cart/sepadirectdebit"/>
<div class="title-container">
<p class="integration-list-item-title">Sepa Direct Debit</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="cart/dotpay"/>
<div class="title-container">
<p class="integration-list-item-title">Dotpay</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="cart/giropay"/>
<div class="title-container">
<p class="integration-list-item-title">Giropay</p>
</div>
</a>
</li>
<li class="integration-list-item">
<a class="integration-list-item-link" href="cart/ach"/>
<div class="title-container">
<p class="integration-list-item-title">ACH</p>
</div>
</a>
</li>
</ul>
</div>
{% endblock %}

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta 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">
<link rel="stylesheet" type="text/css" href="/css/main.css">
<title>Checkout Demo</title>
</head>
<body>
<header id="header">
<a href="/">
<img src="/img/mystore-logo.svg" alt="MyStore logo">
</a>
</header>
<div class="container">
{% block content %} {% endblock %}
</div>
</body>
</html>