diff --git a/enabler/decs.d.ts b/enabler/decs.d.ts
index d5cf927..0f8afe3 100644
--- a/enabler/decs.d.ts
+++ b/enabler/decs.d.ts
@@ -1 +1,8 @@
declare module '*.scss';
+declare module "@adyen/adyen-web"
+declare module "@adyen/adyen-web/dist/types/components/ApplePay"
+declare module "@adyen/adyen-web/dist/types/components/GooglePay"
+declare module "@adyen/adyen-web/dist/types/core"
+declare module "@adyen/adyen-web/dist/types/core/core"
+declare module "@adyen/adyen-web/dist/types/components/Redirect/Redirect"
+declare module "@adyen/adyen-web/dist/types/core/types"
diff --git a/enabler/package-lock.json b/enabler/package-lock.json
index d646c2b..d8d6823 100644
--- a/enabler/package-lock.json
+++ b/enabler/package-lock.json
@@ -8,6 +8,7 @@
"name": "enabler",
"version": "1.0.0",
"dependencies": {
+ "@adyen/adyen-web": "5.51.0",
"serve": "14.2.1"
},
"devDependencies": {
@@ -20,6 +21,20 @@
"vite-plugin-css-injected-by-js": "3.4.0"
}
},
+ "node_modules/@adyen/adyen-web": {
+ "version": "5.51.0",
+ "resolved": "https://registry.npmjs.org/@adyen/adyen-web/-/adyen-web-5.51.0.tgz",
+ "integrity": "sha512-jH3Us9k57AhdOrwBL444EQdQ+xTgg4BTD/9/RSOmldw9EM1LzX1+6AKkX/7QioJoYO6L7gI5DUu5iq915ju0IA==",
+ "dependencies": {
+ "@babel/runtime": "^7.15.4",
+ "@babel/runtime-corejs3": "^7.20.1",
+ "@types/applepayjs": "^3.0.4",
+ "@types/googlepay": "^0.7.0",
+ "classnames": "^2.3.1",
+ "core-js-pure": "^3.25.3",
+ "preact": "10.13.2"
+ }
+ },
"node_modules/@ampproject/remapping": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
@@ -600,6 +615,29 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/runtime": {
+ "version": "7.23.9",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
+ "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/runtime-corejs3": {
+ "version": "7.23.9",
+ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.23.9.tgz",
+ "integrity": "sha512-oeOFTrYWdWXCvXGB5orvMTJ6gCZ9I6FBjR+M38iKNXCsPxr4xT0RTdg5uz1H7QP8pp74IzPtwritEr+JscqHXQ==",
+ "dependencies": {
+ "core-js-pure": "^3.30.2",
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/template": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz",
@@ -1676,6 +1714,11 @@
"optional": true,
"peer": true
},
+ "node_modules/@types/applepayjs": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/applepayjs/-/applepayjs-3.0.4.tgz",
+ "integrity": "sha512-RqaVZWy1Kj4e1PoUoOI8uA+4UuuLpicQFxfU9Y/xWJFZFT6mFB4PiiY911iDxFk7pdvaj5HKH7VsWRisRca1Rg=="
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1723,6 +1766,11 @@
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
+ "node_modules/@types/googlepay": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@types/googlepay/-/googlepay-0.7.5.tgz",
+ "integrity": "sha512-158egcRaqkMSpW6unyGV4uG4FpoCklRf3J5emCzOXSRVAohMfIuZ481JNvp4X6+KxoNjxWiGtMx5vb1YfQADPw=="
+ },
"node_modules/@types/graceful-fs": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
@@ -2353,6 +2401,11 @@
"integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==",
"dev": true
},
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ },
"node_modules/cli-boxes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
@@ -2544,6 +2597,16 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"dev": true
},
+ "node_modules/core-js-pure": {
+ "version": "3.36.0",
+ "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.0.tgz",
+ "integrity": "sha512-cN28qmhRNgbMZZMc/RFu5w8pK9VJzpb2rJVR/lHuZJKwmXnoWOpXmMkxqBB514igkp1Hu8WGROsiOAzUcKdHOQ==",
+ "hasInstallScript": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
"node_modules/create-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
@@ -4411,6 +4474,15 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/preact": {
+ "version": "10.13.2",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.2.tgz",
+ "integrity": "sha512-q44QFLhOhty2Bd0Y46fnYW0gD/cbVM9dUVtNTDKPcdXSMA7jfY+Jpd6rk3GB0lcQss0z5s/6CmVP0Z/hV+g6pw==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
"node_modules/pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
@@ -4511,6 +4583,11 @@
"node": ">=8.10.0"
}
},
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+ },
"node_modules/registry-auth-token": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
diff --git a/enabler/package.json b/enabler/package.json
index 95cf241..dc1ef6f 100644
--- a/enabler/package.json
+++ b/enabler/package.json
@@ -12,6 +12,7 @@
"serve": "npm run build && serve dist -l 3000 --cors"
},
"dependencies": {
+ "@adyen/adyen-web": "5.51.0",
"serve": "14.2.1"
},
"devDependencies": {
diff --git a/enabler/src/components/base.ts b/enabler/src/components/base.ts
index c3c3fbf..b3814b7 100644
--- a/enabler/src/components/base.ts
+++ b/enabler/src/components/base.ts
@@ -1,53 +1,50 @@
-import { FakeSdk } from '../fake-sdk';
-import { ComponentOptions, PaymentComponent, PaymentMethod, PaymentResult } from '../payment-enabler/payment-enabler';
-
-export type ElementOptions = {
- paymentMethod: PaymentMethod;
-};
+import Core from '@adyen/adyen-web/dist/types/core/core';
+import { ComponentOptions, PaymentComponent, PaymentMethod } from '../payment-enabler/payment-enabler';
+import ApplePay from '@adyen/adyen-web/dist/types/components/ApplePay';
+import GooglePay from '@adyen/adyen-web/dist/types/components/GooglePay';
+import RedirectElement from '@adyen/adyen-web/dist/types/components/Redirect/Redirect';
export type BaseOptions = {
- sdk: FakeSdk;
- processorUrl: string;
- sessionId: string;
- environment: string;
- config: {
- showPayButton?: boolean;
- };
- onComplete: (result: PaymentResult) => void;
- onError: (error?: any) => void;
+ adyenCheckout: typeof Core;
}
/**
* Base Web Component
*/
export abstract class BaseComponent implements PaymentComponent {
- protected paymentMethod: ElementOptions['paymentMethod'];
- protected sdk: FakeSdk;
- protected processorUrl: BaseOptions['processorUrl'];
- protected sessionId: BaseOptions['sessionId'];
- protected environment: BaseOptions['environment'];
- protected config: BaseOptions['config'];
- protected showPayButton: boolean;
- protected onComplete: (result: PaymentResult) => void;
- protected onError: (error?: any) => void;
+ protected paymentMethod: PaymentMethod;
+ protected adyenCheckout: typeof Core;
+ protected config: ComponentOptions['config'];
+ protected component: typeof ApplePay | typeof GooglePay | typeof RedirectElement;
- constructor(baseOptions: BaseOptions, componentOptions: ComponentOptions) {
- this.sdk = baseOptions.sdk;
- this.processorUrl = baseOptions.processorUrl;
- this.sessionId = baseOptions.sessionId;
- this.environment = baseOptions.environment;
- this.config = baseOptions.config;
- this.onComplete = baseOptions.onComplete;
- this.onError = baseOptions.onError;
- this.showPayButton =
- 'showPayButton' in componentOptions.config ? !!componentOptions.config.showPayButton :
- 'showPayButton' in baseOptions.config ? !!baseOptions.config.showPayButton :
- true;
+ constructor(paymentMethod: PaymentMethod, baseOptions: BaseOptions, componentOptions: ComponentOptions) {
+ this.paymentMethod = paymentMethod;
+ this.adyenCheckout = baseOptions.adyenCheckout;
+ this.config = componentOptions.config;
+ this.component = this._create();
}
- abstract submit(): void;
+ protected _create(): typeof ApplePay | typeof GooglePay | typeof RedirectElement {
+ return this.adyenCheckout.create(this.paymentMethod, this.config);
+ }
- abstract mount(selector: string): void ;
+ submit() {
+ this.component.submit();
+ };
+
+ mount(selector: string) {
+ if ('isAvailable' in this.component) {
+ this.component.isAvailable()
+ .then(() => {
+ this.component.mount(selector);
+ })
+ .catch((e: unknown) => {
+ console.log(`${this.paymentMethod } is not available`, e);
+ });
+ } else {
+ this.component.mount(selector);
+ }
+ }
showValidation?(): void;
isValid?(): boolean;
diff --git a/enabler/src/components/payment-methods/applepay.ts b/enabler/src/components/payment-methods/applepay.ts
new file mode 100644
index 0000000..1234c84
--- /dev/null
+++ b/enabler/src/components/payment-methods/applepay.ts
@@ -0,0 +1,14 @@
+import { BaseComponent, BaseOptions } from '../base';
+import { ComponentOptions, PaymentMethod } from '../../payment-enabler/payment-enabler';
+
+/**
+ * Apple pay component
+ *
+ * Configuration options:
+ * https://docs.adyen.com/payment-methods/apple-pay/web-component/
+ */
+export class Applepay extends BaseComponent {
+ constructor(baseOptions: BaseOptions, componentOptions: ComponentOptions) {
+ super(PaymentMethod.applepay, baseOptions, componentOptions);
+ }
+}
diff --git a/enabler/src/components/payment-methods/card.ts b/enabler/src/components/payment-methods/card.ts
new file mode 100644
index 0000000..157447f
--- /dev/null
+++ b/enabler/src/components/payment-methods/card.ts
@@ -0,0 +1,50 @@
+import { ComponentOptions, PaymentMethod } from '../../payment-enabler/payment-enabler';
+import { BaseComponent, BaseOptions } from '../base';
+
+/**
+ * Credit card component
+ *
+ * Configuration options:
+ * https://docs.adyen.com/payment-methods/cards/web-component/
+ */
+
+
+export class Card extends BaseComponent {
+ private endDigits: string;
+
+ constructor(baseOptions: BaseOptions, componentOptions: ComponentOptions) {
+ super(PaymentMethod.card, baseOptions, componentOptions);
+ }
+
+ protected _create() {
+ const that = this;
+ return this.adyenCheckout.create(this.paymentMethod, {
+ onFieldValid : function(data) {
+ const { endDigits, fieldType } = data;
+ if (endDigits && fieldType === 'encryptedCardNumber') {
+ that.endDigits = endDigits;
+ }
+ },
+ ...this.config,
+ });
+ }
+
+
+ showValidation() {
+ this.component.showValidation();
+ }
+
+ isValid() {
+ return this.component.isValid;
+ }
+
+ getState() {
+ return {
+ card: {
+ endDigits: this.endDigits,
+ brand: this.component.state.selectedBrandValue,
+ }
+ };
+ }
+
+}
diff --git a/enabler/src/components/payment-methods/card/card.ts b/enabler/src/components/payment-methods/card/card.ts
deleted file mode 100644
index 870816d..0000000
--- a/enabler/src/components/payment-methods/card/card.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import { ComponentOptions } from '../../../payment-enabler/payment-enabler';
-import buttonStyles from '../../../style/button.module.scss';
-import inputFieldStyles from '../../../style/inputField.module.scss';
-import styles from '../../../style/style.module.scss';
-import { BaseComponent, BaseOptions } from '../../base';
-import { addFormFieldsEventListeners, fieldIds, getCardBrand, getInput, validateAllFields } from './utils';
-
-export class Card extends BaseComponent {
- constructor(baseOptions: BaseOptions, componentOptions: ComponentOptions) {
- super(baseOptions, componentOptions);
- this.paymentMethod = 'card';
- }
-
- mount(selector: string) {
- document.querySelector(selector).insertAdjacentHTML("afterbegin", this._getTemplate());
- if (this.showPayButton) {
- document.querySelector('#creditCardForm-paymentButton').addEventListener('click', (e) => {
- e.preventDefault();
- this.submit();
- });
- }
-
- addFormFieldsEventListeners();
- }
-
- async submit() {
- // here we would call the SDK to submit the payment
- this.sdk.init({ environment: this.environment });
- const isFormValid = validateAllFields();
- if (!isFormValid) {
- return;
- }
- try {
- const requestData = {
- paymentMethod: {
- type: this.paymentMethod,
- cardNumber: getInput(fieldIds.cardNumber).value.replace(/\s/g, ''),
- expiryMonth: getInput(fieldIds.expiryDate).value.split('/')[0],
- expiryYear: getInput(fieldIds.expiryDate).value.split('/')[1],
- cvc: getInput(fieldIds.cvv).value,
- holderName: getInput(fieldIds.holderName).value,
- }
- };
- const response = await fetch(this.processorUrl + '/payments', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json', 'X-Session-Id': this.sessionId },
- body: JSON.stringify(requestData),
- });
- const data = await response.json();
-
- if (data.outcome === 'Authorized' && data.paymentReference) {
- this.onComplete && this.onComplete({ isSuccess: true, paymentReference: data.paymentReference });
- } else {
- this.onComplete && this.onComplete({ isSuccess: false });
- }
- } catch(e) {
- this.onError('Some error occurred. Please try again.');
- }
- }
-
- private _getTemplate() {
- const payButton = this.showPayButton ? `` : '';
- return `
-
- `
- }
-
- showValidation() {
- validateAllFields();
- }
-
- isValid() {
- return validateAllFields();
- }
-
- getState() {
- return {
- card: {
- endDigits: getInput(fieldIds.cardNumber).value.slice(-4),
- brand: getCardBrand(getInput(fieldIds.cardNumber).value),
- expiryDate: getInput(fieldIds.expiryDate).value,
- },
- };
- }
-}
diff --git a/enabler/src/components/payment-methods/card/utils.ts b/enabler/src/components/payment-methods/card/utils.ts
deleted file mode 100644
index 1211f26..0000000
--- a/enabler/src/components/payment-methods/card/utils.ts
+++ /dev/null
@@ -1,180 +0,0 @@
-import styles from '../../../style/style.module.scss';
-import inputFieldStyles from '../../../style/inputField.module.scss';
-
-export const fieldIds = {
- cardNumber: 'creditCardForm-cardNumber',
- expiryDate: 'creditCardForm-expiryDate',
- cvv: 'creditCardForm-cvv',
- holderName: 'creditCardForm-holderNameLabel',
-};
-
-export const getInput = (field: string) => (document.querySelector(`#${field}`) as HTMLInputElement);
-
-const showErrorIfInvalid = (field: string) => {
- if (!isFieldValid(field)) {
- const input = getInput(field);
- input.parentElement.classList.add(inputFieldStyles.error);
- input.parentElement.querySelector(`#${field} + .${inputFieldStyles.errorField}`).classList.remove(styles.hidden);
- }
-}
-
-const hideErrorIfValid = (field: string) => {
- if (isFieldValid(field)) {
- const input = getInput(field);
- input.parentElement.classList.remove(inputFieldStyles.error);
- input.parentElement.querySelector(`#${field} + .${inputFieldStyles.errorField}`).classList.add(styles.hidden);
- }
-}
-
-export const validateAllFields = () => {
- let isValid = true;
- Object.values(fieldIds).forEach((field) => {
- if (!isFieldValid(field)) {
- isValid = false;
- showErrorIfInvalid(field);
- }
- });
- return isValid;
-}
-
-const handleFieldValidation = (field: string) => {
- const input = getInput(field);
- input.addEventListener('input', () => {
- hideErrorIfValid(field);
- });
- input.addEventListener('focusout', () => {
- showErrorIfInvalid(field);
- input.value.length > 0 ? input.parentElement.classList.add(inputFieldStyles.containValue) : input.parentElement.classList.remove(inputFieldStyles.containValue);
- });
-}
-
-const isFieldValid = (field: string) => {
- const input = getInput(field);
- switch (field) {
- case 'creditCardForm-cardNumber':
- return input.value.replace(/\s/g, '').length === 16;
- case 'creditCardForm-expiryDate':
- return input.value.length === 5;
- case 'creditCardForm-cvv':
- return input.value.length === 3;
- case 'creditCardForm-holderNameLabel':
- return input.value.length > 0;
- default:
- return false;
- }
-}
-
-export const getCardBrand = (cardNumber: string) => {
- if (cardNumber.startsWith('4')) {
- return 'visa';
- }
- if (cardNumber.startsWith('5')) {
- return 'mastercard';
- }
- if (cardNumber.startsWith('6')) {
- return 'maestro';
- }
- if (cardNumber.startsWith('3')) {
- return 'amex';
- }
- return 'unknown';
-}
-
-const dateFormatter = (): ((inputValue: string) => string) => {
- let previousValue = '';
- return (inputValue: string): string => {
- let output = inputValue;
- let isInvalidValue = false;
- const pattern = /[0-9/]/;
- const lastCharacter = inputValue.slice(-1);
- const inputLength = inputValue.length;
- /***
- * should not allow any character other than 0-9 and /
- * should convert eg: 1/ => 01/
- * eg: 12/3/ =>12/3
- * eg: 18 =>01/8
- * eg: 123 =>12/3
- * eg: 12(previous value = 1) =>12/
- * eg: 12(previous value = 12/) =>1 (delete behaviour)
- * eg: 12/345 =>12/34
- */
- if (!pattern.test(lastCharacter)) {
- isInvalidValue = true;
- } else if (lastCharacter === '/') {
- if (inputLength === 2) {
- output = `0${inputValue}`;
- } else if (inputLength !== 3) {
- isInvalidValue = true;
- }
- } else if (inputLength === 2) {
- if (previousValue !== `${output}/`) {
- if (Number(inputValue) > 12) {
- output = `0${inputValue[0]}/${inputValue[1]}`;
- } else {
- output = `${output}/`;
- }
- } else {
- output = output[0];
- }
- } else if (inputLength === 3 && lastCharacter != '/') {
- output = `${inputValue[0]}${inputValue[1]}/${inputValue[2]}`;
- } else if (inputLength > 5) {
- isInvalidValue = true;
- }
-
- if (isInvalidValue) {
- output = inputValue.substring(0, inputLength - 1);
- }
- previousValue = output;
- return output;
- };
-};
-
-const addCardNumberEventListeners = () => {
- const cardNumber = getInput(fieldIds.cardNumber);
- cardNumber.addEventListener('input', () => {
- cardNumber.value = cardNumber.value.replace(/\D/g,'').replace(/(\d{4})/g, '$1 ').trim();
- const brand = getCardBrand(cardNumber.value);
- const cardIcons = document.querySelectorAll(`.${styles.cardIcon}`);
- cardIcons.forEach((icon) => {
- icon.classList.add(styles.hidden);
- });
- const cardIcon = document.querySelector(`#creditCardForm-${brand}`);
- if (cardIcon) {
- cardIcon.classList.remove(styles.hidden);
- }
- });
- handleFieldValidation(fieldIds.cardNumber);
-}
-
-const addDateEventListeners = () => {
- const expiryDate = getInput(fieldIds.expiryDate);
- expiryDate.addEventListener('input', () => {
- expiryDate.value = dateFormatter()(expiryDate.value);
- });
- handleFieldValidation(fieldIds.expiryDate);
-}
-
-const addCvvEventListeners = () => {
- const cvv = getInput(fieldIds.cvv);
- cvv.addEventListener('input', () => {
- if (isNaN(Number(cvv.value))) {
- cvv.value = cvv.value.slice(0, -1);
- }
- if (cvv.value.length > 3) {
- cvv.value = cvv.value.slice(0, 3);
- }
- });
- handleFieldValidation(fieldIds.cvv);
-}
-
-const addHolderNameEventListeners = () => {
- handleFieldValidation(fieldIds.holderName);
-}
-
-export const addFormFieldsEventListeners = () => {
- addCardNumberEventListeners();
- addDateEventListeners();
- addCvvEventListeners();
- addHolderNameEventListeners();
-}
diff --git a/enabler/src/components/payment-methods/googlepay.ts b/enabler/src/components/payment-methods/googlepay.ts
new file mode 100644
index 0000000..f0d02af
--- /dev/null
+++ b/enabler/src/components/payment-methods/googlepay.ts
@@ -0,0 +1,14 @@
+import { BaseComponent, BaseOptions } from '../base';
+import { ComponentOptions, PaymentMethod } from '../../payment-enabler/payment-enabler';
+
+/**
+ * Google pay component
+ *
+ * Configuration options:
+ * https://docs.adyen.com/payment-methods/google-pay/web-component/
+ */
+export class Googlepay extends BaseComponent {
+ constructor(baseOptions: BaseOptions, componentOptions: ComponentOptions) {
+ super(PaymentMethod.googlepay, baseOptions, componentOptions);
+ }
+}
diff --git a/enabler/src/components/payment-methods/ideal.ts b/enabler/src/components/payment-methods/ideal.ts
new file mode 100644
index 0000000..d6301dc
--- /dev/null
+++ b/enabler/src/components/payment-methods/ideal.ts
@@ -0,0 +1,21 @@
+import { ComponentOptions, PaymentMethod } from '../../payment-enabler/payment-enabler';
+import { BaseComponent, BaseOptions } from '../base';
+/**
+ * Ideal component
+ *
+ * Configuration options:
+ * https://docs.adyen.com/payment-methods/ideal/web-component/
+ */
+export class Ideal extends BaseComponent {
+ constructor(baseOptions: BaseOptions, componentOptions: ComponentOptions) {
+ super(PaymentMethod.ideal, baseOptions, componentOptions);
+ }
+
+ showValidation() {
+ this.component.showValidation();
+ }
+
+ isValid() {
+ return this.component.isValid;
+ }
+}
diff --git a/enabler/src/components/payment-methods/klarna.ts b/enabler/src/components/payment-methods/klarna.ts
new file mode 100644
index 0000000..25a9d56
--- /dev/null
+++ b/enabler/src/components/payment-methods/klarna.ts
@@ -0,0 +1,17 @@
+import { BaseComponent, BaseOptions } from '../base';
+import { ComponentOptions, PaymentMethod } from '../../payment-enabler/payment-enabler';
+
+/**
+ * Klarna component
+ *
+ * Configuration options:
+ * https://docs.adyen.com/payment-methods/klarna/web-component/
+ */
+export class Klarna extends BaseComponent {
+ constructor(baseOptions: BaseOptions, componentOptions: ComponentOptions) {
+ /* todo:
+ pass locale
+ */
+ super(PaymentMethod.klarna, baseOptions, componentOptions);
+ }
+}
diff --git a/enabler/src/components/payment-methods/paypal.ts b/enabler/src/components/payment-methods/paypal.ts
new file mode 100644
index 0000000..2b794c0
--- /dev/null
+++ b/enabler/src/components/payment-methods/paypal.ts
@@ -0,0 +1,27 @@
+import { BaseComponent, BaseOptions } from '../base';
+import { ComponentOptions, PaymentMethod } from '../../payment-enabler/payment-enabler';
+
+/**
+ * Paypal component
+ *
+ * Configuration options:
+ * https://docs.adyen.com/payment-methods/paypal/web-component/
+ */
+export class Paypal extends BaseComponent {
+ constructor(baseOptions: BaseOptions, componentOptions: ComponentOptions) {
+ // TODO:
+
+ /*
+
+ Hide Venmo
+
+ If you and your shopper are both located in the US, Venmo is shown in the PayPal Component by default. To hide Venmo in the PayPal Component, set blockPayPalVenmoButton to true.
+
+ Use the create method of your AdyenCheckout instance, in this case checkout, to create an instance of the Component. Add the configuration object if you created one.
+
+
+ const paypalComponent = checkout.create('paypal', paypalConfiguration).mount('#paypal-container');
+ */
+ super(PaymentMethod.paypal, baseOptions, componentOptions);
+ }
+}
diff --git a/enabler/src/fake-sdk.ts b/enabler/src/fake-sdk.ts
deleted file mode 100644
index b1be1b9..0000000
--- a/enabler/src/fake-sdk.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export class FakeSdk {
- private environment: string;
- constructor({ environment }) {
- this.environment = environment;
- console.log('FakeSdk constructor', this.environment);
- }
- init(opts: any) {
- console.log('FakeSdk init', opts);
- }
-}
diff --git a/enabler/src/main.ts b/enabler/src/main.ts
index 5eef9db..6f02271 100644
--- a/enabler/src/main.ts
+++ b/enabler/src/main.ts
@@ -1,3 +1,3 @@
-import { MockPaymentEnabler } from './payment-enabler/payment-enabler-mock';
+import { AdyenPaymentEnabler } from './payment-enabler/adyen-payment-enabler';
-export { MockPaymentEnabler as Enabler };
+export { AdyenPaymentEnabler as Enabler };
diff --git a/enabler/src/payment-enabler/adyen-payment-enabler.ts b/enabler/src/payment-enabler/adyen-payment-enabler.ts
new file mode 100644
index 0000000..b092ebe
--- /dev/null
+++ b/enabler/src/payment-enabler/adyen-payment-enabler.ts
@@ -0,0 +1,143 @@
+import '@adyen/adyen-web/dist/adyen.css';
+import AdyenCheckout from '@adyen/adyen-web';
+import { CoreOptions } from '@adyen/adyen-web/dist/types/core/types';
+import { BaseOptions } from '../components/base';
+
+import { ComponentOptions, PaymentEnabler, EnablerOptions } from './payment-enabler';
+import { Card } from '../components/payment-methods/card';
+import { Ideal } from '../components/payment-methods/ideal';
+import { Googlepay } from '../components/payment-methods/googlepay';
+import { Applepay } from '../components/payment-methods/applepay';
+import { Klarna } from '../components/payment-methods/klarna';
+
+type AdyenEnablerOptions = EnablerOptions & {
+ config: Omit;
+ onActionRequired?: (action: any) => Promise;
+};
+
+export class AdyenPaymentEnabler implements PaymentEnabler {
+ setupData: Promise<{ baseOptions: BaseOptions }>;
+
+ constructor(options: AdyenEnablerOptions) {
+ this.setupData = AdyenPaymentEnabler._Setup(options);
+ }
+
+ private static _Setup = async (options: AdyenEnablerOptions): Promise<{ baseOptions: BaseOptions }> => {
+ const [sessionResponse, configResponse] = await Promise.all([
+ fetch(options.processorUrl + '/sessions', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', 'X-Session-Id': options.sessionId },
+ body: JSON.stringify({}),
+ }),
+ fetch(options.processorUrl + '/operations/config', {
+ method: 'GET',
+ headers: { 'Content-Type': 'application/json', 'X-Session-Id': options.sessionId },
+ }),
+ ]);
+
+ const [sessionJson, configJson] = await Promise.all([sessionResponse.json(), configResponse.json()]);
+
+ const { sessionData: data, paymentReference } = sessionJson;
+
+ const adyenCheckout = await AdyenCheckout({
+ onPaymentCompleted: (result, component) => {
+ debugger;
+ console.info(result, component);
+ window.location.href = options.processorUrl + '/confirm';
+ },
+ onError: (error, component) => {
+ console.error(error.name, error.message, error.stack, component);
+ },
+ onSubmit: async (state, component) => {
+ try {
+ const reqData = {
+ ...state.data,
+ channel: 'Web',
+ paymentReference,
+ };
+ const response = await fetch(options.processorUrl + '/payments', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', 'X-Session-Id': options.sessionId },
+ body: JSON.stringify(reqData),
+ });
+ const data = await response.json();
+ console.log('onSubmit test', state, component, data)
+ if (data.action) {
+ options.onActionRequired && options.onActionRequired({
+ offsite: data.action.type === 'redirect',
+ });
+ component.handleAction(data.action);
+ } else {
+ if (data.resultCode === 'Authorised') {
+ component.setStatus('success');
+ options.onComplete && options.onComplete({ isSuccess: true, paymentReference });
+ } else {
+ options.onComplete && options.onComplete({ isSuccess: false });
+ component.setStatus('error');
+ }
+ }
+ } catch (e) {
+ console.log('Payment aborted by client');
+ component.setStatus('ready');
+ }
+ },
+ onAdditionalDetails: async (state, component) => {
+ console.log('onAdditionalDetails', state, component);
+ const requestData = {
+ ...state.data,
+ paymentReference,
+ };
+ const url = options.processorUrl.endsWith('/')
+ ? `${options.processorUrl}payment/details`
+ : `${options.processorUrl}/payment/details`;
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', 'X-Session-Id': options.sessionId },
+ body: JSON.stringify(requestData),
+ });
+ const data = await response.json();
+ if (data.resultCode === 'Authorised') {
+ component.setStatus('success');
+ options.onComplete && options.onComplete({ isSuccess: true, paymentReference });
+ } else {
+ options.onComplete && options.onComplete({ isSuccess: false });
+ component.setStatus('error');
+ }
+ },
+ analytics: {
+ enabled: true,
+ },
+
+ ...(options.config?.locale ? { locale: options.config.locale } : {}),
+ ...(options.config?.showPayButton ? { showPayButton: options.config.showPayButton } : {}),
+
+ environment: configJson.environment,
+ clientKey: configJson.clientKey,
+ session: {
+ id: data.id,
+ sessionData: data.sessionData,
+ },
+ });
+
+ return {
+ baseOptions: {
+ adyenCheckout: adyenCheckout
+ }
+ };
+ }
+
+ async createComponent(type: string, componentOptions: ComponentOptions) {
+ const { baseOptions } = await this.setupData;
+ const supportedMethods = {
+ applepay: Applepay,
+ card: Card,
+ googlepay: Googlepay,
+ ideal: Ideal,
+ klarna: Klarna,
+ }
+ if (!Object.keys(supportedMethods).includes(type)) {
+ throw new Error(`Component type not supported: ${type}. Supported types: ${Object.keys(supportedMethods).join(', ')}`);
+ }
+ return new supportedMethods[type](baseOptions, componentOptions);
+ }
+}
diff --git a/enabler/src/payment-enabler/payment-enabler-mock.ts b/enabler/src/payment-enabler/payment-enabler-mock.ts
deleted file mode 100644
index f161abd..0000000
--- a/enabler/src/payment-enabler/payment-enabler-mock.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { BaseOptions } from '../components/base';
-import { Card } from '../components/payment-methods/card/card';
-import { FakeSdk } from '../fake-sdk';
-import { ComponentOptions, EnablerOptions, PaymentEnabler, PaymentMethod } from './payment-enabler';
-
-const SupportedMethods: PaymentMethod[] = ['card'];
-
-declare global {
- interface ImportMeta {
- env: any;
- }
-}
-
-export class MockPaymentEnabler implements PaymentEnabler {
- setupData: Promise<{ baseOptions: BaseOptions }>;
-
- private constructor(options: EnablerOptions) {
- this.setupData = MockPaymentEnabler._Setup(options);
- }
-
- private static _Setup = async (options: EnablerOptions): Promise<{ baseOptions: BaseOptions }> => {
- // Fetch SDK config from processor if needed, for example:
-
- // const configResponse = await fetch(instance.processorUrl + '/config', {
- // method: 'GET',
- // headers: { 'Content-Type': 'application/json' },
- // });
-
- // const configJson = await configResponse.json();
-
- const sdkOptions = {
- // environment: configJson.environment,
- environment: 'test'
- }
-
- return Promise.resolve({ baseOptions: {
- sdk: new FakeSdk(sdkOptions),
- processorUrl: options.processorUrl,
- sessionId: options.sessionId,
- environment: sdkOptions.environment,
- config: options.config || {},
- onComplete: options.onComplete || (() => {}),
- onError: options.onError || (() => {}),
- }
- });
- }
-
- async createComponent(type: string, componentOptions: ComponentOptions) {
- const { baseOptions } = await this.setupData;
-
- switch (type) {
- case 'card':
- return new Card(baseOptions, componentOptions);
- }
- throw new Error(`Payment method not supported: ${type}. Supported methods: ${Object.keys(SupportedMethods).join(', ')}`);
- }
-}
diff --git a/enabler/src/payment-enabler/payment-enabler.ts b/enabler/src/payment-enabler/payment-enabler.ts
index 5a9e6e0..75cd131 100644
--- a/enabler/src/payment-enabler/payment-enabler.ts
+++ b/enabler/src/payment-enabler/payment-enabler.ts
@@ -15,12 +15,25 @@ export interface PaymentComponent {
export type EnablerOptions = {
processorUrl: string;
sessionId: string;
- config?: { showPayButton?: boolean };
+ config?: {
+ locale?: string;
+ showPayButton?: boolean;
+ };
+ onActionRequired?: () => Promise;
onComplete?: (result: PaymentResult) => void;
onError?: (error: any) => void;
};
-export type PaymentMethod = 'card';
+
+export enum PaymentMethod {
+ applepay = "applepay",
+ card = "card",
+ dropin = "dropin",
+ googlepay = "googlepay",
+ ideal = "ideal",
+ klarna = "klarna",
+ paypal = "paypal",
+}
export type PaymentResult = {
isSuccess: true;
@@ -37,5 +50,5 @@ export interface PaymentEnabler {
/**
* @throws {Error}
*/
- createComponent: (type: string, opts: ComponentOptions, sessionId?: string) => Promise
+ createComponent: (type: string, opts: ComponentOptions) => Promise
}