446 lines
11 KiB
TypeScript
446 lines
11 KiB
TypeScript
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
|
import { callApi } from '../../../api/gramjs';
|
|
|
|
import type { ApiChat, ApiInvoice, ApiRequestInputInvoice } from '../../../api/types';
|
|
import { PaymentStep } from '../../../types';
|
|
|
|
import { DEBUG_PAYMENT_SMART_GLOCAL } from '../../../config';
|
|
import {
|
|
selectPaymentRequestId,
|
|
selectProviderPublishableKey,
|
|
selectStripeCredentials,
|
|
selectChatMessage,
|
|
selectChat,
|
|
selectPaymentFormId,
|
|
selectProviderPublicToken,
|
|
selectSmartGlocalCredentials,
|
|
selectPaymentInputInvoice,
|
|
} from '../../selectors';
|
|
import { getStripeError } from '../../helpers';
|
|
import { buildQueryString } from '../../../util/requestQuery';
|
|
import {
|
|
updateShippingOptions,
|
|
setPaymentStep,
|
|
setRequestInfoId,
|
|
setPaymentForm,
|
|
setStripeCardInfo,
|
|
setReceipt,
|
|
clearPayment,
|
|
closeInvoice,
|
|
setSmartGlocalCardInfo, addUsers, setInvoiceInfo, updatePayment,
|
|
} from '../../reducers';
|
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
|
|
|
addActionHandler('validateRequestedInfo', (global, actions, payload) => {
|
|
const inputInvoice = selectPaymentInputInvoice(global);
|
|
if (!inputInvoice) {
|
|
return;
|
|
}
|
|
|
|
const { requestInfo, saveInfo } = payload;
|
|
if ('slug' in inputInvoice) {
|
|
void validateRequestedInfo(inputInvoice, requestInfo, saveInfo);
|
|
} else {
|
|
const chat = selectChat(global, inputInvoice.chatId);
|
|
if (!chat) {
|
|
return;
|
|
}
|
|
|
|
void validateRequestedInfo({
|
|
chat,
|
|
messageId: inputInvoice.messageId,
|
|
}, requestInfo, saveInfo);
|
|
}
|
|
});
|
|
|
|
addActionHandler('openInvoice', async (global, actions, payload) => {
|
|
let invoice: ApiInvoice | undefined;
|
|
if ('slug' in payload) {
|
|
invoice = await getPaymentForm({ slug: payload.slug });
|
|
} else {
|
|
const chat = selectChat(global, payload.chatId);
|
|
if (!chat) {
|
|
return;
|
|
}
|
|
|
|
invoice = await getPaymentForm({
|
|
chat,
|
|
messageId: payload.messageId,
|
|
});
|
|
}
|
|
|
|
if (!invoice) {
|
|
return;
|
|
}
|
|
|
|
global = getGlobal();
|
|
global = setInvoiceInfo(global, invoice);
|
|
setGlobal({
|
|
...global,
|
|
payment: {
|
|
...global.payment,
|
|
inputInvoice: payload,
|
|
isPaymentModalOpen: true,
|
|
status: 'cancelled',
|
|
},
|
|
});
|
|
});
|
|
|
|
async function getPaymentForm(inputInvoice: ApiRequestInputInvoice): Promise<ApiInvoice | undefined> {
|
|
const result = await callApi('getPaymentForm', inputInvoice);
|
|
if (!result) {
|
|
return undefined;
|
|
}
|
|
|
|
const { form, invoice } = result;
|
|
|
|
let global = setPaymentForm(getGlobal(), form);
|
|
global = setPaymentStep(global, PaymentStep.Checkout);
|
|
setGlobal(global);
|
|
|
|
return invoice;
|
|
}
|
|
|
|
addActionHandler('getReceipt', (global, actions, payload) => {
|
|
const { receiptMessageId, chatId, messageId } = payload;
|
|
const chat = chatId && selectChat(global, chatId);
|
|
if (!messageId || !receiptMessageId || !chat) {
|
|
return;
|
|
}
|
|
|
|
void getReceipt(chat, messageId, receiptMessageId);
|
|
});
|
|
|
|
async function getReceipt(chat: ApiChat, messageId: number, receiptMessageId: number) {
|
|
const result = await callApi('getReceipt', chat, receiptMessageId);
|
|
if (!result) {
|
|
return;
|
|
}
|
|
|
|
let global = getGlobal();
|
|
const message = selectChatMessage(global, chat.id, messageId);
|
|
global = setReceipt(global, result, message);
|
|
setGlobal(global);
|
|
}
|
|
|
|
addActionHandler('clearPaymentError', (global) => {
|
|
setGlobal({
|
|
...global,
|
|
payment: {
|
|
...global.payment,
|
|
error: undefined,
|
|
},
|
|
});
|
|
});
|
|
|
|
addActionHandler('clearReceipt', (global) => {
|
|
setGlobal({
|
|
...global,
|
|
payment: {
|
|
...global.payment,
|
|
receipt: undefined,
|
|
},
|
|
});
|
|
});
|
|
|
|
addActionHandler('sendCredentialsInfo', (global, actions, payload) => {
|
|
const { nativeProvider } = global.payment;
|
|
const { credentials } = payload;
|
|
const { data } = credentials;
|
|
|
|
if (nativeProvider === 'stripe') {
|
|
const publishableKey = selectProviderPublishableKey(global);
|
|
if (!publishableKey) {
|
|
return;
|
|
}
|
|
void sendStripeCredentials(data, publishableKey);
|
|
} else if (nativeProvider === 'smartglocal') {
|
|
const publicToken = selectProviderPublicToken(global);
|
|
if (!publicToken) {
|
|
return;
|
|
}
|
|
void sendSmartGlocalCredentials(data, publicToken);
|
|
}
|
|
});
|
|
|
|
addActionHandler('sendPaymentForm', async (global, actions, payload) => {
|
|
const {
|
|
shippingOptionId, saveCredentials, savedCredentialId, tipAmount,
|
|
} = payload;
|
|
const inputInvoice = selectPaymentInputInvoice(global);
|
|
const formId = selectPaymentFormId(global);
|
|
const requestInfoId = selectPaymentRequestId(global);
|
|
const { nativeProvider, temporaryPassword } = global.payment;
|
|
const publishableKey = nativeProvider === 'stripe'
|
|
? selectProviderPublishableKey(global) : selectProviderPublicToken(global);
|
|
|
|
if (!inputInvoice || !publishableKey || !formId || !nativeProvider) {
|
|
return;
|
|
}
|
|
|
|
let requestInputInvoice;
|
|
if ('slug' in inputInvoice) {
|
|
requestInputInvoice = {
|
|
slug: inputInvoice.slug,
|
|
};
|
|
} else {
|
|
const chat = selectChat(global, inputInvoice.chatId);
|
|
if (!chat) {
|
|
return;
|
|
}
|
|
|
|
requestInputInvoice = {
|
|
chat,
|
|
messageId: inputInvoice.messageId,
|
|
};
|
|
}
|
|
|
|
setGlobal(updatePayment(global, { status: 'pending' }));
|
|
|
|
const credentials = {
|
|
save: saveCredentials,
|
|
data: nativeProvider === 'stripe' ? selectStripeCredentials(global) : selectSmartGlocalCredentials(global),
|
|
};
|
|
const result = await callApi('sendPaymentForm', {
|
|
inputInvoice: requestInputInvoice,
|
|
formId,
|
|
credentials,
|
|
requestedInfoId: requestInfoId,
|
|
shippingOptionId,
|
|
savedCredentialId,
|
|
temporaryPassword: temporaryPassword?.value,
|
|
tipAmount,
|
|
});
|
|
|
|
if (!result) {
|
|
return;
|
|
}
|
|
|
|
global = getGlobal();
|
|
global = clearPayment(global);
|
|
global = updatePayment(global, { status: 'paid' });
|
|
global = closeInvoice(global);
|
|
setGlobal(global);
|
|
});
|
|
|
|
async function sendStripeCredentials(
|
|
data: {
|
|
cardNumber: string;
|
|
cardholder?: string;
|
|
expiryMonth: string;
|
|
expiryYear: string;
|
|
cvv: string;
|
|
country: string;
|
|
zip: string;
|
|
},
|
|
publishableKey: string,
|
|
) {
|
|
const query = buildQueryString({
|
|
'card[number]': data.cardNumber,
|
|
'card[exp_month]': data.expiryMonth,
|
|
'card[exp_year]': data.expiryYear,
|
|
'card[cvc]': data.cvv,
|
|
'card[address_zip]': data.zip,
|
|
'card[address_country]': data.country,
|
|
});
|
|
|
|
const response = await fetch(`https://api.stripe.com/v1/tokens${query}`, {
|
|
method: 'POST',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
Authorization: `Bearer ${publishableKey}`,
|
|
},
|
|
});
|
|
const result = await response.json();
|
|
if (result.error) {
|
|
const error = getStripeError(result.error);
|
|
const global = getGlobal();
|
|
setGlobal({
|
|
...global,
|
|
payment: {
|
|
...global.payment,
|
|
status: 'failed',
|
|
error: {
|
|
...error,
|
|
},
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
let global = setStripeCardInfo(getGlobal(), {
|
|
type: result.type,
|
|
id: result.id,
|
|
});
|
|
global = setPaymentStep(global, PaymentStep.Checkout);
|
|
setGlobal(global);
|
|
}
|
|
|
|
async function sendSmartGlocalCredentials(
|
|
data: {
|
|
cardNumber: string;
|
|
cardholder?: string;
|
|
expiryMonth: string;
|
|
expiryYear: string;
|
|
cvv: string;
|
|
},
|
|
publicToken: string,
|
|
) {
|
|
const params = {
|
|
card: {
|
|
number: data.cardNumber.replace(/\D+/g, ''),
|
|
expiration_month: data.expiryMonth,
|
|
expiration_year: data.expiryYear,
|
|
security_code: data.cvv.replace(/\D+/g, ''),
|
|
},
|
|
};
|
|
const url = DEBUG_PAYMENT_SMART_GLOCAL
|
|
? 'https://tgb-playground.smart-glocal.com/cds/v1/tokenize/card'
|
|
: 'https://tgb.smart-glocal.com/cds/v1/tokenize/card';
|
|
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
Accept: 'application/json',
|
|
'Content-Type': 'application/json',
|
|
'X-PUBLIC-TOKEN': publicToken,
|
|
},
|
|
body: JSON.stringify(params),
|
|
});
|
|
const result = await response.json();
|
|
|
|
if (result.status !== 'ok') {
|
|
// TODO после получения документации сделать аналог getStripeError(result.error);
|
|
const error = { description: 'payment error' };
|
|
const global = getGlobal();
|
|
setGlobal({
|
|
...global,
|
|
payment: {
|
|
...global.payment,
|
|
status: 'failed',
|
|
error: {
|
|
...error,
|
|
},
|
|
},
|
|
});
|
|
return;
|
|
}
|
|
|
|
let global = setSmartGlocalCardInfo(getGlobal(), {
|
|
type: 'card',
|
|
token: result.data.token,
|
|
});
|
|
global = setPaymentStep(global, PaymentStep.Checkout);
|
|
setGlobal(global);
|
|
}
|
|
|
|
addActionHandler('setPaymentStep', (global, actions, payload = {}) => {
|
|
return setPaymentStep(global, payload.step ?? PaymentStep.Checkout);
|
|
});
|
|
|
|
addActionHandler('closePremiumModal', (global, actions, payload) => {
|
|
if (!global.premiumModal) return undefined;
|
|
const { isClosed } = payload || {};
|
|
return {
|
|
...global,
|
|
premiumModal: {
|
|
...global.premiumModal,
|
|
...(isClosed && { isOpen: false }),
|
|
isClosing: !isClosed,
|
|
},
|
|
};
|
|
});
|
|
|
|
addActionHandler('openPremiumModal', async (global, actions, payload) => {
|
|
const {
|
|
initialSection, fromUserId, isSuccess, isGift, monthsAmount, toUserId,
|
|
} = payload || {};
|
|
|
|
actions.loadPremiumStickers();
|
|
|
|
const result = await callApi('fetchPremiumPromo');
|
|
if (!result) return;
|
|
|
|
global = getGlobal();
|
|
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
|
|
|
setGlobal({
|
|
...global,
|
|
premiumModal: {
|
|
promo: result.promo,
|
|
initialSection,
|
|
isOpen: true,
|
|
fromUserId,
|
|
toUserId,
|
|
isGift,
|
|
monthsAmount,
|
|
isSuccess,
|
|
},
|
|
});
|
|
});
|
|
|
|
addActionHandler('openGiftPremiumModal', async (global, actions, payload) => {
|
|
const { forUserId } = payload || {};
|
|
const result = await callApi('fetchPremiumPromo');
|
|
if (!result) return;
|
|
|
|
global = getGlobal();
|
|
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
|
|
|
// TODO Support all subscription options
|
|
const month = result.promo.options.find((option) => option.months === 1)!;
|
|
|
|
setGlobal({
|
|
...global,
|
|
giftPremiumModal: {
|
|
isOpen: true,
|
|
forUserId,
|
|
monthlyCurrency: month.currency,
|
|
monthlyAmount: month.amount,
|
|
},
|
|
});
|
|
});
|
|
|
|
addActionHandler('closeGiftPremiumModal', (global) => {
|
|
setGlobal({
|
|
...global,
|
|
giftPremiumModal: { isOpen: false },
|
|
});
|
|
});
|
|
|
|
addActionHandler('validatePaymentPassword', async (global, actions, { password }) => {
|
|
const result = await callApi('fetchTemporaryPaymentPassword', password);
|
|
|
|
global = getGlobal();
|
|
|
|
if (!result) {
|
|
global = updatePayment(global, { error: { message: 'Unknown Error', field: 'password' } });
|
|
} else if ('error' in result) {
|
|
global = updatePayment(global, { error: { message: result.error, field: 'password' } });
|
|
} else {
|
|
global = updatePayment(global, { temporaryPassword: result, step: PaymentStep.Checkout });
|
|
}
|
|
|
|
setGlobal(global);
|
|
});
|
|
|
|
async function validateRequestedInfo(inputInvoice: ApiRequestInputInvoice, requestInfo: any, shouldSave?: true) {
|
|
const result = await callApi('validateRequestedInfo', {
|
|
inputInvoice, requestInfo, shouldSave,
|
|
});
|
|
if (!result) {
|
|
return;
|
|
}
|
|
|
|
const { id, shippingOptions } = result;
|
|
|
|
let global = setRequestInfoId(getGlobal(), id);
|
|
if (shippingOptions) {
|
|
global = updateShippingOptions(global, shippingOptions);
|
|
global = setPaymentStep(global, PaymentStep.Shipping);
|
|
} else {
|
|
global = setPaymentStep(global, PaymentStep.Checkout);
|
|
}
|
|
setGlobal(global);
|
|
}
|