2025-01-27 23:50:53 +01:00

692 lines
15 KiB
TypeScript

import BigInt from 'big-integer';
import { Api as GramJs } from '../../../lib/gramjs';
import type {
ApiChat,
ApiInputStorePaymentPurpose,
ApiPeer,
ApiRequestInputInvoice,
ApiRequestInputSavedStarGift,
ApiStarGiftRegular,
ApiThemeParameters,
} from '../../types';
import { DEBUG } from '../../../config';
import {
buildApiSavedStarGift,
buildApiStarGift,
buildApiStarGiftAttribute,
} from '../apiBuilders/gifts';
import {
buildApiBoost,
buildApiBoostsStatus,
buildApiCheckedGiftCode,
buildApiGiveawayInfo,
buildApiMyBoost,
buildApiPaymentForm,
buildApiPremiumGiftCodeOption,
buildApiPremiumPromo,
buildApiReceipt,
buildApiStarsAmount,
buildApiStarsGiftOptions,
buildApiStarsGiveawayOptions,
buildApiStarsSubscription,
buildApiStarsTransaction,
buildApiStarTopupOption,
buildShippingOptions,
} from '../apiBuilders/payments';
import { buildApiPeerId } from '../apiBuilders/peers';
import {
buildInputInvoice,
buildInputPeer,
buildInputSavedStarGift,
buildInputStorePaymentPurpose,
buildInputThemeParams,
buildShippingInfo,
} from '../gramjsBuilders';
import {
deserializeBytes,
serializeBytes,
} from '../helpers';
import localDb from '../localDb';
import { sendApiUpdate } from '../updates/apiUpdateEmitter';
import { handleGramJsUpdate, invokeRequest } from './client';
import { getTemporaryPaymentPassword } from './twoFaSettings';
export async function validateRequestedInfo({
inputInvoice,
requestInfo,
shouldSave,
}: {
inputInvoice: ApiRequestInputInvoice;
requestInfo: GramJs.TypePaymentRequestedInfo;
shouldSave?: boolean;
}): Promise<{
id: string;
shippingOptions: any;
} | undefined> {
const result = await invokeRequest(new GramJs.payments.ValidateRequestedInfo({
invoice: buildInputInvoice(inputInvoice),
save: shouldSave || undefined,
info: buildShippingInfo(requestInfo),
}));
if (!result) {
return undefined;
}
const { id, shippingOptions } = result;
if (!id) {
return undefined;
}
return {
id,
shippingOptions: buildShippingOptions(shippingOptions),
};
}
export async function sendPaymentForm({
inputInvoice,
formId,
requestedInfoId,
shippingOptionId,
credentials,
savedCredentialId,
temporaryPassword,
tipAmount,
}: {
inputInvoice: ApiRequestInputInvoice;
formId: string;
credentials: any;
requestedInfoId?: string;
shippingOptionId?: string;
savedCredentialId?: string;
temporaryPassword?: string;
tipAmount?: number;
}) {
const inputCredentials = temporaryPassword && savedCredentialId
? new GramJs.InputPaymentCredentialsSaved({
id: savedCredentialId,
tmpPassword: deserializeBytes(temporaryPassword),
})
: new GramJs.InputPaymentCredentials({
save: credentials.save,
data: new GramJs.DataJSON({ data: JSON.stringify(credentials.data) }),
});
const result = await invokeRequest(new GramJs.payments.SendPaymentForm({
formId: BigInt(formId),
invoice: buildInputInvoice(inputInvoice),
requestedInfoId,
shippingOptionId,
credentials: inputCredentials,
...(tipAmount && { tipAmount: BigInt(tipAmount) }),
}));
if (!result) return false;
if (result instanceof GramJs.payments.PaymentVerificationNeeded) {
sendApiUpdate({
'@type': 'updatePaymentVerificationNeeded',
url: result.url,
});
return undefined;
} else {
handleGramJsUpdate(result.updates);
}
return Boolean(result);
}
export async function sendStarPaymentForm({
formId,
inputInvoice,
}: {
formId: string;
inputInvoice: ApiRequestInputInvoice;
}) {
const result = await invokeRequest(new GramJs.payments.SendStarsForm({
formId: BigInt(formId),
invoice: buildInputInvoice(inputInvoice),
}));
if (!result) return undefined;
if (result instanceof GramJs.payments.PaymentVerificationNeeded) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.warn('Unexpected PaymentVerificationNeeded in sendStarsForm');
}
return undefined;
}
handleGramJsUpdate(result.updates);
if (inputInvoice.type === 'chatInviteSubscription') {
const updates = 'updates' in result.updates ? result.updates.updates : undefined;
const mtpChannelId = updates?.find((update): update is GramJs.UpdateChannel => (
update instanceof GramJs.UpdateChannel
))?.channelId;
if (!mtpChannelId) {
return undefined;
}
return {
channelId: buildApiPeerId(mtpChannelId, 'channel'),
};
}
return {
completed: true,
};
}
export async function getPaymentForm(inputInvoice: ApiRequestInputInvoice, theme?: ApiThemeParameters) {
try {
const result = await invokeRequest(new GramJs.payments.GetPaymentForm({
invoice: buildInputInvoice(inputInvoice),
themeParams: theme ? buildInputThemeParams(theme) : undefined,
}), {
shouldThrow: true,
});
if (!result) {
return undefined;
}
return buildApiPaymentForm(result);
} catch (err) {
if (err instanceof Error) {
// Can be removed if separate error handling is added to payment UI
sendApiUpdate({
'@type': 'error',
error: {
message: err.message,
hasErrorKey: true,
},
});
return {
error: err.message,
};
}
return undefined;
}
}
export async function getReceipt(chat: ApiChat, msgId: number) {
const result = await invokeRequest(new GramJs.payments.GetPaymentReceipt({
peer: buildInputPeer(chat.id, chat.accessHash),
msgId,
}));
if (!result) {
return undefined;
}
return {
receipt: buildApiReceipt(result),
};
}
export async function fetchPremiumPromo() {
const result = await invokeRequest(new GramJs.help.GetPremiumPromo());
if (!result) return undefined;
result.videos.forEach((video) => {
if (video instanceof GramJs.Document) {
localDb.documents[video.id.toString()] = video;
}
});
return {
promo: buildApiPremiumPromo(result),
};
}
export async function fetchTemporaryPaymentPassword(password: string) {
const result = await getTemporaryPaymentPassword(password);
if (!result) {
return undefined;
}
if ('error' in result) {
return result;
}
return {
value: serializeBytes(result.tmpPassword),
validUntil: result.validUntil,
};
}
export async function fetchMyBoosts() {
const result = await invokeRequest(new GramJs.premium.GetMyBoosts());
if (!result) return undefined;
const boosts = result.myBoosts.map(buildApiMyBoost);
return {
boosts,
};
}
export async function applyBoost({
chat,
slots,
} : {
chat: ApiChat;
slots: number[];
}) {
const result = await invokeRequest(new GramJs.premium.ApplyBoost({
peer: buildInputPeer(chat.id, chat.accessHash),
slots,
}));
if (!result) return undefined;
const boosts = result.myBoosts.map(buildApiMyBoost);
return {
boosts,
};
}
export async function fetchBoostStatus({
chat,
}: {
chat: ApiChat;
}) {
const result = await invokeRequest(new GramJs.premium.GetBoostsStatus({
peer: buildInputPeer(chat.id, chat.accessHash),
}));
if (!result) {
return undefined;
}
return buildApiBoostsStatus(result);
}
export async function fetchBoostList({
chat,
isGifts,
offset = '',
limit,
}: {
chat: ApiChat;
isGifts?: boolean;
offset?: string;
limit?: number;
}) {
const result = await invokeRequest(new GramJs.premium.GetBoostsList({
peer: buildInputPeer(chat.id, chat.accessHash),
gifts: isGifts || undefined,
offset,
limit,
}));
if (!result) {
return undefined;
}
const boostList = result.boosts.map(buildApiBoost);
return {
count: result.count,
boostList,
nextOffset: result.nextOffset,
};
}
export async function fetchGiveawayInfo({
peer,
messageId,
}: {
peer: ApiPeer;
messageId: number;
}) {
const result = await invokeRequest(new GramJs.payments.GetGiveawayInfo({
peer: buildInputPeer(peer.id, peer.accessHash),
msgId: messageId,
}));
if (!result) {
return undefined;
}
return buildApiGiveawayInfo(result);
}
export async function checkGiftCode({
slug,
}: {
slug: string;
}) {
const result = await invokeRequest(new GramJs.payments.CheckGiftCode({
slug,
}));
if (!result) {
return undefined;
}
return {
code: buildApiCheckedGiftCode(result),
};
}
export function applyGiftCode({
slug,
}: {
slug: string;
}) {
return invokeRequest(new GramJs.payments.ApplyGiftCode({
slug,
}), {
shouldReturnTrue: true,
});
}
export async function getPremiumGiftCodeOptions({
chat,
}: {
chat?: ApiChat;
}) {
const result = await invokeRequest(new GramJs.payments.GetPremiumGiftCodeOptions({
boostPeer: chat && buildInputPeer(chat.id, chat.accessHash),
}));
if (!result) {
return undefined;
}
return result.map(buildApiPremiumGiftCodeOption);
}
export async function getStarsGiftOptions({
chat,
}: {
chat?: ApiChat;
}) {
const result = await invokeRequest(new GramJs.payments.GetStarsGiftOptions({
userId: chat && buildInputPeer(chat.id, chat.accessHash),
}));
if (!result) {
return undefined;
}
return result.map(buildApiStarsGiftOptions);
}
export async function fetchStarsGiveawayOptions() {
const result = await invokeRequest(new GramJs.payments.GetStarsGiveawayOptions());
if (!result) {
return undefined;
}
return result.map(buildApiStarsGiveawayOptions);
}
export async function fetchStarGifts() {
const result = await invokeRequest(new GramJs.payments.GetStarGifts({}));
if (!result || result instanceof GramJs.payments.StarGiftsNotModified) {
return undefined;
}
// Right now, only regular star gifts can be bought, but API are not specific
return result.gifts.map(buildApiStarGift).filter((gift): gift is ApiStarGiftRegular => gift.type === 'starGift');
}
export async function fetchSavedStarGifts({
peer,
offset = '',
limit,
}: {
peer: ApiPeer;
offset?: string;
limit?: number;
}) {
const result = await invokeRequest(new GramJs.payments.GetSavedStarGifts({
peer: buildInputPeer(peer.id, peer.accessHash),
offset,
limit,
}));
if (!result) {
return undefined;
}
const gifts = result.gifts.map((g) => buildApiSavedStarGift(g, peer.id));
return {
gifts,
nextOffset: result.nextOffset,
};
}
export function saveStarGift({
inputGift,
shouldUnsave,
}: {
inputGift: ApiRequestInputSavedStarGift;
shouldUnsave?: boolean;
}) {
return invokeRequest(new GramJs.payments.SaveStarGift({
stargift: buildInputSavedStarGift(inputGift),
unsave: shouldUnsave || undefined,
}));
}
export function convertStarGift({
inputSavedGift,
}: {
inputSavedGift: ApiRequestInputSavedStarGift;
}) {
return invokeRequest(new GramJs.payments.ConvertStarGift({
stargift: buildInputSavedStarGift(inputSavedGift),
}));
}
export function launchPrepaidGiveaway({
chat,
giveawayId,
paymentPurpose,
}: {
chat: ApiChat;
giveawayId: string;
paymentPurpose: ApiInputStorePaymentPurpose;
}) {
return invokeRequest(new GramJs.payments.LaunchPrepaidGiveaway({
peer: buildInputPeer(chat.id, chat.accessHash),
giveawayId: BigInt(giveawayId),
purpose: buildInputStorePaymentPurpose(paymentPurpose),
}), {
shouldReturnTrue: true,
});
}
export async function fetchStarsStatus() {
const result = await invokeRequest(new GramJs.payments.GetStarsStatus({
peer: new GramJs.InputPeerSelf(),
}));
if (!result) {
return undefined;
}
return {
nextHistoryOffset: result.nextOffset,
history: result.history?.map(buildApiStarsTransaction),
nextSubscriptionOffset: result.subscriptionsNextOffset,
subscriptions: result.subscriptions?.map(buildApiStarsSubscription),
balance: buildApiStarsAmount(result.balance),
};
}
export async function fetchStarsTransactions({
peer,
offset,
isInbound,
isOutbound,
}: {
peer?: ApiPeer;
offset?: string;
isInbound?: true;
isOutbound?: true;
}) {
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
const result = await invokeRequest(new GramJs.payments.GetStarsTransactions({
peer: inputPeer,
offset,
inbound: isInbound,
outbound: isOutbound,
}));
if (!result) {
return undefined;
}
return {
nextOffset: result.nextOffset,
history: result.history?.map(buildApiStarsTransaction),
balance: buildApiStarsAmount(result.balance),
};
}
export async function fetchStarsTransactionById({
id, peer,
}: {
id: string;
peer?: ApiPeer;
}) {
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
const result = await invokeRequest(new GramJs.payments.GetStarsTransactionsByID({
peer: inputPeer,
id: [new GramJs.InputStarsTransaction({
id,
})],
}));
if (!result?.history?.[0]) {
return undefined;
}
return {
transaction: buildApiStarsTransaction(result?.history[0]),
};
}
export async function fetchStarsSubscriptions({
offset, peer,
}: {
offset?: string;
peer?: ApiPeer;
}) {
const inputPeer = peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf();
const result = await invokeRequest(new GramJs.payments.GetStarsSubscriptions({
peer: inputPeer,
offset,
}));
if (!result?.subscriptions) {
return undefined;
}
return {
nextOffset: result.subscriptionsNextOffset,
subscriptions: result.subscriptions.map(buildApiStarsSubscription),
balance: buildApiStarsAmount(result.balance),
};
}
export async function changeStarsSubscription({
peer, subscriptionId, isCancelled,
}: {
peer?: ApiPeer;
subscriptionId: string;
isCancelled: boolean;
}) {
const result = await invokeRequest(new GramJs.payments.ChangeStarsSubscription({
peer: peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf(),
subscriptionId,
canceled: isCancelled,
}));
return result;
}
export async function fulfillStarsSubscription({
peer, subscriptionId,
}: {
peer?: ApiPeer;
subscriptionId: string;
}) {
const result = await invokeRequest(new GramJs.payments.FulfillStarsSubscription({
peer: peer ? buildInputPeer(peer.id, peer.accessHash) : new GramJs.InputPeerSelf(),
subscriptionId,
}));
return result;
}
export async function fetchStarsTopupOptions() {
const result = await invokeRequest(new GramJs.payments.GetStarsTopupOptions());
if (!result) {
return undefined;
}
return result.map(buildApiStarTopupOption);
}
export async function fetchUniqueStarGift({ slug }: {
slug: string;
}) {
const result = await invokeRequest(new GramJs.payments.GetUniqueStarGift({ slug }));
if (!result) return undefined;
const gift = buildApiStarGift(result.gift);
if (gift.type !== 'starGiftUnique') return undefined;
return gift;
}
export async function fetchStarGiftUpgradePreview({
giftId,
}: {
giftId: string;
}) {
const result = await invokeRequest(new GramJs.payments.GetStarGiftUpgradePreview({
giftId: BigInt(giftId),
}));
if (!result) {
return undefined;
}
return result.sampleAttributes.map(buildApiStarGiftAttribute).filter(Boolean);
}
export function upgradeGift({
inputSavedGift,
shouldKeepOriginalDetails,
}: {
inputSavedGift: ApiRequestInputSavedStarGift;
shouldKeepOriginalDetails?: true;
}) {
return invokeRequest(new GramJs.payments.UpgradeStarGift({
stargift: buildInputSavedStarGift(inputSavedGift),
keepOriginalDetails: shouldKeepOriginalDetails,
}), {
shouldReturnTrue: true,
});
}