+
+
diff --git a/examples/credit-card/init-payment-flow.js b/examples/credit-card/init-payment-flow.js
new file mode 100644
index 0000000..3863b9a
--- /dev/null
+++ b/examples/credit-card/init-payment-flow.js
@@ -0,0 +1,267 @@
+/**
+ * Run `buildPaymentFlow` function only when `DOMContentLoaded` to access all the DOM nodes needed.
+ */
+document.addEventListener('DOMContentLoaded', buildPaymentFlow);
+
+function buildPaymentFlow() {
+ if (typeof PayStationSdk === 'undefined') {
+ alert(`
+ It seems SDK library is undefined.
+ Please, link CDN source or create local build (recommended to test purposes only).
+ `);
+ throw new Error('PayStationSdk not found');
+ }
+ /**
+ * To learn more about creating tokens,
+ * please read https://developers.xsolla.com/api/pay-station/operation/create-token/
+ */
+ const accessToken = 'qnntUAwBk6NOUTuIojHW3pRJvFuXwJaI_lc_ru';
+
+ if (!accessToken) {
+ alert('No token provided. Please, check the documentation');
+ throw new Error('No token provided');
+ }
+
+ /**
+ * The SDK is available under the PayStationSdk namespace.
+ * To begin initialization, obtain a reference to the headlessCheckout object.
+ */
+ const { headlessCheckout } = PayStationSdk;
+
+ function handleRedirectAction(redirectAction) {
+ /**
+ * Handle redirect to 3DS secure procedure.
+ */
+ const url = new URL(redirectAction.data.redirect.redirectUrl);
+ const params = Object.entries(redirectAction.data.redirect.data);
+ params.forEach((entry) => {
+ const [key, value] = entry;
+ url.searchParams.append(key, value);
+ });
+
+ /**
+ * Open 3DS Secure.
+ */
+ this.window.location.href = url.toString();
+ }
+
+ function renderRequiredFields(requiredFields, formElement) {
+ /**
+ * It's important to render every required field as a component according to its own type or
+ * other specific parameters. In the current case, we could encounter fields of the following types:
+ * 'text', 'select', or a 'text' field with the name 'card_number'.
+ *
+ * Every type is mapped to a suitable component, and then the component is rendered into the DOM.
+ */
+ requiredFields.forEach((field) => {
+ if (field.type === 'text' && field.name === 'card_number') {
+ renderCardNumberComponent(formElement, field);
+ return;
+ }
+ if (field.type === 'text') {
+ renderTextComponent(formElement, field);
+ return;
+ }
+ if (field.type === 'select') {
+ renderSelectComponent(formElement, field);
+ return;
+ }
+ });
+ }
+
+ function renderCardNumberComponent(formElement, field) {
+ /**
+ * You can use as well
+ */
+ const input = new PayStationSdk.CardNumberComponent();
+ input.setAttribute('name', field.name);
+ formElement.append(input);
+ }
+
+ function renderSelectComponent(formElement, field) {
+ /**
+ * You can use as well
+ */
+ const input = new PayStationSdk.SelectComponent();
+ input.setAttribute('name', field.name);
+ formElement.append(input);
+ }
+
+ function renderTextComponent(formElement, field) {
+ /**
+ * You can use as well
+ */
+ const input = new PayStationSdk.TextComponent();
+ input.setAttribute('name', field.name);
+ formElement.append(input);
+ }
+
+ function renderSubmitButton(formElement) {
+ /**
+ * Render submit form button.
+ * You can use as well
+ */
+ const submitButton = new PayStationSdk.SubmitButtonComponent();
+ submitButton.setAttribute('text', 'Pay Now');
+ formElement.append(submitButton);
+ }
+
+ function handle3dsAction(threeDsAction) {
+ /**
+ * Create 3ds component. It will be handle 3ds verification flow.
+ * You can use component as well
+ */
+
+ const threeDsComponent = new PayStationSdk.ThreeDsComponent();
+
+ threeDsComponent.setAttribute(
+ 'data-challenge',
+ JSON.stringify(threeDsAction.data.data),
+ );
+
+ document.getElementById('right-col').append(threeDsComponent);
+ }
+
+ function renderStatusComponent(statusElement) {
+ /**
+ * Create status component. It will be updated once payment status changed.
+ * You can use component as well
+ */
+ const statusComponent = new PayStationSdk.StatusComponent();
+ statusElement.append(statusComponent);
+ }
+
+ function clearFormFields(formElement) {
+ /**
+ * In some cases, we need to remove all form fields that were rendered before.
+ * This may be necessary when processing Brazilian credit cards, because
+ * they also have a second step with extra fields that need to be filled.
+ * To create a fluent user experience, it would be good practice to clear fields already submitted and
+ * render new fields in the same place.
+ */
+ formElement.innerHTML = '';
+ }
+
+ function getRequiredFields(fields) {
+ /**
+ * form.fields provide available fields for selected payment method.
+ * You can filter it by `isMandatory` flag to get required fields only
+ */
+ return fields.filter((field) => field.isMandatory === '1');
+ }
+
+ async function initPayStationSdk() {
+ /**
+ * Call the `init()` method with the provided environment object.
+ * The isWebView parameter is required and indicates whether your
+ * integration type is a webview or not.
+ * You can set sandbox payment mode with `sandbox` parameter
+ * Please note that this command executes asynchronously.
+ */
+ await headlessCheckout.init({
+ isWebView: false,
+ sandbox: false,
+ returnUrl: '/',
+ });
+
+ /**
+ * Set styles for secure components
+ */
+ await headlessCheckout.setSecureComponentStyles(`
+ @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=swap');
+
+ input {
+ padding: 0;
+ border: 1px solid grey;
+ border-radius: 8px;
+ font-family: 'Roboto', sans-serif;
+ font-size: 14px;
+ height: 30px !important;
+ }
+
+ input:focus {
+ outline: none;
+ }
+ `);
+
+ /**
+ * After the Payments SDK has been initialized, the next step is setting the token.
+ * To learn more about creating tokens,
+ * please read https://developers.xsolla.com/api/pay-station/operation/create-token/
+ */
+ await headlessCheckout.setToken(accessToken);
+
+ /**
+ * Define payment method id.
+ * To get lists of payment methods use psdk-payment-methods.
+ * Please see `examples/select-method` for more details
+ */
+ const creditCardMethodId = 1380;
+
+ /**
+ * Initialize payment.
+ * `returnUrl` will be opened after payment completed on 3DS secure side.
+ */
+ const form = await headlessCheckout.form.init({
+ paymentMethodId: creditCardMethodId,
+ returnUrl: 'http://localhost:3000/return.html',
+ });
+
+ /**
+ * Retrieving DOM elements to render form fields and display status messages.
+ */
+ const formElement = document.querySelector('#form-container');
+ const statusElement = document.querySelector('#status-container');
+
+ /**
+ * Subscribe to payment actions
+ */
+ headlessCheckout.form.onNextAction((nextAction) => {
+ console.log('nextAction', nextAction);
+ /**
+ * Handle 'show_fields' action.
+ */
+ if (nextAction.type === 'show_fields') {
+ clearFormFields(formElement);
+ renderRequiredFields(nextAction.data.fields, formElement);
+ renderSubmitButton(formElement);
+ }
+ /**
+ * Handle 'check_status' action.
+ */
+ if (nextAction.type === 'check_status') {
+ /**
+ * Remove unnecessary form fields to render StatusComponent in the same place.
+ */
+ clearFormFields(formElement);
+ renderStatusComponent(statusElement);
+ }
+
+ /**
+ * Handle '3DS' action.
+ */
+ if (nextAction.type === '3DS') {
+ handle3dsAction(nextAction);
+ }
+
+ /**
+ * Handle '3DS' redirect.
+ */
+ if (nextAction.type === 'redirect') {
+ handleRedirectAction(nextAction);
+ }
+ });
+
+ const requiredFields = getRequiredFields(form.fields);
+
+ /**
+ * Render requried fields
+ */
+ renderRequiredFields(requiredFields, formElement);
+
+ renderSubmitButton(formElement);
+ }
+
+ // initialize sdk
+ initPayStationSdk();
+}
diff --git a/examples/credit-card/return.html b/examples/credit-card/return.html
new file mode 100644
index 0000000..36b1e2d
--- /dev/null
+++ b/examples/credit-card/return.html
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+ Credit card integration
+
+
+
+
+
+
+
+
+
+
+
Credit card integration
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/credit-card/return.js b/examples/credit-card/return.js
new file mode 100644
index 0000000..19d4c89
--- /dev/null
+++ b/examples/credit-card/return.js
@@ -0,0 +1,43 @@
+/**
+ * Run `buildPaymentFlow` function only when `DOMContentLoaded` to access all the DOM nodes needed.
+ */
+document.addEventListener('DOMContentLoaded', buildPaymentFlow);
+
+function buildPaymentFlow() {
+ if (typeof PayStationSdk === 'undefined') {
+ alert(`
+ It seems SDK library is undefined.
+ Please, link CDN source or create local build (recommended to test purposes only).
+ `);
+ throw new Error('PayStationSdk not found');
+ }
+
+ /**
+ * To learn more about creating tokens,
+ * please read https://developers.xsolla.com/api/pay-station/operation/create-token/
+ */
+ const accessToken = 'qnntUAwBk6NOUTuIojHW3pRJvFuXwJaI_lc_ru';
+
+ if (!accessToken) {
+ alert('No token provided. Please, check the documentation');
+ throw new Error('No token provided');
+ }
+
+ /**
+ * The SDK is available under the PayStationSdk namespace.
+ * To begin initialization, obtain a reference to the headlessCheckout object.
+ */
+ const { headlessCheckout } = PayStationSdk;
+
+ async function initPayStationSdk() {
+ await headlessCheckout.init({
+ isWebview: false,
+ sandbox: false,
+ });
+
+ await headlessCheckout.setToken(accessToken);
+ }
+
+ // initialize sdk
+ initPayStationSdk();
+}
diff --git a/examples/credit-card/script.js b/examples/credit-card/script.js
new file mode 100644
index 0000000..698f633
--- /dev/null
+++ b/examples/credit-card/script.js
@@ -0,0 +1,267 @@
+/**
+ * Run `buildPaymentFlow` function only when `DOMContentLoaded` to access all the DOM nodes needed.
+ */
+document.addEventListener('DOMContentLoaded', buildPaymentFlow);
+
+function buildPaymentFlow() {
+ if (typeof PayStationSdk === 'undefined') {
+ alert(`
+ It seems SDK library is undefined.
+ Please, link CDN source or create local build (recommended to test purposes only).
+ `);
+ throw new Error('PayStationSdk not found');
+ }
+ /**
+ * To learn more about creating tokens,
+ * please read https://developers.xsolla.com/api/pay-station/operation/create-token/
+ */
+ const accessToken = 'qnnt5fkqQjvxM34SoMXR4sVpSOEZFg0H_lc_ru';
+
+ if (!accessToken) {
+ alert('No token provided. Please, check the documentation');
+ throw new Error('No token provided');
+ }
+
+ /**
+ * The SDK is available under the PayStationSdk namespace.
+ * To begin initialization, obtain a reference to the headlessCheckout object.
+ */
+ const { headlessCheckout } = PayStationSdk;
+
+ function handleRedirectAction(redirectAction) {
+ /**
+ * Handle redirect to 3DS secure procedure.
+ */
+ const url = new URL(redirectAction.data.redirect.redirectUrl);
+ const params = Object.entries(redirectAction.data.redirect.data);
+ params.forEach((entry) => {
+ const [key, value] = entry;
+ url.searchParams.append(key, value);
+ });
+
+ /**
+ * Open 3DS Secure.
+ */
+ this.window.location.href = url.toString();
+ }
+
+ function renderRequiredFields(requiredFields, formElement) {
+ /**
+ * It's important to render every required field as a component according to its own type or
+ * other specific parameters. In the current case, we could encounter fields of the following types:
+ * 'text', 'select', or a 'text' field with the name 'card_number'.
+ *
+ * Every type is mapped to a suitable component, and then the component is rendered into the DOM.
+ */
+ requiredFields.forEach((field) => {
+ if (field.type === 'text' && field.name === 'card_number') {
+ renderCardNumberComponent(formElement, field);
+ return;
+ }
+ if (field.type === 'text') {
+ renderTextComponent(formElement, field);
+ return;
+ }
+ if (field.type === 'select') {
+ renderSelectComponent(formElement, field);
+ return;
+ }
+ });
+ }
+
+ function renderCardNumberComponent(formElement, field) {
+ /**
+ * You can use as well
+ */
+ const input = new PayStationSdk.CardNumberComponent();
+ input.setAttribute('name', field.name);
+ formElement.append(input);
+ }
+
+ function renderSelectComponent(formElement, field) {
+ /**
+ * You can use as well
+ */
+ const input = new PayStationSdk.SelectComponent();
+ input.setAttribute('name', field.name);
+ formElement.append(input);
+ }
+
+ function renderTextComponent(formElement, field) {
+ /**
+ * You can use as well
+ */
+ const input = new PayStationSdk.TextComponent();
+ input.setAttribute('name', field.name);
+ formElement.append(input);
+ }
+
+ function renderSubmitButton(formElement) {
+ /**
+ * Render submit form button.
+ * You can use as well
+ */
+ const submitButton = new PayStationSdk.SubmitButtonComponent();
+ submitButton.setAttribute('text', 'Pay Now');
+ formElement.append(submitButton);
+ }
+
+ function handle3dsAction(threeDsAction) {
+ /**
+ * Create 3ds component. It will be handle 3ds verification flow.
+ * You can use component as well
+ */
+
+ const threeDsComponent = new PayStationSdk.ThreeDsComponent();
+
+ threeDsComponent.setAttribute(
+ 'data-challenge',
+ JSON.stringify(threeDsAction.data.data),
+ );
+
+ document.getElementById('right-col').append(threeDsComponent);
+ }
+
+ function renderStatusComponent(statusElement) {
+ /**
+ * Create status component. It will be updated once payment status changed.
+ * You can use component as well
+ */
+ const statusComponent = new PayStationSdk.StatusComponent();
+ statusElement.append(statusComponent);
+ }
+
+ function clearFormFields(formElement) {
+ /**
+ * In some cases, we need to remove all form fields that were rendered before.
+ * This may be necessary when processing Brazilian credit cards, because
+ * they also have a second step with extra fields that need to be filled.
+ * To create a fluent user experience, it would be good practice to clear fields already submitted and
+ * render new fields in the same place.
+ */
+ formElement.innerHTML = '';
+ }
+
+ function getRequiredFields(fields) {
+ /**
+ * form.fields provide available fields for selected payment method.
+ * You can filter it by `isMandatory` flag to get required fields only
+ */
+ return fields.filter((field) => field.isMandatory === '1');
+ }
+
+ async function initPayStationSdk() {
+ /**
+ * Call the `init()` method with the provided environment object.
+ * The isWebView parameter is required and indicates whether your
+ * integration type is a webview or not.
+ * You can set sandbox payment mode with `sandbox` parameter
+ * Please note that this command executes asynchronously.
+ */
+ await headlessCheckout.init({
+ isWebView: false,
+ sandbox: false,
+ returnUrl: '/',
+ });
+
+ /**
+ * Set styles for secure components
+ */
+ await headlessCheckout.setSecureComponentStyles(`
+ @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=swap');
+
+ input {
+ padding: 0;
+ border: 1px solid grey;
+ border-radius: 8px;
+ font-family: 'Roboto', sans-serif;
+ font-size: 14px;
+ height: 30px !important;
+ }
+
+ input:focus {
+ outline: none;
+ }
+ `);
+
+ /**
+ * After the Payments SDK has been initialized, the next step is setting the token.
+ * To learn more about creating tokens,
+ * please read https://developers.xsolla.com/api/pay-station/operation/create-token/
+ */
+ await headlessCheckout.setToken(accessToken);
+
+ /**
+ * Define payment method id.
+ * To get lists of payment methods use psdk-payment-methods.
+ * Please see `examples/select-method` for more details
+ */
+ const creditCardMethodId = 1380;
+
+ /**
+ * Initialize payment.
+ * `returnUrl` will be opened after payment completed on 3DS secure side.
+ */
+ const form = await headlessCheckout.form.init({
+ paymentMethodId: creditCardMethodId,
+ returnUrl: 'http://localhost:3000',
+ });
+
+ /**
+ * Retrieving DOM elements to render form fields and display status messages.
+ */
+ const formElement = document.querySelector('#form-container');
+ const statusElement = document.querySelector('#status-container');
+
+ /**
+ * Subscribe to payment actions
+ */
+ headlessCheckout.form.onNextAction((nextAction) => {
+ console.log('nextAction', nextAction);
+ /**
+ * Handle 'show_fields' action.
+ */
+ if (nextAction.type === 'show_fields') {
+ clearFormFields(formElement);
+ renderRequiredFields(nextAction.data.fields, formElement);
+ renderSubmitButton(formElement);
+ }
+ /**
+ * Handle 'check_status' action.
+ */
+ if (nextAction.type === 'check_status') {
+ /**
+ * Remove unnecessary form fields to render StatusComponent in the same place.
+ */
+ clearFormFields(formElement);
+ renderStatusComponent(statusElement);
+ }
+
+ /**
+ * Handle '3DS' action.
+ */
+ if (nextAction.type === '3DS') {
+ handle3dsAction(nextAction);
+ }
+
+ /**
+ * Handle '3DS' redirect.
+ */
+ if (nextAction.type === 'redirect') {
+ handleRedirectAction(nextAction);
+ }
+ });
+
+ const requiredFields = getRequiredFields(form.fields);
+
+ /**
+ * Render requried fields
+ */
+ renderRequiredFields(requiredFields, formElement);
+
+ renderSubmitButton(formElement);
+ }
+
+ // initialize sdk
+ initPayStationSdk();
+}
diff --git a/examples/credit-card/style.css b/examples/credit-card/style.css
new file mode 100644
index 0000000..49756b3
--- /dev/null
+++ b/examples/credit-card/style.css
@@ -0,0 +1,135 @@
+/* *** *** COMMON: START *** *** */
+
+.application {
+ margin: 0 auto;
+ width: 100%;
+ max-width: 700px;
+}
+
+.columns-wrapper {
+ display: flex;
+ padding-bottom: 20px;
+}
+
+.left-col,
+.right-col {
+ width: 50%;
+}
+
+/* *** *** COMMON: END *** *** */
+
+/* *** *** CONTROLS: START *** *** */
+psdk-text,
+psdk-card-number {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ width: 400px;
+ height: 60px;
+ margin-bottom: 30px;
+}
+
+psdk-card-number .wrapper {
+ align-items: center;
+}
+
+psdk-text iframe,
+psdk-card-number iframe {
+ border: none;
+ width: inherit;
+ height: 30px;
+}
+
+.field-error {
+ color: #f30;
+}
+
+/* select style: dropdown will be hidden */
+psdk-select button.select {
+ width: 100%;
+ max-width: 200px;
+}
+
+.dropdown-wrapper {
+ display: none;
+}
+
+/* select style: dropdown will be opened */
+.dropdown-wrapper.dropdown-opened {
+ display: block;
+}
+
+.dropdown {
+ border: 1px solid #c2c2c2;
+ border-radius: 8px;
+}
+
+.dropdown .select-options .option {
+ padding: 4px 8px;
+ cursor: pointer;
+ display: block;
+}
+
+.dropdown .select-options .option:hover {
+ background: #e8e8e8;
+}
+
+psdk-submit-button {
+ width: 100%;
+ max-width: 300px;
+ padding: 3px 6px;
+}
+
+/* *** *** CONTROLS: END *** *** */
+
+/* *** *** FINANCE DETAILS: START *** *** */
+
+psdk-finance-details {
+ display: block;
+ background: #f5f5f5;
+ margin-right: 10px;
+ padding: 10px;
+}
+
+.subtotal-row,
+.total-row {
+ display: flex;
+ width: 100%;
+ justify-content: space-between;
+ margin: 10px 0;
+}
+
+/* *** *** FINANCE DETAILS: END *** *** */
+
+/* *** *** STATUS: START *** *** */
+psdk-status {
+ display: flex;
+}
+
+psdk-status .image {
+ display: block;
+ width: 100%;
+ height: auto;
+}
+
+psdk-status .title-text {
+ text-align: center;
+}
+/* *** *** STATUS: END *** *** */
+
+/* *** *** FOOTER LINKS: START *** *** */
+.company {
+ padding-top: 5px;
+ display: flex;
+}
+
+.company .logo {
+ margin-right: 3px;
+}
+
+.legal-links {
+ padding-top: 5px;
+ display: flex;
+ justify-content: space-between;
+}
+/* *** *** FOOTER LINKS: END *** *** */