Payment Modal: Various fixes (#2124)
This commit is contained in:
parent
f272f63afc
commit
d10df1a705
@ -166,6 +166,7 @@ export interface ApiPoll {
|
|||||||
export type ApiInputInvoice = {
|
export type ApiInputInvoice = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
|
isExtendedMedia?: boolean;
|
||||||
} | {
|
} | {
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const VISA = /^4[0-9]{12}(?:[0-9]{1,3})?$/;
|
const VISA = /^4\d/;
|
||||||
const MASTERCARD1 = /^5[1-5][0-9]{11,14}$/;
|
const MASTERCARD1 = /^5[1-5]/;
|
||||||
const MASTERCARD2 = /^2[2-7][0-9]{11,14}$/;
|
const MASTERCARD2 = /^2[2-7]\d{2}/;
|
||||||
const MIR = /^220[0-4]/;
|
const MIR = /^220[0-4]/;
|
||||||
|
|
||||||
export enum CardType {
|
export enum CardType {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, { memo, useCallback, useEffect } from '../../../lib/teact/teact';
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiMessage } from '../../../api/types';
|
import type { ApiMessage } from '../../../api/types';
|
||||||
@ -7,7 +7,6 @@ import type { ApiMessage } from '../../../api/types';
|
|||||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||||
import { selectChatMessage, selectCurrentMessageList } from '../../../global/selectors';
|
import { selectChatMessage, selectCurrentMessageList } from '../../../global/selectors';
|
||||||
import useMouseInside from '../../../hooks/useMouseInside';
|
import useMouseInside from '../../../hooks/useMouseInside';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
|
||||||
|
|
||||||
import Menu from '../../ui/Menu';
|
import Menu from '../../ui/Menu';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
@ -31,16 +30,6 @@ const BotKeyboardMenu: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose);
|
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose);
|
||||||
const { isKeyboardSingleUse } = message || {};
|
const { isKeyboardSingleUse } = message || {};
|
||||||
const [forceOpen, markForceOpen, unmarkForceOpen] = useFlag(true);
|
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
|
||||||
unmarkForceOpen();
|
|
||||||
onClose();
|
|
||||||
}, [onClose, unmarkForceOpen]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
markForceOpen();
|
|
||||||
}, [markForceOpen, message?.keyboardButtons]);
|
|
||||||
|
|
||||||
if (!message || !message.keyboardButtons) {
|
if (!message || !message.keyboardButtons) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -48,13 +37,13 @@ const BotKeyboardMenu: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isOpen || forceOpen}
|
isOpen={isOpen}
|
||||||
autoClose={isKeyboardSingleUse}
|
autoClose={isKeyboardSingleUse}
|
||||||
positionX="right"
|
positionX="right"
|
||||||
positionY="bottom"
|
positionY="bottom"
|
||||||
onClose={handleClose}
|
onClose={onClose}
|
||||||
className="BotKeyboardMenu"
|
className="BotKeyboardMenu"
|
||||||
onCloseAnimationEnd={handleClose}
|
onCloseAnimationEnd={onClose}
|
||||||
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
onMouseEnter={!IS_TOUCH_ENV ? handleMouseEnter : undefined}
|
||||||
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
onMouseLeave={!IS_TOUCH_ENV ? handleMouseLeave : undefined}
|
||||||
noCompact
|
noCompact
|
||||||
|
|||||||
@ -56,6 +56,7 @@ const InvoiceMediaPreview: FC<OwnProps> = ({
|
|||||||
openInvoice({
|
openInvoice({
|
||||||
chatId,
|
chatId,
|
||||||
messageId: id,
|
messageId: id,
|
||||||
|
isExtendedMedia: true,
|
||||||
});
|
});
|
||||||
}, [chatId, id, openInvoice]);
|
}, [chatId, id, openInvoice]);
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import './ConfirmPayment.scss';
|
|||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
url: string;
|
url: string;
|
||||||
|
noRedirect?: boolean;
|
||||||
|
onClose: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IframeCallbackEvent {
|
interface IframeCallbackEvent {
|
||||||
@ -19,8 +21,8 @@ interface IframeCallbackEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConfirmPayment: FC<OwnProps> = ({ url }) => {
|
const ConfirmPayment: FC<OwnProps> = ({ url, noRedirect, onClose }) => {
|
||||||
const { closePaymentModal, openTelegramLink } = getActions();
|
const { openTelegramLink } = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
@ -33,13 +35,16 @@ const ConfirmPayment: FC<OwnProps> = ({ url }) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkUrl = TME_LINK_PREFIX + eventData.path_full;
|
if (!noRedirect) {
|
||||||
openTelegramLink({ url: linkUrl });
|
const linkUrl = TME_LINK_PREFIX + eventData.path_full;
|
||||||
closePaymentModal();
|
openTelegramLink({ url: linkUrl });
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Ignore other messages
|
// Ignore other messages
|
||||||
}
|
}
|
||||||
}, [closePaymentModal, openTelegramLink]);
|
}, [onClose, noRedirect, openTelegramLink]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('message', handleMessage);
|
window.addEventListener('message', handleMessage);
|
||||||
|
|||||||
@ -4,7 +4,7 @@ $modalHeaderAndFooterHeight: 8.375rem;
|
|||||||
&.recurring {
|
&.recurring {
|
||||||
.Transition {
|
.Transition {
|
||||||
height: 33rem;
|
height: 33rem;
|
||||||
max-height: calc(90vh - $modalHeaderAndFooterHeight);
|
max-height: calc(92vh - $modalHeaderAndFooterHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ $modalHeaderAndFooterHeight: 8.375rem;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Transition {
|
.Transition {
|
||||||
height: min(27rem, 60vh);
|
height: min(27rem, 68vh);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-content {
|
.empty-content {
|
||||||
|
|||||||
@ -39,7 +39,7 @@ const SUPPORTED_PROVIDERS = new Set([DEFAULT_PROVIDER, DONATE_PROVIDER]);
|
|||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
onClose: () => void;
|
onClose: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
@ -64,6 +64,7 @@ type StateProps = {
|
|||||||
stripeId?: string;
|
stripeId?: string;
|
||||||
savedCredentials?: ApiPaymentCredentials[];
|
savedCredentials?: ApiPaymentCredentials[];
|
||||||
passwordValidUntil?: number;
|
passwordValidUntil?: number;
|
||||||
|
isExtendedMedia?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type GlobalStateProps = Pick<GlobalState['payment'], (
|
type GlobalStateProps = Pick<GlobalState['payment'], (
|
||||||
@ -105,6 +106,7 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
|||||||
stripeId,
|
stripeId,
|
||||||
savedCredentials,
|
savedCredentials,
|
||||||
passwordValidUntil,
|
passwordValidUntil,
|
||||||
|
isExtendedMedia,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadPasswordInfo,
|
loadPasswordInfo,
|
||||||
@ -321,6 +323,8 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
|||||||
return (
|
return (
|
||||||
<ConfirmPayment
|
<ConfirmPayment
|
||||||
url={confirmPaymentUrl!}
|
url={confirmPaymentUrl!}
|
||||||
|
noRedirect={isExtendedMedia}
|
||||||
|
onClose={closeModal}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
@ -476,8 +480,27 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
|||||||
? lang('Checkout.PayPrice', formatCurrency(totalPrice, currency!, lang.code))
|
? lang('Checkout.PayPrice', formatCurrency(totalPrice, currency!, lang.code))
|
||||||
: lang('Next');
|
: lang('Next');
|
||||||
|
|
||||||
const isSubmitDisabled = isLoading
|
function getIsSubmitDisabled() {
|
||||||
|| Boolean(step === PaymentStep.Checkout && invoice?.isRecurring && !isTosAccepted);
|
if (isLoading) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (step) {
|
||||||
|
case PaymentStep.Checkout:
|
||||||
|
return Boolean(invoice?.isRecurring && !isTosAccepted);
|
||||||
|
|
||||||
|
case PaymentStep.PaymentInfo:
|
||||||
|
return Boolean(
|
||||||
|
paymentState.cardNumber === ''
|
||||||
|
|| (needCardholderName && paymentState.cardholder === '')
|
||||||
|
|| paymentState.cvv === ''
|
||||||
|
|| paymentState.expiry === '',
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isProviderError) {
|
if (isProviderError) {
|
||||||
return (
|
return (
|
||||||
@ -501,6 +524,8 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isSubmitDisabled = getIsSubmitDisabled();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className={buildClassName('PaymentModal', invoice?.isRecurring && 'recurring')}
|
className={buildClassName('PaymentModal', invoice?.isRecurring && 'recurring')}
|
||||||
@ -569,6 +594,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
smartGlocalCredentials,
|
smartGlocalCredentials,
|
||||||
savedCredentials,
|
savedCredentials,
|
||||||
temporaryPassword,
|
temporaryPassword,
|
||||||
|
isExtendedMedia,
|
||||||
} = global.payment;
|
} = global.payment;
|
||||||
|
|
||||||
const chat = inputInvoice && 'chatId' in inputInvoice ? selectChat(global, inputInvoice.chatId) : undefined;
|
const chat = inputInvoice && 'chatId' in inputInvoice ? selectChat(global, inputInvoice.chatId) : undefined;
|
||||||
@ -615,6 +641,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
stripeId: stripeCredentials?.id,
|
stripeId: stripeCredentials?.id,
|
||||||
savedCredentials,
|
savedCredentials,
|
||||||
passwordValidUntil: temporaryPassword?.validUntil,
|
passwordValidUntil: temporaryPassword?.validUntil,
|
||||||
|
isExtendedMedia,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(PaymentModal));
|
)(PaymentModal));
|
||||||
|
|||||||
@ -144,7 +144,7 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
max-height: 90vh;
|
max-height: 92vh;
|
||||||
|
|
||||||
b,
|
b,
|
||||||
strong {
|
strong {
|
||||||
|
|||||||
@ -25,7 +25,6 @@ import {
|
|||||||
setPaymentForm,
|
setPaymentForm,
|
||||||
setStripeCardInfo,
|
setStripeCardInfo,
|
||||||
setReceipt,
|
setReceipt,
|
||||||
clearPayment,
|
|
||||||
closeInvoice,
|
closeInvoice,
|
||||||
setSmartGlocalCardInfo, addUsers, setInvoiceInfo, updatePayment,
|
setSmartGlocalCardInfo, addUsers, setInvoiceInfo, updatePayment,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
@ -82,6 +81,7 @@ addActionHandler('openInvoice', async (global, actions, payload) => {
|
|||||||
inputInvoice: payload,
|
inputInvoice: payload,
|
||||||
isPaymentModalOpen: true,
|
isPaymentModalOpen: true,
|
||||||
status: 'cancelled',
|
status: 'cancelled',
|
||||||
|
isExtendedMedia: (payload as any).isExtendedMedia,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -217,7 +217,6 @@ addActionHandler('sendPaymentForm', async (global, actions, payload) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = clearPayment(global);
|
|
||||||
global = updatePayment(global, { status: 'paid' });
|
global = updatePayment(global, { status: 'paid' });
|
||||||
global = closeInvoice(global);
|
global = closeInvoice(global);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { addActionHandler } from '../../index';
|
import { addActionHandler } from '../../index';
|
||||||
|
|
||||||
import { IS_PRODUCTION_HOST } from '../../../util/environment';
|
import { IS_PRODUCTION_HOST } from '../../../util/environment';
|
||||||
import { clearPayment } from '../../reducers';
|
import { closeInvoice } from '../../reducers';
|
||||||
import * as langProvider from '../../../util/langProvider';
|
import * as langProvider from '../../../util/langProvider';
|
||||||
import { formatCurrency } from '../../../util/formatCurrency';
|
import { formatCurrency } from '../../../util/formatCurrency';
|
||||||
import { selectChatMessage } from '../../selectors';
|
import { selectChatMessage } from '../../selectors';
|
||||||
@ -28,8 +28,9 @@ addActionHandler('apiUpdate', (global, actions, update) => {
|
|||||||
|
|
||||||
// On the production host, the payment frame receives a message with the payment event,
|
// On the production host, the payment frame receives a message with the payment event,
|
||||||
// after which the payment form closes. In other cases, the payment form must be closed manually.
|
// after which the payment form closes. In other cases, the payment form must be closed manually.
|
||||||
|
// Closing the invoice will cause the closing of the Payment Modal dialog and then closing the payment.
|
||||||
if (!IS_PRODUCTION_HOST) {
|
if (!IS_PRODUCTION_HOST) {
|
||||||
global = clearPayment(global);
|
global = closeInvoice(global);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update.slug && inputInvoice && 'slug' in inputInvoice && inputInvoice.slug !== update.slug) {
|
if (update.slug && inputInvoice && 'slug' in inputInvoice && inputInvoice.slug !== update.slug) {
|
||||||
|
|||||||
@ -110,5 +110,5 @@ export function clearPayment(global: GlobalState): GlobalState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function closeInvoice(global: GlobalState): GlobalState {
|
export function closeInvoice(global: GlobalState): GlobalState {
|
||||||
return updatePayment(global, { isPaymentModalOpen: undefined });
|
return updatePayment(global, { isPaymentModalOpen: undefined, isExtendedMedia: undefined });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -504,6 +504,7 @@ export type GlobalState = {
|
|||||||
description?: string;
|
description?: string;
|
||||||
};
|
};
|
||||||
isPaymentModalOpen?: boolean;
|
isPaymentModalOpen?: boolean;
|
||||||
|
isExtendedMedia?: boolean;
|
||||||
confirmPaymentUrl?: string;
|
confirmPaymentUrl?: string;
|
||||||
temporaryPassword?: {
|
temporaryPassword?: {
|
||||||
value: string;
|
value: string;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user