mirror of
https://github.com/valitydev/checkout.git
synced 2024-11-06 10:35:20 +00:00
Terminal qiwi support. (#202)
This commit is contained in:
parent
89528bb8e6
commit
573963d73a
@ -20,13 +20,14 @@
|
||||
"date-fns": "~1.28.5",
|
||||
"did-you-mean": "0.0.1",
|
||||
"ismobilejs": "~0.4.1",
|
||||
"libphonenumber-js": "~1.0.3",
|
||||
"lodash": "~4.17.4",
|
||||
"react": "~15.5.4",
|
||||
"react-dom": "~15.5.4",
|
||||
"react-redux": "~5.0.5",
|
||||
"react-transition-group": "~1.1.3",
|
||||
"redux": "~3.7.2",
|
||||
"redux-form": "~7.2.0",
|
||||
"redux-form": "~7.2.1",
|
||||
"redux-thunk": "~2.2.0",
|
||||
"tokenizer": "git+ssh://git@github.com/rbkmoney/tokenizer.js.git#892793a1cdb035db16de6a067e43bbca2a5215e1",
|
||||
"uri-template": "~1.0.1",
|
||||
|
@ -1,2 +1 @@
|
||||
export * from './to-card-form-info';
|
||||
export * from './to-modal-interaction';
|
||||
|
@ -1,23 +1,15 @@
|
||||
import {
|
||||
BrowserPostRequest,
|
||||
BrowserRequest,
|
||||
ChangeType,
|
||||
Event,
|
||||
InteractionType,
|
||||
PaymentInteractionRequested,
|
||||
Redirect,
|
||||
RequestType
|
||||
} from 'checkout/backend';
|
||||
import { ModalInteraction } from 'checkout/state';
|
||||
import { getLastChange } from 'checkout/utils';
|
||||
|
||||
const getRedirect = (redirect: Redirect): BrowserPostRequest => {
|
||||
if (redirect.request.requestType === RequestType.BrowserPostRequest) {
|
||||
return redirect.request as BrowserPostRequest;
|
||||
}
|
||||
throw new Error('Unsupported user interaction browser request type');
|
||||
};
|
||||
|
||||
const toRequest = (events: Event[]): BrowserPostRequest => {
|
||||
const toRequest = (events: Event[]): BrowserRequest => {
|
||||
const change = getLastChange(events);
|
||||
if (!change || change.changeType !== ChangeType.PaymentInteractionRequested) {
|
||||
throw new Error('ChangeType must be PaymentInteractionRequested');
|
||||
@ -25,10 +17,11 @@ const toRequest = (events: Event[]): BrowserPostRequest => {
|
||||
const interaction = (change as PaymentInteractionRequested).userInteraction;
|
||||
switch (interaction.interactionType) {
|
||||
case InteractionType.Redirect:
|
||||
return getRedirect(interaction as Redirect);
|
||||
return (interaction as Redirect).request;
|
||||
case InteractionType.PaymentTerminalReceipt:
|
||||
throw new Error('Unsupported user interaction PaymentTerminalReceipt');
|
||||
}
|
||||
throw new Error('Unsupported InteractionType');
|
||||
};
|
||||
|
||||
export const toModalInteraction = (events: Event[]) => new ModalInteraction(toRequest(events), true);
|
||||
|
9
src/app/actions/modal-actions/forget-payment-attempt.ts
Normal file
9
src/app/actions/modal-actions/forget-payment-attempt.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { AbstractAction, TypeKeys } from 'checkout/actions';
|
||||
|
||||
export interface ForgetPaymentAttempt extends AbstractAction {
|
||||
type: TypeKeys.FORGET_PAYMENT_ATTEMPT;
|
||||
}
|
||||
|
||||
export const forgetPaymentAttempt = (): ForgetPaymentAttempt => ({
|
||||
type: TypeKeys.FORGET_PAYMENT_ATTEMPT
|
||||
});
|
22
src/app/actions/modal-actions/go-to-form-info-action.ts
Normal file
22
src/app/actions/modal-actions/go-to-form-info-action.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { FormInfo} from 'checkout/state';
|
||||
import { AbstractAction, TypeKeys } from 'checkout/actions';
|
||||
|
||||
export enum Direction {
|
||||
back = 'back',
|
||||
forward = 'forward'
|
||||
}
|
||||
|
||||
export interface GoToPayload {
|
||||
formInfo: FormInfo;
|
||||
direction: Direction;
|
||||
}
|
||||
|
||||
export interface GoToFormInfo extends AbstractAction<GoToPayload> {
|
||||
type: TypeKeys.GO_TO_FORM_INFO;
|
||||
payload: GoToPayload;
|
||||
}
|
||||
|
||||
export const goToFormInfo = (formInfo: FormInfo, direction: Direction = Direction.forward): GoToFormInfo => ({
|
||||
type: TypeKeys.GO_TO_FORM_INFO,
|
||||
payload: {formInfo, direction}
|
||||
});
|
@ -3,6 +3,6 @@ export * from './set-modal-state';
|
||||
export * from './view-info-actions';
|
||||
export * from './set-modal-from-events';
|
||||
export * from './prepare-payment-actions';
|
||||
export * from './set-form-info';
|
||||
export * from './set-error-form-info-action';
|
||||
export * from './go-to-form-info-action';
|
||||
export * from './modal-interaction-polling-status-action';
|
||||
export * from './forget-payment-attempt';
|
||||
|
@ -1,27 +1,38 @@
|
||||
import { ChangeType } from 'checkout/backend';
|
||||
import { ChangeType, PaymentMethod, PaymentMethodName } from 'checkout/backend';
|
||||
import {
|
||||
PaymentMethodsFormInfo,
|
||||
ModalForms,
|
||||
ModalState,
|
||||
ModelState,
|
||||
ResultFormInfo,
|
||||
ResultType
|
||||
ResultType,
|
||||
CardFormInfo
|
||||
} from 'checkout/state';
|
||||
import { TypeKeys } from 'checkout/actions';
|
||||
import { SetModalState } from './set-modal-state';
|
||||
import { InitConfig } from 'checkout/config';
|
||||
import { toCardFormInfo, toModalInteraction } from './converters';
|
||||
import { toModalInteraction } from './converters';
|
||||
import { getLastChange } from 'checkout/utils';
|
||||
|
||||
const isMultiMethods = (c: InitConfig, m: ModelState) => c.terminals && m.paymentMethods.length > 1;
|
||||
const checkPaymentMethodsConfig = (c: InitConfig, methods: PaymentMethod[]): boolean =>
|
||||
methods.reduce((acc, current): boolean => {
|
||||
switch (current.method) {
|
||||
// case PaymentMethodName.PaymentTerminal:
|
||||
// return acc || c.terminals;
|
||||
case PaymentMethodName.DigitalWallet:
|
||||
return acc || c.wallets;
|
||||
}
|
||||
}, false);
|
||||
|
||||
const isMultiMethods = (c: InitConfig, m: ModelState) => m.paymentMethods.length > 1 && checkPaymentMethodsConfig(c, m.paymentMethods);
|
||||
|
||||
const toInitialState = (c: InitConfig, m: ModelState): ModalState => {
|
||||
const formInfo = isMultiMethods(c, m) ? new PaymentMethodsFormInfo(true) : toCardFormInfo(c, m.invoiceTemplate);
|
||||
const formInfo = isMultiMethods(c, m) ? new PaymentMethodsFormInfo() : new CardFormInfo();
|
||||
return new ModalForms([formInfo], true);
|
||||
};
|
||||
|
||||
const toInitialModalResult = (): ModalState => {
|
||||
const formInfo = new ResultFormInfo(ResultType.processed, true);
|
||||
const formInfo = new ResultFormInfo(ResultType.processed);
|
||||
return new ModalForms([formInfo], true);
|
||||
};
|
||||
|
||||
|
@ -1,7 +0,0 @@
|
||||
import { SetFormInfo, TypeKeys } from 'checkout/actions';
|
||||
import { ResultFormInfo, ResultType } from 'checkout/state';
|
||||
|
||||
export const setErrorFormInfo = (): SetFormInfo => ({
|
||||
type: TypeKeys.SET_FORM_INFO,
|
||||
payload: new ResultFormInfo(ResultType.error, true)
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
import { FormInfo } from 'checkout/state';
|
||||
import { AbstractAction, TypeKeys } from 'checkout/actions';
|
||||
|
||||
export interface SetFormInfo extends AbstractAction<FormInfo> {
|
||||
type: TypeKeys.SET_FORM_INFO;
|
||||
payload: FormInfo;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { ResultFormInfo, ResultType } from 'checkout/state';
|
||||
import { SetFormInfo, TypeKeys } from 'checkout/actions';
|
||||
import { Direction, GoToFormInfo, TypeKeys } from 'checkout/actions';
|
||||
import { ChangeType, Event } from 'checkout/backend';
|
||||
import { SetModalState } from './set-modal-state';
|
||||
import { toModalInteraction } from './converters';
|
||||
@ -16,13 +16,16 @@ const prepareFromEvents = (events: Event[]): SetStateFromEvents => {
|
||||
case ChangeType.PaymentStatusChanged:
|
||||
case ChangeType.InvoiceStatusChanged:
|
||||
return {
|
||||
type: TypeKeys.SET_FORM_INFO,
|
||||
payload: new ResultFormInfo(ResultType.processed, true)
|
||||
type: TypeKeys.GO_TO_FORM_INFO,
|
||||
payload: {
|
||||
formInfo: new ResultFormInfo(ResultType.processed),
|
||||
direction: Direction.forward
|
||||
}
|
||||
};
|
||||
}
|
||||
throw new Error('Unhandled invoice changeType');
|
||||
};
|
||||
|
||||
export type SetStateFromEvents = SetFormInfo | SetModalState;
|
||||
export type SetStateFromEvents = GoToFormInfo | SetModalState;
|
||||
|
||||
export const setModalFromEvents = (events: Event[]): SetStateFromEvents => prepareFromEvents(events);
|
||||
|
@ -1,34 +1,21 @@
|
||||
import { AbstractAction, TypeKeys } from 'checkout/actions';
|
||||
import { FormName } from 'checkout/state';
|
||||
|
||||
interface Meta {
|
||||
formName: FormName;
|
||||
}
|
||||
|
||||
export interface SetViewInfoError extends AbstractAction<boolean, Meta> {
|
||||
export interface SetViewInfoError extends AbstractAction<boolean> {
|
||||
type: TypeKeys.SET_VIEW_INFO_ERROR;
|
||||
payload: boolean;
|
||||
meta: Meta;
|
||||
}
|
||||
|
||||
export const setViewInfoError = (hasError: boolean, formName: FormName): SetViewInfoError => ({
|
||||
export const setViewInfoError = (hasError: boolean): SetViewInfoError => ({
|
||||
type: TypeKeys.SET_VIEW_INFO_ERROR,
|
||||
payload: hasError,
|
||||
meta: {
|
||||
formName
|
||||
}
|
||||
payload: hasError
|
||||
});
|
||||
|
||||
export interface SetViewInfoInProcess extends AbstractAction<boolean, Meta> {
|
||||
type: TypeKeys.SET_VIEW_INFO_IN_PROCESS;
|
||||
payload: boolean;
|
||||
meta: Meta;
|
||||
export interface SetViewInfoHeight extends AbstractAction<number> {
|
||||
type: TypeKeys.SET_VIEW_INFO_HEIGHT;
|
||||
payload: number;
|
||||
}
|
||||
|
||||
export const setViewInfoInProcess = (inProcess: boolean, formName: FormName): SetViewInfoInProcess => ({
|
||||
type: TypeKeys.SET_VIEW_INFO_IN_PROCESS,
|
||||
payload: inProcess,
|
||||
meta: {
|
||||
formName
|
||||
}
|
||||
export const setViewInfoHeight = (height: number): SetViewInfoHeight => ({
|
||||
type: TypeKeys.SET_VIEW_INFO_HEIGHT,
|
||||
payload: height
|
||||
});
|
||||
|
@ -1,14 +1,24 @@
|
||||
import {
|
||||
PaymentToolType,
|
||||
createPaymentResource as capiRequest,
|
||||
PaymentResource
|
||||
PaymentResource,
|
||||
DigitalWalletType
|
||||
} from 'checkout/backend';
|
||||
import { CardFormValues } from 'checkout/state';
|
||||
import { CardFormValues, WalletFormValues } from 'checkout/state';
|
||||
import { PaymentSubject } from './payment-subject';
|
||||
|
||||
const replaceSpaces = (str: string): string => str.replace(/\s+/g, '');
|
||||
|
||||
export const createPaymentResource = (s: PaymentSubject, endpoint: string, v: CardFormValues): Promise<PaymentResource> => {
|
||||
export const createPaymentResourceDigitalWalletQiwi = (s: PaymentSubject, endpoint: string, v: WalletFormValues): Promise<PaymentResource> => {
|
||||
const paymentTool = {
|
||||
paymentToolType: PaymentToolType.DigitalWalletData,
|
||||
digitalWalletType: DigitalWalletType.DigitalWalletQIWI,
|
||||
phoneNumber: replaceSpaces(v.phone)
|
||||
};
|
||||
return capiRequest(endpoint, s.accessToken, paymentTool);
|
||||
};
|
||||
|
||||
export const createPaymentResourceCardData = (s: PaymentSubject, endpoint: string, v: CardFormValues): Promise<PaymentResource> => {
|
||||
const cardNumber = replaceSpaces(v.cardNumber);
|
||||
const expDate = replaceSpaces(v.expireDate);
|
||||
const paymentTool = {
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { PaymentResource, createPayment as capiRequest, FlowType, PayerType } from 'checkout/backend';
|
||||
import { PaymentResource, createPayment as capiRequest, PayerType } from 'checkout/backend';
|
||||
import { PaymentSubject } from './payment-subject';
|
||||
import { PaymentFlow } from 'checkout/backend/model/payment-flow';
|
||||
|
||||
export const createPayment = (s: PaymentSubject, endpoint: string, r: PaymentResource, email: string) => {
|
||||
export const createPayment = (s: PaymentSubject, endpoint: string, r: PaymentResource, email: string, flow: PaymentFlow) => {
|
||||
const {paymentToolToken, paymentSession} = r;
|
||||
const request = {
|
||||
flow: {
|
||||
type: FlowType.PaymentFlowInstant
|
||||
},
|
||||
flow,
|
||||
payer: {
|
||||
payerType: PayerType.PaymentResourcePayer,
|
||||
paymentToolToken,
|
||||
|
@ -1,15 +1,37 @@
|
||||
import { CardFormValues, ConfigState, ModelState } from 'checkout/state';
|
||||
import { createPayment, createPaymentResource, getPaymentSubject, pollEvents } from './';
|
||||
import { CardFormValues, ConfigState, ModelState, PayableFormValues, WalletFormValues } from 'checkout/state';
|
||||
import {
|
||||
createPayment,
|
||||
createPaymentResourceCardData,
|
||||
getPaymentSubject,
|
||||
pollEvents,
|
||||
createPaymentResourceDigitalWalletQiwi
|
||||
} from './';
|
||||
import { PayActionPayload } from '../pay-action';
|
||||
import { FlowType, PaymentFlow, PaymentResource } from 'checkout/backend';
|
||||
import { PaymentSubject } from './payment-subject';
|
||||
|
||||
export const pay = (c: ConfigState, m: ModelState, v: CardFormValues): Promise<PayActionPayload> => {
|
||||
type CreatePaymentResourceFn = (s: PaymentSubject, endpoint: string, v: PayableFormValues) => Promise<PaymentResource>;
|
||||
|
||||
const toPaymentFlow = (c: ConfigState): PaymentFlow => {
|
||||
const instant = {type: FlowType.PaymentFlowInstant};
|
||||
const hold = {type: FlowType.PaymentFlowHold, onHoldExpiration: c.initConfig.holdExpiration};
|
||||
return c.initConfig.paymentFlowHold ? hold : instant;
|
||||
};
|
||||
|
||||
const pay = (c: ConfigState, m: ModelState, v: PayableFormValues, createPaymentResourceFn: CreatePaymentResourceFn): Promise<PayActionPayload> => {
|
||||
const endpoint = c.appConfig.capiEndpoint;
|
||||
const email = c.initConfig.email || v.email;
|
||||
return getPaymentSubject(c, m, v.amount).then((subject) =>
|
||||
createPaymentResource(subject, endpoint, v).then((paymentResource) =>
|
||||
createPayment(subject, endpoint, paymentResource, email).then(() =>
|
||||
createPaymentResourceFn(subject, endpoint, v).then((paymentResource) =>
|
||||
createPayment(subject, endpoint, paymentResource, email, toPaymentFlow(c)).then(() =>
|
||||
pollEvents(endpoint, subject, m.invoiceEvents).then((events) => ({
|
||||
invoiceEvents: events,
|
||||
invoiceAccessToken: subject.accessToken
|
||||
})))));
|
||||
};
|
||||
|
||||
export const payCardData = (c: ConfigState, m: ModelState, v: CardFormValues): Promise<PayActionPayload> =>
|
||||
pay(c, m, v, createPaymentResourceCardData);
|
||||
|
||||
export const payDigitalWalletQiwi = (c: ConfigState, m: ModelState, v: WalletFormValues): Promise<PayActionPayload> =>
|
||||
pay(c, m, v, createPaymentResourceDigitalWalletQiwi);
|
||||
|
@ -7,6 +7,14 @@ import { IntegrationType } from 'checkout/config';
|
||||
const pollingRetries = 60;
|
||||
const pollingTimeout = 300;
|
||||
|
||||
const continuePolling = (pollCount: number, retries: number, pollFn: () => any, reject: (reason: any) => any): number => {
|
||||
const count = pollCount + 1;
|
||||
count >= retries
|
||||
? reject({code: 'error.events.timeout'})
|
||||
: pollFn();
|
||||
return count;
|
||||
};
|
||||
|
||||
export const pollEvents = (endpoint: string, subject: PaymentSubject, e: Event[]): Promise<Event[]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let pollCount = 0;
|
||||
@ -16,17 +24,18 @@ export const pollEvents = (endpoint: string, subject: PaymentSubject, e: Event[]
|
||||
const lastEventID = (e && !templateIntegration) ? last(e).id : 0;
|
||||
getInvoiceEvents(endpoint, subject.accessToken, subject.invoiceID, 10, lastEventID).then((events) => {
|
||||
const change = getLastChange(events);
|
||||
switch (change.changeType) {
|
||||
case ChangeType.InvoiceStatusChanged:
|
||||
case ChangeType.PaymentStatusChanged:
|
||||
case ChangeType.PaymentInteractionRequested:
|
||||
resolve(events);
|
||||
break;
|
||||
default:
|
||||
pollCount++;
|
||||
pollCount >= pollingRetries
|
||||
? reject({code: 'error.events.timeout'})
|
||||
: poll();
|
||||
if (change) {
|
||||
switch (change.changeType) {
|
||||
case ChangeType.InvoiceStatusChanged:
|
||||
case ChangeType.PaymentStatusChanged:
|
||||
case ChangeType.PaymentInteractionRequested:
|
||||
resolve(events);
|
||||
break;
|
||||
default:
|
||||
pollCount = continuePolling(pollCount, pollingRetries, poll, reject);
|
||||
}
|
||||
} else {
|
||||
pollCount = continuePolling(pollCount, pollingRetries, poll, reject);
|
||||
}
|
||||
}).catch((error) => reject(error));
|
||||
}, pollingTimeout);
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import { CardFormValues, ConfigState, ModelState } from 'checkout/state';
|
||||
import { CardFormValues, ConfigState, ModelState, WalletFormValues } from 'checkout/state';
|
||||
import { Event } from 'checkout/backend';
|
||||
import { AbstractAction, SetErrorAction, TypeKeys } from 'checkout/actions';
|
||||
import { pay as payOperation } from './operations';
|
||||
import {
|
||||
payCardData as payCardDataOperation,
|
||||
payDigitalWalletQiwi as payDigitalWalletQiwiOperation
|
||||
} from './operations';
|
||||
|
||||
export interface PayActionPayload {
|
||||
invoiceEvents: Event[];
|
||||
@ -16,8 +19,19 @@ export interface PayAction extends AbstractAction<PayActionPayload> {
|
||||
|
||||
export type PayDispatch = (dispatch: Dispatch<PayAction | SetErrorAction>) => void;
|
||||
|
||||
export const pay = (c: ConfigState, m: ModelState, v: CardFormValues): PayDispatch =>
|
||||
(dispatch) => payOperation(c, m, v)
|
||||
export const payCardData = (c: ConfigState, m: ModelState, v: CardFormValues): PayDispatch =>
|
||||
(dispatch) => payCardDataOperation(c, m, v)
|
||||
.then((payload) => dispatch({
|
||||
type: TypeKeys.PAY,
|
||||
payload
|
||||
}))
|
||||
.catch((error) => dispatch({
|
||||
type: TypeKeys.SET_ERROR,
|
||||
payload: error
|
||||
}));
|
||||
|
||||
export const payDigitalWalletQiwi = (c: ConfigState, m: ModelState, v: WalletFormValues): PayDispatch =>
|
||||
(dispatch) => payDigitalWalletQiwiOperation(c, m, v)
|
||||
.then((payload) => dispatch({
|
||||
type: TypeKeys.PAY,
|
||||
payload
|
||||
|
@ -23,5 +23,9 @@ export const pollInvoiceEvents = (capiEndpoint: string, accessToken: string, eve
|
||||
.then((event) => dispatch({
|
||||
type: TypeKeys.POLL_EVENTS,
|
||||
payload: event
|
||||
}))
|
||||
.catch((error) => dispatch({
|
||||
type: TypeKeys.SET_ERROR,
|
||||
payload: error
|
||||
}));
|
||||
};
|
||||
|
@ -6,12 +6,13 @@ export enum TypeKeys {
|
||||
SET_RESULT = 'SET_RESULT',
|
||||
SET_ERROR = 'SET_ERROR',
|
||||
SET_MODAL_STATE = 'SET_MODAL_STATE',
|
||||
SET_FORM_INFO = 'SET_FORM_INFO',
|
||||
GO_TO_FORM_INFO = 'GO_TO_FORM_INFO',
|
||||
PAY = 'PAY',
|
||||
POLL_EVENTS = 'POLL_EVENTS',
|
||||
SET_VIEW_INFO_ERROR = 'SET_VIEW_INFO_ERROR',
|
||||
SET_VIEW_INFO_IN_PROCESS = 'SET_VIEW_INFO_IN_PROCESS',
|
||||
SET_VIEW_INFO_HEIGHT = 'SET_VIEW_INFO_HEIGHT',
|
||||
PREPARE_TO_PAY = 'PREPARE_TO_PAY',
|
||||
PREPARE_TO_RETRY = 'PREPARE_TO_RETRY',
|
||||
SET_MODAL_INTERACTION_POLLING = 'SET_MODAL_INTERACTION_POLLING'
|
||||
FORGET_PAYMENT_ATTEMPT = 'FORGET_PAYMENT_ATTEMPT',
|
||||
SET_MODAL_INTERACTION_POLLING = 'SET_MODAL_INTERACTION_POLLING',
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { PaymentSystem } from './payment-system';
|
||||
import { PaymentMethod } from './payment-method';
|
||||
import { PaymentMethod, PaymentMethodName } from './payment-method';
|
||||
|
||||
export class BankCard extends PaymentMethod {
|
||||
method: 'BankCard';
|
||||
method: PaymentMethodName.BankCard;
|
||||
paymentSystems: PaymentSystem[];
|
||||
}
|
||||
|
7
src/app/backend/model/digital-wallet.ts
Normal file
7
src/app/backend/model/digital-wallet.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { PaymentSystem } from './payment-system';
|
||||
import { PaymentMethod, PaymentMethodName } from './payment-method';
|
||||
|
||||
export class DigitalWallet extends PaymentMethod {
|
||||
method: PaymentMethodName.DigitalWallet;
|
||||
paymentSystems: PaymentSystem[];
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
import { BrowserRequest } from './browser-request';
|
||||
|
||||
export class BrowserGetRequest extends BrowserRequest {
|
||||
}
|
@ -6,3 +6,4 @@ export * from './payment-terminal-receipt';
|
||||
export * from './redirect';
|
||||
export * from './request-type';
|
||||
export * from './user-interaction';
|
||||
export * from './browser-get-request';
|
||||
|
@ -4,6 +4,7 @@ export * from './access-token';
|
||||
export * from './invoice';
|
||||
export * from './invoice-template-details';
|
||||
export * from './bank-card';
|
||||
export * from './digital-wallet';
|
||||
export * from './payment-method';
|
||||
export * from './payment-system';
|
||||
export * from './payment-terminal';
|
||||
|
@ -1,4 +1,4 @@
|
||||
export enum HoldExpiration {
|
||||
export enum HoldExpirationType {
|
||||
cancel = 'cancel',
|
||||
capture = 'capture'
|
||||
}
|
@ -2,3 +2,4 @@ export * from './flow-type';
|
||||
export * from './payment-flow';
|
||||
export * from './payment-flow-instant';
|
||||
export * from './payment-flow-hold';
|
||||
export * from './hold-expiration-type';
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { PaymentFlow } from './payment-flow';
|
||||
import { FlowType } from './flow-type';
|
||||
import { HoldExpirationType } from './hold-expiration-type';
|
||||
|
||||
export class PaymentFlowHold extends PaymentFlow {
|
||||
type: FlowType.PaymentFlowHold;
|
||||
onHoldExpiration: 'cancel' | 'capture';
|
||||
onHoldExpiration: HoldExpirationType;
|
||||
}
|
||||
|
@ -1,3 +1,9 @@
|
||||
export abstract class PaymentMethod {
|
||||
method: 'BankCard' | 'PaymentTerminal';
|
||||
export enum PaymentMethodName {
|
||||
'BankCard' = 'BankCard',
|
||||
'PaymentTerminal' = 'PaymentTerminal',
|
||||
'DigitalWallet' = 'DigitalWallet'
|
||||
}
|
||||
|
||||
export abstract class PaymentMethod {
|
||||
method: PaymentMethodName;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { PaymentMethod } from './payment-method';
|
||||
import { PaymentMethod, PaymentMethodName } from './payment-method';
|
||||
|
||||
export class PaymentTerminal extends PaymentMethod {
|
||||
method: 'PaymentTerminal';
|
||||
method: PaymentMethodName.PaymentTerminal;
|
||||
providers: 'euroset';
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
import { DigitalWalletDetails } from './digital-wallet-details';
|
||||
|
||||
export class DigitalWalletDetailsQiwi extends DigitalWalletDetails {
|
||||
phoneNumberMask: string;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export enum DigitalWalletDetailsType {
|
||||
DigitalWalletDetailsQIWI = 'DigitalWalletDetailsQIWI'
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { DigitalWalletDetailsType } from './digital-wallet-details-type';
|
||||
|
||||
export class DigitalWalletDetails {
|
||||
digitalWalletDetailsType: DigitalWalletDetailsType;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export * from './digital-wallet-details';
|
||||
export * from './digital-wallet-details-qiwi';
|
||||
export * from './digital-wallet-details-type';
|
@ -2,3 +2,5 @@ export * from './payment-tool-details';
|
||||
export * from './payment-tool-details-bank-card';
|
||||
export * from './payment-tool-details-payment-terminal';
|
||||
export * from './payment-tool-details-type';
|
||||
export * from './payment-tool-details-digital-wallet';
|
||||
export * from './digital-wallet-details';
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { PaymentToolDetails } from './payment-tool-details';
|
||||
import { PaymentToolDetailsType } from './payment-tool-details-type';
|
||||
|
||||
export class PaymentToolDetailsBankCard extends PaymentToolDetails {
|
||||
detailsType: PaymentToolDetailsType.PaymentToolDetailsBankCard;
|
||||
cardNumberMask: string;
|
||||
paymentSystem: string;
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
import { PaymentToolDetails } from './payment-tool-details';
|
||||
import { DigitalWalletDetails, DigitalWalletDetailsType } from './digital-wallet-details';
|
||||
import { PaymentToolDetailsType } from './payment-tool-details-type';
|
||||
import { applyMixins } from 'checkout/utils';
|
||||
|
||||
export class PaymentToolDetailsDigitalWallet implements PaymentToolDetails, DigitalWalletDetails {
|
||||
detailsType: PaymentToolDetailsType;
|
||||
digitalWalletDetailsType: DigitalWalletDetailsType;
|
||||
}
|
||||
|
||||
applyMixins(PaymentToolDetailsDigitalWallet, [PaymentToolDetails, DigitalWalletDetails]);
|
@ -1,7 +1,5 @@
|
||||
import { PaymentToolDetails } from './payment-tool-details';
|
||||
import { PaymentToolDetailsType } from './payment-tool-details-type';
|
||||
|
||||
export class PaymentToolDetailsPaymentTerminal extends PaymentToolDetails {
|
||||
detailsType: PaymentToolDetailsType.PaymentToolDetailsPaymentTerminal;
|
||||
provider: string;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
export enum PaymentToolDetailsType {
|
||||
PaymentToolDetailsBankCard = 'PaymentToolDetailsBankCard',
|
||||
PaymentToolDetailsPaymentTerminal = 'PaymentToolDetailsPaymentTerminal'
|
||||
PaymentToolDetailsPaymentTerminal = 'PaymentToolDetailsPaymentTerminal',
|
||||
PaymentToolDetailsDigitalWallet = 'PaymentToolDetailsDigitalWallet'
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
import { PaymentTool } from '../payment-tool';
|
||||
import { DigitalWalletType } from './digital-wallet-type';
|
||||
|
||||
export class DigitalWalletData extends PaymentTool {
|
||||
digitalWalletType: DigitalWalletType;
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { DigitalWalletData } from './';
|
||||
|
||||
export class DigitalWalletQiwi extends DigitalWalletData {
|
||||
phoneNumber: string;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export enum DigitalWalletType {
|
||||
DigitalWalletQIWI = 'DigitalWalletQIWI'
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export * from './digital-wallet-data';
|
||||
export * from './digital-wallet-qiwi';
|
||||
export * from './digital-wallet-type';
|
@ -2,3 +2,4 @@ export * from './card-data';
|
||||
export * from './payment-terminal-data';
|
||||
export * from './payment-tool';
|
||||
export * from './payment-tool-type';
|
||||
export * from './digital-wallet-data';
|
||||
|
@ -1,4 +1,5 @@
|
||||
export enum PaymentToolType {
|
||||
CardData = 'CardData',
|
||||
PaymentTerminalData = 'PaymentTerminalData'
|
||||
PaymentTerminalData = 'PaymentTerminalData',
|
||||
DigitalWalletData = 'DigitalWalletData'
|
||||
}
|
||||
|
@ -24,13 +24,13 @@ const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
|
||||
|
||||
class AppDef extends React.Component<AppProps> {
|
||||
|
||||
componentDidMount() {
|
||||
componentWillMount() {
|
||||
this.props.loadConfig(this.props.config.initConfig.locale);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props: AppProps) {
|
||||
const {config, model, modalReady} = props;
|
||||
if (config.ready && model.status === ModelStatus.none) {
|
||||
const {config, model, modalReady, error} = props;
|
||||
if (config.ready && model.status === ModelStatus.none && !error) {
|
||||
props.initModel(props.config);
|
||||
}
|
||||
if (!modalReady && model.status === ModelStatus.initialized) {
|
||||
|
@ -1,15 +1,5 @@
|
||||
export const appearContainer: string;
|
||||
export const popup: string;
|
||||
export const enterContainer: string;
|
||||
export const leaveContainer: string;
|
||||
export const popout: string;
|
||||
export const container: string;
|
||||
export const fadein: string;
|
||||
export const fadeout: string;
|
||||
export const slidedown: string;
|
||||
export const slideup: string;
|
||||
export const growth: string;
|
||||
export const rotatein: string;
|
||||
export const rotateout: string;
|
||||
export const shake: string;
|
||||
export const animationContainer: string;
|
||||
|
@ -6,10 +6,21 @@ import * as styles from './modal-container.scss';
|
||||
import { Modal } from './modal';
|
||||
import { Footer } from './footer';
|
||||
import { UserInteractionModal } from './user-interaction-modal';
|
||||
import { ErrorStatus, ModalName, ModalState, ModelState, State, ModelStatus, ModalInteraction } from 'checkout/state';
|
||||
import {
|
||||
accept, pollInvoiceEvents, setErrorFormInfo, acceptError, setModalFromEvents,
|
||||
setModalInteractionPollingStatus
|
||||
ErrorStatus,
|
||||
ModalName,
|
||||
ModalState,
|
||||
ModelState,
|
||||
State,
|
||||
ModelStatus,
|
||||
ModalInteraction,
|
||||
FormInfo,
|
||||
ResultFormInfo,
|
||||
ResultType
|
||||
} from 'checkout/state';
|
||||
import {
|
||||
accept, pollInvoiceEvents, acceptError, setModalFromEvents,
|
||||
setModalInteractionPollingStatus, goToFormInfo
|
||||
} from 'checkout/actions';
|
||||
import { AppConfig, Event } from 'checkout/backend';
|
||||
import { ModalLoader } from './modal-loader';
|
||||
@ -22,13 +33,13 @@ export interface ModalContainerProps {
|
||||
setModalFromEvents: (events: Event[]) => any;
|
||||
acceptModel: () => any;
|
||||
pollInvoiceEvents: (capiEndpoint: string, accessToken: string, events: Event[]) => any;
|
||||
setErrorFormInfo: () => any;
|
||||
goToFormInfo: (formInfo: FormInfo) => any;
|
||||
acceptError: () => any;
|
||||
setModalInteractionPollingStatus: (status: boolean) => any;
|
||||
}
|
||||
|
||||
const isInteractionPolling = (modal: ModalState) =>
|
||||
(modal.name === ModalName.modalInteraction && (modal as ModalInteraction).pollingEvents);
|
||||
modal.name === ModalName.modalInteraction && (modal as ModalInteraction).pollingEvents;
|
||||
|
||||
class ModalContainerDef extends React.Component<ModalContainerProps> {
|
||||
|
||||
@ -49,7 +60,7 @@ class ModalContainerDef extends React.Component<ModalContainerProps> {
|
||||
props.acceptModel();
|
||||
}
|
||||
if (props.unhandledError) {
|
||||
props.setErrorFormInfo();
|
||||
props.goToFormInfo(new ResultFormInfo(ResultType.error));
|
||||
props.acceptError();
|
||||
}
|
||||
}
|
||||
@ -104,7 +115,7 @@ const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
|
||||
setModalFromEvents: bindActionCreators(setModalFromEvents, dispatch),
|
||||
acceptModel: bindActionCreators(accept, dispatch),
|
||||
pollInvoiceEvents: bindActionCreators(pollInvoiceEvents, dispatch),
|
||||
setErrorFormInfo: bindActionCreators(setErrorFormInfo, dispatch),
|
||||
goToFormInfo: bindActionCreators(goToFormInfo, dispatch),
|
||||
acceptError: bindActionCreators(acceptError, dispatch),
|
||||
setModalInteractionPollingStatus: bindActionCreators(setModalInteractionPollingStatus, dispatch)
|
||||
});
|
||||
|
@ -0,0 +1,8 @@
|
||||
import { values } from 'lodash';
|
||||
import { FieldsConfig, ItemConfig } from './fields-config';
|
||||
|
||||
export const calcFormHeight = (defaultHeight: number, fieldsConfig: FieldsConfig): number => {
|
||||
const count = values(fieldsConfig)
|
||||
.reduce((acc: number, current: ItemConfig) => current.visible ? ++acc : acc, 0);
|
||||
return defaultHeight + count * 52;
|
||||
};
|
@ -1,14 +1,21 @@
|
||||
import { CardFormInfo, CardFormState, CardFormValues, ConfigState, FormName, ModelState } from 'checkout/state';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import {
|
||||
CardFormInfo,
|
||||
CardFormValues,
|
||||
ConfigState,
|
||||
ModelState,
|
||||
} from 'checkout/state';
|
||||
import {Locale} from 'checkout/locale';
|
||||
import { FieldsConfig } from '../fields-config';
|
||||
|
||||
export interface CardFormProps {
|
||||
locale: Locale;
|
||||
config: ConfigState;
|
||||
model: ModelState;
|
||||
cardFormInfo: CardFormInfo;
|
||||
cardForm: CardFormState;
|
||||
locale: Locale;
|
||||
formValues: CardFormValues;
|
||||
fieldsConfig: FieldsConfig;
|
||||
pay: (c: ConfigState, m: ModelState, v: CardFormValues) => any;
|
||||
setViewInfoError: (hasError: boolean, formName: FormName) => any;
|
||||
setViewInfoError: (hasError: boolean) => any;
|
||||
prepareToPay: () => any;
|
||||
setViewInfoHeight: (height: number) => any;
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
.pay_button {
|
||||
margin-top: 20px;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export const pay_button: string;
|
||||
export const form: string;
|
||||
export const _withAmount: string;
|
@ -1,18 +1,27 @@
|
||||
import { connect } from 'react-redux';
|
||||
import * as React from 'react';
|
||||
import { InjectedFormProps, reduxForm } from 'redux-form';
|
||||
import { get } from 'lodash';
|
||||
import * as styles from './card-form.scss';
|
||||
import * as formStyles from '../form-container.scss';
|
||||
import * as commonFormStyles from 'checkout/styles/forms.scss';
|
||||
import { CardFormProps } from './card-form-props';
|
||||
import { Button } from '../button';
|
||||
import { Amount, CardHolder, CardNumber, Email, ExpireDate, SecureCode } from './fields';
|
||||
import { CardFormValues, FormName, ModalForms, ModalName, ModalState, PaymentStatus, State } from 'checkout/state';
|
||||
import { getAmount } from '../../amount-resolver';
|
||||
import { findNamed, formatAmount } from 'checkout/utils';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { pay, prepareToPay, setViewInfoError } from 'checkout/actions';
|
||||
import { get } from 'lodash';
|
||||
import * as formStyles from 'checkout/styles/forms.scss';
|
||||
import { CardFormProps } from './card-form-props';
|
||||
import { CardHolder, CardNumber, ExpireDate, SecureCode } from './fields';
|
||||
import {
|
||||
CardFormValues,
|
||||
FormName,
|
||||
ModalForms,
|
||||
ModalName,
|
||||
ModalState,
|
||||
PaymentStatus,
|
||||
State
|
||||
} from 'checkout/state';
|
||||
import { findNamed } from 'checkout/utils';
|
||||
import { payCardData, prepareToPay, setViewInfoError, setViewInfoHeight } from 'checkout/actions';
|
||||
import { PayButton } from '../pay-button';
|
||||
import { Header } from '../header/header';
|
||||
import { calcFormHeight } from '../calc-form-height';
|
||||
import { toFieldsConfig } from '../fields-config';
|
||||
import { Email, Amount } from '../common-fields';
|
||||
|
||||
const toCardFormInfo = (modals: ModalState[]) => {
|
||||
const info = (findNamed(modals, ModalName.modalForms) as ModalForms).formsInfo;
|
||||
@ -23,26 +32,20 @@ const mapStateToProps = (state: State) => ({
|
||||
cardFormInfo: toCardFormInfo(state.modals),
|
||||
config: state.config,
|
||||
model: state.model,
|
||||
cardForm: state.form.cardForm,
|
||||
formValues: get(state.form, 'cardForm.values'),
|
||||
locale: state.config.locale,
|
||||
formValues: get(state.form, 'cardForm.values')
|
||||
fieldsConfig: toFieldsConfig(state.config.initConfig, state.model.invoiceTemplate)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
|
||||
pay: bindActionCreators(pay, dispatch),
|
||||
pay: bindActionCreators(payCardData, dispatch),
|
||||
setViewInfoError: bindActionCreators(setViewInfoError, dispatch),
|
||||
prepareToPay: bindActionCreators(prepareToPay, dispatch)
|
||||
prepareToPay: bindActionCreators(prepareToPay, dispatch),
|
||||
setViewInfoHeight: bindActionCreators(setViewInfoHeight, dispatch)
|
||||
});
|
||||
|
||||
type Props = InjectedFormProps & CardFormProps;
|
||||
|
||||
const PayButton: React.SFC<CardFormProps> = (props) => {
|
||||
const amount = formatAmount(getAmount(props.config.initConfig.integrationType, props.model));
|
||||
const label = amount ? `${amount.value} ${amount.symbol}` : null;
|
||||
return <Button className={styles.pay_button} type='submit'
|
||||
style='primary' id='pay-btn'>{props.locale['form.button.pay.label']} {label}</Button>;
|
||||
};
|
||||
|
||||
class CardFormDef extends React.Component<Props> {
|
||||
|
||||
constructor(props: Props) {
|
||||
@ -50,64 +53,75 @@ class CardFormDef extends React.Component<Props> {
|
||||
this.submit = this.submit.bind(this);
|
||||
}
|
||||
|
||||
submit(values: CardFormValues) {
|
||||
const activeElement = document.activeElement as HTMLInputElement;
|
||||
activeElement.blur();
|
||||
this.props.prepareToPay();
|
||||
pay(values: CardFormValues) {
|
||||
const {config, model} = this.props;
|
||||
this.props.prepareToPay();
|
||||
this.props.pay(config, model, values);
|
||||
}
|
||||
|
||||
init(values: CardFormValues) {
|
||||
this.props.initialize({
|
||||
email: get(values, 'email'),
|
||||
amount: get(values, 'amount')
|
||||
});
|
||||
}
|
||||
|
||||
submit(values: CardFormValues) {
|
||||
(document.activeElement as HTMLElement).blur();
|
||||
this.pay(values);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
switch (this.props.cardFormInfo.paymentStatus) {
|
||||
const {cardFormInfo: {paymentStatus}, formValues} = this.props;
|
||||
this.props.setViewInfoError(false);
|
||||
switch (paymentStatus) {
|
||||
case PaymentStatus.pristine:
|
||||
this.props.reset();
|
||||
this.init(formValues);
|
||||
break;
|
||||
case PaymentStatus.needRetry:
|
||||
this.props.prepareToPay();
|
||||
const {config, model, formValues} = this.props;
|
||||
this.props.pay(config, model, formValues);
|
||||
this.pay(formValues);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.setViewInfoHeight(calcFormHeight(288, this.props.fieldsConfig));
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props: Props) {
|
||||
if (props.submitFailed) {
|
||||
this.props.setViewInfoError(true, FormName.cardForm);
|
||||
props.setViewInfoError(true);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const locale = this.props.locale;
|
||||
const {fieldsConfig} = this.props.cardFormInfo;
|
||||
const {handleSubmit, fieldsConfig: {email, amount}, locale} = this.props;
|
||||
return (
|
||||
<form onSubmit={this.props.handleSubmit(this.submit)} className={styles.form}>
|
||||
<div className={formStyles.header}>
|
||||
{/*{hasBack(this.props.formsFlow) ? <ChevronBack/> : null}*/}
|
||||
<div className={formStyles.title}>
|
||||
{locale['form.header.pay.card.label']}
|
||||
<form onSubmit={handleSubmit(this.submit)}>
|
||||
<div>
|
||||
<Header title={locale['form.header.pay.card.label']}/>
|
||||
<div className={formStyles.formGroup}>
|
||||
<CardNumber/>
|
||||
</div>
|
||||
<div className={formStyles.formGroup}>
|
||||
<ExpireDate/>
|
||||
<SecureCode/>
|
||||
</div>
|
||||
<div className={formStyles.formGroup}>
|
||||
<CardHolder/>
|
||||
</div>
|
||||
{email.visible ?
|
||||
<div className={formStyles.formGroup}>
|
||||
<Email/>
|
||||
</div> : false
|
||||
}
|
||||
{amount.visible ?
|
||||
<div className={formStyles.formGroup}>
|
||||
<Amount cost={amount.cost}/>
|
||||
</div> : false
|
||||
}
|
||||
</div>
|
||||
<div className={commonFormStyles.formGroup}>
|
||||
<CardNumber/>
|
||||
</div>
|
||||
<div className={commonFormStyles.formGroup}>
|
||||
<ExpireDate/>
|
||||
<SecureCode/>
|
||||
</div>
|
||||
<div className={commonFormStyles.formGroup}>
|
||||
<CardHolder/>
|
||||
</div>
|
||||
{fieldsConfig.email.visible ?
|
||||
<div className={commonFormStyles.formGroup}>
|
||||
<Email/>
|
||||
</div> : false
|
||||
}
|
||||
{fieldsConfig.amount.visible ?
|
||||
<div className={commonFormStyles.formGroup}>
|
||||
<Amount/>
|
||||
</div> : false
|
||||
}
|
||||
<PayButton {...this.props}/>
|
||||
<PayButton/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
import { CostType, InvoiceTemplateLineCostRange } from 'checkout/backend';
|
||||
import { toNumber } from 'lodash';
|
||||
import { validateAmount } from '../validation';
|
||||
import { AmountProps } from './amount';
|
||||
|
||||
export const validate = (value: string, props: AmountProps): boolean => {
|
||||
const binded = validateAmount.bind(null, toNumber(value) * 100);
|
||||
switch (props.cost.costType) {
|
||||
case CostType.InvoiceTemplateLineCostRange:
|
||||
const range = (props.cost as InvoiceTemplateLineCostRange).range;
|
||||
return binded(range.lowerBound, range.upperBound);
|
||||
case CostType.InvoiceTemplateLineCostUnlim:
|
||||
return binded();
|
||||
}
|
||||
};
|
@ -7,7 +7,7 @@ import { Input } from '../../../input';
|
||||
import { cardHolderFormatter } from '../format';
|
||||
import { validateCardHolder } from '../validation';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { isError } from '../error-predicate';
|
||||
import { isError } from '../../../common-fields/error-predicate';
|
||||
|
||||
type FieldProps = WrappedFieldInputProps & WrappedFieldProps;
|
||||
|
||||
|
@ -5,7 +5,7 @@ import * as styles from './card-number.scss';
|
||||
import { cardNumberFormatter } from '../format/index';
|
||||
import { Locale } from 'src/locale/locale';
|
||||
import { State } from 'checkout/state';
|
||||
import { isError } from '../error-predicate';
|
||||
import { isError } from '../../../common-fields/error-predicate';
|
||||
import { IconType, Input } from 'checkout/components';
|
||||
|
||||
type FieldProps = WrappedFieldInputProps & WrappedFieldProps;
|
||||
|
@ -7,7 +7,7 @@ import { Input } from '../../../input';
|
||||
import { expireDateFormatter } from '../format';
|
||||
import { validateExpireDate } from '../validation';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { isError } from '../error-predicate';
|
||||
import { isError } from '../../../common-fields/error-predicate';
|
||||
|
||||
type FieldProps = WrappedFieldInputProps & WrappedFieldProps;
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
export * from './card-holder';
|
||||
export * from './card-number';
|
||||
export * from './email';
|
||||
export * from './expire-date';
|
||||
export * from './secure-code';
|
||||
export * from './amount';
|
||||
|
@ -7,7 +7,7 @@ import { secureCodeFormatter } from '../format';
|
||||
import { validateSecureCode } from '../validation';
|
||||
import { State } from 'checkout/state';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { isError } from '../error-predicate';
|
||||
import { isError } from '../../../common-fields/error-predicate';
|
||||
|
||||
type FieldProps = WrappedFieldInputProps & WrappedFieldProps;
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
export * from './card-number';
|
||||
export * from './card-holder';
|
||||
export * from './email';
|
||||
export * from './expire-date';
|
||||
export * from './secure-code';
|
||||
export * from './amount';
|
||||
|
@ -1,19 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import {Icon, IconType} from 'checkout/components';
|
||||
|
||||
class ChevronBackDef extends React.Component<any> {
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render(): any {
|
||||
// return (
|
||||
// <div className={formStyles.back_btn} onClick={this.back}>
|
||||
// <Icon icon={IconType.chevronLeft}/>
|
||||
// </div>
|
||||
// );
|
||||
return null;
|
||||
}
|
||||
interface ChevronBackProps {
|
||||
className: string;
|
||||
back: () => any;
|
||||
}
|
||||
|
||||
export const ChevronBack = ChevronBackDef;
|
||||
export const ChevronBack: React.SFC<ChevronBackProps> = (props) => (
|
||||
<div className={props.className} onClick={props.back}>
|
||||
<Icon icon={IconType.chevronLeft}/>
|
||||
</div>
|
||||
);
|
||||
|
@ -2,19 +2,28 @@ import * as React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Field, WrappedFieldInputProps, WrappedFieldProps } from 'redux-form';
|
||||
import { IconType, Input } from 'checkout/components';
|
||||
import { CardFormInfo, FormName, ModalForms, ModalName, ModalState, State } from 'checkout/state';
|
||||
import { InvoiceTemplateLineCostRange, InvoiceTemplateLineCostUnlim } from 'checkout/backend';
|
||||
import { getPlaceholder } from './get-placeholder';
|
||||
import { validate } from './validate';
|
||||
import { isError } from '../error-predicate';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { InvoiceTemplateLineCostRange, InvoiceTemplateLineCostUnlim } from 'checkout/backend';
|
||||
import { State } from 'checkout/state';
|
||||
|
||||
type FieldProps = WrappedFieldInputProps & WrappedFieldProps;
|
||||
|
||||
interface OwnProps {
|
||||
cost: InvoiceTemplateLineCostRange | InvoiceTemplateLineCostUnlim;
|
||||
}
|
||||
|
||||
export interface AmountProps {
|
||||
cost: InvoiceTemplateLineCostRange | InvoiceTemplateLineCostUnlim;
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
type FieldProps = WrappedFieldInputProps & WrappedFieldProps;
|
||||
const mapStateToProps = (state: State, ownProps: OwnProps) => ({
|
||||
cost: ownProps.cost,
|
||||
locale: state.config.locale
|
||||
});
|
||||
|
||||
const CustomInput: React.SFC<FieldProps & AmountProps> = (props) => (
|
||||
<Input
|
||||
@ -33,20 +42,8 @@ const AmountDef: React.SFC<AmountProps> = (props) => (
|
||||
<Field
|
||||
name='amount'
|
||||
component={(fieldProps: FieldProps) => CustomInput({...fieldProps, ...props})}
|
||||
validate={(value) => validate(value, props)}
|
||||
validate={(value) => validate(value, props.cost)}
|
||||
/>
|
||||
);
|
||||
|
||||
const toCost = (s: ModalState[]) => {
|
||||
const modalForms = s.find((modal) => modal.name === ModalName.modalForms) as ModalForms;
|
||||
const cardFormInfo = modalForms.formsInfo.find((info) => info.name === FormName.cardForm) as CardFormInfo;
|
||||
return cardFormInfo.fieldsConfig.amount.cost;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
cost: toCost(state.modals),
|
||||
locale: state.config.locale
|
||||
}
|
||||
);
|
||||
|
||||
export const Amount = connect(mapStateToProps)(AmountDef);
|
@ -0,0 +1,14 @@
|
||||
import { CostType, InvoiceTemplateLineCostRange, InvoiceTemplateLineCostUnlim } from 'checkout/backend';
|
||||
import { toNumber } from 'lodash';
|
||||
import { validateAmount } from '../validation/amount';
|
||||
|
||||
export const validate = (value: string, cost: InvoiceTemplateLineCostRange | InvoiceTemplateLineCostUnlim): boolean => {
|
||||
const binded = validateAmount.bind(null, toNumber(value) * 100);
|
||||
switch (cost.costType) {
|
||||
case CostType.InvoiceTemplateLineCostRange:
|
||||
const range = (cost as InvoiceTemplateLineCostRange).range;
|
||||
return binded(range.lowerBound, range.upperBound);
|
||||
case CostType.InvoiceTemplateLineCostUnlim:
|
||||
return binded();
|
||||
}
|
||||
};
|
@ -3,10 +3,10 @@ import { connect } from 'react-redux';
|
||||
import { Field, WrappedFieldInputProps, WrappedFieldProps } from 'redux-form';
|
||||
import { IconType } from 'checkout/components/ui';
|
||||
import { State } from 'checkout/state';
|
||||
import { Input } from '../../../input';
|
||||
import { validateEmail } from '../validation';
|
||||
import { Input } from '../../input';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { isError } from '../error-predicate';
|
||||
import { validateEmail } from '../validation/email';
|
||||
|
||||
type FieldProps = WrappedFieldInputProps & WrappedFieldProps;
|
||||
|
@ -0,0 +1 @@
|
||||
export * from './phone/format-phone-number';
|
@ -0,0 +1,19 @@
|
||||
import * as libphonenumber from 'libphonenumber-js';
|
||||
|
||||
const format = (e: KeyboardEvent) => {
|
||||
const target = e.currentTarget as HTMLInputElement;
|
||||
const value = target.value;
|
||||
if (value.slice(0, 2) === '+7') {
|
||||
target.value = new libphonenumber.AsYouType('RU').input(value);
|
||||
} else {
|
||||
target.value = `+${libphonenumber.getPhoneCode('RU')} `;
|
||||
}
|
||||
};
|
||||
|
||||
export function phoneNumberFormatter(element: Element) {
|
||||
element.addEventListener('focus', format);
|
||||
element.addEventListener('keypress', format);
|
||||
element.addEventListener('keydown', format);
|
||||
element.addEventListener('change', format);
|
||||
element.addEventListener('input', format);
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export * from './email';
|
||||
export * from './amount';
|
||||
export * from './phone';
|
@ -0,0 +1 @@
|
||||
export * from './phone';
|
@ -0,0 +1,44 @@
|
||||
import * as React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Field, WrappedFieldInputProps, WrappedFieldProps } from 'redux-form';
|
||||
import { IconType } from 'checkout/components/ui';
|
||||
import { State } from 'checkout/state';
|
||||
import { Input } from '../../input';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { isError } from '../error-predicate';
|
||||
import { validatePhone } from '../validation/phone';
|
||||
import { phoneNumberFormatter } from '../format';
|
||||
|
||||
type FieldProps = WrappedFieldInputProps & WrappedFieldProps;
|
||||
|
||||
export interface PhoneDefProps {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
locale: state.config.locale
|
||||
});
|
||||
|
||||
const CustomInput: React.SFC<FieldProps & PhoneDefProps> = (props) => (
|
||||
<Input
|
||||
{...props.input}
|
||||
{...props.meta}
|
||||
error={isError(props.meta)}
|
||||
formatter={phoneNumberFormatter}
|
||||
icon={IconType.phone}
|
||||
placeholder={props.locale['form.input.phone.placeholder']}
|
||||
mark={true}
|
||||
type='tel'
|
||||
id='phone-input'
|
||||
/>
|
||||
);
|
||||
|
||||
export const PhoneDef: React.SFC<PhoneDefProps> = (props) => (
|
||||
<Field
|
||||
name='phone'
|
||||
component={(fieldProps: FieldProps) => CustomInput({...fieldProps, ...props})}
|
||||
validate={validatePhone}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Phone = connect(mapStateToProps)(PhoneDef);
|
@ -0,0 +1 @@
|
||||
export * from './phone';
|
@ -0,0 +1,8 @@
|
||||
import * as libphonenumber from 'libphonenumber-js';
|
||||
|
||||
export function validatePhone(value: string): boolean {
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
return !libphonenumber.isValidNumber(value, 'RU');
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import { InvoiceTemplateLineCostRange, InvoiceTemplateLineCostUnlim } from 'checkout/backend';
|
||||
|
||||
export interface ItemConfig {
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export interface AmountConfig extends ItemConfig {
|
||||
cost?: InvoiceTemplateLineCostRange | InvoiceTemplateLineCostUnlim;
|
||||
}
|
||||
|
||||
export interface EmailConfig extends ItemConfig {
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface FieldsConfig {
|
||||
amount: AmountConfig;
|
||||
email: EmailConfig;
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export * from './fields-config';
|
||||
export * from './to-fields-config';
|
@ -1,18 +1,11 @@
|
||||
import { values } from 'lodash';
|
||||
import {
|
||||
InvoiceTemplate,
|
||||
InvoiceTemplateLineCostRange,
|
||||
InvoiceTemplateLineCostUnlim,
|
||||
InvoiceTemplateSingleLine
|
||||
} from 'checkout/backend';
|
||||
import {
|
||||
FieldsConfig,
|
||||
CardFormInfo,
|
||||
ItemConfig,
|
||||
AmountConfig,
|
||||
EmailConfig
|
||||
} from 'checkout/state';
|
||||
import { InitConfig, IntegrationType } from 'checkout/config';
|
||||
import { AmountConfig, EmailConfig, FieldsConfig } from './fields-config';
|
||||
|
||||
const toSingleLineAmountConfig = (c: InvoiceTemplateSingleLine): AmountConfig => {
|
||||
const result = {visible: false} as AmountConfig;
|
||||
@ -51,18 +44,7 @@ const toEmailConfig = (email: string): EmailConfig => {
|
||||
: {visible: true};
|
||||
};
|
||||
|
||||
const toFieldsConfig = (c: InitConfig, t: InvoiceTemplate): FieldsConfig => ({
|
||||
export const toFieldsConfig = (c: InitConfig, t: InvoiceTemplate): FieldsConfig => ({
|
||||
amount: toAmountConfig(c.integrationType, t),
|
||||
email: toEmailConfig(c.email)
|
||||
});
|
||||
|
||||
const calcHeight = (fieldsConfig: FieldsConfig): number => {
|
||||
const count = values(fieldsConfig)
|
||||
.reduce((acc: number, current: ItemConfig) => current.visible ? ++acc : acc, 0);
|
||||
return 288 + count * 52;
|
||||
};
|
||||
|
||||
export const toCardFormInfo = (c: InitConfig, t: InvoiceTemplate): CardFormInfo => {
|
||||
const fieldConfig = toFieldsConfig(c, t);
|
||||
return new CardFormInfo(calcHeight(fieldConfig), fieldConfig, true);
|
||||
};
|
@ -1,5 +1,7 @@
|
||||
import { FormInfo } from 'checkout/state/modal/form-info';
|
||||
import { FormViewInfo } from 'checkout/state';
|
||||
|
||||
export interface FormContainerProps {
|
||||
activeFormInfo: FormInfo;
|
||||
viewInfo: FormViewInfo;
|
||||
}
|
||||
|
@ -18,11 +18,11 @@
|
||||
padding: 30px 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: height .4s;
|
||||
|
||||
@include responsive(sm) {
|
||||
padding: 30px;
|
||||
min-height: auto;
|
||||
transition: height .75s;
|
||||
}
|
||||
|
||||
&._error {
|
||||
@ -30,24 +30,46 @@
|
||||
}
|
||||
}
|
||||
|
||||
.animationFormContainer {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
form {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.back_btn {
|
||||
display: none;
|
||||
|
||||
@include responsive(sm) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.back_btn {
|
||||
display: none;
|
||||
height: 20px;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
@include responsive(sm) {
|
||||
display: flex;
|
||||
}
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
|
||||
svg {
|
||||
height: 15px;
|
||||
width: 9px;
|
||||
@ -68,14 +90,3 @@
|
||||
}
|
||||
}
|
||||
|
||||
.appearLoader {
|
||||
animation: fadein .5s;
|
||||
}
|
||||
|
||||
.enterLoader {
|
||||
animation: fadein .5s;
|
||||
}
|
||||
|
||||
.leaveLoader {
|
||||
animation: fadeout .25s;
|
||||
}
|
||||
|
@ -1,18 +1,12 @@
|
||||
export const container: string;
|
||||
export const form: string;
|
||||
export const _error: string;
|
||||
export const shake: string;
|
||||
export const header: string;
|
||||
export const back_btn: string;
|
||||
export const title: string;
|
||||
export const growth: string;
|
||||
export const popup: string;
|
||||
export const appearForm: string;
|
||||
export const enterForm: string;
|
||||
export const leaveForm: string;
|
||||
export const appearLoader: string;
|
||||
export const enterLoader: string;
|
||||
export const leaveLoader: string;
|
||||
export const _cardForm: string;
|
||||
export const _resultForm: string;
|
||||
export const _cardFormAmount: string;
|
||||
export const animationFormContainer: string;
|
||||
|
@ -4,59 +4,47 @@ import * as CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';
|
||||
import * as cx from 'classnames';
|
||||
import * as styles from './form-container.scss';
|
||||
import { CardForm } from './card-form';
|
||||
import { FormName, ModalForms, ModalName, ModalState, State } from 'checkout/state';
|
||||
import { FormName, ModalForms, ModalName, State } from 'checkout/state';
|
||||
import { PaymentMethods } from './payment-methods';
|
||||
import { FormContainerProps } from './form-container-props';
|
||||
import { FormLoader } from './form-loader';
|
||||
import { ResultForm } from './result-form';
|
||||
import { findNamed } from 'checkout/utils';
|
||||
import { WalletForm } from './wallet-form';
|
||||
|
||||
const toActiveFormInfo = (modals: ModalState[]) => {
|
||||
const info = (findNamed(modals, ModalName.modalForms) as ModalForms).formsInfo;
|
||||
return info.find((item) => item.active);
|
||||
const mapStateToProps = (state: State) => {
|
||||
const modalForms = (findNamed(state.modals, ModalName.modalForms) as ModalForms);
|
||||
return {
|
||||
activeFormInfo: modalForms.formsInfo.find((item) => item.active),
|
||||
viewInfo: modalForms.viewInfo
|
||||
};
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
activeFormInfo: toActiveFormInfo(state.modals)
|
||||
});
|
||||
class FormContainerDef extends React.Component<FormContainerProps> {
|
||||
|
||||
const FormContainerDef: React.SFC<FormContainerProps> = (props) => {
|
||||
const {name, viewInfo} = props.activeFormInfo;
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={cx(styles.form, {
|
||||
[styles._error]: viewInfo.error
|
||||
})}
|
||||
style={{height: viewInfo.height}}>
|
||||
<CSSTransitionGroup
|
||||
component='div'
|
||||
transitionName={viewInfo.slideDirection}
|
||||
transitionEnterTimeout={550}
|
||||
transitionLeaveTimeout={550}
|
||||
>
|
||||
{name === FormName.paymentMethods ? <PaymentMethods/> : null}
|
||||
{name === FormName.cardForm ? <CardForm/> : null}
|
||||
{name === FormName.resultForm ? <ResultForm/> : null}
|
||||
</CSSTransitionGroup>
|
||||
<CSSTransitionGroup
|
||||
component='div'
|
||||
transitionName={{
|
||||
appear: styles.appearLoader,
|
||||
enter: styles.enterLoader,
|
||||
leave: styles.leaveLoader
|
||||
}}
|
||||
transitionLeaveTimeout={200}
|
||||
transitionEnterTimeout={450}
|
||||
transitionAppearTimeout={450}
|
||||
transitionAppear={true}
|
||||
transitionEnter={true}
|
||||
transitionLeave={true}
|
||||
>
|
||||
render() {
|
||||
const {activeFormInfo: {name}, viewInfo} = this.props;
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div
|
||||
className={cx(styles.form, {[styles._error]: viewInfo.error})}
|
||||
style={{height: viewInfo.height}}>
|
||||
<CSSTransitionGroup
|
||||
component='div'
|
||||
className={styles.animationFormContainer}
|
||||
transitionName={viewInfo.slideDirection}
|
||||
transitionEnterTimeout={550}
|
||||
transitionLeaveTimeout={550}>
|
||||
{name === FormName.paymentMethods ? <PaymentMethods/> : null}
|
||||
{name === FormName.cardForm ? <CardForm/> : null}
|
||||
{name === FormName.walletForm ? <WalletForm/> : null}
|
||||
{name === FormName.resultForm ? <ResultForm/> : null}
|
||||
</CSSTransitionGroup>
|
||||
{viewInfo.inProcess ? <FormLoader/> : null}
|
||||
</CSSTransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const FormContainer = connect(mapStateToProps)(FormContainerDef);
|
||||
|
@ -14,3 +14,11 @@
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, .9);
|
||||
}
|
||||
|
||||
.appear {
|
||||
animation: fadein .5s;
|
||||
}
|
||||
|
||||
.leave {
|
||||
animation: fadeout .2s;
|
||||
}
|
||||
|
@ -1 +1,3 @@
|
||||
export const loader: string;
|
||||
export const appear: string;
|
||||
export const leave: string;
|
||||
|
@ -1,9 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import * as styles from './form-loader.scss';
|
||||
import { CSSTransitionGroup } from 'react-transition-group';
|
||||
import { appear, leave, loader } from './form-loader.scss';
|
||||
import { Loader } from 'checkout/components';
|
||||
|
||||
export const FormLoader: React.SFC = () => (
|
||||
<div className={styles.loader} id='form-loader'>
|
||||
<Loader/>
|
||||
</div>
|
||||
<CSSTransitionGroup
|
||||
transitionName={{enter: null, appear, leave}}
|
||||
transitionEnter={false}
|
||||
transitionAppear={true}
|
||||
transitionAppearTimeout={500}
|
||||
transitionLeaveTimeout={200}>
|
||||
<div key='form-loader' className={loader} id='form-loader'>
|
||||
<Loader/>
|
||||
</div>
|
||||
</CSSTransitionGroup>
|
||||
);
|
||||
|
@ -0,0 +1,43 @@
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import * as React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import * as formStyles from '../form-container.scss';
|
||||
import { Direction, goToFormInfo } from 'checkout/actions';
|
||||
import { findInfoWithPrevious, findNamed } from 'checkout/utils';
|
||||
import { FormInfo, ModalForms, ModalName, ModalState, State } from 'checkout/state';
|
||||
import { ChevronBack } from '../chevron-back';
|
||||
|
||||
export interface HeaderProps {
|
||||
title: string;
|
||||
goToFormInfo: (formInfo: FormInfo, direction: Direction) => any;
|
||||
destination: FormInfo;
|
||||
}
|
||||
|
||||
const getDestination = (modals: ModalState[]): FormInfo => {
|
||||
const modalForms = findNamed(modals, ModalName.modalForms) as ModalForms;
|
||||
const withPrevious = findInfoWithPrevious(modals);
|
||||
return withPrevious ? findNamed(modalForms.formsInfo, withPrevious.previous) as FormInfo : null;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
destination: getDestination(state.modals)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
|
||||
goToFormInfo: bindActionCreators(goToFormInfo, dispatch)
|
||||
});
|
||||
|
||||
const HeaderDef: React.SFC<HeaderProps> = (props) => (
|
||||
<div className={formStyles.header}>
|
||||
{props.destination ?
|
||||
<ChevronBack
|
||||
className={formStyles.back_btn}
|
||||
back={props.goToFormInfo.bind(null, props.destination, Direction.back)}/> : null
|
||||
}
|
||||
<div className={formStyles.title}>
|
||||
{props.title}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Header = connect(mapStateToProps, mapDispatchToProps)(HeaderDef);
|
@ -0,0 +1 @@
|
||||
export * from './header';
|
@ -1,2 +1,3 @@
|
||||
export * from './form-container';
|
||||
export * from './form-container-props';
|
||||
export * from './common-fields';
|
||||
|
@ -0,0 +1 @@
|
||||
export * from './pay-button';
|
@ -0,0 +1,34 @@
|
||||
import * as React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ModelState, State } from 'checkout/state';
|
||||
import { IntegrationType } from 'checkout/config';
|
||||
import { getAmount } from '../../amount-resolver';
|
||||
import { formatAmount } from 'checkout/utils';
|
||||
import { Button } from 'checkout/components';
|
||||
import { Locale } from 'checkout/locale';
|
||||
|
||||
export interface PayButtonProps {
|
||||
locale: Locale;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const PayButtonDef: React.SFC<PayButtonProps> = (props) => (
|
||||
<Button
|
||||
type='submit'
|
||||
style='primary'
|
||||
id='pay-btn'>
|
||||
{props.locale['form.button.pay.label']} {props.label}
|
||||
</Button>
|
||||
);
|
||||
|
||||
const toLabel = (integrationType: IntegrationType, model: ModelState): string => {
|
||||
const amount = formatAmount(getAmount(integrationType, model));
|
||||
return amount ? `${amount.value} ${amount.symbol}` : null;
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
locale: state.config.locale,
|
||||
label: toLabel(state.config.initConfig.integrationType, state.model)
|
||||
});
|
||||
|
||||
export const PayButton = connect(mapStateToProps)(PayButtonDef);
|
@ -0,0 +1,22 @@
|
||||
import * as React from 'react';
|
||||
import * as styles from '../payment-methods.scss';
|
||||
import { Locale } from 'checkout/locale';
|
||||
import { CardFormInfo, FormInfo, FormName } from 'checkout/state';
|
||||
import { BankCardIcon } from './icons/bank-card-icon';
|
||||
|
||||
interface BankCardProps {
|
||||
locale: Locale;
|
||||
setFormInfo: (formInfo: FormInfo) => any;
|
||||
}
|
||||
|
||||
const toBankCard = (props: BankCardProps) => props.setFormInfo(new CardFormInfo(FormName.paymentMethods));
|
||||
|
||||
export const BankCard: React.SFC<BankCardProps> = (props) => (
|
||||
<li className={styles.method} onClick={toBankCard.bind(null, props)}>
|
||||
<BankCardIcon/>
|
||||
<div className={styles.title}>
|
||||
{props.locale['form.payment.method.name.card.label']}
|
||||
<hr/>
|
||||
</div>
|
||||
</li>
|
||||
);
|
@ -0,0 +1,31 @@
|
||||
import * as React from 'react';
|
||||
import * as styles from '../../payment-methods.scss';
|
||||
|
||||
export const BankCardIcon: React.SFC = () => (
|
||||
<div className={styles.icon}>
|
||||
{/* tslint:disable:max-line-length */}
|
||||
<svg width='40px' height='40px' viewBox='0 0 40 40'>
|
||||
<g stroke='none' strokeWidth='1' fill='none' fillRule='evenodd'>
|
||||
<g transform='translate(-45.000000, -366.000000)'>
|
||||
<g transform='translate(5.000000, 276.000000)'>
|
||||
<g transform='translate(20.000000, 70.000000)'>
|
||||
<g transform='translate(20.000000, 20.000000)'>
|
||||
<rect fill='#FFFFFF' x='0' y='0' width='40' height='40'/>
|
||||
<path
|
||||
d='M29.99436,25 L5.0068,25 C3.899,25 3,24.0712 3,22.9252 L3,9.0748 C3,7.9288 3.899,7 5.0068,7 L29.99436,7 C31.10216,7 32,7.9288 32,9.0748 L32,22.9252 C32,24.0712 31.10216,25 29.99436,25 Z'
|
||||
stroke='#0077FF' strokeWidth='2' fill='#FFFFFF'/>
|
||||
<path
|
||||
d='M34.99436,34 L10.0068,34 C8.899,34 8,33.0712 8,31.9252 L8,18.0748 C8,16.9288 8.899,16 10.0068,16 L34.99436,16 C36.10216,16 37,16.9288 37,18.0748 L37,31.9252 C37,33.0712 36.10216,34 34.99436,34 Z'
|
||||
stroke='#0077FF' strokeWidth='2' fill='#FFFFFF'/>
|
||||
<polygon fill='#0077FF' points='4 13 31 13 31 12 4 12'/>
|
||||
<polygon fill='#0077FF' points='11 28 27 28 27 27 11 27'/>
|
||||
<polygon fill='#0077FF' points='30 28 34 28 34 27 30 27'/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
{/* tslint:enable:max-line-length */}
|
||||
</div>
|
||||
);
|
@ -0,0 +1,31 @@
|
||||
import * as React from 'react';
|
||||
import * as styles from '../../payment-methods.scss';
|
||||
|
||||
export const TerminalsIcon: React.SFC = () => (
|
||||
<div className={styles.icon}>
|
||||
{/* tslint:disable:max-line-length */}
|
||||
<svg width='40px' height='40px' viewBox='0 0 40 40'>
|
||||
<g stroke='none' strokeWidth='1' fill='none' fillRule='evenodd'>
|
||||
<g transform='translate(-45.000000, -456.000000)'>
|
||||
<g transform='translate(5.000000, 276.000000)'>
|
||||
<g transform='translate(20.000000, 160.000000)'>
|
||||
<g transform='translate(20.000000, 20.000000)'>
|
||||
<rect fill='#FFFFFF' x='0' y='0' width='40' height='40'/>
|
||||
<g transform='translate(8.000000, 3.000000)'>
|
||||
<path
|
||||
d='M24,33 L0,33 L0,2.3674 C0,1.0602 1.05726316,0 2.36084211,0 L21.6391579,0 C22.9427368,0 24,1.0602 24,2.3674 L24,33 Z'
|
||||
stroke='#0077FF' strokeWidth='2' fill='#FFFFFF'/>
|
||||
<path d='M3,16 L21,16 L21,3 L3,3 L3,16 Z M4,15 L20,15 L20,4 L4,4 L4,15 Z'
|
||||
fill='#0077FF'/>
|
||||
<polygon fill='#0077FF' points='0 19 24 19 24 18 0 18'/>
|
||||
<polygon fill='#0077FF' points='15 24 21 24 21 23 15 23'/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
{/* tslint:enable:max-line-length */}
|
||||
</div>
|
||||
);
|
@ -0,0 +1,43 @@
|
||||
import * as React from 'react';
|
||||
import * as styles from '../../payment-methods.scss';
|
||||
|
||||
export const WalletsIcon: React.SFC = () => (
|
||||
<div className={styles.icon}>
|
||||
{/* tslint:disable:max-line-length */}
|
||||
<svg width='40px' height='40px' viewBox='0 0 40 40' version='1.1'>
|
||||
<g stroke='none' strokeWidth='1' fill='none' fillRule='evenodd'>
|
||||
<g transform='translate(-700.000000, -384.000000)'>
|
||||
<g transform='translate(675.000000, 170.000000)'>
|
||||
<g transform='translate(0.000000, 194.000000)'>
|
||||
<g transform='translate(25.000000, 20.000000)'>
|
||||
<rect fill='#FFFFFF' x='0' y='0' width='40' height='40'/>
|
||||
<g strokeWidth='1' transform='translate(5.000000, 6.000000)'>
|
||||
<g transform='translate(0.000000, 2.000000)'>
|
||||
<g transform='translate(1.000000, 0.000000)' fill='#FFFFFF'>
|
||||
<path
|
||||
d='M29,9.56521739 L29,23.7155652 C29,24.9775217 27.98964,26 26.74264,26 L2.25736,26 C1.01036,26 0,24.9775217 0,23.7155652 L0,0.138695652'
|
||||
id='Fill-1'/>
|
||||
</g>
|
||||
<path
|
||||
d='M30,9.56521739 L30,23.7155652 C30,24.9775217 28.98964,26 27.74264,26 L2.25736,26 C1.01036,26 0,24.9775217 0,23.7155652 L0,0.138695652'
|
||||
id='Stroke-3' stroke='#0077FF' strokeWidth='2'/>
|
||||
</g>
|
||||
<path
|
||||
d='M30,12 L30,9.5784 C30,7.602 28.4514,6 27.68,6 L3.42432,6 C1.02312,6 0,4.9416 0,3.636 L0,2.364 C0,1.0584 1.02312,0 2.2852,0 L21.83128,0 C23.76848,0 25.33912,1.6248 25.33912,3.6288 L25.33912,6'
|
||||
id='Stroke-5' stroke='#0077FF' strokeWidth='2'/>
|
||||
<g transform='translate(7.000000, 13.000000)'>
|
||||
<polyline fill='#FFFFFF'
|
||||
points='0.566666667 7.875 8.5 0 8.5 9 16.4333333 1.125'/>
|
||||
<polyline stroke='#0077FF'
|
||||
points='0.566666667 7.875 8.5 0 8.5 9 16.4333333 1.125'/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
{/* tslint:enable:max-line-length */}
|
||||
</div>
|
||||
);
|
@ -0,0 +1,3 @@
|
||||
export * from './bank-card';
|
||||
export * from './wallets';
|
||||
export * from './terminals';
|
@ -0,0 +1,23 @@
|
||||
import * as React from 'react';
|
||||
import * as styles from '../payment-methods.scss';
|
||||
import {Locale} from 'checkout/locale';
|
||||
import {TerminalsIcon} from './icons/terminals-icon';
|
||||
|
||||
interface TerminalsProps {
|
||||
locale: Locale;
|
||||
}
|
||||
|
||||
export const Terminals: React.SFC<TerminalsProps> = (props) => (
|
||||
<li className={styles.method}>
|
||||
<TerminalsIcon />
|
||||
<div className={styles.text}>
|
||||
<h5 className={styles.title}>
|
||||
{props.locale['form.payment.method.name.cash.label']}
|
||||
<hr/>
|
||||
</h5>
|
||||
<p className={styles.description}>
|
||||
{props.locale['form.payment.method.description.euroset.text']}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
);
|
@ -0,0 +1,27 @@
|
||||
import * as React from 'react';
|
||||
import * as styles from '../payment-methods.scss';
|
||||
import {Locale} from 'checkout/locale';
|
||||
import {WalletsIcon} from './icons/wallets-icon';
|
||||
import { FormInfo, FormName, WalletFormInfo } from 'checkout/state';
|
||||
|
||||
interface WalletsProps {
|
||||
locale: Locale;
|
||||
setFormInfo: (formInfo: FormInfo) => any;
|
||||
}
|
||||
|
||||
const toWallets = (props: WalletsProps) => props.setFormInfo(new WalletFormInfo(FormName.paymentMethods));
|
||||
|
||||
export const Wallets: React.SFC<WalletsProps> = (props) => (
|
||||
<li className={styles.method} onClick={toWallets.bind(null, props)}>
|
||||
<WalletsIcon />
|
||||
<div className={styles.text}>
|
||||
<h5 className={styles.title}>
|
||||
{props.locale['form.payment.method.name.wallet.label']}
|
||||
<hr/>
|
||||
</h5>
|
||||
<p className={styles.description}>
|
||||
{props.locale['form.payment.method.description.qiwi.text']}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
);
|
@ -53,9 +53,8 @@
|
||||
display: inline-table;
|
||||
|
||||
hr {
|
||||
border-color: $light-blue;
|
||||
opacity: .4;
|
||||
border-bottom: 0;
|
||||
border: 1px solid $lightest-blue2;
|
||||
border-bottom-width: 0;
|
||||
padding: 0;
|
||||
margin: 0 0 3px;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user