1 import type { PlanIDs } from 'proton-account/src/app/signup/interfaces';
3 import type { Autopay, PAYMENT_TOKEN_STATUS, WrappedCryptoPayment } from '@proton/components/payments/core';
4 import { PAYMENT_METHOD_TYPES } from '@proton/components/payments/core';
8 BillingAddressProperty,
9 ChargeablePaymentParameters,
17 } from '@proton/components/payments/core/interface';
18 import { isTokenPaymentMethod, isV5PaymentToken } from '@proton/components/payments/core/interface';
19 import type { INVOICE_OWNER, INVOICE_STATE, INVOICE_TYPE } from '@proton/shared/lib/constants';
20 import { FREE_PLAN } from '@proton/shared/lib/subscription/freePlans';
22 import type { ProductParam } from '../apps/product';
23 import { getProductHeaders } from '../apps/product';
24 import type { Api, Currency, Cycle, FreePlanDefault, Renew, Subscription } from '../interfaces';
26 export type PaymentsVersion = 'v4' | 'v5';
27 let paymentsVersion: PaymentsVersion = 'v4';
29 export function setPaymentsVersion(version: PaymentsVersion) {
30 paymentsVersion = version;
33 export function getPaymentsVersion(): PaymentsVersion {
34 return paymentsVersion;
37 export const queryFreePlan = (params?: QueryPlansParams) => ({
38 url: `payments/${paymentsVersion}/plans/default`,
43 export const getFreePlan = ({ api, currency }: { api: Api; currency?: Currency }) =>
44 api<{ Plans: FreePlanDefault }>(queryFreePlan(currency ? { Currency: currency } : undefined))
45 .then(({ Plans }): FreePlanDefault => {
48 MaxBaseSpace: Plans.MaxBaseSpace ?? Plans.MaxSpace,
49 MaxBaseRewardSpace: Plans.MaxBaseRewardSpace ?? Plans.MaxRewardSpace,
50 MaxDriveSpace: Plans.MaxDriveSpace ?? Plans.MaxSpace,
51 MaxDriveRewardSpace: Plans.MaxDriveRewardSpace ?? Plans.MaxRewardSpace,
54 .catch(() => FREE_PLAN);
56 export const getSubscription = (forceVersion?: PaymentsVersion) => ({
57 url: `payments/${forceVersion ?? paymentsVersion}/subscription`,
61 export interface FeedbackDowngradeData {
64 ReasonDetails?: string;
65 Context?: 'vpn' | 'mail';
68 export const deleteSubscription = (data: FeedbackDowngradeData, version: PaymentsVersion) => ({
69 url: `payments/${version}/subscription`,
74 export type CheckSubscriptionData = {
83 BillingAddress?: BillingAddress;
86 type CommonSubscribeData = {
91 } & AmountAndCurrency;
93 type SubscribeDataV4 = CommonSubscribeData & TokenPaymentMethod & BillingAddressProperty;
94 type SubscribeDataV5 = CommonSubscribeData & V5PaymentToken & BillingAddressProperty;
95 type SubscribeDataNoPayment = CommonSubscribeData;
96 export type SubscribeData = SubscribeDataV4 | SubscribeDataV5 | SubscribeDataNoPayment;
98 function isCommonSubscribeData(data: any): data is CommonSubscribeData {
99 return !!data.Plans && !!data.Currency && !!data.Cycle && !!data.Amount && !!data.Currency;
102 function isSubscribeDataV4(data: any): data is SubscribeDataV4 {
103 return isCommonSubscribeData(data) && isTokenPaymentMethod(data);
106 function isSubscribeDataV5(data: any): data is SubscribeDataV5 {
107 return isCommonSubscribeData(data) && isV5PaymentToken(data);
110 function isSubscribeDataNoPayment(data: any): data is SubscribeDataNoPayment {
111 return isCommonSubscribeData(data);
114 export function isSubscribeData(data: any): data is SubscribeData {
115 return isSubscribeDataV4(data) || isSubscribeDataV5(data) || isSubscribeDataNoPayment(data);
118 function prepareSubscribeDataPayload(data: SubscribeData): SubscribeData {
119 const allowedProps: (keyof SubscribeDataV4 | keyof SubscribeDataV5)[] = [
130 const payload: any = {};
131 Object.keys(data).forEach((key: any) => {
132 if (allowedProps.includes(key)) {
133 payload[key] = (data as any)[key];
137 return payload as SubscribeData;
140 export const subscribe = (rawData: SubscribeData, product: ProductParam, version: PaymentsVersion) => {
141 const sanitizedData = prepareSubscribeDataPayload(rawData);
143 let data: SubscribeData = sanitizedData;
144 if (version === 'v5' && isSubscribeDataV4(sanitizedData)) {
145 const v5Data: SubscribeDataV5 = {
147 PaymentToken: sanitizedData.Payment.Details.Token,
152 delete (data as any).Payment;
153 } else if (version === 'v4' && isSubscribeDataV5(sanitizedData)) {
154 const v4Data: SubscribeDataV4 = {
157 Type: PAYMENT_METHOD_TYPES.TOKEN,
159 Token: sanitizedData.PaymentToken,
165 delete (data as any).PaymentToken;
169 url: `payments/${version}/subscription`,
172 headers: getProductHeaders(product, {
173 endpoint: `payments/${version}/subscription`,
182 export enum InvoiceDocument {
184 CreditNote = 'credit_note',
185 CurrencyConversion = 'currency_conversion',
188 export interface QueryInvoicesParams {
194 Owner: INVOICE_OWNER;
195 State?: INVOICE_STATE;
197 Document?: InvoiceDocument;
201 * Query list of invoices for the current user. The response is {@link InvoiceResponse}
203 export const queryInvoices = (params: QueryInvoicesParams, version?: PaymentsVersion) => ({
204 url: `payments/${version ?? paymentsVersion}/invoices`,
209 export interface QueryPlansParams {
213 export const queryPlans = (params?: QueryPlansParams, forceVersion?: PaymentsVersion) => ({
214 url: `payments/${forceVersion ?? paymentsVersion}/plans`,
219 export const getInvoice = (invoiceID: string, version: PaymentsVersion) => ({
220 url: `payments/${version}/invoices/${invoiceID}`,
222 output: 'arrayBuffer',
225 export const checkInvoice = (invoiceID: string, version?: PaymentsVersion, GiftCode?: string) => ({
226 url: `payments/${version ?? paymentsVersion}/invoices/${invoiceID}/check`,
231 export const queryPaymentMethods = (forceVersion?: PaymentsVersion) => ({
232 url: `payments/${forceVersion ?? paymentsVersion}/methods`,
236 export type SetPaymentMethodDataV4 = TokenPayment & { Autopay?: Autopay };
238 export const setPaymentMethodV4 = (data: SetPaymentMethodDataV4) => ({
239 url: 'payments/v4/methods',
244 export type SetPaymentMethodDataV5 = V5PaymentToken & { Autopay?: Autopay };
245 export const setPaymentMethodV5 = (data: SetPaymentMethodDataV5) => ({
246 url: 'payments/v5/methods',
251 export interface UpdatePaymentMethodsData {
255 export const updatePaymentMethod = (methodId: string, data: UpdatePaymentMethodsData, version: PaymentsVersion) => ({
256 url: `payments/${version}/methods/${methodId}`,
261 export const deletePaymentMethod = (methodID: string, version: PaymentsVersion) => ({
262 url: `payments/${version}/methods/${methodID}`,
268 * @param data – does not have to include the payment token if user pays from the credits balance. In this case Amount
269 * must be set to 0 and payment token must not be supplied.
271 export const payInvoice = (
273 data: (TokenPaymentMethod & AmountAndCurrency) | AmountAndCurrency,
274 version: PaymentsVersion
276 url: `payments/${version}/invoices/${invoiceID}`,
281 export const orderPaymentMethods = (PaymentMethodIDs: string[], version: PaymentsVersion) => ({
282 url: `payments/${version}/methods/order`,
284 data: { PaymentMethodIDs },
287 export interface GiftCodeData {
292 export const buyCredit = (
293 data: (TokenPaymentMethod & AmountAndCurrency) | GiftCodeData | ChargeablePaymentParameters,
294 forceVersion: PaymentsVersion
296 url: `payments/${forceVersion ?? paymentsVersion}/credit`,
301 export interface ValidateCreditData {
305 export const validateCredit = (data: ValidateCreditData, version: PaymentsVersion) => ({
306 url: `payments/${version ?? paymentsVersion}/credit/check`,
311 export type CreateBitcoinTokenData = AmountAndCurrency & WrappedCryptoPayment;
313 export type CreateTokenData =
314 | ((AmountAndCurrency | {}) & (WrappedPaypalPayment | WrappedCardPayment | ExistingPayment))
315 | CreateBitcoinTokenData;
317 export const createToken = (data: CreateTokenData, version: PaymentsVersion) => ({
318 url: `payments/${version}/tokens`,
323 export const createTokenV4 = (data: CreateTokenData) => createToken(data, 'v4');
324 export const createTokenV5 = (data: CreateTokenData) => createToken(data, 'v5');
326 export const getTokenStatus = (paymentToken: string, version: PaymentsVersion) => ({
327 url: `payments/${version}/tokens/${paymentToken}`,
331 export const getTokenStatusV4 = (paymentToken: string) => getTokenStatus(paymentToken, 'v4');
332 export const getTokenStatusV5 = (paymentToken: string) => getTokenStatus(paymentToken, 'v5');
334 export const getLastCancelledSubscription = () => ({
335 url: `payments/${paymentsVersion}/subscription/latest`,
339 export type RenewalStateData =
341 RenewalState: Renew.Enabled;
344 RenewalState: Renew.Disabled;
345 CancellationFeedback: FeedbackDowngradeData;
348 export const changeRenewState = (data: RenewalStateData, version: PaymentsVersion) => ({
349 url: `payments/${version}/subscription/renew`,
354 export type SubscribeV5Data = {
355 PaymentToken?: string;
363 export type CreatePaymentIntentPaypalData = AmountAndCurrency & {
369 export type CreatePaymentIntentCardData = AmountAndCurrency & {
378 export type CreatePaymentIntentSavedCardData = AmountAndCurrency & {
379 PaymentMethodID: string;
382 export type CreatePaymentIntentData =
383 | CreatePaymentIntentPaypalData
384 | CreatePaymentIntentCardData
385 | CreatePaymentIntentSavedCardData;
387 export const createPaymentIntentV5 = (data: CreatePaymentIntentData) => ({
388 url: `payments/v5/tokens`,
393 export type BackendPaymentIntent = {
395 Status: 'inited' | 'authorized';
397 GatewayAccountID: string;
399 PaymentMethodType: 'card' | 'paypal';
403 ResourceVersion: number;
404 Object: 'payment_intent';
406 CurrencyCode: Currency;
411 export type FetchPaymentIntentV5Response = {
413 Status: PAYMENT_TOKEN_STATUS;
414 Data: BackendPaymentIntent;
417 export const fetchPaymentIntentV5 = (
419 data: CreatePaymentIntentData,
421 ): Promise<FetchPaymentIntentV5Response> => {
422 return api<FetchPaymentIntentV5Response>({
423 ...createPaymentIntentV5(data),
428 export type FetchPaymentIntentForExistingV5Response = {
430 Status: PAYMENT_TOKEN_STATUS;
431 Data: BackendPaymentIntent | null;
434 export const fetchPaymentIntentForExistingV5 = (
436 data: CreatePaymentIntentData,
438 ): Promise<FetchPaymentIntentForExistingV5Response> => {
439 return api<FetchPaymentIntentForExistingV5Response>({
440 ...createPaymentIntentV5(data),
445 export interface GetChargebeeConfigurationResponse {
448 PublishableKey: string;
452 export const getChargebeeConfiguration = () => ({
453 url: `payments/v5/web-configuration`,
457 // returns the ID. Or is it user's ID? hopefully.
458 // Call only if ChargebeeEnabled is set to 0 (the system already supports cb but this user was not migrated yet)
459 // Do not call for signups.
460 // Do not call if ChargebeeEnabled is undefined.
461 // If ChargebeeEnabled === 1 then always go to v5 and do not call this.
462 export const importAccount = () => ({
463 url: 'payments/v5/import',
467 export const checkImport = () => ({
468 url: 'payments/v5/import',
472 // no parameter, ideally. Always call before importAccount.
473 export const cleanupImport = () => ({
474 url: 'payments/v5/import',
478 export type GetSubscriptionResponse = {
479 Subscription: Subscription;
480 UpcomingSubscription?: Subscription;
483 export type GetPaymentMethodsResponse = {
484 PaymentMethods: SavedPaymentMethod[];