diff --git a/apps/web/__e2e__/checkout.test.ts b/apps/web/__e2e__/checkout.test.ts
index 28a054d7d..85c65f37d 100644
--- a/apps/web/__e2e__/checkout.test.ts
+++ b/apps/web/__e2e__/checkout.test.ts
@@ -123,6 +123,7 @@ test("applying coupon should change discount & total price", async () => {
const pricing = await plan.open();
const title = await pricing.getTitle();
if (!title) continue;
+ await pricing.waitForPaddleFrame();
await pricing.applyCoupon("INTRO50");
planPrices[title.toLowerCase()] = roundOffPrices(await pricing.getPrices());
@@ -141,6 +142,7 @@ test("apply coupon through url", async () => {
for (const plan of ["monthly", "yearly"] as const) {
await app.checkout.goto(plan, "INTRO50");
const pricing = await app.checkout.getPricing();
+ await pricing.waitForPaddleFrame();
await pricing.waitForCoupon();
planPrices[plan] = roundOffPrices(await pricing.getPrices());
@@ -164,6 +166,7 @@ test("apply coupon after changing country", async () => {
const pricing = await plan.open();
const title = await pricing.getTitle();
if (!title) continue;
+ await pricing.waitForPaddleFrame();
await pricing.changeCountry("IN", 110001);
await pricing.applyCoupon("INTRO50");
diff --git a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-IN-discounted-prices-Chromium-linux.txt b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-IN-discounted-prices-Chromium-linux.txt
index fb80a9428..a5dde9acb 100644
--- a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-IN-discounted-prices-Chromium-linux.txt
+++ b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-IN-discounted-prices-Chromium-linux.txt
@@ -1,4 +1,4 @@
Subtotal: ₹300
Sales tax: ₹200
Discount: -₹300
-Total: ₹300
\ No newline at end of file
+Total: ₹300/mo
\ No newline at end of file
diff --git a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-IN-discounted-prices-Chromium-win32.txt b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-IN-discounted-prices-Chromium-win32.txt
index fb80a9428..a5dde9acb 100644
--- a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-IN-discounted-prices-Chromium-win32.txt
+++ b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-IN-discounted-prices-Chromium-win32.txt
@@ -1,4 +1,4 @@
Subtotal: ₹300
Sales tax: ₹200
Discount: -₹300
-Total: ₹300
\ No newline at end of file
+Total: ₹300/mo
\ No newline at end of file
diff --git a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-IN-discounted-prices-Firefox-win32.txt b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-IN-discounted-prices-Firefox-win32.txt
index fb80a9428..a5dde9acb 100644
--- a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-IN-discounted-prices-Firefox-win32.txt
+++ b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-IN-discounted-prices-Firefox-win32.txt
@@ -1,4 +1,4 @@
Subtotal: ₹300
Sales tax: ₹200
Discount: -₹300
-Total: ₹300
\ No newline at end of file
+Total: ₹300/mo
\ No newline at end of file
diff --git a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-discounted-prices-Chromium-linux.txt b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-discounted-prices-Chromium-linux.txt
index dc7d77815..70be12204 100644
--- a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-discounted-prices-Chromium-linux.txt
+++ b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-discounted-prices-Chromium-linux.txt
@@ -1,4 +1,4 @@
Subtotal: $100
Sales tax: $0
Discount: -$100
-Total: $100
\ No newline at end of file
+Total: $100/mo
\ No newline at end of file
diff --git a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-discounted-prices-Chromium-win32.txt b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-discounted-prices-Chromium-win32.txt
index dc7d77815..70be12204 100644
--- a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-discounted-prices-Chromium-win32.txt
+++ b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-discounted-prices-Chromium-win32.txt
@@ -1,4 +1,4 @@
Subtotal: $100
Sales tax: $0
Discount: -$100
-Total: $100
\ No newline at end of file
+Total: $100/mo
\ No newline at end of file
diff --git a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-discounted-prices-Firefox-win32.txt b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-discounted-prices-Firefox-win32.txt
index dc7d77815..70be12204 100644
--- a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-discounted-prices-Firefox-win32.txt
+++ b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-monthly-discounted-prices-Firefox-win32.txt
@@ -1,4 +1,4 @@
Subtotal: $100
Sales tax: $0
Discount: -$100
-Total: $100
\ No newline at end of file
+Total: $100/mo
\ No newline at end of file
diff --git a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-IN-discounted-prices-Chromium-linux.txt b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-IN-discounted-prices-Chromium-linux.txt
index e06f69aad..0c0d65560 100644
--- a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-IN-discounted-prices-Chromium-linux.txt
+++ b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-IN-discounted-prices-Chromium-linux.txt
@@ -1,4 +1,4 @@
Subtotal: ₹400
Sales tax: ₹300
Discount: -₹400
-Total: ₹400
\ No newline at end of file
+Total: ₹400/yr
\ No newline at end of file
diff --git a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-IN-discounted-prices-Chromium-win32.txt b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-IN-discounted-prices-Chromium-win32.txt
index e06f69aad..0c0d65560 100644
--- a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-IN-discounted-prices-Chromium-win32.txt
+++ b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-IN-discounted-prices-Chromium-win32.txt
@@ -1,4 +1,4 @@
Subtotal: ₹400
Sales tax: ₹300
Discount: -₹400
-Total: ₹400
\ No newline at end of file
+Total: ₹400/yr
\ No newline at end of file
diff --git a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-IN-discounted-prices-Firefox-win32.txt b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-IN-discounted-prices-Firefox-win32.txt
index e06f69aad..0c0d65560 100644
--- a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-IN-discounted-prices-Firefox-win32.txt
+++ b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-IN-discounted-prices-Firefox-win32.txt
@@ -1,4 +1,4 @@
Subtotal: ₹400
Sales tax: ₹300
Discount: -₹400
-Total: ₹400
\ No newline at end of file
+Total: ₹400/yr
\ No newline at end of file
diff --git a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-discounted-prices-Chromium-linux.txt b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-discounted-prices-Chromium-linux.txt
index fbbb62387..fb2d11bb2 100644
--- a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-discounted-prices-Chromium-linux.txt
+++ b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-discounted-prices-Chromium-linux.txt
@@ -1,4 +1,4 @@
Subtotal: $200
Sales tax: $0
Discount: -$200
-Total: $200
\ No newline at end of file
+Total: $200/yr
\ No newline at end of file
diff --git a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-discounted-prices-Chromium-win32.txt b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-discounted-prices-Chromium-win32.txt
index fbbb62387..fb2d11bb2 100644
--- a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-discounted-prices-Chromium-win32.txt
+++ b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-discounted-prices-Chromium-win32.txt
@@ -1,4 +1,4 @@
Subtotal: $200
Sales tax: $0
Discount: -$200
-Total: $200
\ No newline at end of file
+Total: $200/yr
\ No newline at end of file
diff --git a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-discounted-prices-Firefox-win32.txt b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-discounted-prices-Firefox-win32.txt
index fbbb62387..fb2d11bb2 100644
--- a/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-discounted-prices-Firefox-win32.txt
+++ b/apps/web/__e2e__/checkout.test.ts-snapshots/checkout-yearly-discounted-prices-Firefox-win32.txt
@@ -1,4 +1,4 @@
Subtotal: $200
Sales tax: $0
Discount: -$200
-Total: $200
\ No newline at end of file
+Total: $200/yr
\ No newline at end of file
diff --git a/apps/web/__e2e__/models/checkout.model.ts b/apps/web/__e2e__/models/checkout.model.ts
index 71b3be1ed..bec91cfcb 100644
--- a/apps/web/__e2e__/models/checkout.model.ts
+++ b/apps/web/__e2e__/models/checkout.model.ts
@@ -101,6 +101,10 @@ class PricingModel {
});
}
+ async waitForPaddleFrame() {
+ await getPaddleFrame(this.page);
+ }
+
getTitle() {
return this.title.textContent();
}
diff --git a/apps/web/src/components/field/index.tsx b/apps/web/src/components/field/index.tsx
index 51b0c805b..dde2a34c6 100644
--- a/apps/web/src/components/field/index.tsx
+++ b/apps/web/src/components/field/index.tsx
@@ -68,7 +68,6 @@ function Field(props: FieldProps) {
m: "2px",
mr: "2px",
opacity: disabled ? 0.7 : 1,
- pointerEvents: disabled ? "none" : "all",
...sx,
flexDirection: "column"
}}
@@ -109,6 +108,7 @@ function Field(props: FieldProps) {
sx={{
flex: 1,
...styles?.input,
+ pointerEvents: disabled ? "none" : "all",
":disabled": {
bg: "background-disabled"
},
diff --git a/apps/web/src/dialogs/buy-dialog/buy-dialog.tsx b/apps/web/src/dialogs/buy-dialog/buy-dialog.tsx
index 4473352bc..422e89983 100644
--- a/apps/web/src/dialogs/buy-dialog/buy-dialog.tsx
+++ b/apps/web/src/dialogs/buy-dialog/buy-dialog.tsx
@@ -481,10 +481,16 @@ function SelectedPlan(props: SelectedPlanProps) {
)}
{pricingInfo ? (
<>
-
- {pricingInfo.coupon && (
-
- )}
+ {IS_TESTING ? (
+ <>
+
+ {pricingInfo.coupon && (
+
+ )}
+ >
+ ) : null}
();
+ const {
+ plan,
+ onPriceUpdated,
+ coupon,
+ theme,
+ onCouponApplied,
+ onCompleted,
+ user
+ } = props;
const [isLoading, setIsLoading] = useState(true);
const appliedCouponCode = useRef();
const checkoutId = useRef();
@@ -81,7 +87,11 @@ export function PaddleCheckout(props: PaddleCheckoutProps) {
const reloadCheckout = useCallback(() => {
if (!checkoutRef.current) return;
setIsLoading(true);
- checkoutRef.current.src = `${PADDLE_ORIGIN}/checkout/?checkout_id=${checkoutId.current}&display_mode=inline&apple_pay_enabled=false`;
+ checkoutRef.current.src = `${PADDLE_ORIGIN}/checkout/?checkout_id=${
+ checkoutId.current
+ }&display_mode=inline&apple_pay_enabled=${isFeatureSupported(
+ "applePaySupported"
+ )}`;
}, []);
const updatePrice = useCallback(
@@ -96,12 +106,37 @@ export function PaddleCheckout(props: PaddleCheckoutProps) {
[onPriceUpdated, plan]
);
+ const updateCoupon = useCallback(
+ async (checkoutId: string) => {
+ if (
+ appliedCouponCode.current === coupon ||
+ !appliedCouponCode.current === !coupon
+ )
+ return false;
+ if (onCouponApplied) onCouponApplied();
+ const checkoutData = coupon
+ ? await applyCoupon(checkoutId, coupon).catch(() => false)
+ : await removeCoupon(checkoutId).catch(() => false);
+ if (!checkoutData) {
+ await updatePrice(checkoutId, true).catch(() => false);
+ return false;
+ }
+ appliedCouponCode.current = coupon;
+ return true;
+ },
+ [coupon, updatePrice, onCouponApplied]
+ );
+
useEffect(() => {
- (async function () {
- const url = await getCheckoutURL(props);
- setSourceUrl(url);
- })();
- }, [props]);
+ createCheckout({ plan, theme, user, coupon }).then((checkoutData) => {
+ if (!checkoutData) return;
+ const pricingInfo = getPricingInfo(plan, checkoutData);
+ if (onPriceUpdated) onPriceUpdated(pricingInfo);
+ appliedCouponCode.current = pricingInfo.coupon;
+ checkoutId.current = checkoutData.public_checkout_id;
+ reloadCheckout();
+ });
+ }, [plan, theme, user]);
useEffect(() => {
async function onMessage(ev: MessageEvent) {
@@ -110,10 +145,7 @@ export function PaddleCheckout(props: PaddleCheckoutProps) {
const { event, event_name, callback_data } = ev.data;
const { checkout } = callback_data;
- if (event === PaddleEvents["Checkout.RemoveSpinner"]) {
- setIsLoading(false);
- return;
- }
+ if (event === PaddleEvents["Checkout.RemoveSpinner"]) setIsLoading(false);
if (
!checkout ||
@@ -125,32 +157,30 @@ export function PaddleCheckout(props: PaddleCheckoutProps) {
return;
}
- if (
- event_name === PaddleEvents["Checkout.Customer.Details"] &&
- !checkoutId.current
- ) {
- submitCustomerInfo(
- checkout.id,
- user.email,
- callback_data.user?.country || "US"
- ).finally(() => {
- checkoutId.current = checkout.id;
- reloadCheckout();
- });
- }
-
if (event_name === PaddleEvents["Checkout.Complete"]) {
onCompleted && onCompleted();
return;
}
- if (event_name === PaddleEvents["Checkout.Loaded"] && checkoutId.current)
+
+ if (event_name === PaddleEvents["Checkout.Loaded"]) {
setIsLoading(false);
+ }
- const pricingInfo = await updatePrice(checkout.id);
- if (!pricingInfo) return;
-
+ const pricingInfo = getPricingInfo(plan, {
+ public_checkout_id: checkout.id,
+ ip_geo_country_code: callback_data.user?.country || "US",
+ items: [
+ {
+ prices: checkout.prices.customer.items,
+ recurring: { prices: checkout.recurring_prices.customer.items }
+ }
+ ]
+ });
+ if (onPriceUpdated) onPriceUpdated(pricingInfo);
appliedCouponCode.current = pricingInfo.coupon;
checkoutId.current = checkout.id;
+
+ await updateCoupon(checkout.id);
}
window.addEventListener("message", onMessage, false);
return () => {
@@ -162,38 +192,16 @@ export function PaddleCheckout(props: PaddleCheckoutProps) {
plan,
onCompleted,
user.email,
- reloadCheckout
+ reloadCheckout,
+ updateCoupon
]);
useEffect(() => {
- const checkout_id = checkoutId.current;
- if (
- !checkout_id ||
- appliedCouponCode.current === coupon ||
- !appliedCouponCode.current === !coupon
- )
- return;
- if (onCouponApplied) onCouponApplied();
-
- (async function () {
- const checkoutData = coupon
- ? await applyCoupon(checkout_id, coupon).catch(() => false)
- : await removeCoupon(checkout_id).catch(() => false);
- if (!checkoutData) {
- await updatePrice(checkout_id, true);
- return;
- }
- appliedCouponCode.current = coupon;
- reloadCheckout();
- })();
- }, [
- coupon,
- onCouponApplied,
- onPriceUpdated,
- plan,
- reloadCheckout,
- updatePrice
- ]);
+ if (!checkoutId.current) return;
+ updateCoupon(checkoutId.current).then((result) => {
+ if (result) reloadCheckout();
+ });
+ }, [coupon]);
return (
0;
return {
- country: checkoutData.customer.country_code,
+ country: checkoutData.ip_geo_country_code,
currency: price.currency,
discount: {
amount: discount?.gross_discount || 0,
@@ -317,9 +329,6 @@ async function applyCoupon(
if (!response.ok) return false;
const json = (await response.json()) as CheckoutDataResponse;
-
- await sendCheckoutEvent(checkoutId, PaddleEvents["Checkout.Coupon.Applied"]);
-
return json.data;
}
@@ -348,8 +357,8 @@ async function submitCustomerInfo(
});
if (!response.ok) return false;
- const json = (await response.json()) as CheckoutDataResponse;
+ const json = (await response.json()) as CheckoutDataResponse;
return json.data;
}
@@ -361,25 +370,9 @@ async function removeCoupon(checkoutId: string): Promise {
});
if (!response.ok) return false;
const json = (await response.json()) as CheckoutDataResponse;
-
- await sendCheckoutEvent(checkoutId, PaddleEvents["Checkout.Coupon.Remove"]);
-
return json.data;
}
-async function sendCheckoutEvent(checkoutId: string, eventName: PaddleEvents) {
- const url = ` ${CHECKOUT_SERVICE_ORIGIN}/checkout/${checkoutId}/event`;
- const body = { data: { event_name: eventName } };
- const headers = new Headers();
- headers.set("content-type", "application/json");
- const response = await fetch(url, {
- body: JSON.stringify(body),
- headers,
- method: "POST"
- });
- return response.ok;
-}
-
async function getCheckoutData(
checkoutId: string
): Promise {
@@ -389,3 +382,35 @@ async function getCheckoutData(
const json = (await response.json()) as CheckoutDataResponse;
return json.data;
}
+
+async function createCheckout(props: {
+ plan: PaddleCheckoutProps["plan"];
+ user: PaddleCheckoutProps["user"];
+ theme: PaddleCheckoutProps["theme"];
+ coupon?: PaddleCheckoutProps["coupon"];
+}) {
+ const { plan, user, theme, coupon } = props;
+ const url = getCheckoutURL({ plan, user, theme });
+ const response = await fetch(url);
+ if (!response.ok) return false;
+
+ const json = (await response.json()) as CheckoutDataResponse;
+
+ let checkoutData = json.data;
+ const checkoutId = checkoutData.public_checkout_id;
+
+ checkoutData = await submitCustomerInfo(
+ checkoutId,
+ user.email,
+ json.data.ip_geo_country_code
+ )
+ .then((res) => (res ? res : checkoutData))
+ .catch(() => checkoutData);
+
+ if (coupon)
+ checkoutData = await applyCoupon(checkoutId, coupon)
+ .then((res) => (res ? res : checkoutData))
+ .catch(() => checkoutData);
+
+ return checkoutData;
+}
diff --git a/apps/web/src/dialogs/buy-dialog/types.ts b/apps/web/src/dialogs/buy-dialog/types.ts
index b3e5f6c66..ee10b8493 100644
--- a/apps/web/src/dialogs/buy-dialog/types.ts
+++ b/apps/web/src/dialogs/buy-dialog/types.ts
@@ -77,6 +77,7 @@ export enum PaddleEvents {
export interface CallbackData {
checkout?: Checkout;
+ coupon?: { coupon_code: string };
user?: {
email: string;
id: string;
@@ -86,6 +87,16 @@ export interface CallbackData {
export interface Checkout {
id?: string;
+ prices: {
+ customer: {
+ items: CheckoutPrices[];
+ };
+ };
+ recurring_prices: {
+ customer: {
+ items: CheckoutPrices[];
+ };
+ };
}
export type PaddleEvent = {
@@ -126,8 +137,11 @@ export type Discount = {
export interface Price {
gross: number;
+ gross_after_discount?: number;
net: number;
+ net_after_discount?: number;
tax: number;
+ tax_after_discount?: number;
currency?: string;
}
@@ -137,67 +151,67 @@ export interface CheckoutDataResponse {
export interface CheckoutData {
public_checkout_id: string;
- type: string;
- uuid: string;
- vendor: Vendor;
- display_currency: string;
- charge_currency: string;
- customer: Customer;
+ // type: string;
+ // uuid: string;
+ // vendor: Vendor;
+ // display_currency: string;
+ // charge_currency: string;
+ // customer: Customer;
items: Item[];
- available_payment_methods: unknown[];
- total: TotalPrice[];
- pending_payment: boolean;
- completed: boolean;
- payment_method_type: null;
- flagged_for_review: boolean;
+ // available_payment_methods: unknown[];
+ // total: TotalPrice[];
+ // pending_payment: boolean;
+ // completed: boolean;
+ // payment_method_type: null;
+ // flagged_for_review: boolean;
ip_geo_country_code: string;
- tax: null;
- name: null;
- image_url: null;
- message: null;
- passthrough: string;
- redirect_url: null;
- created_at: Date;
+ // tax: null;
+ // name: null;
+ // image_url: null;
+ // message: null;
+ // passthrough: string;
+ // redirect_url: null;
+ // created_at: Date;
}
export interface Customer {
- id: number;
- email: string;
+ // id: number;
+ // email: string;
country_code: string;
- postcode: null;
- audience_opt_in: boolean;
+ // postcode: null;
+ // audience_opt_in: boolean;
}
export interface Item {
- checkout_product_id: number;
- product_id: number;
- name: string;
- custom_message: string;
- quantity: number;
- allow_quantity: boolean;
- min_quantity: number;
- max_quantity: number;
- icon_url: string;
+ // checkout_product_id: number;
+ // product_id: number;
+ // name: string;
+ // custom_message: string;
+ // quantity: number;
+ // allow_quantity: boolean;
+ // min_quantity: number;
+ // max_quantity: number;
+ // icon_url: string;
prices: CheckoutPrices[];
recurring: Recurring;
- webhook_url: null;
+ // webhook_url: null;
}
export interface CheckoutPrices {
currency: string;
unit_price: CheckoutPrice;
- line_price: CheckoutPrice;
+ // line_price: CheckoutPrice;
discounts: CheckoutDiscount[];
- tax_rate: number;
+ // tax_rate: number;
}
export interface CheckoutDiscount {
- rank: number;
- type: string;
- net_discount: number;
+ // rank: number;
+ // type: string;
+ // net_discount: number;
gross_discount: number;
code: string;
- description: string;
+ // description: string;
}
export interface CheckoutPrice {
@@ -212,9 +226,9 @@ export interface CheckoutPrice {
}
export interface Recurring {
- period: string;
- interval: number;
- trial_days: number;
+ // period: string;
+ // interval: number;
+ // trial_days: number;
prices: CheckoutPrices[];
}