Skip to content

Shopify App Billing Issue #979

Closed
Closed
@admirsaheta

Description

@admirsaheta

We are experiencing intermittent failures with billing.request() calls, inconsistent confirmation URL generation, and issues with subscription state management.

  1. Observed Problems

Intermittent failures during billing.request() calls.

Inconsistent confirmation URL generation.

Subscription state inconsistencies, affecting plan upgrades and downgrades.

  1. Current Implementation

Using billing.request() with proper plan validation.

Implementing subscription cancellation before requesting a new plan.

Comprehensive error handling and logging.

Proper return URL formatting for Shopify admin.

  1. Attempted Solutions

Added 1500ms delay after subscription cancellation.

Implemented detailed error logging with request IDs.

Added confirmation URL validation.

Using proper billing API parameters (isTest: true).

  1. Error Patterns

ShopifyBillingError types.

Invalid confirmation URLs returned.

Subscription state inconsistencies causing unexpected behaviors.

  1. Impact

Affects plan upgrades/downgrades.

Disrupts user subscription flow.

Potential billing state inconsistencies leading to incorrect charges.

export const loader = async ({ request, params }: LoaderFunctionArgs) => {
	const { billing, session } = await authenticate.admin(request);
	const { shop } = session;
	const user = shop.replace(".myshopify.com", "");

	const selectedPlan = params.plan?.toUpperCase();
	const validPlans = Object.values(PLANS).map((plan) => plan.toUpperCase());

	if (!selectedPlan || !validPlans.includes(selectedPlan)) {
		return createErrorResponse(
			"[FlyrankAI] - Your plain is invalid!",
			undefined,
			400,
		);
	}

	try {
		const currentSubscription = await billing.check({
			plans: [PLANS.SPARROW, PLANS.OWL, PLANS.EAGLE],
			isTest: true,
		});

		if (currentSubscription.hasActivePayment) {
			const existingSubscription = currentSubscription.appSubscriptions[0];
			console.log("[Plan Selection] Cancelling existing subscription:", {
				shop,
				subscriptionId: existingSubscription.id,
			});

			await billing.cancel({
				subscriptionId: existingSubscription.id,
				isTest: true,
				prorate: true,
			});

			await new Promise((resolve) => setTimeout(resolve, 1500));
		}

		console.log("[Plan Selection] Requesting new subscription:", {
			shop,
			selectedPlan,
		});

		console.log("[Plan Selection] Preparing billing request:", {
			shop,
			selectedPlan,
			returnUrl: `https://admin.shopify.com/store/${user}/apps/flyrank/app/billing/confirm?shop=${shop}`,
		});

		const confirmationUrl = await billing
			.request({
				plan: selectedPlan as keyof typeof PLANS,
				isTest: true,
				returnUrl: `https://admin.shopify.com/store/${user}/apps/flyrank/app/billing/confirm?shop=${shop}`,
			})
			.catch((error) => {
				console.error("[Plan Selection] Billing request failed:", {
					error: error instanceof Error ? error.message : String(error),
					shop,
					selectedPlan,
					timestamp: new Date().toISOString(),
				});
				throw error;
			});

		// Add confirmation URL validation
		if (!confirmationUrl || typeof confirmationUrl !== "string") {
			console.error("[Plan Selection] Invalid confirmation URL:", {
				confirmationUrl,
			});
			throw new Error("Invalid confirmation URL received from billing request");
		}

		console.log("[Plan Selection] Successfully generated confirmation URL:", {
			shop,
			selectedPlan,
			confirmationUrl,
		});

		return redirect(confirmationUrl);
	} catch (error) {
		if (error instanceof Response) {
			return error;
		}

		const errorDetails = {
			errorMessage: error instanceof Error ? error.message : String(error),
			errorCode: (error as any)?.code,
			errorType: (error as any)?.type || "UNKNOWN",
			shop,
			selectedPlan,
			timestamp: new Date().toISOString(),
			requestId: crypto.randomUUID(),
			stack: error instanceof Error ? error.stack : undefined,
			billingContext: {
				isTest: true,
				plans: [PLANS.SPARROW, PLANS.OWL, PLANS.EAGLE],
				selectedPlan,
			},
		};

		console.error("[Plan Selection] Billing Error:", errorDetails);

		let errorMessage = "[FlyrankAI] - ";
		if ((error as any)?.type === "ShopifyBillingError") {
			errorMessage +=
				"Unable to process billing request. Please try again later.";
		} else if ((error as any)?.type === "ValidationError") {
			errorMessage += "Invalid plan selection.";
		} else {
			errorMessage +=
				"An unexpected error occurred while processing your request.";
		}

		return createErrorResponse(
			errorMessage,
			error instanceof Error ? error : undefined,
			500,
		);
	}
};

export default function Plans() {
	return <Spinner />;
}

Anyone experienced same issue ? Response from Shopify Billing API brings us nowhere

remix │ [Plan Selection] Requesting new subscription: { shop: 'admir-dev-store.myshopify.com', selectedPlan: 'SPARROW' }
14:32:10 │            remix │ [Plan Selection] Preparing billing request: {
14:32:10 │            remix │   shop: 'admir-dev-store.myshopify.com',
14:32:10 │            remix │   selectedPlan: 'SPARROW',
14:32:10 │            remix │   returnUrl: 'https://admin.shopify.com/store/admir-dev-store/apps/flyrank/app/billing/confirm?shop=admir-dev-store.myshopify.com'
14:32:10 │            remix │ }
14:32:10 │            remix │ [shopify-app/INFO] Requesting billing | {shop: admir-dev-store.myshopify.com, plan: SPARROW, isTest: true, returnUrl:
https://admin.shopify.com/store/admir-dev-store/apps/flyrank/app/billing/confirm?shop=admir-dev-store.myshopify.com}
14:32:11 │            remix │ [Plan Selection] Billing request failed: {
14:32:11 │            remix │   error: 'Error while billing the store',
14:32:11 │            remix │   shop: 'admir-dev-store.myshopify.com',
14:32:11 │            remix │   selectedPlan: 'SPARROW',
14:32:11 │            remix │   timestamp: '2025-02-27T13:32:11.129Z'
14:32:11 │            remix │ }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions