mirror of
https://github.com/valitydev/checkout.git
synced 2024-11-06 10:35:20 +00:00
FE-576: Multicurrency (#228)
This commit is contained in:
parent
0975019efa
commit
4dfe657df2
@ -1,20 +1,21 @@
|
||||
import { IntegrationType, InvoiceInitConfig, InvoiceTemplateInitConfig } from 'checkout/config';
|
||||
import { getAmount } from 'checkout/components/app/modal-container/modal/amount-resolver';
|
||||
import { ConfigState, ModelState } from 'checkout/state';
|
||||
import toNumber from 'lodash-es/toNumber';
|
||||
import { IntegrationType, InvoiceInitConfig, InvoiceTemplateInitConfig } from 'checkout/config';
|
||||
import { ConfigState, ModelState } from 'checkout/state';
|
||||
import { createInvoiceWithTemplate } from 'checkout/backend';
|
||||
import { PaymentSubject } from './payment-subject';
|
||||
import { Amount, resolveAmount } from 'checkout/utils';
|
||||
|
||||
const resolveAmount = (c: ConfigState, m: ModelState, formAmount: string): number =>
|
||||
formAmount ? toNumber(formAmount) * 100 : getAmount(m, c.initConfig.amount).value;
|
||||
const infoToAmount = (amountInfo: Amount, formAmount: string): number =>
|
||||
formAmount ? toNumber(formAmount) * 100 : amountInfo.value;
|
||||
|
||||
const resolveInvoiceTemplate = (c: ConfigState, m: ModelState, formAmount: string): Promise<PaymentSubject> => {
|
||||
const endpoint = c.appConfig.capiEndpoint;
|
||||
const initConfig = c.initConfig as InvoiceTemplateInitConfig;
|
||||
const amount = resolveAmount(c, m, formAmount);
|
||||
const amountInfo = resolveAmount(m, c.initConfig.amount);
|
||||
const amount = infoToAmount(amountInfo, formAmount);
|
||||
const metadata = m.invoiceTemplate.metadata;
|
||||
const params = {amount, metadata, currency: 'RUB'}; // TODO fix hardcoded currency
|
||||
return createInvoiceWithTemplate(endpoint, initConfig.invoiceTemplateAccessToken, initConfig.invoiceTemplateID, params)
|
||||
const params = {amount, metadata, currency: amountInfo.currencyCode};
|
||||
const {invoiceTemplateAccessToken, invoiceTemplateID} = c.initConfig as InvoiceTemplateInitConfig;
|
||||
return createInvoiceWithTemplate(endpoint, invoiceTemplateAccessToken, invoiceTemplateID, params)
|
||||
.then((invoiceAndToken) => ({
|
||||
integrationType: IntegrationType.invoiceTemplate,
|
||||
invoiceID: invoiceAndToken.invoice.id,
|
||||
@ -22,13 +23,12 @@ const resolveInvoiceTemplate = (c: ConfigState, m: ModelState, formAmount: strin
|
||||
}));
|
||||
};
|
||||
|
||||
const resolveInvoice = (c: InvoiceInitConfig): Promise<PaymentSubject> => {
|
||||
return Promise.resolve({
|
||||
const resolveInvoice = (c: InvoiceInitConfig): Promise<PaymentSubject> =>
|
||||
Promise.resolve({
|
||||
integrationType: IntegrationType.invoice,
|
||||
invoiceID: c.invoiceID,
|
||||
accessToken: c.invoiceAccessToken
|
||||
});
|
||||
};
|
||||
|
||||
export const getPaymentSubject = (c: ConfigState, m: ModelState, formAmount: string): Promise<PaymentSubject> => {
|
||||
switch (c.initConfig.integrationType) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
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';
|
||||
import { applyMixins } from 'checkout/utils/apply-mixins';
|
||||
|
||||
export class PaymentToolDetailsDigitalWallet implements PaymentToolDetails, DigitalWalletDetails {
|
||||
detailsType: PaymentToolDetailsType;
|
||||
|
@ -1,63 +0,0 @@
|
||||
import { ModelState } from 'checkout/state';
|
||||
import {
|
||||
CostType,
|
||||
InvoiceChangeType,
|
||||
InvoiceCreated,
|
||||
InvoiceTemplateLineCostFixed,
|
||||
InvoiceTemplateMultiLine,
|
||||
InvoiceTemplateSingleLine
|
||||
} from 'checkout/backend';
|
||||
import { Amount, findChange } from 'checkout/utils';
|
||||
|
||||
const getAmountFromSingleLine = (model: ModelState, configAmount: number | null): Amount | null => {
|
||||
const details = model.invoiceTemplate.details as InvoiceTemplateSingleLine;
|
||||
const price = details.price;
|
||||
if (price) {
|
||||
switch (price.costType) {
|
||||
case CostType.InvoiceTemplateLineCostFixed:
|
||||
const fixed = price as InvoiceTemplateLineCostFixed;
|
||||
return {
|
||||
value: fixed.amount,
|
||||
currencyCode: fixed.currency
|
||||
};
|
||||
case CostType.InvoiceTemplateLineCostRange:
|
||||
case CostType.InvoiceTemplateLineCostUnlim:
|
||||
return configAmount ? {
|
||||
value: configAmount,
|
||||
currencyCode: 'RUB' // TODO fix hardcoded currency
|
||||
} : null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getAmountFromMultiLine = (details: InvoiceTemplateMultiLine): Amount => ({
|
||||
value: details.cart.reduce((p, c) => p + (c.price * c.quantity), 0),
|
||||
currencyCode: details.currency
|
||||
});
|
||||
|
||||
const getAmountFromInvoiceTemplate = (model: ModelState, configAmount: number | null): Amount => {
|
||||
switch (model.invoiceTemplate.details.templateType) {
|
||||
case 'InvoiceTemplateSingleLine':
|
||||
return getAmountFromSingleLine(model, configAmount);
|
||||
case 'InvoiceTemplateMultiLine':
|
||||
return getAmountFromMultiLine(model.invoiceTemplate.details as InvoiceTemplateMultiLine);
|
||||
}
|
||||
};
|
||||
|
||||
const getAmountFromInvoice = (invoiceCreated: InvoiceCreated): Amount => {
|
||||
const {invoice: {amount, currency}} = invoiceCreated;
|
||||
return {
|
||||
value: amount,
|
||||
currencyCode: currency
|
||||
};
|
||||
};
|
||||
|
||||
export const getAmount = (m: ModelState, configAmount: number | null): Amount | null => {
|
||||
if (!m.invoiceEvents && !m.invoiceTemplate) {
|
||||
return;
|
||||
}
|
||||
const invoiceCreated = findChange(m.invoiceEvents, InvoiceChangeType.InvoiceCreated);
|
||||
return invoiceCreated
|
||||
? getAmountFromInvoice(invoiceCreated as InvoiceCreated)
|
||||
: getAmountFromInvoiceTemplate(m, configAmount);
|
||||
};
|
@ -1,9 +1,8 @@
|
||||
import { InvoiceTemplateLineCostRange, InvoiceTemplateLineCostUnlim } from 'checkout/backend';
|
||||
import { formatAmount, getSymbol } from 'checkout/utils';
|
||||
|
||||
const toUnlimPlaceholder = (localeString: string): string => {
|
||||
return `${localeString} ${getSymbol('RUB')}`;
|
||||
};
|
||||
const toUnlimPlaceholder = (localeString: string, currency: string): string =>
|
||||
`${localeString} ${getSymbol(currency)}`;
|
||||
|
||||
const toRangePlaceholder = (cost: InvoiceTemplateLineCostRange): string => {
|
||||
const range = cost.range;
|
||||
@ -18,7 +17,7 @@ export const getPlaceholder = (cost: InvoiceTemplateLineCostRange | InvoiceTempl
|
||||
}
|
||||
switch (cost.costType) {
|
||||
case 'InvoiceTemplateLineCostUnlim':
|
||||
return toUnlimPlaceholder(localeString);
|
||||
return toUnlimPlaceholder(localeString, 'RUB'); // TODO unlim cost type does't support currency
|
||||
case 'InvoiceTemplateLineCostRange':
|
||||
return toRangePlaceholder(cost as InvoiceTemplateLineCostRange);
|
||||
}
|
||||
|
@ -4,8 +4,7 @@ import * as formStyles from '../../form-container.scss';
|
||||
import * as styles from './interaction-terminal-form.scss';
|
||||
import { State } from 'checkout/state';
|
||||
import { Header } from '../../header';
|
||||
import { getAmount } from '../../../amount-resolver';
|
||||
import { formatAmount } from 'checkout/utils';
|
||||
import { formatAmount, resolveAmount } from 'checkout/utils';
|
||||
import { bindActionCreators, Dispatch } from 'redux';
|
||||
import { setViewInfoHeight } from 'checkout/actions';
|
||||
import { PaymentTerminalReceipt } from 'checkout/backend';
|
||||
@ -16,7 +15,7 @@ import { ReceiptInfo } from './receipt-info';
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
locale: state.config.locale,
|
||||
amount: formatAmount(getAmount(state.model, state.config.initConfig.amount))
|
||||
amount: formatAmount(resolveAmount(state.model, state.config.initConfig.amount, true))
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
|
||||
|
@ -2,8 +2,7 @@ import * as React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ModelState, State } from 'checkout/state';
|
||||
import { InitConfig, IntegrationType } from 'checkout/config';
|
||||
import { getAmount } from '../../amount-resolver';
|
||||
import { formatAmount } from 'checkout/utils';
|
||||
import { formatAmount, resolveAmount } from 'checkout/utils';
|
||||
import { Button } from 'checkout/components';
|
||||
import { Locale } from 'checkout/locale';
|
||||
|
||||
@ -21,7 +20,7 @@ const PayButtonDef: React.SFC<PayButtonProps> = (props) => (
|
||||
);
|
||||
|
||||
const toInvoiceLabel = (locale: Locale, initConfig: InitConfig, model: ModelState): string => {
|
||||
const amount = formatAmount(getAmount(model, initConfig.amount));
|
||||
const amount = formatAmount(resolveAmount(model, initConfig.amount));
|
||||
const amountLabel = amount ? ` ${amount.value} ${amount.symbol}` : '';
|
||||
return `${locale['form.button.pay.label']}${amountLabel}`;
|
||||
};
|
||||
|
@ -21,8 +21,7 @@ import { toFieldsConfig } from '../fields-config';
|
||||
import { payTerminalEuroset, prepareToPay, setViewInfoError, setViewInfoHeight } from 'checkout/actions';
|
||||
import { TerminalFormProps } from './terminal-form-props';
|
||||
import { NextButton } from './next-button';
|
||||
import { getAmount } from '../../amount-resolver';
|
||||
import { findNamed, formatAmount } from 'checkout/utils';
|
||||
import { findNamed, formatAmount, resolveAmount } from 'checkout/utils';
|
||||
import { AmountInfo } from './amount-info';
|
||||
|
||||
const toTerminalFormInfo = (modals: ModalState[]) => {
|
||||
@ -37,7 +36,7 @@ const mapStateToProps = (state: State) => ({
|
||||
formValues: get(state.form, 'terminalForm.values'),
|
||||
config: state.config,
|
||||
model: state.model,
|
||||
amount: formatAmount(getAmount(state.model, state.config.initConfig.amount))
|
||||
amount: formatAmount(resolveAmount(state.model, state.config.initConfig.amount))
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch<any>) => ({
|
||||
|
@ -1,111 +1,65 @@
|
||||
import * as React from 'react';
|
||||
import * as cx from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import * as styles from './info.scss';
|
||||
import { InitConfig } from 'checkout/config';
|
||||
import { ModelState, State } from 'checkout/state';
|
||||
import { getAmount } from '../amount-resolver';
|
||||
import { Amount, formatAmount } from 'checkout/utils';
|
||||
import { State } from 'checkout/state';
|
||||
import {
|
||||
formatAmount,
|
||||
FormattedAmount,
|
||||
resolveAmount
|
||||
} from 'checkout/utils';
|
||||
import { Locale } from 'checkout/locale';
|
||||
|
||||
interface InfoState {
|
||||
amount: Amount;
|
||||
help?: boolean;
|
||||
}
|
||||
|
||||
export interface InfoProps {
|
||||
initConfig: InitConfig;
|
||||
model: ModelState;
|
||||
locale: Locale;
|
||||
name: string;
|
||||
description: string;
|
||||
email: string;
|
||||
formattedAmount: FormattedAmount;
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
initConfig: state.config.initConfig,
|
||||
locale: state.config.locale,
|
||||
model: state.model
|
||||
});
|
||||
const mapStateToProps = (s: State) => {
|
||||
const {config: {initConfig, locale}} = s;
|
||||
return {
|
||||
locale,
|
||||
name: initConfig.name,
|
||||
description: initConfig.description,
|
||||
email: initConfig.email,
|
||||
formattedAmount: formatAmount(resolveAmount(s.model, initConfig.amount))
|
||||
};
|
||||
};
|
||||
|
||||
class InfoDef extends React.Component<InfoProps, InfoState> {
|
||||
|
||||
constructor(props: InfoProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
amount: null,
|
||||
help: true
|
||||
};
|
||||
this.toggleHelp = this.toggleHelp.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
amount: getAmount(this.props.model, this.props.initConfig.amount)
|
||||
});
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props: InfoProps) {
|
||||
this.setState({
|
||||
amount: getAmount(props.model, this.props.initConfig.amount)
|
||||
});
|
||||
}
|
||||
|
||||
toggleHelp() {
|
||||
this.setState({
|
||||
help: !this.state.help
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const locale = this.props.locale;
|
||||
const name = this.props.initConfig.name;
|
||||
const description = this.props.initConfig.description;
|
||||
const email = this.props.initConfig.email;
|
||||
const dueDate = false;
|
||||
const recurrent = false;
|
||||
const formattedAmount = formatAmount(this.state.amount);
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
<div>
|
||||
{name ? <h4 className={styles.company_name} id='company-name-label'>{name}</h4> : false}
|
||||
{formattedAmount ?
|
||||
<h1 className={styles.amount}>
|
||||
{formattedAmount.value}
|
||||
<span> {formattedAmount.symbol}</span>
|
||||
</h1> : false}
|
||||
{description ?
|
||||
<div>
|
||||
<div className={styles.order}>{locale['info.order.label']}</div>
|
||||
<div className={styles.product_description} id='product-description'>{description}</div>
|
||||
</div>
|
||||
: false}
|
||||
{email ?
|
||||
<div>
|
||||
<div className={styles.order}>{locale['info.email.label']}</div>
|
||||
<div className={styles.email}>{email}</div>
|
||||
</div>
|
||||
: false}
|
||||
{dueDate ? <div className={styles.dueDate}>{locale['info.dueTime.text']} 23:56</div> : false}
|
||||
</div>
|
||||
{recurrent ? <div>
|
||||
<div className={styles.subscription} onClick={this.toggleHelp}>
|
||||
<span>{locale['info.subscription.label']}</span>
|
||||
<svg width='15' height='15'>
|
||||
<g transform='translate(1 1)' fill='none'>
|
||||
<circle cx='7' cy='7' r='7'/>
|
||||
<text>
|
||||
<tspan x='4' y='10'>?</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
const InfoDef: React.SFC<InfoProps> = (props) => {
|
||||
const {
|
||||
formattedAmount,
|
||||
locale,
|
||||
name,
|
||||
description,
|
||||
email
|
||||
} = props;
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
<div>
|
||||
{name ? <h4 className={styles.company_name} id='company-name-label'>{name}</h4> : false}
|
||||
{formattedAmount ?
|
||||
<h1 className={styles.amount}>
|
||||
{formattedAmount.value}
|
||||
<span> {formattedAmount.symbol}</span>
|
||||
</h1> : false}
|
||||
{description ?
|
||||
<div>
|
||||
<div className={styles.order}>{locale['info.order.label']}</div>
|
||||
<div className={styles.product_description} id='product-description'>{description}</div>
|
||||
</div>
|
||||
<p className={cx(styles.help, {
|
||||
[styles._hide]: !this.state.help
|
||||
})}>
|
||||
{locale['info.subscription.help.text']}
|
||||
</p>
|
||||
</div> : false}
|
||||
: false}
|
||||
{email ?
|
||||
<div>
|
||||
<div className={styles.order}>{locale['info.email.label']}</div>
|
||||
<div className={styles.email}>{email}</div>
|
||||
</div>
|
||||
: false}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Info = connect(mapStateToProps)(InfoDef);
|
||||
|
@ -1,20 +0,0 @@
|
||||
import { findCurrency, format } from 'currency-formatter';
|
||||
|
||||
export interface Amount {
|
||||
value: number;
|
||||
currencyCode: string;
|
||||
}
|
||||
|
||||
export interface FormattedAmount {
|
||||
value: string;
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
export const getSymbol = (currencyCode: string): string => {
|
||||
return findCurrency(currencyCode).symbol;
|
||||
};
|
||||
|
||||
export const formatAmount = (amount: Amount): FormattedAmount | null => (amount ? {
|
||||
value: format(amount.value / 100, {decimal: ', ', thousand: ' '}),
|
||||
symbol: getSymbol(amount.currencyCode)
|
||||
} : null);
|
4
src/app/utils/amount-formatter/amount.ts
Normal file
4
src/app/utils/amount-formatter/amount.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface Amount {
|
||||
value: number;
|
||||
currencyCode: string;
|
||||
}
|
10
src/app/utils/amount-formatter/format-amount.ts
Normal file
10
src/app/utils/amount-formatter/format-amount.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { format } from 'currency-formatter';
|
||||
import { Amount } from './amount';
|
||||
import { FormattedAmount } from './formatted-amount';
|
||||
import { getSymbol } from './get-symbol';
|
||||
|
||||
export const formatAmount = (amount: Amount): FormattedAmount | null =>
|
||||
(amount && amount.value ? {
|
||||
value: format(amount.value / 100, {decimal: ', ', thousand: ' '}),
|
||||
symbol: getSymbol(amount.currencyCode)
|
||||
} : null);
|
4
src/app/utils/amount-formatter/formatted-amount.ts
Normal file
4
src/app/utils/amount-formatter/formatted-amount.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface FormattedAmount {
|
||||
value: string;
|
||||
symbol: string;
|
||||
}
|
3
src/app/utils/amount-formatter/get-symbol.ts
Normal file
3
src/app/utils/amount-formatter/get-symbol.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { findCurrency } from 'currency-formatter';
|
||||
|
||||
export const getSymbol = (currencyCode: string): string => findCurrency(currencyCode).symbol;
|
5
src/app/utils/amount-formatter/index.ts
Normal file
5
src/app/utils/amount-formatter/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './amount';
|
||||
export * from './format-amount';
|
||||
export * from './formatted-amount';
|
||||
export * from './get-symbol';
|
||||
export * from './resolve-amount';
|
@ -0,0 +1,42 @@
|
||||
import { getAmountFromMultiLine } from './get-amount-from-multi-line';
|
||||
|
||||
it('should return amount', () => {
|
||||
const multiLine = {
|
||||
cart: [
|
||||
{
|
||||
cost: 100000,
|
||||
price: 100000,
|
||||
product: 'Product 1',
|
||||
quantity: 1
|
||||
},
|
||||
{
|
||||
cost: 400000,
|
||||
price: 200000,
|
||||
product: 'Product 2',
|
||||
quantity: 2,
|
||||
taxMode: {
|
||||
rate: '0%',
|
||||
type: 'InvoiceLineTaxVAT'
|
||||
}
|
||||
},
|
||||
{
|
||||
cost: 500000,
|
||||
price: 500000,
|
||||
product: 'Product 3',
|
||||
quantity: 1,
|
||||
taxMode: {
|
||||
rate: '18%',
|
||||
type: 'InvoiceLineTaxVAT'
|
||||
}
|
||||
}
|
||||
],
|
||||
currency: 'RUB',
|
||||
templateType: 'InvoiceTemplateMultiLine'
|
||||
} as any;
|
||||
const actual = getAmountFromMultiLine(multiLine);
|
||||
const expected = {
|
||||
currencyCode: 'RUB',
|
||||
value: 1000000
|
||||
};
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
import { InvoiceTemplateMultiLine } from 'checkout/backend';
|
||||
import { Amount } from '../amount';
|
||||
|
||||
export const getAmountFromMultiLine = (details: InvoiceTemplateMultiLine): Amount => ({
|
||||
value: details.cart.reduce((p, c) => p + (c.price * c.quantity), 0),
|
||||
currencyCode: details.currency
|
||||
});
|
@ -0,0 +1,51 @@
|
||||
import { InvoiceTemplateLineCostFixed } from 'checkout/backend';
|
||||
import { getAmountFromSingleLine } from './get-amount-from-single-line';
|
||||
|
||||
it('InvoiceTemplateLineCostFixed should return amount', () => {
|
||||
const singleLine = {
|
||||
price: {
|
||||
costType: 'InvoiceTemplateLineCostFixed',
|
||||
amount: 149900,
|
||||
currency: 'RUB'
|
||||
}
|
||||
} as any;
|
||||
const actual = getAmountFromSingleLine(singleLine, 111);
|
||||
const expected = {
|
||||
currencyCode: 'RUB',
|
||||
value: 149900
|
||||
};
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('InvoiceTemplateLineCostRange should return amount', () => {
|
||||
const singleLine = {
|
||||
price: {
|
||||
costType: 'InvoiceTemplateLineCostRange',
|
||||
range: {
|
||||
lowerBound: 1000,
|
||||
upperBound: 2000
|
||||
},
|
||||
currency: 'RUB'
|
||||
}
|
||||
} as any;
|
||||
const actual = getAmountFromSingleLine(singleLine, 111);
|
||||
const expected = {
|
||||
currencyCode: 'RUB',
|
||||
value: 111
|
||||
};
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('InvoiceTemplateLineCostUnlim should return amount', () => {
|
||||
const singleLine = {
|
||||
price: {
|
||||
costType: 'InvoiceTemplateLineCostUnlim'
|
||||
}
|
||||
} as any;
|
||||
const actual = getAmountFromSingleLine(singleLine, 111);
|
||||
const expected = {
|
||||
currencyCode: 'RUB',
|
||||
value: 111
|
||||
};
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
@ -0,0 +1,32 @@
|
||||
import {
|
||||
CostType,
|
||||
InvoiceTemplateLineCostFixed,
|
||||
InvoiceTemplateLineCostRange,
|
||||
InvoiceTemplateSingleLine
|
||||
} from 'checkout/backend';
|
||||
import { Amount } from '../amount';
|
||||
|
||||
export const getAmountFromSingleLine = (details: InvoiceTemplateSingleLine, configAmount: number): Amount => {
|
||||
const price = details.price;
|
||||
if (!price) {
|
||||
return null;
|
||||
}
|
||||
switch (price.costType) {
|
||||
case CostType.InvoiceTemplateLineCostFixed:
|
||||
const fixed = price as InvoiceTemplateLineCostFixed;
|
||||
return {
|
||||
value: fixed.amount,
|
||||
currencyCode: fixed.currency
|
||||
};
|
||||
case CostType.InvoiceTemplateLineCostRange:
|
||||
return {
|
||||
value: configAmount,
|
||||
currencyCode: (price as InvoiceTemplateLineCostRange).currency
|
||||
};
|
||||
case CostType.InvoiceTemplateLineCostUnlim:
|
||||
return {
|
||||
value: configAmount,
|
||||
currencyCode: 'RUB' // TODO unlim cost type does't support currency
|
||||
};
|
||||
}
|
||||
};
|
1
src/app/utils/amount-formatter/resolve-amount/index.ts
Normal file
1
src/app/utils/amount-formatter/resolve-amount/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './resolve-amount';
|
@ -0,0 +1,60 @@
|
||||
import { resolveAmount } from './resolve-amount';
|
||||
import { resolveInvoiceTemplate } from './resolve-invoice-template';
|
||||
import { resolveInvoice } from './resolve-invoice';
|
||||
import { findChange } from '../../event-utils';
|
||||
|
||||
jest.mock('./resolve-invoice-template');
|
||||
jest.mock('../../event-utils');
|
||||
jest.mock('./resolve-invoice');
|
||||
|
||||
it('null params should return null', () => {
|
||||
const actual = resolveAmount(null, null);
|
||||
expect(actual).toBeNull();
|
||||
});
|
||||
|
||||
it('empty invoiceEvents and invoiceTemplate should return null', () => {
|
||||
const actual = resolveAmount({
|
||||
invoiceEvents: null,
|
||||
invoiceTemplate: null
|
||||
}, null);
|
||||
expect(actual).toBeNull();
|
||||
});
|
||||
|
||||
describe('resolveInvoiceTemplate', () => {
|
||||
const resolveInvoiceTemplateMocked = resolveInvoiceTemplate as any;
|
||||
|
||||
it('non empty invoiceTemplate should call resolveInvoiceTemplate', () => {
|
||||
resolveInvoiceTemplateMocked.mockReturnValueOnce(null);
|
||||
resolveAmount({
|
||||
invoiceTemplate: {},
|
||||
invoiceEvents: []
|
||||
} as any, 111);
|
||||
expect(resolveInvoiceTemplateMocked).toBeCalledWith({}, 111);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveInvoice', () => {
|
||||
const findChangeMocked = findChange as any;
|
||||
const resolveInvoiceMocked = resolveInvoice as any;
|
||||
const findChangeResult = 'findChangeMocked result';
|
||||
|
||||
beforeEach(() => {
|
||||
findChangeMocked.mockReturnValueOnce(findChangeResult);
|
||||
resolveInvoiceMocked.mockReturnValueOnce(null);
|
||||
});
|
||||
|
||||
it('non empty invoiceEvents and empty invoiceTemplate should call resolveInvoice', () => {
|
||||
resolveAmount({
|
||||
invoiceEvents: []
|
||||
} as any, 111);
|
||||
expect(resolveInvoiceMocked).toBeCalledWith(findChangeResult);
|
||||
});
|
||||
|
||||
it('setting invoiceEventsFirst flag to true should call resolveInvoice', () => {
|
||||
resolveAmount({
|
||||
invoiceTemplate: {},
|
||||
invoiceEvents: []
|
||||
} as any, 111, true);
|
||||
expect(resolveInvoiceMocked).toBeCalledWith(findChangeResult);
|
||||
});
|
||||
});
|
@ -0,0 +1,15 @@
|
||||
import { findChange } from '../../event-utils';
|
||||
import { ModelState } from 'checkout/state';
|
||||
import { InvoiceChangeType, InvoiceCreated } from 'checkout/backend';
|
||||
import { Amount } from '../amount';
|
||||
import { resolveInvoice } from './resolve-invoice';
|
||||
import { resolveInvoiceTemplate } from './resolve-invoice-template';
|
||||
|
||||
export const resolveAmount = (m: ModelState, configAmount: number, invoiceEventsFirst: boolean = false): Amount => {
|
||||
if (!m || (!m.invoiceEvents && !m.invoiceTemplate)) {
|
||||
return null;
|
||||
}
|
||||
return m.invoiceTemplate && !invoiceEventsFirst
|
||||
? resolveInvoiceTemplate(m.invoiceTemplate, configAmount)
|
||||
: resolveInvoice(findChange(m.invoiceEvents, InvoiceChangeType.InvoiceCreated) as InvoiceCreated);
|
||||
};
|
@ -0,0 +1,32 @@
|
||||
import { getAmountFromSingleLine } from './get-amount-from-single-line';
|
||||
import { getAmountFromMultiLine } from './get-amount-from-multi-line';
|
||||
import { resolveInvoiceTemplate } from './resolve-invoice-template';
|
||||
import { TemplateType } from 'checkout/backend';
|
||||
|
||||
jest.mock('./get-amount-from-single-line');
|
||||
jest.mock('./get-amount-from-multi-line');
|
||||
|
||||
const getAmountFromSingleLineMocked = getAmountFromSingleLine as any;
|
||||
const getAmountFromMultiLineMocked = getAmountFromMultiLine as any;
|
||||
|
||||
it('InvoiceTemplateSingleLine should call getAmountFromSingleLine', () => {
|
||||
const singleLine = {
|
||||
details: {
|
||||
templateType: TemplateType.InvoiceTemplateSingleLine
|
||||
}
|
||||
} as any;
|
||||
getAmountFromSingleLineMocked.mockReturnValueOnce(singleLine.details);
|
||||
resolveInvoiceTemplate(singleLine, 111);
|
||||
expect(getAmountFromSingleLineMocked).toBeCalledWith(singleLine.details, 111);
|
||||
});
|
||||
|
||||
it('InvoiceTemplateMultiLine should call getAmountFromMultiLine', () => {
|
||||
const multiLine = {
|
||||
details: {
|
||||
templateType: TemplateType.InvoiceTemplateMultiLine
|
||||
}
|
||||
} as any;
|
||||
getAmountFromMultiLineMocked.mockReturnValueOnce(multiLine.details);
|
||||
resolveInvoiceTemplate(multiLine, 111);
|
||||
expect(getAmountFromMultiLineMocked).toBeCalledWith(multiLine.details);
|
||||
});
|
@ -0,0 +1,18 @@
|
||||
import {
|
||||
InvoiceTemplate,
|
||||
InvoiceTemplateMultiLine,
|
||||
InvoiceTemplateSingleLine,
|
||||
TemplateType
|
||||
} from 'checkout/backend';
|
||||
import { Amount } from '../amount';
|
||||
import { getAmountFromSingleLine } from './get-amount-from-single-line';
|
||||
import { getAmountFromMultiLine } from './get-amount-from-multi-line';
|
||||
|
||||
export const resolveInvoiceTemplate = (invoiceTemplate: InvoiceTemplate, configAmount: number): Amount => {
|
||||
switch (invoiceTemplate.details.templateType) {
|
||||
case TemplateType.InvoiceTemplateSingleLine:
|
||||
return getAmountFromSingleLine(invoiceTemplate.details as InvoiceTemplateSingleLine, configAmount);
|
||||
case TemplateType.InvoiceTemplateMultiLine:
|
||||
return getAmountFromMultiLine(invoiceTemplate.details as InvoiceTemplateMultiLine);
|
||||
}
|
||||
};
|
@ -0,0 +1,16 @@
|
||||
import { resolveInvoice } from './resolve-invoice';
|
||||
|
||||
it('should return amount', () => {
|
||||
const invoiceCreated = {
|
||||
invoice: {
|
||||
amount: 10000,
|
||||
currency: 'RUB'
|
||||
}
|
||||
} as any;
|
||||
const actual = resolveInvoice(invoiceCreated);
|
||||
const expected = {
|
||||
currencyCode: 'RUB',
|
||||
value: 10000
|
||||
};
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
@ -0,0 +1,10 @@
|
||||
import { InvoiceCreated } from 'checkout/backend';
|
||||
import { Amount } from '../amount';
|
||||
|
||||
export const resolveInvoice = (invoiceCreated: InvoiceCreated): Amount => {
|
||||
const {invoice: {amount, currency}} = invoiceCreated;
|
||||
return {
|
||||
value: amount,
|
||||
currencyCode: currency
|
||||
};
|
||||
};
|
@ -7,3 +7,4 @@ export * from './find-named';
|
||||
export * from './find-previous';
|
||||
export * from './uri-serializer';
|
||||
export * from './get-nocache-value';
|
||||
export * from './amount-formatter';
|
||||
|
Loading…
Reference in New Issue
Block a user