From d6a9ba568364a7b51fabf53248870c8002626d07 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Fri, 25 Feb 2022 22:52:29 +0200 Subject: [PATCH] Payments: Support more providers (#1641) --- src/api/gramjs/apiBuilders/payments.ts | 1 + src/api/gramjs/methods/payments.ts | 25 ++++- src/api/gramjs/provider.ts | 2 + src/api/gramjs/updater.ts | 6 +- src/api/types/payments.ts | 15 +-- src/api/types/updates.ts | 11 ++- src/assets/smartglocal-logo.png | Bin 0 -> 402 bytes src/components/left/NewChatButton.scss | 2 +- src/components/mediaViewer/ZoomControls.scss | 2 +- src/components/payment/CardInput.tsx | 6 +- src/components/payment/Checkout.scss | 12 ++- src/components/payment/Checkout.tsx | 17 ++-- src/components/payment/ConfirmPayment.scss | 13 +++ src/components/payment/ConfirmPayment.tsx | 27 ++++++ src/components/payment/ExpiryInput.tsx | 4 +- src/components/payment/PaymentInfo.tsx | 10 +- src/components/payment/PaymentModal.scss | 8 +- src/components/payment/PaymentModal.tsx | 40 +++++--- src/components/payment/Shipping.tsx | 2 +- src/components/payment/ShippingInfo.tsx | 20 ++-- src/config.ts | 2 + src/global/types.ts | 13 +-- src/modules/actions/all.ts | 1 + src/modules/actions/api/payments.ts | 96 ++++++++++++++++--- src/modules/actions/apiUpdaters/misc.ts | 16 +++- src/modules/actions/apiUpdaters/payments.ts | 15 +++ src/modules/reducers/payments.ts | 25 +++++ src/modules/selectors/payments.ts | 10 +- src/types/index.ts | 1 + src/util/getReadableErrorText.ts | 1 + 30 files changed, 324 insertions(+), 79 deletions(-) create mode 100644 src/assets/smartglocal-logo.png create mode 100644 src/components/payment/ConfirmPayment.scss create mode 100644 src/components/payment/ConfirmPayment.tsx create mode 100644 src/modules/actions/apiUpdaters/payments.ts diff --git a/src/api/gramjs/apiBuilders/payments.ts b/src/api/gramjs/apiBuilders/payments.ts index 08f0f12ef..359309ffc 100644 --- a/src/api/gramjs/apiBuilders/payments.ts +++ b/src/api/gramjs/apiBuilders/payments.ts @@ -116,6 +116,7 @@ export function buildPaymentForm(form: GramJs.payments.PaymentForm) { needCountry: nativeData.need_country, needZip: nativeData.need_zip, publishableKey: nativeData.publishable_key, + publicToken: nativeData?.public_token, }, }; } diff --git a/src/api/gramjs/methods/payments.ts b/src/api/gramjs/methods/payments.ts index 7246e4626..4c2eb8c78 100644 --- a/src/api/gramjs/methods/payments.ts +++ b/src/api/gramjs/methods/payments.ts @@ -3,7 +3,13 @@ import { Api as GramJs } from '../../../lib/gramjs'; import { invokeRequest } from './client'; import { buildInputPeer, buildShippingInfo } from '../gramjsBuilders'; import { buildShippingOptions, buildPaymentForm, buildReceipt } from '../apiBuilders/payments'; -import { ApiChat } from '../../types'; +import { ApiChat, OnApiUpdate } from '../../types'; + +let onUpdate: OnApiUpdate; + +export function init(_onUpdate: OnApiUpdate) { + onUpdate = _onUpdate; +} export async function validateRequestedInfo({ chat, @@ -40,7 +46,7 @@ export async function validateRequestedInfo({ }; } -export function sendPaymentForm({ +export async function sendPaymentForm({ chat, messageId, formId, @@ -55,7 +61,7 @@ export function sendPaymentForm({ requestedInfoId?: string; shippingOptionId?: string; }) { - return invokeRequest(new GramJs.payments.SendPaymentForm({ + const result = await invokeRequest(new GramJs.payments.SendPaymentForm({ formId: BigInt(formId), peer: buildInputPeer(chat.id, chat.accessHash), msgId: messageId, @@ -65,7 +71,18 @@ export function sendPaymentForm({ save: credentials.save, data: new GramJs.DataJSON({ data: JSON.stringify(credentials.data) }), }), - }), true); + })); + + if (result instanceof GramJs.payments.PaymentVerificationNeeded) { + onUpdate({ + '@type': 'updatePaymentVerificationNeeded', + url: result.url, + }); + + return undefined; + } + + return Boolean(result); } export async function getPaymentForm({ diff --git a/src/api/gramjs/provider.ts b/src/api/gramjs/provider.ts index 0a509dea6..de5ab435c 100644 --- a/src/api/gramjs/provider.ts +++ b/src/api/gramjs/provider.ts @@ -18,6 +18,7 @@ import { init as initStickers } from './methods/symbols'; import { init as initManagement } from './methods/management'; import { init as initTwoFaSettings } from './methods/twoFaSettings'; import { init as initCalls } from './methods/calls'; +import { init as initPayments } from './methods/payments'; import * as methods from './methods'; let onUpdate: OnApiUpdate; @@ -34,6 +35,7 @@ export async function initApi(_onUpdate: OnApiUpdate, initialArgs: ApiInitialArg initManagement(handleUpdate); initTwoFaSettings(handleUpdate); initCalls(handleUpdate); + initPayments(handleUpdate); await initClient(handleUpdate, initialArgs); } diff --git a/src/api/gramjs/updater.ts b/src/api/gramjs/updater.ts index d23cb6983..136943269 100644 --- a/src/api/gramjs/updater.ts +++ b/src/api/gramjs/updater.ts @@ -188,7 +188,11 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) { if (update.message instanceof GramJs.MessageService) { const { action } = update.message; - if (action instanceof GramJs.MessageActionChatEditTitle) { + if (action instanceof GramJs.MessageActionPaymentSent) { + onUpdate({ + '@type': 'updatePaymentStateCompleted', + }); + } else if (action instanceof GramJs.MessageActionChatEditTitle) { onUpdate({ '@type': 'updateChat', id: message.chatId, diff --git a/src/api/types/payments.ts b/src/api/types/payments.ts index 1ded688e8..c3fe6cc1e 100644 --- a/src/api/types/payments.ts +++ b/src/api/types/payments.ts @@ -32,12 +32,15 @@ export interface ApiPaymentForm { currency?: string; prices?: ApiLabeledPrice[]; }; - nativeParams: { - needCardholderName: boolean; - needCountry: boolean; - needZip: boolean; - publishableKey: string; - }; + nativeParams: ApiPaymentFormNativeParams; +} + +export interface ApiPaymentFormNativeParams { + needCardholderName?: boolean; + needCountry?: boolean; + needZip?: boolean; + publishableKey?: string; + publicToken?: string; } export interface ApiLabeledPrice { diff --git a/src/api/types/updates.ts b/src/api/types/updates.ts index 1c425d29d..3aab76bb1 100644 --- a/src/api/types/updates.ts +++ b/src/api/types/updates.ts @@ -381,6 +381,15 @@ export type ApiUpdatePeerBlocked = { isBlocked: boolean; }; +export type ApiUpdatePaymentVerificationNeeded = { + '@type': 'updatePaymentVerificationNeeded'; + url: string; +}; + +export type ApiUpdatePaymentStateCompleted = { + '@type': 'updatePaymentStateCompleted'; +}; + export type ApiUpdatePrivacy = { '@type': 'updatePrivacy'; key: 'phoneNumber' | 'lastSeen' | 'profilePhoto' | 'forwards' | 'chatInvite'; @@ -467,7 +476,7 @@ export type ApiUpdate = ( ApiUpdateServerTimeOffset | ApiUpdateShowInvite | ApiUpdateMessageReactions | ApiUpdateGroupCallParticipants | ApiUpdateGroupCallConnection | ApiUpdateGroupCall | ApiUpdateGroupCallStreams | ApiUpdateGroupCallConnectionState | ApiUpdateGroupCallLeavePresentation | ApiUpdateGroupCallChatId | - ApiUpdatePendingJoinRequests + ApiUpdatePendingJoinRequests | ApiUpdatePaymentVerificationNeeded | ApiUpdatePaymentStateCompleted ); export type OnApiUpdate = (update: ApiUpdate) => void; diff --git a/src/assets/smartglocal-logo.png b/src/assets/smartglocal-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..386061c3c508ef0fb9126a1a37da68b07004dffd GIT binary patch literal 402 zcmV;D0d4+?P)Px#Fi=cXMF4vL|Ns99fd9AI{{VRZE0ONt|Bvckod5s<07*naR7l6|m7z|`!qyhKM>pYAW*oqgjPM1&ve8qJZK+&Z7VZAGHx&@G2#4@nu__Q5=sY&^IR7e*-%_TQLDH19XUblmGw#07*qoM6N<$g5i_@% literal 0 HcmV?d00001 diff --git a/src/components/left/NewChatButton.scss b/src/components/left/NewChatButton.scss index ab31c9528..d35f87f4f 100644 --- a/src/components/left/NewChatButton.scss +++ b/src/components/left/NewChatButton.scss @@ -22,7 +22,7 @@ @media (max-width: 600px) { // Force rendering in the composite layer to fix the z-index rendering issue - transform: translate3d(0, 0, 10px); + transform: translate3d(0, 0, 0.625rem); transform-style: preserve-3d; } } diff --git a/src/components/mediaViewer/ZoomControls.scss b/src/components/mediaViewer/ZoomControls.scss index 9c3684fc2..3ec69ccbf 100644 --- a/src/components/mediaViewer/ZoomControls.scss +++ b/src/components/mediaViewer/ZoomControls.scss @@ -7,7 +7,7 @@ width: 100%; height: 3.375rem; max-width: 274px; - transform: translate3d(-50%, 0, 10px); + transform: translate3d(-50%, 0, 0.625rem); transition: opacity 0.3s ease-in; pointer-events: none; diff --git a/src/components/payment/CardInput.tsx b/src/components/payment/CardInput.tsx index e1072ecac..51b2a555c 100644 --- a/src/components/payment/CardInput.tsx +++ b/src/components/payment/CardInput.tsx @@ -2,9 +2,10 @@ import React, { FC, memo, useCallback, useState, useRef, useEffect, } from '../../lib/teact/teact'; -import useFocusAfterAnimation from '../../hooks/useFocusAfterAnimation'; import { formatCardNumber } from '../middle/helpers/inputFormatters'; import { detectCardType, CardType } from '../common/helpers/detectCardType'; +import useFocusAfterAnimation from '../../hooks/useFocusAfterAnimation'; +import useLang from '../../hooks/useLang'; import InputText from '../ui/InputText'; @@ -22,6 +23,7 @@ export type OwnProps = { }; const CardInput : FC = ({ value, error, onChange }) => { + const lang = useLang(); // eslint-disable-next-line no-null/no-null const cardNumberRef = useRef(null); @@ -51,7 +53,7 @@ const CardInput : FC = ({ value, error, onChange }) => { {cardIcon} = ({ ) }
- {paymentMethod && renderCheckoutItem('icon-card', paymentMethod, 'Payment method')} - {paymentProvider && renderCheckoutItem('stripe-provider', paymentProvider, 'Payment provider')} - {shippingAddress && renderCheckoutItem('icon-location', shippingAddress, 'Shipping address')} - {name && renderCheckoutItem('icon-user', name, 'Name')} - {phone && renderCheckoutItem('icon-phone', phone, 'Phone number')} - {shippingMethod && renderCheckoutItem('icon-truck', shippingMethod, 'Shipping method')} + {paymentMethod && renderCheckoutItem('icon-card', paymentMethod, lang('PaymentCheckoutMethod'))} + {paymentProvider && renderCheckoutItem( + buildClassName('provider', paymentProvider.toLowerCase()), + paymentProvider, + lang('PaymentCheckoutProvider'), + )} + {shippingAddress && renderCheckoutItem('icon-location', shippingAddress, lang('PaymentShippingAddress'))} + {name && renderCheckoutItem('icon-user', name, lang('PaymentCheckoutName'))} + {phone && renderCheckoutItem('icon-phone', phone, lang('PaymentCheckoutPhoneNumber'))} + {shippingMethod && renderCheckoutItem('icon-truck', shippingMethod, lang('PaymentCheckoutShippingMethod'))}
); diff --git a/src/components/payment/ConfirmPayment.scss b/src/components/payment/ConfirmPayment.scss new file mode 100644 index 000000000..78198fb6d --- /dev/null +++ b/src/components/payment/ConfirmPayment.scss @@ -0,0 +1,13 @@ +.ConfirmPayment { + display: flex; + height: 100%; + border-bottom-left-radius: var(--border-radius-default-small); + border-bottom-right-radius: var(--border-radius-default-small); + overflow: hidden; + + &__content { + width: 100%; + height: 100%; + border: none; + } +} diff --git a/src/components/payment/ConfirmPayment.tsx b/src/components/payment/ConfirmPayment.tsx new file mode 100644 index 000000000..8b3645ab1 --- /dev/null +++ b/src/components/payment/ConfirmPayment.tsx @@ -0,0 +1,27 @@ +import React, { FC, memo } from '../../lib/teact/teact'; + +import useLang from '../../hooks/useLang'; + +import './ConfirmPayment.scss'; + +export type OwnProps = { + url: string; +}; + +const ConfirmPayment: FC = ({ url }) => { + const lang = useLang(); + + return ( +
+