PaymentModal: Fix payment (#4146)
This commit is contained in:
parent
89e3640962
commit
35ae18dddc
@ -90,6 +90,7 @@ export function buildApiPaymentForm(form: GramJs.payments.PaymentForm): ApiPayme
|
||||
savedInfo,
|
||||
invoice,
|
||||
savedCredentials,
|
||||
url,
|
||||
} = form;
|
||||
|
||||
const {
|
||||
@ -118,6 +119,7 @@ export function buildApiPaymentForm(form: GramJs.payments.PaymentForm): ApiPayme
|
||||
const nativeData = nativeParams ? JSON.parse(nativeParams.data) : {};
|
||||
|
||||
return {
|
||||
url,
|
||||
canSaveCredentials,
|
||||
isPasswordMissing,
|
||||
formId: String(formId),
|
||||
|
||||
@ -20,6 +20,7 @@ export interface ApiPaymentSavedInfo {
|
||||
}
|
||||
|
||||
export interface ApiPaymentForm {
|
||||
url: string;
|
||||
canSaveCredentials?: boolean;
|
||||
isPasswordMissing?: boolean;
|
||||
formId: string;
|
||||
|
||||
@ -47,6 +47,7 @@ export type OwnProps = {
|
||||
dispatch?: FormEditDispatch;
|
||||
onAcceptTos?: (isAccepted: boolean) => void;
|
||||
savedCredentials?: ApiPaymentCredentials[];
|
||||
isPaymentFormUrl?: boolean;
|
||||
};
|
||||
|
||||
const Checkout: FC<OwnProps> = ({
|
||||
@ -64,6 +65,7 @@ const Checkout: FC<OwnProps> = ({
|
||||
needAddress,
|
||||
hasShippingOptions,
|
||||
savedCredentials,
|
||||
isPaymentFormUrl,
|
||||
}) => {
|
||||
const { setPaymentStep } = getActions();
|
||||
|
||||
@ -185,7 +187,7 @@ const Checkout: FC<OwnProps> = ({
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.invoiceInfo}>
|
||||
{renderCheckoutItem({
|
||||
{!isPaymentFormUrl && renderCheckoutItem({
|
||||
title: paymentMethod || savedCredentials?.[0].title,
|
||||
label: lang('PaymentCheckoutMethod'),
|
||||
icon: 'card',
|
||||
|
||||
@ -12,16 +12,32 @@ export type OwnProps = {
|
||||
url: string;
|
||||
noRedirect?: boolean;
|
||||
onClose: NoneToVoidFunction;
|
||||
onPaymentFormSubmit?: (eventData: PaymentFormSubmitEvent['eventData']) => void;
|
||||
};
|
||||
|
||||
interface IframeCallbackEvent {
|
||||
eventType: string;
|
||||
export interface PaymentFormSubmitEvent {
|
||||
eventType: 'payment_form_submit';
|
||||
eventData: {
|
||||
path_full: string;
|
||||
credentials: {
|
||||
token: string;
|
||||
type: string;
|
||||
};
|
||||
title: string;
|
||||
};
|
||||
}
|
||||
|
||||
const ConfirmPayment: FC<OwnProps> = ({ url, noRedirect, onClose }) => {
|
||||
interface WebAppOpenTgLinkEvent {
|
||||
eventType: 'web_app_open_tg_link';
|
||||
eventData: {
|
||||
path_full?: string;
|
||||
};
|
||||
}
|
||||
|
||||
type IframeCallbackEvent = PaymentFormSubmitEvent | WebAppOpenTgLinkEvent;
|
||||
|
||||
const ConfirmPayment: FC<OwnProps> = ({
|
||||
url, noRedirect, onClose, onPaymentFormSubmit,
|
||||
}) => {
|
||||
const { openTelegramLink } = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
@ -30,21 +46,27 @@ const ConfirmPayment: FC<OwnProps> = ({ url, noRedirect, onClose }) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data) as IframeCallbackEvent;
|
||||
const { eventType, eventData } = data;
|
||||
|
||||
if (eventType !== 'web_app_open_tg_link') {
|
||||
return;
|
||||
switch (eventType) {
|
||||
case 'web_app_open_tg_link':
|
||||
if (!noRedirect) {
|
||||
const linkUrl = TME_LINK_PREFIX + eventData.path_full!;
|
||||
openTelegramLink({ url: linkUrl });
|
||||
}
|
||||
onClose();
|
||||
break;
|
||||
case 'payment_form_submit':
|
||||
if (onPaymentFormSubmit) {
|
||||
onPaymentFormSubmit(eventData);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
onClose();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!noRedirect) {
|
||||
const linkUrl = TME_LINK_PREFIX + eventData.path_full;
|
||||
openTelegramLink({ url: linkUrl });
|
||||
}
|
||||
|
||||
onClose();
|
||||
} catch (err) {
|
||||
// Ignore other messages
|
||||
}
|
||||
}, [onClose, noRedirect, openTelegramLink]);
|
||||
}, [onClose, noRedirect, openTelegramLink, onPaymentFormSubmit]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('message', handleMessage);
|
||||
|
||||
@ -8,6 +8,7 @@ import type { ApiChat, ApiCountry, ApiPaymentCredentials } from '../../api/types
|
||||
import type { TabState } from '../../global/types';
|
||||
import type { FormState } from '../../hooks/reducers/usePaymentReducer';
|
||||
import type { Price, ShippingOption } from '../../types';
|
||||
import type { PaymentFormSubmitEvent } from './ConfirmPayment';
|
||||
import { PaymentStep } from '../../types';
|
||||
|
||||
import { selectChat, selectTabState } from '../../global/selectors';
|
||||
@ -37,6 +38,7 @@ import './PaymentModal.scss';
|
||||
|
||||
const DEFAULT_PROVIDER = 'stripe';
|
||||
const DONATE_PROVIDER = 'smartglocal';
|
||||
const DONATE_PROVIDER_URL = 'https://payment.smart-glocal.com';
|
||||
const SUPPORTED_PROVIDERS = new Set([DEFAULT_PROVIDER, DONATE_PROVIDER]);
|
||||
|
||||
export type OwnProps = {
|
||||
@ -67,6 +69,7 @@ type StateProps = {
|
||||
savedCredentials?: ApiPaymentCredentials[];
|
||||
passwordValidUntil?: number;
|
||||
isExtendedMedia?: boolean;
|
||||
isPaymentFormUrl?: boolean;
|
||||
};
|
||||
|
||||
type GlobalStateProps = Pick<TabState['payment'], (
|
||||
@ -109,6 +112,7 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
||||
savedCredentials,
|
||||
passwordValidUntil,
|
||||
isExtendedMedia,
|
||||
isPaymentFormUrl,
|
||||
}) => {
|
||||
const {
|
||||
loadPasswordInfo,
|
||||
@ -118,6 +122,7 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
||||
sendCredentialsInfo,
|
||||
clearPaymentError,
|
||||
validatePaymentPassword,
|
||||
setSmartGlocalCardInfo,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
@ -267,6 +272,21 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
const sendForm = useCallback(() => {
|
||||
sendPaymentForm({
|
||||
shippingOptionId: paymentState.shipping,
|
||||
saveCredentials: paymentState.saveCredentials,
|
||||
savedCredentialId: paymentState.savedCredentialId,
|
||||
tipAmount: paymentState.tipAmount,
|
||||
});
|
||||
}, [sendPaymentForm, paymentState]);
|
||||
|
||||
const handlePaymentFormSubmit = useCallback((eventData: PaymentFormSubmitEvent['eventData']) => {
|
||||
const { credentials } = eventData;
|
||||
setSmartGlocalCardInfo(credentials);
|
||||
sendForm();
|
||||
}, [sendForm]);
|
||||
|
||||
function renderModalContent(currentStep: PaymentStep) {
|
||||
switch (currentStep) {
|
||||
case PaymentStep.Checkout:
|
||||
@ -281,6 +301,7 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
||||
totalPrice={totalPrice}
|
||||
invoice={invoice}
|
||||
checkoutInfo={checkoutInfo}
|
||||
isPaymentFormUrl
|
||||
currency={currency!}
|
||||
hasShippingOptions={hasShippingOptions}
|
||||
tipAmount={paymentState.tipAmount}
|
||||
@ -346,6 +367,7 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
||||
<ConfirmPayment
|
||||
url={confirmPaymentUrl!}
|
||||
noRedirect={isExtendedMedia}
|
||||
onPaymentFormSubmit={handlePaymentFormSubmit}
|
||||
onClose={closeModal}
|
||||
/>
|
||||
);
|
||||
@ -367,15 +389,6 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
||||
});
|
||||
}, [sendCredentialsInfo, paymentState]);
|
||||
|
||||
const sendForm = useCallback(() => {
|
||||
sendPaymentForm({
|
||||
shippingOptionId: paymentState.shipping,
|
||||
saveCredentials: paymentState.saveCredentials,
|
||||
savedCredentialId: paymentState.savedCredentialId,
|
||||
tipAmount: paymentState.tipAmount,
|
||||
});
|
||||
}, [sendPaymentForm, paymentState]);
|
||||
|
||||
const handleButtonClick = useCallback(() => {
|
||||
switch (step) {
|
||||
case PaymentStep.ShippingInfo:
|
||||
@ -407,6 +420,12 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
||||
break;
|
||||
|
||||
case PaymentStep.Checkout: {
|
||||
if (isPaymentFormUrl) {
|
||||
setIsLoading(true);
|
||||
setStep(PaymentStep.ConfirmPayment);
|
||||
return;
|
||||
}
|
||||
|
||||
if (savedInfo && !requestId && !paymentState.shipping) {
|
||||
setIsLoading(true);
|
||||
validateRequest();
|
||||
@ -455,7 +474,7 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
||||
}, [
|
||||
isEmailRequested, isNameRequested, isPhoneRequested, isShippingAddressRequested, nativeProvider, passwordValidUntil,
|
||||
paymentDispatch, paymentState, requestId, savedInfo, sendCredentials, sendForm, setStep, smartGlocalToken, step,
|
||||
stripeId, twoFaPassword, validatePaymentPassword, validateRequest,
|
||||
stripeId, twoFaPassword, validatePaymentPassword, validateRequest, isPaymentFormUrl,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -574,7 +593,11 @@ const PaymentModal: FC<OwnProps & StateProps & GlobalStateProps> = ({
|
||||
<h3>{modalHeader}</h3>
|
||||
</div>
|
||||
{step !== undefined ? (
|
||||
<Transition name="slide" activeKey={step}>
|
||||
<Transition
|
||||
name="slide"
|
||||
activeKey={step}
|
||||
cleanupKey={PaymentStep.ConfirmPayment}
|
||||
>
|
||||
<div className="content custom-scroll">
|
||||
{renderModalContent(step)}
|
||||
</div>
|
||||
@ -622,10 +645,16 @@ export default memo(withGlobal<OwnProps>(
|
||||
savedCredentials,
|
||||
temporaryPassword,
|
||||
isExtendedMedia,
|
||||
url,
|
||||
} = selectTabState(global).payment;
|
||||
|
||||
let providerName = nativeProvider;
|
||||
if (!providerName && url) {
|
||||
providerName = url.startsWith(DONATE_PROVIDER_URL) ? DONATE_PROVIDER : undefined;
|
||||
}
|
||||
|
||||
const chat = inputInvoice && 'chatId' in inputInvoice ? selectChat(global, inputInvoice.chatId) : undefined;
|
||||
const isProviderError = Boolean(invoice && (!nativeProvider || !SUPPORTED_PROVIDERS.has(nativeProvider)));
|
||||
const isProviderError = Boolean(invoice && (!providerName || !SUPPORTED_PROVIDERS.has(providerName)));
|
||||
const { needCardholderName, needCountry, needZip } = (nativeParams || {});
|
||||
const {
|
||||
isNameRequested,
|
||||
@ -644,7 +673,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
shippingOptions,
|
||||
savedInfo,
|
||||
canSaveCredentials,
|
||||
nativeProvider,
|
||||
nativeProvider: providerName,
|
||||
passwordMissing,
|
||||
isNameRequested,
|
||||
isShippingAddressRequested,
|
||||
@ -660,7 +689,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
needCountry,
|
||||
needZip,
|
||||
error,
|
||||
confirmPaymentUrl,
|
||||
confirmPaymentUrl: confirmPaymentUrl ?? url,
|
||||
isPaymentFormUrl: Boolean(!nativeProvider && url),
|
||||
countryList: global.countryList.general,
|
||||
requestId,
|
||||
hasShippingOptions: Boolean(shippingOptions?.length),
|
||||
|
||||
@ -34,6 +34,7 @@ export type TransitionProps = {
|
||||
shouldRestoreHeight?: boolean;
|
||||
shouldCleanup?: boolean;
|
||||
cleanupExceptionKey?: number;
|
||||
cleanupKey?: number;
|
||||
// Used by async components which are usually remounted during first animation
|
||||
shouldWrap?: boolean;
|
||||
wrapExceptionKey?: number;
|
||||
@ -73,6 +74,7 @@ function Transition({
|
||||
shouldRestoreHeight,
|
||||
shouldCleanup,
|
||||
cleanupExceptionKey,
|
||||
cleanupKey,
|
||||
shouldWrap,
|
||||
wrapExceptionKey,
|
||||
id,
|
||||
@ -120,13 +122,16 @@ function Transition({
|
||||
useLayoutEffect(() => {
|
||||
function cleanup() {
|
||||
if (!shouldCleanup) {
|
||||
if (cleanupKey !== undefined) {
|
||||
delete rendersRef.current[cleanupKey];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const preservedRender = cleanupExceptionKey !== undefined ? rendersRef.current[cleanupExceptionKey] : undefined;
|
||||
|
||||
rendersRef.current = preservedRender ? { [cleanupExceptionKey!]: preservedRender } : {};
|
||||
|
||||
if (cleanupExceptionKey !== undefined) {
|
||||
rendersRef.current = { [cleanupExceptionKey]: rendersRef.current[cleanupExceptionKey] };
|
||||
} else {
|
||||
rendersRef.current = {};
|
||||
}
|
||||
forceUpdate();
|
||||
}
|
||||
|
||||
@ -312,6 +317,7 @@ function Transition({
|
||||
shouldDisableAnimation,
|
||||
forceUpdate,
|
||||
withSwipeControl,
|
||||
cleanupKey,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -185,10 +185,8 @@ addActionHandler('sendPaymentForm', async (global, actions, payload): Promise<vo
|
||||
const formId = selectPaymentFormId(global, tabId);
|
||||
const requestInfoId = selectPaymentRequestId(global, tabId);
|
||||
const { nativeProvider, temporaryPassword } = selectTabState(global, tabId).payment;
|
||||
const publishableKey = nativeProvider === 'stripe'
|
||||
? selectProviderPublishableKey(global, tabId) : selectProviderPublicToken(global, tabId);
|
||||
|
||||
if (!inputInvoice || !publishableKey || !formId || !nativeProvider) {
|
||||
if (!inputInvoice || !formId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -341,6 +339,14 @@ async function sendSmartGlocalCredentials<T extends GlobalState>(
|
||||
setGlobal(global);
|
||||
}
|
||||
|
||||
addActionHandler('setSmartGlocalCardInfo', (global, actions, payload): ActionReturnType => {
|
||||
const { tabId = getCurrentTabId(), type, token } = payload;
|
||||
return setSmartGlocalCardInfo(global, {
|
||||
type,
|
||||
token,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('setPaymentStep', (global, actions, payload): ActionReturnType => {
|
||||
const { step, tabId = getCurrentTabId() } = payload;
|
||||
return setPaymentStep(global, step ?? PaymentStep.Checkout, tabId);
|
||||
|
||||
@ -472,6 +472,7 @@ export type TabState = {
|
||||
value: string;
|
||||
validUntil: number;
|
||||
};
|
||||
url?: string;
|
||||
};
|
||||
|
||||
chatCreation?: {
|
||||
@ -1504,6 +1505,10 @@ export interface ActionPayloads {
|
||||
sendCredentialsInfo: {
|
||||
credentials: ApiCredentials;
|
||||
} & WithTabId;
|
||||
setSmartGlocalCardInfo: {
|
||||
type: string;
|
||||
token: string;
|
||||
} & WithTabId;
|
||||
clearPaymentError: WithTabId | undefined;
|
||||
clearReceipt: WithTabId | undefined;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user