diff --git a/examples/credit-card/index.html b/examples/credit-card/index.html new file mode 100644 index 0000000..4c3f70b --- /dev/null +++ b/examples/credit-card/index.html @@ -0,0 +1,51 @@ + + + + + + + Credit card integration + + + + + + + + + +
+

Credit card integration

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