mirror of
https://github.com/valitydev/checkout.git
synced 2024-11-06 02:25:18 +00:00
FE-79: added unified error handling. Added form buttons disabling / hiding functionality. Changing styles. Refactoring payform (#22)
This commit is contained in:
parent
6c167522e4
commit
50865b10b6
@ -1,13 +1,14 @@
|
||||
export default class Initialization {
|
||||
|
||||
static sendInit(endpoint, data) {
|
||||
static sendInit(endpoint, invoiceId, token, email) {
|
||||
const request = this.buildRequest(invoiceId, token, email);
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
body: JSON.stringify(request)
|
||||
}).then(response => {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
resolve();
|
||||
@ -17,4 +18,15 @@ export default class Initialization {
|
||||
}).catch(() => reject('Error send to init endpoint'));
|
||||
});
|
||||
}
|
||||
|
||||
static buildRequest(invoiceId, token, email) {
|
||||
return {
|
||||
invoiceId: invoiceId,
|
||||
token: token.token,
|
||||
session: token.session,
|
||||
contractInfo: {
|
||||
email: email
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
src/payform/backend-communication/Tokenization.js
Normal file
32
src/payform/backend-communication/Tokenization.js
Normal file
@ -0,0 +1,32 @@
|
||||
export default class Tokenization {
|
||||
|
||||
constructor(tokenizer) {
|
||||
this.Tokenizer = tokenizer;
|
||||
}
|
||||
|
||||
setPublicKey(key) {
|
||||
this.Tokenizer.setPublicKey(key);
|
||||
}
|
||||
|
||||
createToken(cardHolder, cardNumber, expDate, cvv) {
|
||||
const request = Tokenization.buildRequest(cardHolder, cardNumber, expDate, cvv);
|
||||
const tokenizer = this.Tokenizer;
|
||||
return new Promise((resolve, reject) => {
|
||||
tokenizer.card.createToken(request, paymentTools => resolve(paymentTools), error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
static buildRequest(cardHolder, cardNumber, expDate, cvv) {
|
||||
return {
|
||||
paymentToolType: 'CardData',
|
||||
cardHolder: cardHolder,
|
||||
cardNumber: Tokenization.replaceSpaces(cardNumber),
|
||||
expDate: Tokenization.replaceSpaces(expDate),
|
||||
cvv: cvv
|
||||
}
|
||||
}
|
||||
|
||||
static replaceSpaces(str) {
|
||||
return str.replace(/\s+/g, '');
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
export default class RequestBuilder {
|
||||
|
||||
static buildTokenizationRequest(cardHolder, cardNumber, expDate, cvv) {
|
||||
return {
|
||||
paymentToolType: 'CardData',
|
||||
cardHolder: cardHolder,
|
||||
cardNumber: this.replaceSpaces(cardNumber),
|
||||
expDate: this.replaceSpaces(expDate),
|
||||
cvv: cvv
|
||||
}
|
||||
}
|
||||
|
||||
static buildInitRequest(invoiceId, token, email) {
|
||||
return {
|
||||
invoiceId: invoiceId,
|
||||
token: token.token,
|
||||
session: token.session,
|
||||
contractInfo: {
|
||||
email: email
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static replaceSpaces(str) {
|
||||
return str.replace(/\s+/g, '');
|
||||
}
|
||||
}
|
18
src/payform/elements/CloseButton.js
Normal file
18
src/payform/elements/CloseButton.js
Normal file
@ -0,0 +1,18 @@
|
||||
export default class CloseButton {
|
||||
|
||||
constructor() {
|
||||
this.element = document.querySelector('.modal--close');
|
||||
}
|
||||
|
||||
set onclick(handler) {
|
||||
this.element.onclick = handler;
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.element.style.display = 'none';
|
||||
}
|
||||
|
||||
show() {
|
||||
this.element.style.display = 'block';
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
export default class PayButton {
|
||||
constructor() {
|
||||
this.element = document.querySelector('.payform--pay-button');
|
||||
this.disable();
|
||||
}
|
||||
|
||||
renderText(amount, currency) {
|
||||
@ -12,4 +13,16 @@ export default class PayButton {
|
||||
setPayButtonColor(color) {
|
||||
this.element.style.background = color;
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.element.setAttribute('disabled', 'true');
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.element.removeAttribute('disabled');
|
||||
}
|
||||
|
||||
set onclick(handler) {
|
||||
this.element.onclick = handler;
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,10 @@ export default class TokenizerScript {
|
||||
}
|
||||
|
||||
render() {
|
||||
document.getElementsByTagName('head')[0].appendChild(this.element);
|
||||
return new Promise((resolve, error) => {
|
||||
document.getElementsByTagName('head')[0].appendChild(this.element);
|
||||
this.element.onload = () => resolve();
|
||||
this.element.onerror = () => error();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'whatwg-fetch';
|
||||
import Initialization from './backend-communication/Initialization';
|
||||
import EventPoller from './backend-communication/EventPoller';
|
||||
import Tokenization from './backend-communication/Tokenization';
|
||||
import Form from './elements/Form';
|
||||
import Spinner from './elements/Spinner';
|
||||
import Checkmark from './elements/Checkmark';
|
||||
@ -9,7 +10,7 @@ import ErrorPanel from './elements/ErrorPanel';
|
||||
import Form3ds from './elements/Form3ds';
|
||||
import TokenizerScript from './elements/TokenizerScript';
|
||||
import StyleLink from './elements/StyleLink';
|
||||
import RequestBuilder from './builders/RequestBuilder';
|
||||
import CloseButton from './elements/CloseButton';
|
||||
import settings from '../settings';
|
||||
import domReady from '../utils/domReady';
|
||||
import Listener from '../communication/Listener';
|
||||
@ -26,44 +27,47 @@ domReady(function () {
|
||||
const checkmark = new Checkmark();
|
||||
const errorPanel = new ErrorPanel();
|
||||
const payButton = new PayButton();
|
||||
const closeButton = new CloseButton();
|
||||
closeButton.onclick = () => communicator.send({type: 'close'});
|
||||
const communicator = new ParentCommunicator();
|
||||
|
||||
Listener.addListener(message => {
|
||||
if (message.type === 'init' || message.type === 'resume') {
|
||||
tokenizerScript.render();
|
||||
styleLink.rerender();
|
||||
params = message.data;
|
||||
payButton.renderText(params.amount, params.currency);
|
||||
if (params.logo) {
|
||||
form.setLogo(params.logo);
|
||||
}
|
||||
if (params.name) {
|
||||
form.setName(params.name);
|
||||
}
|
||||
if (params.buttonColor) {
|
||||
payButton.setPayButtonColor(params.buttonColor);
|
||||
}
|
||||
styleLink.rerender();
|
||||
customizeForm();
|
||||
if (params.state && params.state === 'inProgress') {
|
||||
spinner.show();
|
||||
form.hide();
|
||||
polling();
|
||||
pollEvents();
|
||||
}
|
||||
tokenizerScript.render()
|
||||
.then(() => payButton.enable())
|
||||
.catch(() => errorPanel.show('Tokenizer is not available'));
|
||||
}
|
||||
});
|
||||
|
||||
window.payformClose = () => communicator.send({type: 'close'});
|
||||
payButton.onclick = () => {
|
||||
if (form.isValid()) {
|
||||
spinner.show();
|
||||
form.hide();
|
||||
errorPanel.hide();
|
||||
closeButton.hide();
|
||||
const tokenization = new Tokenization(window.Tokenizer);
|
||||
tokenization.setPublicKey(params.key);
|
||||
tokenization.createToken(form.getCardHolder(), form.getCardNumber(), form.getExpDate(), form.getCvv())
|
||||
.then(paymentTools => sendInitRequest(paymentTools))
|
||||
.catch(error => resolveElementsAfterError('Create token error', error, 'Card tokenization failed'));
|
||||
}
|
||||
};
|
||||
|
||||
const polling = () => {
|
||||
console.info('polling start');
|
||||
function pollEvents() {
|
||||
EventPoller.pollEvents(params.endpointEvents, params.invoiceId, params.orderId, settings.pollingTimeout, settings.pollingRetries).then(result => {
|
||||
console.info('polling resolve, data:', result);
|
||||
if (result.type === 'success') {
|
||||
console.info('polling result: success, post message: done');
|
||||
spinner.hide();
|
||||
checkmark.show();
|
||||
communicator.sendWithTimeout({type: 'done'}, settings.closeFormTimeout);
|
||||
} else if (result.type === 'interact') {
|
||||
console.info('polling result: interact, post message: interact, starts 3ds interaction...');
|
||||
communicator.send({type: 'interact'});
|
||||
const redirectUrl = `${params.locationHost}/cart/checkout/review`;
|
||||
const form3ds = new Form3ds(result.data, redirectUrl);
|
||||
@ -71,52 +75,37 @@ domReady(function () {
|
||||
form3ds.submit();
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('polling error, data:', error);
|
||||
spinner.hide();
|
||||
if (error.type === 'error') {
|
||||
errorPanel.show(`Error:\n${error.data.eventType}\nStatus: ${error.data.status}`);
|
||||
} else if (error.type === 'long polling') {
|
||||
errorPanel.show('Too long polling');
|
||||
} else {
|
||||
errorPanel.show('Unknown error');
|
||||
}
|
||||
communicator.sendWithTimeout({type: 'error'}, settings.closeFormTimeout);
|
||||
resolveElementsAfterError('Polling error:', error, 'An error occurred while processing your card');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const onTokenCreate = paymentTools => {
|
||||
console.info('tokenization done, data:', paymentTools);
|
||||
const initRequest = RequestBuilder.buildInitRequest(params.invoiceId, paymentTools, form.getEmail());
|
||||
console.info('request to initialization endpoint start, data:', initRequest);
|
||||
Initialization.sendInit(params.endpointInit, initRequest).then(() => {
|
||||
console.info('request to initialization endpoint done');
|
||||
polling();
|
||||
});
|
||||
};
|
||||
|
||||
window.pay = () => {
|
||||
if (window.Tokenizer === undefined) {
|
||||
form.hide();
|
||||
errorPanel.show('Tokenizer.js is not available');
|
||||
communicator.sendWithTimeout({type: 'error'}, settings.closeFormTimeout);
|
||||
return false;
|
||||
}
|
||||
if (form.isValid()) {
|
||||
spinner.show();
|
||||
form.hide();
|
||||
window.Tokenizer.setPublicKey(params.key);
|
||||
const request = RequestBuilder.buildTokenizationRequest(
|
||||
form.getCardHolder(),
|
||||
form.getCardNumber(),
|
||||
form.getExpDate(),
|
||||
form.getCvv()
|
||||
);
|
||||
console.info('tokenization start, data:', request);
|
||||
window.Tokenizer.card.createToken(request, onTokenCreate, error => {
|
||||
spinner.hide();
|
||||
errorPanel.show(`Error create token:\n${error.message}`);
|
||||
function sendInitRequest(paymentTools) {
|
||||
Initialization.sendInit(params.endpointInit, params.invoiceId, paymentTools, form.getEmail())
|
||||
.then(() => pollEvents())
|
||||
.catch(error => {
|
||||
resolveElementsAfterError('Send init request error', error, error.message);
|
||||
communicator.sendWithTimeout({type: 'error'}, settings.closeFormTimeout);
|
||||
});
|
||||
}
|
||||
|
||||
function customizeForm() {
|
||||
payButton.renderText(params.amount, params.currency);
|
||||
if (params.logo) {
|
||||
form.setLogo(params.logo);
|
||||
}
|
||||
};
|
||||
if (params.name) {
|
||||
form.setName(params.name);
|
||||
}
|
||||
if (params.buttonColor) {
|
||||
payButton.setPayButtonColor(params.buttonColor);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveElementsAfterError(logMessage, error, panelMessage) {
|
||||
console.error(logMessage, error);
|
||||
spinner.hide();
|
||||
form.show();
|
||||
closeButton.show();
|
||||
errorPanel.show(panelMessage);
|
||||
}
|
||||
});
|
||||
|
@ -32,7 +32,7 @@ html(lang='en')
|
||||
path(fill-rule='evenodd', transform='translate(9, 9)', d='M8.8,4 C8.8,1.79086089 7.76640339,4.18628304e-07 5.5,0 C3.23359661,-4.1480896e-07 2.2,1.79086089 2.2,4 L3.2,4 C3.2,2.34314567 3.81102123,0.999999681 5.5,1 C7.18897877,1.00000032 7.80000001,2.34314567 7.80000001,4 L8.8,4 Z M1.99201702,4 C0.891856397,4 0,4.88670635 0,5.99810135 L0,10.0018986 C0,11.1054196 0.900176167,12 1.99201702,12 L9.00798298,12 C10.1081436,12 11,11.1132936 11,10.0018986 L11,5.99810135 C11,4.89458045 10.0998238,4 9.00798298,4 L1.99201702,4 Z M1.99754465,5 C1.44661595,5 1,5.45097518 1,5.99077797 L1,10.009222 C1,10.5564136 1.4463114,11 1.99754465,11 L9.00245535,11 C9.55338405,11 10,10.5490248 10,10.009222 L10,5.99077797 C10,5.44358641 9.5536886,5 9.00245535,5 L1.99754465,5 Z M1.99754465,5')
|
||||
.modal--overlay
|
||||
.modal--container
|
||||
.modal--close(onclick="payformClose()")
|
||||
.modal--close
|
||||
.modal--body
|
||||
.payform
|
||||
.payform--header
|
||||
@ -70,8 +70,9 @@ html(lang='en')
|
||||
.payform--icon
|
||||
svg(fill='#549928')
|
||||
use(xmlns:xlink='http://www.w3.org/1999/xlink', xlink:href='#Icon-envelope-desktop')
|
||||
button.payform--pay-button(type='button', form='payform', onclick="pay()") Оплатить
|
||||
div.spinner(style='transform:scale(0.54);')
|
||||
.error-panel
|
||||
button.payform--pay-button(type='button', form='payform') Оплатить
|
||||
.spinner(style='transform:scale(0.54);')
|
||||
div(style='top:80px;left:93px;width:14px;height:40px;background:#00b2ff;-webkit-transform:rotate(0deg) translate(0,-60px);transform:rotate(0deg) translate(0,-60px);border-radius:10px;position:absolute;')
|
||||
div(style='top:80px;left:93px;width:14px;height:40px;background:#00b2ff;-webkit-transform:rotate(30deg) translate(0,-60px);transform:rotate(30deg) translate(0,-60px);border-radius:10px;position:absolute;')
|
||||
div(style='top:80px;left:93px;width:14px;height:40px;background:#00b2ff;-webkit-transform:rotate(60deg) translate(0,-60px);transform:rotate(60deg) translate(0,-60px);border-radius:10px;position:absolute;')
|
||||
@ -84,9 +85,8 @@ html(lang='en')
|
||||
div(style='top:80px;left:93px;width:14px;height:40px;background:#00b2ff;-webkit-transform:rotate(270deg) translate(0,-60px);transform:rotate(270deg) translate(0,-60px);border-radius:10px;position:absolute;')
|
||||
div(style='top:80px;left:93px;width:14px;height:40px;background:#00b2ff;-webkit-transform:rotate(300deg) translate(0,-60px);transform:rotate(300deg) translate(0,-60px);border-radius:10px;position:absolute;')
|
||||
div(style='top:80px;left:93px;width:14px;height:40px;background:#00b2ff;-webkit-transform:rotate(330deg) translate(0,-60px);transform:rotate(330deg) translate(0,-60px);border-radius:10px;position:absolute;')
|
||||
div.checkmark.icon.icon--order-success.svg
|
||||
.checkmark.icon.icon--order-success.svg
|
||||
svg(xmlns="http://www.w3.org/2000/svg" width="72px" height="72px")
|
||||
g(fill="none" stroke="#8EC343" stroke-width="2")
|
||||
circle(cx="36" cy="36" r="35" style="stroke-dasharray:240px, 240px; stroke-dashoffset: 480px;")
|
||||
path(d="M17.417,37.778l9.93,9.909l25.444-25.393" style="stroke-dasharray:50px, 50px; stroke-dashoffset: 0px;")
|
||||
div.error-panel
|
||||
path(d="M17.417,37.778l9.93,9.909l25.444-25.393" style="stroke-dasharray:50px, 50px; stroke-dashoffset: 0px;")
|
@ -1,7 +1,7 @@
|
||||
.checkmark {
|
||||
margin-left: 78px;
|
||||
margin-top: 57px;
|
||||
margin-bottom: 66px;
|
||||
margin-top: 99px;
|
||||
margin-bottom: 98px;
|
||||
}
|
||||
|
||||
/* animations */
|
||||
|
@ -1,8 +1,6 @@
|
||||
.error-panel {
|
||||
border-radius: 5px;
|
||||
padding: 10px 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
padding: 9px 10px;
|
||||
font-size: 13px;
|
||||
background: #ff7b7b;
|
||||
margin: 70px 0;
|
||||
}
|
@ -453,3 +453,6 @@ input::-ms-clear {
|
||||
background-image: -webkit-linear-gradient(top, #328ac3, #277bbe);
|
||||
background-image: linear-gradient(180deg, #328ac3, #277bbe)
|
||||
}
|
||||
.payform--pay-button[disabled] {
|
||||
background: #828282;
|
||||
}
|
||||
|
@ -24,11 +24,11 @@
|
||||
}
|
||||
|
||||
.spinner {
|
||||
margin-left: 17px;
|
||||
position: relative;
|
||||
background: none;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
margin: 37px auto;
|
||||
}
|
||||
|
||||
.spinner > div:nth-of-type(2) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
export default {
|
||||
integrationClassName: 'rbkmoney-payform',
|
||||
pollingTimeout: 1000,
|
||||
closeFormTimeout: 2500,
|
||||
pollingRetries: 20
|
||||
closeFormTimeout: 3000,
|
||||
pollingRetries: 10
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user