Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12,318 changes: 12,318 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@eslint/js": "^9.16.0",
"@microsoft/api-extractor": "^7.48.0",
"@paddle/paddle-js": "^1.5.1",
"@paypal/paypal-js": "^8.1.2",
"@revenuecat/purchases-ui-js": "3.9.0",
"@storybook/addon-essentials": "^8.6.15",
"@storybook/addon-interactions": "^8.6.15",
Expand Down
2 changes: 2 additions & 0 deletions src/entities/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export class ErrorCodeUtils {
case BackendErrorCode.BackendInvalidAuthToken:
case BackendErrorCode.BackendInvalidAPIKey:
case BackendErrorCode.BackendInvalidPaddleAPIKey:
case BackendErrorCode.BackendInvalidPayPalAPIKey:
return ErrorCode.InvalidCredentialsError;
case BackendErrorCode.BackendInvalidPaymentModeOrIntroPriceNotProvided:
case BackendErrorCode.BackendProductIdForGoogleReceiptNotProvided:
Expand Down Expand Up @@ -235,6 +236,7 @@ export enum BackendErrorCode {
BackendGatewaySetupErrorMissingRequiredPermission = 7900,
BackendGatewaySetupErrorSandboxModeOnly = 7901,
BackendInvalidPaddleAPIKey = 7967,
BackendInvalidPayPalAPIKey = 7968,
}

/**
Expand Down
5 changes: 5 additions & 0 deletions src/helpers/api-key-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const rc_api_key_regex = /^rcb_[a-zA-Z0-9_.-]+$/;
const paddle_api_key_regex = /^pdl_[a-zA-Z0-9_.-]+$/;
const rc_simulated_store_api_key_regex = /^test_[a-zA-Z0-9_.-]+$/;
const stripe_api_key_regex = /^strp_[a-zA-Z0-9_.-]+$/;
const paypal_api_key_regex = /^ppl_[a-zA-Z0-9_.-]+$/;

export function isWebBillingSandboxApiKey(apiKey: string): boolean {
return apiKey ? apiKey.startsWith("rcb_sb_") : false;
Expand All @@ -23,6 +24,10 @@ export function isStripeApiKey(apiKey: string): boolean {
return isStripeApiKey && isSandboxStripeApiKey;
}

export function isPayPalApiKey(apiKey: string): boolean {
return apiKey ? paypal_api_key_regex.test(apiKey) : false;
}

export function isSimulatedStoreApiKey(apiKey: string): boolean {
return apiKey ? rc_simulated_store_api_key_regex.test(apiKey) : false;
}
4 changes: 3 additions & 1 deletion src/helpers/configuration-validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ErrorCode, PurchasesError } from "../entities/errors";
import { SDK_HEADERS } from "../networking/http-client";
import {
isPaddleApiKey,
isPayPalApiKey,
isSimulatedStoreApiKey,
isStripeApiKey,
isWebBillingApiKey,
Expand All @@ -12,7 +13,8 @@ export function validateApiKey(apiKey: string) {
isWebBillingApiKey(apiKey) ||
isSimulatedStoreApiKey(apiKey) ||
isPaddleApiKey(apiKey) ||
isStripeApiKey(apiKey);
isStripeApiKey(apiKey) ||
isPayPalApiKey(apiKey);

if (!isValidApiKey) {
throw new PurchasesError(
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/purchase-operation-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export enum PurchaseFlowErrorCode {
StripeInvalidTaxOriginAddress = 7,
StripeMissingRequiredPermission = 8,
InvalidPaddleAPIKeyError = 9,
InvalidPayPalAPIKeyError = 10,
}

export class PurchaseFlowError extends Error {
Expand Down Expand Up @@ -101,6 +102,8 @@ export class PurchaseFlowError extends Error {
return PurchaseFlowErrorCode.StripeMissingRequiredPermission;
case BackendErrorCode.BackendInvalidPaddleAPIKey:
return PurchaseFlowErrorCode.InvalidPaddleAPIKeyError;
case BackendErrorCode.BackendInvalidPayPalAPIKey:
return PurchaseFlowErrorCode.InvalidPayPalAPIKeyError;
default:
return null;
}
Expand Down
115 changes: 115 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
} from "./entities/offerings";
import PurchasesUi from "./ui/purchases-ui.svelte";
import PaddlePurchasesUi from "./ui/paddle-purchases-ui.svelte";
import PayPalPurchasesUi from "./ui/paypal-purchases-ui.svelte";
import StripeCheckoutPurchasesUi from "./ui/stripe-checkout-purchases-ui.svelte";

import { type CustomerInfo, toCustomerInfo } from "./entities/customer-info";
Expand All @@ -24,6 +25,7 @@ import { RC_ENDPOINT } from "./helpers/constants";
import { Backend } from "./networking/backend";
import {
isPaddleApiKey,
isPayPalApiKey,
isSimulatedStoreApiKey,
isStripeApiKey,
isWebBillingApiKey,
Expand All @@ -35,6 +37,7 @@ import {
PurchaseOperationHelper,
} from "./helpers/purchase-operation-helper";
import { PaddleService } from "./paddle/paddle-service";
import { PayPalService } from "./paypal/paypal-service";
import { type LogHandler, type LogLevel } from "./entities/logging";
import { Logger } from "./helpers/logger";
import {
Expand Down Expand Up @@ -1081,6 +1084,11 @@ export class Purchases {
return await this.performStripePurchase(params);
}

const isPayPal = isPayPalApiKey(this._API_KEY);
if (isPayPal) {
return await this.performPayPalPurchase(params);
}

return await this.performWebBillingPurchase(params);
}

Expand Down Expand Up @@ -1417,6 +1425,113 @@ export class Purchases {
});
}

private async performPayPalPurchase(
params: PurchaseParams,
): Promise<PurchaseResult> {
const {
rcPackage,
purchaseOption,
customerEmail,
selectedLocale = englishLocale,
defaultLocale = englishLocale,
skipSuccessPage = false,
htmlTarget,
} = params;
const certainHTMLTarget = this.resolveHTMLTarget(htmlTarget);

const appUserId = this._appUserId;

Logger.debugLog(
`Presenting PayPal checkout for package ${rcPackage.identifier}`,
);

const purchaseOptionToUse =
purchaseOption ?? rcPackage.webBillingProduct.defaultPurchaseOption;

const utmParamsMetadata = this._flags.autoCollectUTMAsMetadata
? autoParseUTMParams()
: {};
const metadata = {
source: "paypal",
...utmParamsMetadata,
...(params.metadata || {}),
};

const finalBrandingInfo: BrandingInfoResponse | null = this._brandingInfo;

if (finalBrandingInfo && params.brandingAppearanceOverride) {
finalBrandingInfo.appearance = params.brandingAppearanceOverride;
}

const event = createCheckoutSessionStartEvent({
appearance: this._brandingInfo?.appearance,
rcPackage,
purchaseOptionToUse,
customerEmail,
});
this.eventsTracker.trackSDKEvent(event);

const paypalService = new PayPalService(this.backend, this.eventsTracker);

let component: ReturnType<typeof mount> | null = null;
const isInElement = htmlTarget !== undefined;

return new Promise((resolve, reject) => {
const win = getWindow();
if (!isInElement) {
win.history.pushState({ checkoutOpen: true }, "");
}

const unmountPayPalPurchaseUi = () => {
if (component) {
unmount(component);
}
certainHTMLTarget.innerHTML = "";
};

const onClose =
this.createCheckoutOnCloseHandler(reject, unmountPayPalPurchaseUi) ??
(() => {
unmountPayPalPurchaseUi();
});

const onFinished = this.createCheckoutOnFinishedHandler(
resolve,
appUserId,
rcPackage,
unmountPayPalPurchaseUi,
);

const onError = this.createCheckoutOnErrorHandler(reject);

if (!component) {
component = mount(PayPalPurchasesUi, {
target: certainHTMLTarget,
props: {
eventsTracker: this.eventsTracker,
brandingInfo: this._brandingInfo,
selectedLocale: selectedLocale || defaultLocale,
defaultLocale,
customTranslations: params.labelsOverride,
isInElement,
onClose,
onFinished,
onError,
skipSuccessPage,
productDetails: rcPackage.webBillingProduct,
rcPackage,
appUserId,
purchaseOption: purchaseOptionToUse,
customerEmail,
metadata,
unmountPayPalPurchaseUi,
paypalService,
},
});
}
});
}

/**
* Uses htmlTarget if provided. Otherwise, looks for an element with id "rcb-ui-root".
* If no element is found, creates a new div with className "rcb-ui-root".
Expand Down
14 changes: 13 additions & 1 deletion src/networking/responses/checkout-start-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ export interface PaddleCheckoutStartResponse {
};
}

export interface PayPalCheckoutStartResponse {
operation_session_id: string;
gateway_params: null;
management_url: string | null;
paypal_billing_params: {
client_id: string;
order_id: string;
is_sandbox: boolean;
};
}

export type CheckoutStartResponse =
| WebBillingCheckoutStartResponse
| PaddleCheckoutStartResponse;
| PaddleCheckoutStartResponse
| PayPalCheckoutStartResponse;
Loading