Skip to content

Commit

Permalink
feat(PAYMENTS-17861): add saved methods component
Browse files Browse the repository at this point in the history
  • Loading branch information
ekireevxs committed Jan 30, 2024
1 parent 8dbd3a7 commit 93d7101
Show file tree
Hide file tree
Showing 15 changed files with 488 additions and 9 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20.10.0
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,17 @@ declare const headlessCheckout: {
### Regular components

| **Component** | **Selector** | **Status** |
| --------------------- | -------------------- | ---------- |
| --------------------- |----------------------|-----------|
| Payment Methods | psdk-payment-methods ||
| Saved Methods | | 🕑 |
| Payment Form Messages || 🕑 |
| Saved Methods | psdk-saved-methods | |
| Payment Form Messages | | 🕑 |
| Checkbox | psdk-checkbox ||
| Select | psdk-select ||
| Apple Pay Button || 🕑 |
| Google Pay Button || 🕑 |
| Delete Account Button || 🕑 |
| Apple Pay Button | | 🕑 |
| Google Pay Button | | 🕑 |
| Delete Account Button | | 🕑 |
| Submit Button | psdk-submit-button ||
| User Balance || 🕑 |
| User Balance | | 🕑 |
| Finance Details | psdk-finance-details ||
| Status | psdk-status ||

Expand Down
91 changes: 91 additions & 0 deletions examples/saved-methods/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<!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>
</head>

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

<!--
Define a specific Saved methods component in the HTML markup, e.g., `psdk-saved-methods`.
The `country` attribute indicates the country for which the saved methods are being loaded.
-->
<psdk-saved-methods not-found="Methods not found"></psdk-saved-methods>

<!-- 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');
}

async function initPayStationSdk() {
/**
* The SDK is available under the PayStationSdk namespace.
* To begin initialization, obtain a reference to the headlessCheckout object.
*/
const { headlessCheckout } = PayStationSdk;

/**
* 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.
* 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);

/**
* To gain more control over Pay Station components,
* you can access them as regular HTML elements.
*/
const savedMethods = document.querySelector('psdk-saved-methods');

/**
* To add extra logic to a component, you can subscribe to
* its events and place your code in a callback function.
*/
savedMethods.addEventListener('savedMethodSelected', (event) => {
console.log(event.detail);
});
}

// run initialization script
initPayStationSdk();
</script>
</body>
</html>
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 @@ -2,6 +2,7 @@ export enum WebComponentTagName {
TextComponent = 'psdk-text',
SubmitButtonComponent = 'psdk-submit-button',
PaymentMethodsComponent = 'psdk-payment-methods',
SavedMethodsComponent = 'psdk-saved-methods',
PriceTextComponent = 'psdk-price-text',
FinanceDetailsComponent = 'psdk-finance-details',
LegalComponent = 'psdk-legal',
Expand Down
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 @@ -12,13 +12,15 @@ import { ThreeDsComponent } from '../../features/headless-checkout/web-component
import { PhoneComponent } from '../../features/headless-checkout/web-components/phone-component/phone.component';
import { SelectComponent } from '../../features/headless-checkout/web-components/select/select.component';
import { CheckboxComponent } from '../../features/headless-checkout/web-components/checkbox/checkbox.component';
import { SavedMethodsComponent } from '../../features/headless-checkout/web-components/saved-methods/saved-methods.component';

export const webComponents: {
[key in WebComponentTagName]: CustomElementConstructor;
} = {
[WebComponentTagName.TextComponent]: TextComponent,
[WebComponentTagName.SubmitButtonComponent]: SubmitButtonComponent,
[WebComponentTagName.PaymentMethodsComponent]: PaymentMethodsComponent,
[WebComponentTagName.SavedMethodsComponent]: SavedMethodsComponent,
[WebComponentTagName.PriceTextComponent]: PriceTextComponent,
[WebComponentTagName.FinanceDetailsComponent]: FinanceDetailsComponent,
[WebComponentTagName.LegalComponent]: LegalComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export class PaymentMethodsComponent extends WebComponentAbstract {
protected getHtml(): string {
const paymentMethodsHtml = this.getMethodsHtml();
return `
<input type="text" class="search" placeholder="${this.searchPlaceHolder}">
<ul class="payment-methods">
<input type='text' class='search' placeholder='${this.searchPlaceHolder}'>
<ul class='payment-methods'>
${paymentMethodsHtml ? paymentMethodsHtml.join('') : this.notFoundValue}
</ul>
`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function getExpireDate(date?: { month: string; year: string }): string {
if (!date) {
return '';
}

return `${date.month}/${date.year.slice(2)}`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { SavedMethod } from '../../../../../core/saved-method.interface';

const replacerChar = '*';

function findFirstPositionOfNotReplacedChar(value: string): number {
const replacedCharLastPosition = value.split('').lastIndexOf(replacerChar);
return replacedCharLastPosition > -1
? replacedCharLastPosition + 1
: replacedCharLastPosition;
}

function getTextForCard(cardNumber: string): string {
const notReplacedCharFirstPosition =
findFirstPositionOfNotReplacedChar(cardNumber);
return notReplacedCharFirstPosition === -1
? `${cardNumber}`
: `•• ${cardNumber.slice(notReplacedCharFirstPosition).trim()}`;
}

function getTextForEmail(email: string): string {
const firstPositionChar = email.indexOf('*');
const notReplacedCharFirstPosition =
findFirstPositionOfNotReplacedChar(email);
return (
email.slice(0, firstPositionChar).trim() +
'••' +
email.slice(notReplacedCharFirstPosition).trim()
);
}

export function getCutterName(method: SavedMethod): string {
if (method.type === 'card') {
return getTextForCard(method.name);
}

if (method.name.includes('@')) {
return getTextForEmail(method.name);
}

return method.name.replace(/\*/g, '•');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum SavedMethodAttributes {
paymentMethodId = 'data-payment-method-id',
savedMethodId = 'data-saved-method-id',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { getSavedMethodTemplate } from './saved-method.template';
import { SavedMethod } from '../../../../core/saved-method.interface';

const mockQiwi = {
name: '4476 24** **** 9527',
psName: 'Mastercard',
iconName: 'mastercard.svg',
id: 112233,
} as SavedMethod;

describe('getSavedMethodTemplate', () => {
it('Should draw template', () => {
expect(getSavedMethodTemplate(mockQiwi)).toContain('img');
expect(getSavedMethodTemplate(mockQiwi)).toContain(mockQiwi.name);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { cdnUrl } from '../../environment';
import { SavedMethod } from '../../../../core/saved-method.interface';
import { getExpireDate } from './pipes/get-expire-date.pipe';
import { getCutterName } from './pipes/name-cutter.pipe';

const iconsPath = `${cdnUrl}/paystation4/brand-logos`;

export const getSavedMethodTemplate = (method: SavedMethod): string => {
const expireDate = getExpireDate(method.cardExpiryDate);
const name = getCutterName(method);
let iconName: string;

if (!method.iconName) {
iconName = 'default.svg';
} else {
iconName = method.iconName;
}

return `<li class='saved-method' data-payment-method-id='${
method.pid
}' data-saved-method-id='${method.id}'>
<a tabindex='0' href='${method.id}'>
<span class='icon'>
<img src='${iconsPath}/${iconName}' alt='${method.name}'>
</span>
<span class='payment-method-name'>${method.psName}</span>
<span class='name'>${name}</span>
${expireDate ? `<span class='expire-date'>${expireDate}</span>` : ''}
</a>
</li>`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum SavedMethodsAttributes {
notFound = 'not-found',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum SavedMethodsEvents {
savedMethodSelected = 'savedMethodSelected',
}
Loading

0 comments on commit 93d7101

Please sign in to comment.