Payment Modal: Support Payments 2.0 (#1375)
This commit is contained in:
parent
94526b2362
commit
af8248dcf2
@ -29,7 +29,6 @@ import { getApiChatIdFromMtpPeer } from './chats';
|
|||||||
import { buildStickerFromDocument } from './symbols';
|
import { buildStickerFromDocument } from './symbols';
|
||||||
import { buildApiPhoto, buildApiThumbnailFromStripped } from './common';
|
import { buildApiPhoto, buildApiThumbnailFromStripped } from './common';
|
||||||
import { interpolateArray } from '../../../util/waveform';
|
import { interpolateArray } from '../../../util/waveform';
|
||||||
import { getCurrencySign } from '../../../components/middle/helpers/getCurrencySign';
|
|
||||||
import { buildPeer } from '../gramjsBuilders';
|
import { buildPeer } from '../gramjsBuilders';
|
||||||
import { addPhotoToLocalDb, resolveMessageApiChatId } from '../helpers';
|
import { addPhotoToLocalDb, resolveMessageApiChatId } from '../helpers';
|
||||||
|
|
||||||
@ -113,6 +112,7 @@ type UniversalMessage = (
|
|||||||
|
|
||||||
export function buildApiMessageWithChatId(chatId: number, mtpMessage: UniversalMessage): ApiMessage {
|
export function buildApiMessageWithChatId(chatId: number, mtpMessage: UniversalMessage): ApiMessage {
|
||||||
const fromId = mtpMessage.fromId ? getApiChatIdFromMtpPeer(mtpMessage.fromId) : undefined;
|
const fromId = mtpMessage.fromId ? getApiChatIdFromMtpPeer(mtpMessage.fromId) : undefined;
|
||||||
|
const peerId = mtpMessage.peerId ? getApiChatIdFromMtpPeer(mtpMessage.peerId) : undefined;
|
||||||
const isChatWithSelf = !fromId && chatId === currentUserId;
|
const isChatWithSelf = !fromId && chatId === currentUserId;
|
||||||
const isOutgoing = (mtpMessage.out && !mtpMessage.post) || (isChatWithSelf && !mtpMessage.fwdFrom);
|
const isOutgoing = (mtpMessage.out && !mtpMessage.post) || (isChatWithSelf && !mtpMessage.fwdFrom);
|
||||||
|
|
||||||
@ -131,7 +131,8 @@ export function buildApiMessageWithChatId(chatId: number, mtpMessage: UniversalM
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const action = mtpMessage.action && buildAction(mtpMessage.action, fromId, Boolean(mtpMessage.post), isOutgoing);
|
const action = mtpMessage.action
|
||||||
|
&& buildAction(mtpMessage.action, fromId, peerId, Boolean(mtpMessage.post), isOutgoing);
|
||||||
if (action) {
|
if (action) {
|
||||||
content.action = action;
|
content.action = action;
|
||||||
}
|
}
|
||||||
@ -500,13 +501,15 @@ export function buildInvoice(media: GramJs.MessageMediaInvoice): ApiInvoice {
|
|||||||
const {
|
const {
|
||||||
description: text, title, photo, test, totalAmount, currency, receiptMsgId,
|
description: text, title, photo, test, totalAmount, currency, receiptMsgId,
|
||||||
} = media;
|
} = media;
|
||||||
const currencySign = getCurrencySign(currency);
|
|
||||||
return {
|
return {
|
||||||
text,
|
text,
|
||||||
title,
|
title,
|
||||||
photoUrl: photo && photo.url,
|
photoUrl: photo && photo.url,
|
||||||
receiptMsgId,
|
receiptMsgId,
|
||||||
description: `${currencySign}${(Number(totalAmount) / 100).toFixed(2)} ${test ? 'TEST INVOICE' : ''}`,
|
amount: Number(totalAmount),
|
||||||
|
currency,
|
||||||
|
isTest: test,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -567,6 +570,7 @@ export function buildWebPage(media: GramJs.TypeMessageMedia): ApiWebPage | undef
|
|||||||
function buildAction(
|
function buildAction(
|
||||||
action: GramJs.TypeMessageAction,
|
action: GramJs.TypeMessageAction,
|
||||||
senderId: number | undefined,
|
senderId: number | undefined,
|
||||||
|
targetPeerId: number | undefined,
|
||||||
isChannelPost: boolean,
|
isChannelPost: boolean,
|
||||||
isOutgoing: boolean,
|
isOutgoing: boolean,
|
||||||
): ApiAction | undefined {
|
): ApiAction | undefined {
|
||||||
@ -574,7 +578,9 @@ function buildAction(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = '';
|
let amount: number | undefined;
|
||||||
|
let currency: string | undefined;
|
||||||
|
let text: string;
|
||||||
const translationValues = [];
|
const translationValues = [];
|
||||||
let type: ApiAction['type'] = 'other';
|
let type: ApiAction['type'] = 'other';
|
||||||
let photo: ApiPhoto | undefined;
|
let photo: ApiPhoto | undefined;
|
||||||
@ -661,10 +667,13 @@ function buildAction(
|
|||||||
translationValues.push('%action_origin%');
|
translationValues.push('%action_origin%');
|
||||||
type = 'contactSignUp';
|
type = 'contactSignUp';
|
||||||
} else if (action instanceof GramJs.MessageActionPaymentSent) {
|
} else if (action instanceof GramJs.MessageActionPaymentSent) {
|
||||||
const currencySign = getCurrencySign(action.currency);
|
amount = Number(action.totalAmount);
|
||||||
const amount = (Number(action.totalAmount) / 100).toFixed(2);
|
currency = action.currency;
|
||||||
text = 'Notification.PaymentSent';
|
text = 'PaymentSuccessfullyPaid';
|
||||||
translationValues.push(currencySign, amount, '%product%');
|
if (targetPeerId) {
|
||||||
|
targetUserIds.push(targetPeerId);
|
||||||
|
}
|
||||||
|
translationValues.push('%payment_amount%', '%target_user%', '%product%');
|
||||||
} else if (action instanceof GramJs.MessageActionGroupCall) {
|
} else if (action instanceof GramJs.MessageActionGroupCall) {
|
||||||
if (action.duration) {
|
if (action.duration) {
|
||||||
const mins = Math.max(Math.round(action.duration / 60), 1);
|
const mins = Math.max(Math.round(action.duration / 60), 1);
|
||||||
@ -691,6 +700,8 @@ function buildAction(
|
|||||||
targetUserIds,
|
targetUserIds,
|
||||||
targetChatId,
|
targetChatId,
|
||||||
photo, // TODO Only used internally now, will be used for the UI in future
|
photo, // TODO Only used internally now, will be used for the UI in future
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
translationValues,
|
translationValues,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -739,7 +750,7 @@ function buildReplyButtons(message: UniversalMessage): ApiReplyKeyboard | undefi
|
|||||||
type = 'requestPoll';
|
type = 'requestPoll';
|
||||||
} else if (button instanceof GramJs.KeyboardButtonBuy) {
|
} else if (button instanceof GramJs.KeyboardButtonBuy) {
|
||||||
if (media instanceof GramJs.MessageMediaInvoice && media.receiptMsgId) {
|
if (media instanceof GramJs.MessageMediaInvoice && media.receiptMsgId) {
|
||||||
text = 'Receipt';
|
text = 'PaymentReceipt';
|
||||||
value = media.receiptMsgId;
|
value = media.receiptMsgId;
|
||||||
}
|
}
|
||||||
type = 'buy';
|
type = 'buy';
|
||||||
|
|||||||
@ -63,6 +63,7 @@ export function buildReceipt(receipt: GramJs.payments.PaymentReceipt) {
|
|||||||
|
|
||||||
export function buildPaymentForm(form: GramJs.payments.PaymentForm) {
|
export function buildPaymentForm(form: GramJs.payments.PaymentForm) {
|
||||||
const {
|
const {
|
||||||
|
formId,
|
||||||
canSaveCredentials,
|
canSaveCredentials,
|
||||||
passwordMissing,
|
passwordMissing,
|
||||||
providerId,
|
providerId,
|
||||||
@ -94,6 +95,7 @@ export function buildPaymentForm(form: GramJs.payments.PaymentForm) {
|
|||||||
return {
|
return {
|
||||||
canSaveCredentials,
|
canSaveCredentials,
|
||||||
passwordMissing,
|
passwordMissing,
|
||||||
|
formId: String(formId),
|
||||||
providerId,
|
providerId,
|
||||||
nativeProvider,
|
nativeProvider,
|
||||||
savedInfo,
|
savedInfo,
|
||||||
|
|||||||
@ -1,13 +1,17 @@
|
|||||||
|
import BigInt from 'big-integer';
|
||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
import { invokeRequest } from './client';
|
import { invokeRequest } from './client';
|
||||||
import { buildShippingInfo } from '../gramjsBuilders';
|
import { buildInputPeer, buildShippingInfo } from '../gramjsBuilders';
|
||||||
import { buildShippingOptions, buildPaymentForm, buildReceipt } from '../apiBuilders/payments';
|
import { buildShippingOptions, buildPaymentForm, buildReceipt } from '../apiBuilders/payments';
|
||||||
|
import { ApiChat } from '../../types';
|
||||||
|
|
||||||
export async function validateRequestedInfo({
|
export async function validateRequestedInfo({
|
||||||
|
chat,
|
||||||
messageId,
|
messageId,
|
||||||
requestInfo,
|
requestInfo,
|
||||||
shouldSave,
|
shouldSave,
|
||||||
}: {
|
}: {
|
||||||
|
chat: ApiChat;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
requestInfo: GramJs.TypePaymentRequestedInfo;
|
requestInfo: GramJs.TypePaymentRequestedInfo;
|
||||||
shouldSave?: boolean;
|
shouldSave?: boolean;
|
||||||
@ -16,6 +20,7 @@ export async function validateRequestedInfo({
|
|||||||
shippingOptions: any;
|
shippingOptions: any;
|
||||||
} | undefined> {
|
} | undefined> {
|
||||||
const result = await invokeRequest(new GramJs.payments.ValidateRequestedInfo({
|
const result = await invokeRequest(new GramJs.payments.ValidateRequestedInfo({
|
||||||
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
msgId: messageId,
|
msgId: messageId,
|
||||||
save: shouldSave || undefined,
|
save: shouldSave || undefined,
|
||||||
info: buildShippingInfo(requestInfo),
|
info: buildShippingInfo(requestInfo),
|
||||||
@ -23,10 +28,12 @@ export async function validateRequestedInfo({
|
|||||||
if (!result) {
|
if (!result) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id, shippingOptions } = result;
|
const { id, shippingOptions } = result;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
shippingOptions: buildShippingOptions(shippingOptions),
|
shippingOptions: buildShippingOptions(shippingOptions),
|
||||||
@ -34,17 +41,23 @@ export async function validateRequestedInfo({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function sendPaymentForm({
|
export function sendPaymentForm({
|
||||||
|
chat,
|
||||||
messageId,
|
messageId,
|
||||||
|
formId,
|
||||||
requestedInfoId,
|
requestedInfoId,
|
||||||
shippingOptionId,
|
shippingOptionId,
|
||||||
credentials,
|
credentials,
|
||||||
}: {
|
}: {
|
||||||
|
chat: ApiChat;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
|
formId: string;
|
||||||
credentials: any;
|
credentials: any;
|
||||||
requestedInfoId?: string;
|
requestedInfoId?: string;
|
||||||
shippingOptionId?: string;
|
shippingOptionId?: string;
|
||||||
}) {
|
}) {
|
||||||
return invokeRequest(new GramJs.payments.SendPaymentForm({
|
return invokeRequest(new GramJs.payments.SendPaymentForm({
|
||||||
|
formId: BigInt(formId),
|
||||||
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
msgId: messageId,
|
msgId: messageId,
|
||||||
requestedInfoId,
|
requestedInfoId,
|
||||||
shippingOptionId,
|
shippingOptionId,
|
||||||
@ -56,13 +69,16 @@ export function sendPaymentForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getPaymentForm({
|
export async function getPaymentForm({
|
||||||
messageId,
|
chat, messageId,
|
||||||
}: {
|
}: {
|
||||||
|
chat: ApiChat;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.payments.GetPaymentForm({
|
const result = await invokeRequest(new GramJs.payments.GetPaymentForm({
|
||||||
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
msgId: messageId,
|
msgId: messageId,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -70,10 +86,14 @@ export async function getPaymentForm({
|
|||||||
return buildPaymentForm(result);
|
return buildPaymentForm(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getReceipt(msgId: number) {
|
export async function getReceipt(chat: ApiChat, msgId: number) {
|
||||||
const result = await invokeRequest(new GramJs.payments.GetPaymentReceipt({ msgId }));
|
const result = await invokeRequest(new GramJs.payments.GetPaymentReceipt({
|
||||||
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
|
msgId,
|
||||||
|
}));
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildReceipt(result);
|
return buildReceipt(result);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -131,8 +131,10 @@ export interface ApiInvoice {
|
|||||||
text: string;
|
text: string;
|
||||||
title: string;
|
title: string;
|
||||||
photoUrl?: string;
|
photoUrl?: string;
|
||||||
description?: string;
|
amount: number;
|
||||||
|
currency: string;
|
||||||
receiptMsgId?: number;
|
receiptMsgId?: number;
|
||||||
|
isTest?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiNewPoll = {
|
export type ApiNewPoll = {
|
||||||
@ -150,6 +152,8 @@ export interface ApiAction {
|
|||||||
targetChatId?: number;
|
targetChatId?: number;
|
||||||
type: 'historyClear' | 'contactSignUp' | 'chatCreate' | 'other';
|
type: 'historyClear' | 'contactSignUp' | 'chatCreate' | 'other';
|
||||||
photo?: ApiPhoto;
|
photo?: ApiPhoto;
|
||||||
|
amount?: number;
|
||||||
|
currency?: string;
|
||||||
translationValues: string[];
|
translationValues: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -318,6 +318,11 @@ export type ApiError = {
|
|||||||
textParams?: Record<string, string>;
|
textParams?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiFieldError = {
|
||||||
|
field: string;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiInviteInfo = {
|
export type ApiInviteInfo = {
|
||||||
title: string;
|
title: string;
|
||||||
hash: string;
|
hash: string;
|
||||||
|
|||||||
@ -117,7 +117,7 @@ const AuthPhoneNumber: FC<StateProps & DispatchProps> = ({
|
|||||||
const handleLangChange = useCallback(() => {
|
const handleLangChange = useCallback(() => {
|
||||||
markIsLoading();
|
markIsLoading();
|
||||||
|
|
||||||
setLanguage(suggestedLanguage!, () => {
|
void setLanguage(suggestedLanguage, () => {
|
||||||
unmarkIsLoading();
|
unmarkIsLoading();
|
||||||
|
|
||||||
setSettingOption({ language: suggestedLanguage });
|
setSettingOption({ language: suggestedLanguage });
|
||||||
@ -153,7 +153,7 @@ const AuthPhoneNumber: FC<StateProps & DispatchProps> = ({
|
|||||||
if (!isPreloadInitiated) {
|
if (!isPreloadInitiated) {
|
||||||
isPreloadInitiated = true;
|
isPreloadInitiated = true;
|
||||||
preloadFonts();
|
preloadFonts();
|
||||||
preloadImage(monkeyPath);
|
void preloadImage(monkeyPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { value, selectionStart, selectionEnd } = e.target;
|
const { value, selectionStart, selectionEnd } = e.target;
|
||||||
|
|||||||
@ -64,7 +64,7 @@ const AuthCode: FC<StateProps & DispatchProps> = ({
|
|||||||
const handleLangChange = useCallback(() => {
|
const handleLangChange = useCallback(() => {
|
||||||
markIsLoading();
|
markIsLoading();
|
||||||
|
|
||||||
setLanguage(suggestedLanguage!, () => {
|
void setLanguage(suggestedLanguage, () => {
|
||||||
unmarkIsLoading();
|
unmarkIsLoading();
|
||||||
|
|
||||||
setSettingOption({ language: suggestedLanguage });
|
setSettingOption({ language: suggestedLanguage });
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { LangCode } from '../../../types';
|
||||||
|
|
||||||
export function getSuggestedLanguage() {
|
export function getSuggestedLanguage() {
|
||||||
let suggestedLanguage = navigator.language;
|
let suggestedLanguage = navigator.language;
|
||||||
|
|
||||||
@ -5,5 +7,5 @@ export function getSuggestedLanguage() {
|
|||||||
suggestedLanguage = suggestedLanguage.substr(0, 2);
|
suggestedLanguage = suggestedLanguage.substr(0, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return suggestedLanguage;
|
return suggestedLanguage as LangCode;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
isChat,
|
isChat,
|
||||||
} from '../../../modules/helpers';
|
} from '../../../modules/helpers';
|
||||||
import trimText from '../../../util/trimText';
|
import trimText from '../../../util/trimText';
|
||||||
|
import { formatCurrency } from '../../../util/formatCurrency';
|
||||||
import { TextPart } from './renderMessageText';
|
import { TextPart } from './renderMessageText';
|
||||||
import renderText from './renderText';
|
import renderText from './renderText';
|
||||||
|
|
||||||
@ -37,16 +38,30 @@ export function renderActionMessageText(
|
|||||||
if (!message.content.action) {
|
if (!message.content.action) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const { text, translationValues } = message.content.action;
|
const {
|
||||||
|
text, translationValues, amount, currency,
|
||||||
|
} = message.content.action;
|
||||||
const content: TextPart[] = [];
|
const content: TextPart[] = [];
|
||||||
const textOptions: ActionMessageTextOptions = { ...options, maxTextLength: 32 };
|
const textOptions: ActionMessageTextOptions = { ...options, maxTextLength: 32 };
|
||||||
const translationKey = text === 'Chat.Service.Group.UpdatedPinnedMessage1' && !targetMessage
|
const translationKey = text === 'Chat.Service.Group.UpdatedPinnedMessage1' && !targetMessage
|
||||||
? 'Message.PinnedGenericMessage'
|
? 'Message.PinnedGenericMessage'
|
||||||
: text;
|
: text;
|
||||||
|
|
||||||
let unprocessed: string;
|
let unprocessed = lang(translationKey, translationValues && translationValues.length ? translationValues : undefined);
|
||||||
let processed = processPlaceholder(
|
let processed: TextPart[];
|
||||||
lang(translationKey, translationValues && translationValues.length ? translationValues : undefined),
|
|
||||||
|
if (unprocessed.includes('%payment_amount%')) {
|
||||||
|
processed = processPlaceholder(
|
||||||
|
unprocessed,
|
||||||
|
'%payment_amount%',
|
||||||
|
formatCurrency(amount!, currency, lang.code),
|
||||||
|
);
|
||||||
|
unprocessed = processed.pop() as string;
|
||||||
|
content.push(...processed);
|
||||||
|
}
|
||||||
|
|
||||||
|
processed = processPlaceholder(
|
||||||
|
unprocessed,
|
||||||
'%action_origin%',
|
'%action_origin%',
|
||||||
actionOrigin
|
actionOrigin
|
||||||
? (!options.isEmbedded && renderOriginContent(lang, actionOrigin, options.asPlain)) || NBSP
|
? (!options.isEmbedded && renderOriginContent(lang, actionOrigin, options.asPlain)) || NBSP
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import React, {
|
|||||||
import { withGlobal } from '../../../lib/teact/teactn';
|
import { withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { GlobalActions } from '../../../global/types';
|
import { GlobalActions } from '../../../global/types';
|
||||||
import { ISettings, SettingsScreens } from '../../../types';
|
import { ISettings, LangCode, SettingsScreens } from '../../../types';
|
||||||
import { ApiLanguage } from '../../../api/types';
|
import { ApiLanguage } from '../../../api/types';
|
||||||
|
|
||||||
import { setLanguage } from '../../../util/langProvider';
|
import { setLanguage } from '../../../util/langProvider';
|
||||||
@ -46,7 +46,7 @@ const SettingsLanguage: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
setSelectedLanguage(langCode);
|
setSelectedLanguage(langCode);
|
||||||
markIsLoading();
|
markIsLoading();
|
||||||
|
|
||||||
setLanguage(langCode, () => {
|
void setLanguage(langCode as LangCode, () => {
|
||||||
unmarkIsLoading();
|
unmarkIsLoading();
|
||||||
|
|
||||||
setSettingOption({ language: langCode });
|
setSettingOption({ language: langCode });
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
const CURRENCIES: Record<string, string> = {
|
|
||||||
USD: '$',
|
|
||||||
EUR: '€',
|
|
||||||
GBP: '£',
|
|
||||||
JPY: '¥',
|
|
||||||
RUB: '₽',
|
|
||||||
UAH: '₴',
|
|
||||||
INR: '₹',
|
|
||||||
AED: 'د.إ',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function getCurrencySign(currency: string | undefined): string {
|
|
||||||
if (!currency) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return CURRENCIES[currency] || '';
|
|
||||||
}
|
|
||||||
@ -48,12 +48,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 0.75rem;
|
font-size: .875rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.125rem;
|
right: .1875rem;
|
||||||
top: 0.125rem;
|
top: .1875rem;
|
||||||
display: block;
|
display: block;
|
||||||
transform: rotate(-45deg);
|
|
||||||
|
&.icon-arrow-right {
|
||||||
|
font-size: .75rem;
|
||||||
|
top: .125rem;
|
||||||
|
right: .125rem;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { ApiKeyboardButton, ApiMessage } from '../../../api/types';
|
|||||||
|
|
||||||
import { RE_TME_LINK } from '../../../config';
|
import { RE_TME_LINK } from '../../../config';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
|
|
||||||
@ -15,6 +16,8 @@ type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="InlineButtons">
|
<div className="InlineButtons">
|
||||||
{message.inlineButtons!.map((row) => (
|
{message.inlineButtons!.map((row) => (
|
||||||
@ -26,7 +29,8 @@ const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
|||||||
disabled={button.type === 'NOT_SUPPORTED'}
|
disabled={button.type === 'NOT_SUPPORTED'}
|
||||||
onClick={() => onClick({ button })}
|
onClick={() => onClick({ button })}
|
||||||
>
|
>
|
||||||
{renderText(button.text)}
|
{renderText(lang(button.text))}
|
||||||
|
{button.type === 'buy' && <i className="icon-card" />}
|
||||||
{button.type === 'url' && !button.value!.match(RE_TME_LINK) && <i className="icon-arrow-right" />}
|
{button.type === 'url' && !button.value!.match(RE_TME_LINK) && <i className="icon-arrow-right" />}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -26,6 +26,10 @@
|
|||||||
border-radius: var(--border-radius-messages-small);
|
border-radius: var(--border-radius-messages-small);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-left: .5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,9 @@ import React, { FC, memo } from '../../../lib/teact/teact';
|
|||||||
import { ApiMessage } from '../../../api/types';
|
import { ApiMessage } from '../../../api/types';
|
||||||
|
|
||||||
import { getMessageInvoice } from '../../../modules/helpers';
|
import { getMessageInvoice } from '../../../modules/helpers';
|
||||||
|
import { formatCurrency } from '../../../util/formatCurrency';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
import './Invoice.scss';
|
import './Invoice.scss';
|
||||||
|
|
||||||
@ -14,12 +16,15 @@ type OwnProps = {
|
|||||||
const Invoice: FC<OwnProps> = ({
|
const Invoice: FC<OwnProps> = ({
|
||||||
message,
|
message,
|
||||||
}) => {
|
}) => {
|
||||||
|
const lang = useLang();
|
||||||
const invoice = getMessageInvoice(message);
|
const invoice = getMessageInvoice(message);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
description,
|
amount,
|
||||||
|
currency,
|
||||||
|
isTest,
|
||||||
photoUrl,
|
photoUrl,
|
||||||
} = invoice!;
|
} = invoice!;
|
||||||
|
|
||||||
@ -41,9 +46,10 @@ const Invoice: FC<OwnProps> = ({
|
|||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{description && (
|
<p className="description-text">
|
||||||
<p className="description-text">{renderText(description, ['emoji', 'br'])}</p>
|
{formatCurrency(amount, currency, lang.code)}
|
||||||
)}
|
{isTest && <span>{lang('PaymentTestInvoice')}</span>}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -716,11 +716,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
onCancelMediaTransfer={handleCancelUpload}
|
onCancelMediaTransfer={handleCancelUpload}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{invoice && (
|
{invoice && <Invoice message={message} />}
|
||||||
<Invoice
|
|
||||||
message={message}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,14 +2,16 @@ import React, {
|
|||||||
FC, memo,
|
FC, memo,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import { Price } from '../../types';
|
import { LangCode, Price } from '../../types';
|
||||||
|
|
||||||
|
import { formatCurrency } from '../../util/formatCurrency';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
import './Checkout.scss';
|
import './Checkout.scss';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
invoiceContent?: {
|
invoiceContent?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
|
||||||
text?: string;
|
text?: string;
|
||||||
photoUrl?: string;
|
photoUrl?: string;
|
||||||
};
|
};
|
||||||
@ -35,8 +37,9 @@ const Checkout: FC<OwnProps> = ({
|
|||||||
currency,
|
currency,
|
||||||
totalPrice,
|
totalPrice,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
const lang = useLang();
|
||||||
const { photoUrl, title, text } = (invoiceContent || {});
|
|
||||||
|
const { photoUrl, title, text } = invoiceContent || {};
|
||||||
const {
|
const {
|
||||||
paymentMethod,
|
paymentMethod,
|
||||||
paymentProvider,
|
paymentProvider,
|
||||||
@ -45,26 +48,25 @@ const Checkout: FC<OwnProps> = ({
|
|||||||
phone,
|
phone,
|
||||||
shippingMethod,
|
shippingMethod,
|
||||||
} = (checkoutInfo || {});
|
} = (checkoutInfo || {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Checkout">
|
<div className="Checkout">
|
||||||
<div className="description has-image">
|
<div className="description has-image">
|
||||||
{ photoUrl && (
|
{photoUrl && <img src={photoUrl} alt="" />}
|
||||||
<img src={photoUrl} alt="" />
|
|
||||||
)}
|
|
||||||
<div className="text">
|
<div className="text">
|
||||||
<h5>{ title }</h5>
|
<h5>{title}</h5>
|
||||||
<p>{ text }</p>
|
<p>{text}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="price-info">
|
<div className="price-info">
|
||||||
{ prices && prices.map((item) => (
|
{ prices && prices.map((item) => (
|
||||||
renderPaymentItem(item.label, item.amount, currency, false)
|
renderPaymentItem(lang.code, item.label, item.amount, currency)
|
||||||
)) }
|
)) }
|
||||||
{ shippingPrices && shippingPrices.map((item) => (
|
{ shippingPrices && shippingPrices.map((item) => (
|
||||||
renderPaymentItem(item.label, item.amount, currency, false)
|
renderPaymentItem(lang.code, item.label, item.amount, currency)
|
||||||
)) }
|
)) }
|
||||||
{ totalPrice !== undefined && (
|
{ totalPrice !== undefined && (
|
||||||
renderPaymentItem('Total', totalPrice, currency, true)
|
renderPaymentItem(lang.code, lang('Checkout.TotalAmount'), totalPrice, currency, true)
|
||||||
) }
|
) }
|
||||||
</div>
|
</div>
|
||||||
<div className="invoice-info">
|
<div className="invoice-info">
|
||||||
@ -79,14 +81,16 @@ const Checkout: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderPaymentItem(title: string, value: number, currency?: string, main = false) {
|
function renderPaymentItem(
|
||||||
|
langCode: LangCode | undefined, title: string, value: number, currency?: string, main = false,
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<div className={`price-info-item ${main ? 'price-info-item-main' : ''}`}>
|
<div className={`price-info-item ${main ? 'price-info-item-main' : ''}`}>
|
||||||
<div className="title">
|
<div className="title">
|
||||||
{ title }
|
{ title }
|
||||||
</div>
|
</div>
|
||||||
<div className="value">
|
<div className="value">
|
||||||
{ `${currency || ''} ${(value / 100).toFixed(2)}` }
|
{formatCurrency(value, currency, langCode)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,12 +5,10 @@ import { withGlobal } from '../../lib/teact/teactn';
|
|||||||
|
|
||||||
import { GlobalActions, GlobalState } from '../../global/types';
|
import { GlobalActions, GlobalState } from '../../global/types';
|
||||||
import { PaymentStep, ShippingOption, Price } from '../../types';
|
import { PaymentStep, ShippingOption, Price } from '../../types';
|
||||||
import { ApiError, ApiInviteInfo } from '../../api/types';
|
|
||||||
|
|
||||||
import { pick } from '../../util/iteratees';
|
import { pick } from '../../util/iteratees';
|
||||||
import { getCurrencySign } from '../middle/helpers/getCurrencySign';
|
import { formatCurrency } from '../../util/formatCurrency';
|
||||||
import { detectCardTypeText } from '../common/helpers/detectCardType';
|
import { detectCardTypeText } from '../common/helpers/detectCardType';
|
||||||
import { getShippingErrors } from '../../modules/helpers/payments';
|
|
||||||
import usePaymentReducer, { FormState } from '../../hooks/reducers/usePaymentReducer';
|
import usePaymentReducer, { FormState } from '../../hooks/reducers/usePaymentReducer';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
@ -46,7 +44,6 @@ type StateProps = {
|
|||||||
needCardholderName?: boolean;
|
needCardholderName?: boolean;
|
||||||
needCountry?: boolean;
|
needCountry?: boolean;
|
||||||
needZip?: boolean;
|
needZip?: boolean;
|
||||||
globalDialogs?: (ApiError | ApiInviteInfo)[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type GlobalStateProps = Pick<GlobalState['payment'], 'step' | 'shippingOptions' |
|
type GlobalStateProps = Pick<GlobalState['payment'], 'step' | 'shippingOptions' |
|
||||||
@ -79,7 +76,6 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
|
|||||||
needCountry,
|
needCountry,
|
||||||
needZip,
|
needZip,
|
||||||
error,
|
error,
|
||||||
globalDialogs,
|
|
||||||
validateRequestedInfo,
|
validateRequestedInfo,
|
||||||
sendPaymentForm,
|
sendPaymentForm,
|
||||||
setPaymentStep,
|
setPaymentStep,
|
||||||
@ -87,36 +83,26 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
|
|||||||
clearPaymentError,
|
clearPaymentError,
|
||||||
}) => {
|
}) => {
|
||||||
const [paymentState, paymentDispatch] = usePaymentReducer();
|
const [paymentState, paymentDispatch] = usePaymentReducer();
|
||||||
const currencySign = getCurrencySign(currency);
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (step || error || globalDialogs) {
|
if (step || error) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
}, [step, error, globalDialogs]);
|
}, [step, error]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error && error.field) {
|
if (error && error.field) {
|
||||||
paymentDispatch({
|
paymentDispatch({
|
||||||
type: 'setFormErrors',
|
type: 'setFormErrors',
|
||||||
payload: {
|
payload: {
|
||||||
[error.field]: error.fieldError,
|
[error.field]: error.message,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (globalDialogs && globalDialogs.length) {
|
}, [error, paymentDispatch]);
|
||||||
const errors = getShippingErrors(globalDialogs);
|
|
||||||
paymentDispatch({
|
|
||||||
type: 'setFormErrors',
|
|
||||||
payload: {
|
|
||||||
...errors,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [error, globalDialogs, paymentDispatch]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (savedInfo) {
|
if (savedInfo) {
|
||||||
@ -178,8 +164,8 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderModalContent(cuurentStep: PaymentStep) {
|
function renderModalContent(currentStep: PaymentStep) {
|
||||||
switch (cuurentStep) {
|
switch (currentStep) {
|
||||||
case PaymentStep.ShippingInfo:
|
case PaymentStep.ShippingInfo:
|
||||||
return (
|
return (
|
||||||
<ShippingInfo
|
<ShippingInfo
|
||||||
@ -197,7 +183,7 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
|
|||||||
state={paymentState}
|
state={paymentState}
|
||||||
dispatch={paymentDispatch}
|
dispatch={paymentDispatch}
|
||||||
shippingOptions={shippingOptions || []}
|
shippingOptions={shippingOptions || []}
|
||||||
currency={currencySign}
|
currency={currency}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case PaymentStep.PaymentInfo:
|
case PaymentStep.PaymentInfo:
|
||||||
@ -221,7 +207,7 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
|
|||||||
totalPrice={totalPrice}
|
totalPrice={totalPrice}
|
||||||
invoiceContent={invoiceContent}
|
invoiceContent={invoiceContent}
|
||||||
checkoutInfo={checkoutInfo}
|
checkoutInfo={checkoutInfo}
|
||||||
currency={currencySign}
|
currency={currency}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
@ -287,11 +273,11 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
|
|||||||
const buttonText = useMemo(() => {
|
const buttonText = useMemo(() => {
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case PaymentStep.Checkout:
|
case PaymentStep.Checkout:
|
||||||
return lang('Checkout.PayPrice', `${currencySign}${(totalPrice / 100).toFixed(2)}`);
|
return lang('Checkout.PayPrice', formatCurrency(totalPrice, currency, lang.code));
|
||||||
default:
|
default:
|
||||||
return lang('Next');
|
return lang('Next');
|
||||||
}
|
}
|
||||||
}, [step, lang, currencySign, totalPrice]);
|
}, [step, lang, currency, totalPrice]);
|
||||||
|
|
||||||
if (isProviderError) {
|
if (isProviderError) {
|
||||||
return (
|
return (
|
||||||
@ -412,7 +398,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
needCountry,
|
needCountry,
|
||||||
needZip,
|
needZip,
|
||||||
error,
|
error,
|
||||||
globalDialogs: global.dialogs,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => {
|
(setGlobal, actions): DispatchProps => {
|
||||||
|
|||||||
@ -4,10 +4,9 @@ import React, {
|
|||||||
import { withGlobal } from '../../lib/teact/teactn';
|
import { withGlobal } from '../../lib/teact/teactn';
|
||||||
|
|
||||||
import { Price } from '../../types';
|
import { Price } from '../../types';
|
||||||
import { ApiShippingAddress } from '../../api/types/payments';
|
import { ApiShippingAddress } from '../../api/types';
|
||||||
|
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import { getCurrencySign } from '../middle/helpers/getCurrencySign';
|
|
||||||
|
|
||||||
import Checkout from './Checkout';
|
import Checkout from './Checkout';
|
||||||
import Modal from '../ui/Modal';
|
import Modal from '../ui/Modal';
|
||||||
@ -52,10 +51,10 @@ const ReceiptModal: FC<OwnProps & StateProps> = ({
|
|||||||
shippingMethod,
|
shippingMethod,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const currencySign = getCurrencySign(currency);
|
|
||||||
const checkoutInfo = useMemo(() => {
|
const checkoutInfo = useMemo(() => {
|
||||||
return getCheckoutInfo(credentialsTitle, info, shippingMethod);
|
return getCheckoutInfo(credentialsTitle, info, shippingMethod);
|
||||||
}, [info, shippingMethod, credentialsTitle]);
|
}, [info, shippingMethod, credentialsTitle]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className="PaymentModal PaymentModal-receipt"
|
className="PaymentModal PaymentModal-receipt"
|
||||||
@ -87,7 +86,7 @@ const ReceiptModal: FC<OwnProps & StateProps> = ({
|
|||||||
title,
|
title,
|
||||||
}}
|
}}
|
||||||
checkoutInfo={checkoutInfo}
|
checkoutInfo={checkoutInfo}
|
||||||
currency={currencySign}
|
currency={currency}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -100,7 +99,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { receipt } = global.payment;
|
const { receipt } = global.payment;
|
||||||
const {
|
const {
|
||||||
currency,
|
currency,
|
||||||
prices: mapedPrices,
|
prices,
|
||||||
info,
|
info,
|
||||||
totalAmount,
|
totalAmount,
|
||||||
credentialsTitle,
|
credentialsTitle,
|
||||||
@ -113,7 +112,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
currency,
|
currency,
|
||||||
prices: mapedPrices,
|
prices,
|
||||||
info,
|
info,
|
||||||
totalAmount,
|
totalAmount,
|
||||||
credentialsTitle,
|
credentialsTitle,
|
||||||
|
|||||||
@ -2,9 +2,11 @@ import React, {
|
|||||||
FC, useCallback, memo, useMemo, useEffect,
|
FC, useCallback, memo, useMemo, useEffect,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import { ShippingOption } from '../../types/index';
|
import { ShippingOption } from '../../types';
|
||||||
|
|
||||||
|
import { formatCurrency } from '../../util/formatCurrency';
|
||||||
import { FormState, FormEditDispatch } from '../../hooks/reducers/usePaymentReducer';
|
import { FormState, FormEditDispatch } from '../../hooks/reducers/usePaymentReducer';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
|
||||||
import RadioGroup from '../ui/RadioGroup';
|
import RadioGroup from '../ui/RadioGroup';
|
||||||
|
|
||||||
@ -13,7 +15,7 @@ import './Shipping.scss';
|
|||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
state: FormState;
|
state: FormState;
|
||||||
shippingOptions: ShippingOption[];
|
shippingOptions: ShippingOption[];
|
||||||
currency: string;
|
currency?: string;
|
||||||
dispatch: FormEditDispatch;
|
dispatch: FormEditDispatch;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,6 +25,8 @@ const Shipping: FC<OwnProps> = ({
|
|||||||
currency,
|
currency,
|
||||||
dispatch,
|
dispatch,
|
||||||
}) => {
|
}) => {
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!shippingOptions || state.shipping) {
|
if (!shippingOptions || state.shipping) {
|
||||||
return;
|
return;
|
||||||
@ -36,9 +40,9 @@ const Shipping: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const options = useMemo(() => (shippingOptions.map(({ id: value, title: label, amount }) => ({
|
const options = useMemo(() => (shippingOptions.map(({ id: value, title: label, amount }) => ({
|
||||||
label,
|
label,
|
||||||
subLabel: `${currency} ${String(amount / 100)}`,
|
subLabel: formatCurrency(amount, currency, lang.code),
|
||||||
value,
|
value,
|
||||||
}))), [shippingOptions, currency]);
|
}))), [shippingOptions, currency, lang.code]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Shipping">
|
<div className="Shipping">
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
ApiSession,
|
ApiSession,
|
||||||
ApiNewPoll,
|
ApiNewPoll,
|
||||||
ApiInviteInfo,
|
ApiInviteInfo,
|
||||||
|
ApiFieldError,
|
||||||
} from '../api/types';
|
} from '../api/types';
|
||||||
import {
|
import {
|
||||||
FocusDirection,
|
FocusDirection,
|
||||||
@ -344,18 +345,22 @@ export type GlobalState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
payment: {
|
payment: {
|
||||||
|
chatId?: number;
|
||||||
messageId?: number;
|
messageId?: number;
|
||||||
step?: PaymentStep;
|
step?: PaymentStep;
|
||||||
shippingOptions?: ShippingOption[];
|
shippingOptions?: ShippingOption[];
|
||||||
formId?: string;
|
formId?: string;
|
||||||
|
requestId?: string;
|
||||||
savedInfo?: ApiPaymentSavedInfo;
|
savedInfo?: ApiPaymentSavedInfo;
|
||||||
canSaveCredentials?: boolean;
|
canSaveCredentials?: boolean;
|
||||||
invoice?: Invoice;
|
invoice?: Invoice;
|
||||||
invoiceContent?: {
|
invoiceContent?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
description?: string;
|
|
||||||
photoUrl?: string;
|
photoUrl?: string;
|
||||||
|
amount?: number;
|
||||||
|
currency?: string;
|
||||||
|
isTest?: boolean;
|
||||||
};
|
};
|
||||||
nativeProvider?: string;
|
nativeProvider?: string;
|
||||||
providerId?: number;
|
providerId?: number;
|
||||||
@ -377,7 +382,7 @@ export type GlobalState = {
|
|||||||
receipt?: Receipt;
|
receipt?: Receipt;
|
||||||
error?: {
|
error?: {
|
||||||
field?: string;
|
field?: string;
|
||||||
fieldError?: string;
|
message?: string;
|
||||||
description: string;
|
description: string;
|
||||||
};
|
};
|
||||||
isPaymentModalOpen?: boolean;
|
isPaymentModalOpen?: boolean;
|
||||||
@ -501,7 +506,7 @@ export type ActionTypes = (
|
|||||||
'loadWebPagePreview' | 'clearWebPagePreview' | 'loadWallpapers' | 'uploadWallpaper' | 'setDeviceToken' |
|
'loadWebPagePreview' | 'clearWebPagePreview' | 'loadWallpapers' | 'uploadWallpaper' | 'setDeviceToken' |
|
||||||
'deleteDeviceToken' |
|
'deleteDeviceToken' |
|
||||||
// payment
|
// payment
|
||||||
'openPaymentModal' | 'closePaymentModal' |
|
'openPaymentModal' | 'closePaymentModal' | 'addPaymentError' |
|
||||||
'validateRequestedInfo' | 'setPaymentStep' | 'sendPaymentForm' | 'getPaymentForm' | 'getReceipt' |
|
'validateRequestedInfo' | 'setPaymentStep' | 'sendPaymentForm' | 'getPaymentForm' | 'getReceipt' |
|
||||||
'sendCredentialsInfo' | 'setInvoiceMessageInfo' | 'clearPaymentError' | 'clearReceipt'
|
'sendCredentialsInfo' | 'setInvoiceMessageInfo' | 'clearPaymentError' | 'clearReceipt'
|
||||||
);
|
);
|
||||||
|
|||||||
@ -55,9 +55,9 @@ addReducer('clickInlineButton', (global, actions, payload) => {
|
|||||||
if (value) {
|
if (value) {
|
||||||
actions.getReceipt({ receiptMessageId: value, chatId: chat.id, messageId });
|
actions.getReceipt({ receiptMessageId: value, chatId: chat.id, messageId });
|
||||||
} else {
|
} else {
|
||||||
actions.getPaymentForm({ messageId });
|
actions.getPaymentForm({ chat, messageId });
|
||||||
actions.setInvoiceMessageInfo(selectChatMessage(global, chat.id, messageId));
|
actions.setInvoiceMessageInfo(selectChatMessage(global, chat.id, messageId));
|
||||||
actions.openPaymentModal({ messageId });
|
actions.openPaymentModal({ chatId: chat.id, messageId });
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,20 @@
|
|||||||
import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn';
|
import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { PaymentStep } from '../../../types/index';
|
import { PaymentStep } from '../../../types';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { ApiChat } from '../../../api/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
selectPaymentMessageId,
|
selectPaymentMessageId,
|
||||||
selectPaymentRequestId,
|
selectPaymentRequestId,
|
||||||
selectProviderPublishableKey,
|
selectProviderPublishableKey,
|
||||||
selectStripeCredentials,
|
selectStripeCredentials,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
|
selectPaymentChatId,
|
||||||
|
selectChat,
|
||||||
|
selectPaymentFormId,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
import { callApi } from '../../../api/gramjs';
|
||||||
import { getStripeError } from '../../helpers/payments';
|
import { getStripeError } from '../../helpers';
|
||||||
import { buildQueryString } from '../../../util/requestQuery';
|
import { buildQueryString } from '../../../util/requestQuery';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -27,22 +31,28 @@ import {
|
|||||||
|
|
||||||
addReducer('validateRequestedInfo', (global, actions, payload) => {
|
addReducer('validateRequestedInfo', (global, actions, payload) => {
|
||||||
const { requestInfo, saveInfo } = payload;
|
const { requestInfo, saveInfo } = payload;
|
||||||
|
const chatId = selectPaymentChatId(global);
|
||||||
|
const chat = chatId && selectChat(global, chatId);
|
||||||
const messageId = selectPaymentMessageId(global);
|
const messageId = selectPaymentMessageId(global);
|
||||||
if (!messageId) {
|
if (!chat || !messageId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
validateRequestedInfo(messageId, requestInfo, saveInfo);
|
void validateRequestedInfo(chat, messageId, requestInfo, saveInfo);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function validateRequestedInfo(messageId: number, requestInfo: any, shouldSave?: true) {
|
async function validateRequestedInfo(chat: ApiChat, messageId: number, requestInfo: any, shouldSave?: true) {
|
||||||
const result = await callApi('validateRequestedInfo', { messageId, requestInfo, shouldSave });
|
const result = await callApi('validateRequestedInfo', {
|
||||||
|
chat, messageId, requestInfo, shouldSave,
|
||||||
|
});
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id, shippingOptions } = result;
|
const { id, shippingOptions } = result;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let global = setRequestInfoId(getGlobal(), id);
|
let global = setRequestInfoId(getGlobal(), id);
|
||||||
if (shippingOptions) {
|
if (shippingOptions) {
|
||||||
global = updateShippingOptions(global, shippingOptions);
|
global = updateShippingOptions(global, shippingOptions);
|
||||||
@ -54,16 +64,16 @@ async function validateRequestedInfo(messageId: number, requestInfo: any, should
|
|||||||
}
|
}
|
||||||
|
|
||||||
addReducer('getPaymentForm', (global, actions, payload) => {
|
addReducer('getPaymentForm', (global, actions, payload) => {
|
||||||
const { messageId } = payload;
|
const { chat, messageId } = payload;
|
||||||
if (!messageId) {
|
if (!chat || !messageId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getPaymentForm(messageId);
|
void getPaymentForm(chat, messageId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
async function getPaymentForm(messageId: number) {
|
async function getPaymentForm(chat: ApiChat, messageId: number) {
|
||||||
const result = await callApi('getPaymentForm', { messageId });
|
const result = await callApi('getPaymentForm', { chat, messageId });
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -82,19 +92,22 @@ async function getPaymentForm(messageId: number) {
|
|||||||
|
|
||||||
addReducer('getReceipt', (global, actions, payload) => {
|
addReducer('getReceipt', (global, actions, payload) => {
|
||||||
const { receiptMessageId, chatId, messageId } = payload;
|
const { receiptMessageId, chatId, messageId } = payload;
|
||||||
if (!messageId || !receiptMessageId || !chatId) {
|
const chat = chatId && selectChat(global, chatId);
|
||||||
|
if (!messageId || !receiptMessageId || !chat) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getReceipt(messageId, receiptMessageId, chatId);
|
|
||||||
|
void getReceipt(chat, messageId, receiptMessageId);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getReceipt(messageId: number, receiptMessageId: number, chatId: number) {
|
async function getReceipt(chat: ApiChat, messageId: number, receiptMessageId: number) {
|
||||||
const result = await callApi('getReceipt', receiptMessageId);
|
const result = await callApi('getReceipt', chat, receiptMessageId);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let global = getGlobal();
|
let global = getGlobal();
|
||||||
const message = selectChatMessage(global, chatId, messageId);
|
const message = selectChatMessage(global, chat.id, messageId);
|
||||||
global = setReceipt(global, result, message);
|
global = setReceipt(global, result, message);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}
|
}
|
||||||
@ -126,34 +139,40 @@ addReducer('sendCredentialsInfo', (global, actions, payload) => {
|
|||||||
}
|
}
|
||||||
const { credentials } = payload;
|
const { credentials } = payload;
|
||||||
const { data } = credentials;
|
const { data } = credentials;
|
||||||
sendStipeCredentials(data, publishableKey);
|
void sendStripeCredentials(data, publishableKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
addReducer('sendPaymentForm', (global, actions, payload) => {
|
addReducer('sendPaymentForm', (global, actions, payload) => {
|
||||||
const { shippingOptionId, saveCredentials } = payload;
|
const { shippingOptionId, saveCredentials } = payload;
|
||||||
|
const chatId = selectPaymentChatId(global);
|
||||||
|
const chat = chatId && selectChat(global, chatId);
|
||||||
const messageId = selectPaymentMessageId(global);
|
const messageId = selectPaymentMessageId(global);
|
||||||
|
const formId = selectPaymentFormId(global);
|
||||||
const requestInfoId = selectPaymentRequestId(global);
|
const requestInfoId = selectPaymentRequestId(global);
|
||||||
const publishableKey = selectProviderPublishableKey(global);
|
const publishableKey = selectProviderPublishableKey(global);
|
||||||
const stripeCredentials = selectStripeCredentials(global);
|
const stripeCredentials = selectStripeCredentials(global);
|
||||||
if (!messageId || !publishableKey) {
|
if (!chat || !messageId || !publishableKey || !formId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendPaymentForm(messageId, {
|
|
||||||
|
void sendPaymentForm(chat, messageId, formId, {
|
||||||
save: saveCredentials,
|
save: saveCredentials,
|
||||||
data: stripeCredentials,
|
data: stripeCredentials,
|
||||||
}, requestInfoId, shippingOptionId);
|
}, requestInfoId, shippingOptionId);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function sendStipeCredentials(data: {
|
async function sendStripeCredentials(
|
||||||
cardNumber: string;
|
data: {
|
||||||
cardholder?: string;
|
cardNumber: string;
|
||||||
expiryMonth: string;
|
cardholder?: string;
|
||||||
expiryYear: string;
|
expiryMonth: string;
|
||||||
cvv: string;
|
expiryYear: string;
|
||||||
country: string;
|
cvv: string;
|
||||||
zip: string;
|
country: string;
|
||||||
},
|
zip: string;
|
||||||
publishableKey: string) {
|
},
|
||||||
|
publishableKey: string,
|
||||||
|
) {
|
||||||
const query = buildQueryString({
|
const query = buildQueryString({
|
||||||
'card[number]': data.cardNumber,
|
'card[number]': data.cardNumber,
|
||||||
'card[exp_month]': data.expiryMonth,
|
'card[exp_month]': data.expiryMonth,
|
||||||
@ -195,13 +214,15 @@ publishableKey: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sendPaymentForm(
|
async function sendPaymentForm(
|
||||||
|
chat: ApiChat,
|
||||||
messageId: number,
|
messageId: number,
|
||||||
|
formId: string,
|
||||||
credentials: any,
|
credentials: any,
|
||||||
requestedInfoId?: string,
|
requestedInfoId?: string,
|
||||||
shippingOptionId?: string,
|
shippingOptionId?: string,
|
||||||
) {
|
) {
|
||||||
const result = await callApi('sendPaymentForm', {
|
const result = await callApi('sendPaymentForm', {
|
||||||
messageId, credentials, requestedInfoId, shippingOptionId,
|
chat, messageId, formId, credentials, requestedInfoId, shippingOptionId,
|
||||||
});
|
});
|
||||||
if (result) {
|
if (result) {
|
||||||
const global = clearPayment(getGlobal());
|
const global = clearPayment(getGlobal());
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import { updateUser } from '../../reducers';
|
|||||||
import { setLanguage } from '../../../util/langProvider';
|
import { setLanguage } from '../../../util/langProvider';
|
||||||
import { selectNotifySettings } from '../../selectors';
|
import { selectNotifySettings } from '../../selectors';
|
||||||
import { forceWebsync } from '../../../util/websync';
|
import { forceWebsync } from '../../../util/websync';
|
||||||
|
import { getShippingError } from '../../../util/getReadableErrorText';
|
||||||
|
|
||||||
addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@ -61,7 +62,10 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
actions.signOut();
|
actions.signOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actions.showDialog) {
|
const paymentShippingError = getShippingError(update.error);
|
||||||
|
if (paymentShippingError) {
|
||||||
|
actions.addPaymentError({ error: paymentShippingError });
|
||||||
|
} else if (actions.showDialog) {
|
||||||
actions.showDialog({ data: { ...update.error, hasErrorKey: true } });
|
actions.showDialog({ data: { ...update.error, hasErrorKey: true } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,8 +75,10 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
|
|
||||||
function onUpdateApiReady(global: GlobalState) {
|
function onUpdateApiReady(global: GlobalState) {
|
||||||
const { hasWebNotifications, hasPushNotifications } = selectNotifySettings(global);
|
const { hasWebNotifications, hasPushNotifications } = selectNotifySettings(global);
|
||||||
if (hasWebNotifications && hasPushNotifications) subscribe();
|
if (hasWebNotifications && hasPushNotifications) {
|
||||||
setLanguage(global.settings.byKey.language);
|
void subscribe();
|
||||||
|
}
|
||||||
|
void setLanguage(global.settings.byKey.language);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUpdateAuthorizationState(update: ApiUpdateAuthorizationState) {
|
function onUpdateAuthorizationState(update: ApiUpdateAuthorizationState) {
|
||||||
|
|||||||
@ -17,7 +17,7 @@ addReducer('init', (global) => {
|
|||||||
const { animationLevel, messageTextSize, language } = global.settings.byKey;
|
const { animationLevel, messageTextSize, language } = global.settings.byKey;
|
||||||
const theme = selectTheme(global);
|
const theme = selectTheme(global);
|
||||||
|
|
||||||
setLanguage(language, undefined, true);
|
void setLanguage(language, undefined, true);
|
||||||
|
|
||||||
document.documentElement.style.setProperty(
|
document.documentElement.style.setProperty(
|
||||||
'--composer-text-size', `${Math.max(messageTextSize, IS_IOS ? 16 : 15)}px`,
|
'--composer-text-size', `${Math.max(messageTextSize, IS_IOS ? 16 : 15)}px`,
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { addReducer } from '../../../lib/teact/teactn';
|
import { addReducer } from '../../../lib/teact/teactn';
|
||||||
import {
|
|
||||||
clearPayment, closeInvoice,
|
import { clearPayment, closeInvoice } from '../../reducers';
|
||||||
} from '../../reducers';
|
|
||||||
|
|
||||||
addReducer('openPaymentModal', (global, actions, payload) => {
|
addReducer('openPaymentModal', (global, actions, payload) => {
|
||||||
const { messageId } = payload;
|
const { chatId, messageId } = payload;
|
||||||
return {
|
return {
|
||||||
...global,
|
...global,
|
||||||
payment: {
|
payment: {
|
||||||
...global.payment,
|
...global.payment,
|
||||||
|
chatId,
|
||||||
messageId,
|
messageId,
|
||||||
isPaymentModalOpen: true,
|
isPaymentModalOpen: true,
|
||||||
},
|
},
|
||||||
@ -19,3 +19,15 @@ addReducer('closePaymentModal', (global) => {
|
|||||||
const newGlobal = clearPayment(global);
|
const newGlobal = clearPayment(global);
|
||||||
return closeInvoice(newGlobal);
|
return closeInvoice(newGlobal);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addReducer('addPaymentError', (global, actions, payload) => {
|
||||||
|
const { error } = payload!;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
payment: {
|
||||||
|
...global.payment,
|
||||||
|
error,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|||||||
@ -1,41 +1,41 @@
|
|||||||
import { ApiError, ApiInviteInfo } from '../../api/types';
|
import { ApiFieldError } from '../../api/types';
|
||||||
|
|
||||||
const STRIPE_ERRORS: Record<string, Record<string, string>> = {
|
const STRIPE_ERRORS: Record<string, ApiFieldError> = {
|
||||||
missing_payment_information: {
|
missing_payment_information: {
|
||||||
field: 'cardNumber',
|
field: 'cardNumber',
|
||||||
fieldError: 'Incorrect card number',
|
message: 'Incorrect card number',
|
||||||
},
|
},
|
||||||
invalid_number: {
|
invalid_number: {
|
||||||
field: 'cardNumber',
|
field: 'cardNumber',
|
||||||
fieldError: 'Incorrect card number',
|
message: 'Incorrect card number',
|
||||||
},
|
},
|
||||||
number: {
|
number: {
|
||||||
field: 'cardNumber',
|
field: 'cardNumber',
|
||||||
fieldError: 'Incorrect card number',
|
message: 'Incorrect card number',
|
||||||
},
|
},
|
||||||
exp_year: {
|
exp_year: {
|
||||||
field: 'expiry',
|
field: 'expiry',
|
||||||
fieldError: 'Incorrect year',
|
message: 'Incorrect year',
|
||||||
},
|
},
|
||||||
exp_month: {
|
exp_month: {
|
||||||
field: 'expiry',
|
field: 'expiry',
|
||||||
fieldError: 'Incorrect month',
|
message: 'Incorrect month',
|
||||||
},
|
},
|
||||||
invalid_expiry_year: {
|
invalid_expiry_year: {
|
||||||
field: 'expiry',
|
field: 'expiry',
|
||||||
fieldError: 'Incorrect year',
|
message: 'Incorrect year',
|
||||||
},
|
},
|
||||||
invalid_expiry_month: {
|
invalid_expiry_month: {
|
||||||
field: 'expiry',
|
field: 'expiry',
|
||||||
fieldError: 'Incorrect month',
|
message: 'Incorrect month',
|
||||||
},
|
},
|
||||||
cvc: {
|
cvc: {
|
||||||
field: 'cvv',
|
field: 'cvv',
|
||||||
fieldError: 'Incorrect CVV',
|
message: 'Incorrect CVV',
|
||||||
},
|
},
|
||||||
invalid_cvc: {
|
invalid_cvc: {
|
||||||
field: 'cvv',
|
field: 'cvv',
|
||||||
fieldError: 'Incorrect CVV',
|
message: 'Incorrect CVV',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,65 +44,8 @@ export function getStripeError(error: {
|
|||||||
message: string;
|
message: string;
|
||||||
param?: string;
|
param?: string;
|
||||||
}) {
|
}) {
|
||||||
const { message, code, param } = error;
|
const { message: description, code, param } = error;
|
||||||
const { field, fieldError, description } = param ? STRIPE_ERRORS[param] : STRIPE_ERRORS[code];
|
const { field, message } = param ? STRIPE_ERRORS[param] : STRIPE_ERRORS[code];
|
||||||
return {
|
|
||||||
field,
|
return { field, message, description};
|
||||||
fieldError,
|
|
||||||
description: description || message,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const SHIPPING_ERRORS: Record<string, Record<string, string>> = {
|
|
||||||
ADDRESS_STREET_LINE1_INVALID: {
|
|
||||||
field: 'streetLine1',
|
|
||||||
fieldError: 'Incorrect street address',
|
|
||||||
},
|
|
||||||
ADDRESS_STREET_LINE2_INVALID: {
|
|
||||||
field: 'streetLine2',
|
|
||||||
fieldError: 'Incorrect street address',
|
|
||||||
},
|
|
||||||
ADDRESS_CITY_INVALID: {
|
|
||||||
field: 'city',
|
|
||||||
fieldError: 'Incorrect city',
|
|
||||||
},
|
|
||||||
ADDRESS_COUNTRY_INVALID: {
|
|
||||||
field: 'countryIso2',
|
|
||||||
fieldError: 'Incorrect country',
|
|
||||||
},
|
|
||||||
ADDRESS_POSTCODE_INVALID: {
|
|
||||||
field: 'postCode',
|
|
||||||
fieldError: 'Incorrect post code',
|
|
||||||
},
|
|
||||||
ADDRESS_STATE_INVALID: {
|
|
||||||
field: 'state',
|
|
||||||
fieldError: 'Incorrect state',
|
|
||||||
},
|
|
||||||
REQ_INFO_NAME_INVALID: {
|
|
||||||
field: 'fullName',
|
|
||||||
fieldError: 'Incorrect name',
|
|
||||||
},
|
|
||||||
REQ_INFO_PHONE_INVALID: {
|
|
||||||
field: 'phone',
|
|
||||||
fieldError: 'Incorrect phone',
|
|
||||||
},
|
|
||||||
REQ_INFO_EMAIL_INVALID: {
|
|
||||||
field: 'email',
|
|
||||||
fieldError: 'Incorrect email',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export function getShippingErrors(dialogs: (ApiError | ApiInviteInfo)[]) {
|
|
||||||
return Object.values(dialogs).reduce((acc, cur) => {
|
|
||||||
if (!('hasErrorKey' in cur) || !cur.hasErrorKey) return acc;
|
|
||||||
const error = SHIPPING_ERRORS[cur.message];
|
|
||||||
if (error) {
|
|
||||||
acc = {
|
|
||||||
...acc,
|
|
||||||
[error.field]: error.fieldError,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export function setRequestInfoId(global: GlobalState, id: string): GlobalState {
|
|||||||
...global,
|
...global,
|
||||||
payment: {
|
payment: {
|
||||||
...global.payment,
|
...global.payment,
|
||||||
formId: id,
|
requestId: id,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -42,7 +42,9 @@ export function setInvoiceMessageInfo(global: GlobalState, message: ApiMessage):
|
|||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
description,
|
amount,
|
||||||
|
currency,
|
||||||
|
isTest,
|
||||||
photoUrl,
|
photoUrl,
|
||||||
} = message.content.invoice;
|
} = message.content.invoice;
|
||||||
return {
|
return {
|
||||||
@ -52,8 +54,10 @@ export function setInvoiceMessageInfo(global: GlobalState, message: ApiMessage):
|
|||||||
invoiceContent: {
|
invoiceContent: {
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
description,
|
|
||||||
photoUrl,
|
photoUrl,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
isTest,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,14 +1,22 @@
|
|||||||
|
|
||||||
import { GlobalState } from '../../global/types';
|
import { GlobalState } from '../../global/types';
|
||||||
|
|
||||||
|
export function selectPaymentChatId(global: GlobalState) {
|
||||||
|
return global.payment.chatId;
|
||||||
|
}
|
||||||
|
|
||||||
export function selectPaymentMessageId(global: GlobalState) {
|
export function selectPaymentMessageId(global: GlobalState) {
|
||||||
return global.payment.messageId;
|
return global.payment.messageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectPaymentRequestId(global: GlobalState) {
|
export function selectPaymentFormId(global: GlobalState) {
|
||||||
return global.payment.formId;
|
return global.payment.formId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectPaymentRequestId(global: GlobalState) {
|
||||||
|
return global.payment.requestId;
|
||||||
|
}
|
||||||
|
|
||||||
export function selectProviderPublishableKey(global: GlobalState) {
|
export function selectProviderPublishableKey(global: GlobalState) {
|
||||||
return global.payment.nativeParams ? global.payment.nativeParams.publishableKey : undefined;
|
return global.payment.nativeParams ? global.payment.nativeParams.publishableKey : undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/util/formatCurrency.ts
Normal file
8
src/util/formatCurrency.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { LangCode } from '../types';
|
||||||
|
|
||||||
|
export function formatCurrency(totalPrice: number, currency?: string, locale: LangCode = 'en') {
|
||||||
|
return new Intl.NumberFormat(locale, {
|
||||||
|
style: 'currency',
|
||||||
|
currency,
|
||||||
|
}).format(currency === 'JPY' ? totalPrice : totalPrice / 100);
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { ApiError } from '../api/types';
|
import { ApiError, ApiFieldError } from '../api/types';
|
||||||
|
|
||||||
const READABLE_ERROR_MESSAGES: Record<string, string> = {
|
const READABLE_ERROR_MESSAGES: Record<string, string> = {
|
||||||
CHAT_RESTRICTED: 'You can\'t send messages in this chat, you were restricted',
|
CHAT_RESTRICTED: 'You can\'t send messages in this chat, you were restricted',
|
||||||
@ -64,6 +64,45 @@ const READABLE_ERROR_MESSAGES: Record<string, string> = {
|
|||||||
WALLPAPER_DIMENSIONS_INVALID: 'The wallpaper dimensions are invalid, please select another file',
|
WALLPAPER_DIMENSIONS_INVALID: 'The wallpaper dimensions are invalid, please select another file',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SHIPPING_ERRORS: Record<string, ApiFieldError> = {
|
||||||
|
ADDRESS_STREET_LINE1_INVALID: {
|
||||||
|
field: 'streetLine1',
|
||||||
|
message: 'Incorrect street address',
|
||||||
|
},
|
||||||
|
ADDRESS_STREET_LINE2_INVALID: {
|
||||||
|
field: 'streetLine2',
|
||||||
|
message: 'Incorrect street address',
|
||||||
|
},
|
||||||
|
ADDRESS_CITY_INVALID: {
|
||||||
|
field: 'city',
|
||||||
|
message: 'Incorrect city',
|
||||||
|
},
|
||||||
|
ADDRESS_COUNTRY_INVALID: {
|
||||||
|
field: 'countryIso2',
|
||||||
|
message: 'Incorrect country',
|
||||||
|
},
|
||||||
|
ADDRESS_POSTCODE_INVALID: {
|
||||||
|
field: 'postCode',
|
||||||
|
message: 'Incorrect post code',
|
||||||
|
},
|
||||||
|
ADDRESS_STATE_INVALID: {
|
||||||
|
field: 'state',
|
||||||
|
message: 'Incorrect state',
|
||||||
|
},
|
||||||
|
REQ_INFO_NAME_INVALID: {
|
||||||
|
field: 'fullName',
|
||||||
|
message: 'Incorrect name',
|
||||||
|
},
|
||||||
|
REQ_INFO_PHONE_INVALID: {
|
||||||
|
field: 'phone',
|
||||||
|
message: 'Incorrect phone',
|
||||||
|
},
|
||||||
|
REQ_INFO_EMAIL_INVALID: {
|
||||||
|
field: 'email',
|
||||||
|
message: 'Incorrect email',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default function getReadableErrorText(error: ApiError) {
|
export default function getReadableErrorText(error: ApiError) {
|
||||||
const { message, isSlowMode, textParams } = error;
|
const { message, isSlowMode, textParams } = error;
|
||||||
// Currently, Telegram API doesn't return `SLOWMODE_WAIT_X` error as described in the docs
|
// Currently, Telegram API doesn't return `SLOWMODE_WAIT_X` error as described in the docs
|
||||||
@ -79,3 +118,7 @@ export default function getReadableErrorText(error: ApiError) {
|
|||||||
}
|
}
|
||||||
return errorMessage;
|
return errorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getShippingError(error: ApiError): ApiFieldError | undefined {
|
||||||
|
return SHIPPING_ERRORS[error.message];
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
|
import { getGlobal } from '../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiLangPack, ApiLangString } from '../api/types';
|
import { ApiLangPack, ApiLangString } from '../api/types';
|
||||||
|
import { LangCode } from '../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_LANG_CODE, DEFAULT_LANG_PACK, LANG_CACHE_NAME, LANG_PACKS,
|
DEFAULT_LANG_CODE, DEFAULT_LANG_PACK, LANG_CACHE_NAME, LANG_PACKS,
|
||||||
@ -7,13 +10,12 @@ import * as cacheApi from './cacheApi';
|
|||||||
import { callApi } from '../api/gramjs';
|
import { callApi } from '../api/gramjs';
|
||||||
import { createCallbackManager } from './callbacks';
|
import { createCallbackManager } from './callbacks';
|
||||||
import { formatInteger } from './textFormat';
|
import { formatInteger } from './textFormat';
|
||||||
import { getGlobal } from '../lib/teact/teactn';
|
|
||||||
|
|
||||||
interface LangFn {
|
interface LangFn {
|
||||||
(key: string, value?: any, format?: 'i'): any;
|
(key: string, value?: any, format?: 'i'): any;
|
||||||
|
|
||||||
isRtl?: boolean;
|
isRtl?: boolean;
|
||||||
code?: string;
|
code?: LangCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUBSTITUTION_REGEX = /%\d?\$?[sdf@]/g;
|
const SUBSTITUTION_REGEX = /%\d?\$?[sdf@]/g;
|
||||||
@ -95,7 +97,7 @@ export async function getTranslationForLangString(langCode: string, key: string)
|
|||||||
return processTranslation(translateString, key);
|
return processTranslation(translateString, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setLanguage(langCode: string, callback?: NoneToVoidFunction, withFallback = false) {
|
export async function setLanguage(langCode: LangCode, callback?: NoneToVoidFunction, withFallback = false) {
|
||||||
if (langPack && langCode === currentLangCode) {
|
if (langPack && langCode === currentLangCode) {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user