Skip to content

Commit

Permalink
Merge pull request #59 from xsolla/PAYMENTS-18083
Browse files Browse the repository at this point in the history
feat(PAYMENTS-18083): Google pay component
  • Loading branch information
ekireevxs authored Feb 27, 2024
2 parents 2b520bb + 11ec136 commit 77fdb57
Show file tree
Hide file tree
Showing 11 changed files with 343 additions and 15 deletions.
162 changes: 162 additions & 0 deletions examples/google-pay/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta
name='viewport'
content='width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0'
/>
<meta http-equiv='X-UA-Compatible' content='ie=edge' />
<title>Document</title>
<!--
Link the SDK bundle.
NOTE: In this example, we use a local build just for convenience purposes.
-->
<script src='../../dist/main.js'></script>
<link rel='stylesheet' href='style.css' />
</head>

<body>
<h1>Pay Station SDK</h1>

<h1>GooglePay payment integration (only https)</h1>

<div id='form-container'></div>
<div id='status-container'></div>

<!--
Add finance details component to show purchase details
-->
<psdk-finance-details></psdk-finance-details>

<!--
Add legal component to provide links to legal documents
-->
<psdk-legal></psdk-legal>
<!-- Initialization script -->
<script>
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 = '';

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() {
/**
* 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
});

/**
* 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 googlePayPaymentMethodId = 3431;

const form = await headlessCheckout.form.init({
paymentMethodId: googlePayPaymentMethodId,
returnUrl: 'https://headless-checkout-app.web.app/return'
});

/**
* Subscribe to payment actions
*/
headlessCheckout.form.onNextAction((nextAction) => {
console.log('Next action', nextAction);
const statusContainer = document.querySelector('#status-container');

if (nextAction.type === 'check_status') {
/**
* Remove unnecessary form fields to render StatusComponent in the same place.
*/
formElement.innerHTML = '';

const statusComponent = new PayStationSdk.StatusComponent();
statusContainer.append(statusComponent);
}

if (nextAction.type === 'special_button') {
formElement.innerHTML = '';

if (nextAction.data.buttonName === 'google-pay') {
const googleButton = new PayStationSdk.GooglePayButtonComponent();
formElement.append(googleButton);
}
}
});

/**
* Create payment form
*/
const formElement = document.querySelector('#form-container');

/**
* form.fields provide available fields for selected payment method.
* You can filter it by `isMandatory` flag to get required fields only
*/
const requiredFields = form.fields.filter(
(field) => field.isMandatory === '1'
);
console.log('Required form fields', requiredFields);

/**
* Render requried fields
*/
requiredFields.forEach((field) => {
if (field.type === 'text') {
/**
* You can use <psdk-text name="field.name"></psdk-text> as well
*/
const input = new PayStationSdk.TextComponent();
input.setAttribute('name', field.name);
formElement.append(input);
}
});

/**
* Render submit form button.
* You can use <psdk-submit-button></psdk-submit-button> as well
*/
const submitButton = new PayStationSdk.SubmitButtonComponent();
submitButton.setAttribute('text', 'Pay Now');
formElement.append(submitButton);
}

// initialize sdk
initPayStationSdk();
</script>
</body>
</html>
107 changes: 107 additions & 0 deletions examples/google-pay/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/* *** *** 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-google-pay-button iframe {
border: none;
width: inherit;
height: 40px;
}

psdk-text {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 400px;
height: 60px;
margin-bottom: 30px;
}

psdk-text iframe {
border: none;
width: inherit;
height: 30px;
}

.field-error {
color: #f30;
}

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 *** *** */
15 changes: 1 addition & 14 deletions src/core/actions/check-status.action.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,4 @@ import { Action } from './action.interface';

export type CheckStatusActionType = 'check_status';

export interface CheckStatusActionData {
invoice: number;
signature: string;
locale: string;
testProject?: string;
testPs?: string;
testXsolla?: string;
userReturnStatus?: string;
}

export type CheckStatusAction = Action<
CheckStatusActionType,
CheckStatusActionData
>;
export type CheckStatusAction = Action<CheckStatusActionType, null>;
4 changes: 3 additions & 1 deletion src/core/actions/next-action.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { ShowErrorsAction } from './show-errors.action.type';
import { ShowFieldsAction } from './show-fields.action.type';
import { StatusUpdatedAction } from './status-updated.action.type';
import { ThreeDsAction } from './three-ds/three-ds.action.type';
import { SpecialButtonAction } from './special-button.action.type';

export type NextAction =
| CheckStatusAction
| ShowFieldsAction
| StatusUpdatedAction
| ShowErrorsAction
| RedirectAction
| ThreeDsAction;
| ThreeDsAction
| SpecialButtonAction;
16 changes: 16 additions & 0 deletions src/core/actions/special-button.action.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Action } from './action.interface';

export type SpecialButtonActionType = 'special_button';

export enum SpecialButtonName {
googlePay = 'google-pay',
}

export interface SpecialButtonActionData {
buttonName: SpecialButtonName;
}

export type SpecialButtonAction = Action<
SpecialButtonActionType,
SpecialButtonActionData
>;
1 change: 1 addition & 0 deletions src/core/web-components/web-component-tag-name.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export enum WebComponentTagName {
CheckboxComponent = 'psdk-checkbox',
UserBalanceComponent = 'psdk-user-balance',
PaymentFormMessages = 'psdk-payment-form-messages',
GooglePayButtonComponent = 'psdk-google-pay-button',
}
2 changes: 2 additions & 0 deletions src/core/web-components/web-components.map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { CheckboxComponent } from '../../features/headless-checkout/web-componen
import { SavedMethodsComponent } from '../../features/headless-checkout/web-components/saved-methods/saved-methods.component';
import { UserBalanceComponent } from '../../features/headless-checkout/web-components/user-balance-component/user-balance-component';
import { PaymentFormMessagesComponent } from '../../features/headless-checkout/web-components/payment-form-messages/payment-form-messages.component';
import { GooglePayButtonComponent } from '../../features/headless-checkout/web-components/pages/google-pay/google-pay-button.component';

export const webComponents: {
[key in WebComponentTagName]: CustomElementConstructor;
Expand All @@ -35,4 +36,5 @@ export const webComponents: {
[WebComponentTagName.CheckboxComponent]: CheckboxComponent,
[WebComponentTagName.UserBalanceComponent]: UserBalanceComponent,
[WebComponentTagName.PaymentFormMessages]: PaymentFormMessagesComponent,
[WebComponentTagName.GooglePayButtonComponent]: GooglePayButtonComponent,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { WebComponentTagName } from '../../../../../core/web-components/web-component-tag-name.enum';
import { GooglePayButtonComponent } from '../../../../../web-components';

function createComponent(): void {
const element = document.createElement(
WebComponentTagName.GooglePayButtonComponent,
);
(document.getElementById('container')! as HTMLElement).appendChild(element);
}

describe('GooglePayButtonComponent', () => {
window.customElements.define(
WebComponentTagName.GooglePayButtonComponent,
GooglePayButtonComponent,
);

beforeEach(() => {
document.body.innerHTML = '<div id="container"></div>';
});

afterEach(() => {
document.body.innerHTML = '';
});

it('Should create iframe', () => {
createComponent();

const iframe = document.querySelector('iframe');

expect((iframe as HTMLElement).getAttribute('src')).toContain(
'google-pay-button',
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { SecureComponentAbstract } from '../../../../../core/web-components/secure-component/secure-component.abstract';
import { headlessCheckoutAppUrl } from '../../../environment';

export class GooglePayButtonComponent extends SecureComponentAbstract {
protected componentName = 'pages/google-pay-button';

protected getHtml(): string {
return this.getSecureHtml();
}

protected getSecureHtml(): string {
return `<iframe allow='payment' src='${headlessCheckoutAppUrl}/secure-components/${this.componentName}'></iframe>`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export class SubmitButtonComponent extends WebComponentAbstract {
this.render();
}
});

void this.postMessagesClient.send(
{ name: EventName.submitForm },
submitButtonHandler,
Expand Down
Loading

0 comments on commit 77fdb57

Please sign in to comment.