Description
We are experiencing intermittent failures with billing.request() calls, inconsistent confirmation URL generation, and issues with subscription state management.
- Observed Problems
Intermittent failures during billing.request() calls.
Inconsistent confirmation URL generation.
Subscription state inconsistencies, affecting plan upgrades and downgrades.
- 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.
- 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).
- Error Patterns
ShopifyBillingError types.
Invalid confirmation URLs returned.
Subscription state inconsistencies causing unexpected behaviors.
- 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 │ }