datewise/app/routes/webhooks.app.tsx
metatroncubeswdev 54734e8938 feat(billing): switch to Shopify managed pricing; show chosen time on thank-you page
App Store review 1.2.2 ("fails to register the plan"): follow Offerlyst's approved
managed-pricing setup. The plan is read from the Partner API on every app load,
shops without a plan (new install or reinstall) go to Shopify's plan selection
page, and app_subscriptions/update keeps it in sync. Billing API code removed.

Reviewer's checkout video: Shopify's checkout shows pickup by location name, so
the delivery label rename never reached the shopper. New checkout-time extension
shows the chosen date and time on the thank-you page (all plans) and next to
pickup/shipping options (Plus). Checkout display copy now matches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 16:52:00 -05:00

38 lines
2.1 KiB
TypeScript

import type { ActionFunctionArgs } from "react-router";
import prisma from "../db.server";
import { authenticate } from "../shopify.server";
import { forgetSubscriptions } from "../lib/partner.server";
import { bumpConfig } from "../services/config.server";
import { planFromSubscription } from "../services/plans";
/**
* app/uninstalled: storefront behaviour stops immediately (proxy calls fail without a
* session and the theme extension is removed by Shopify). Data is kept for 48 hours so a
* quick reinstall keeps bookings; Shopify's shop/redact webhook then deletes everything.
*/
export const action = async ({ request }: ActionFunctionArgs) => {
const { shop, topic, session, payload } = await authenticate.webhook(request);
if (topic === "APP_UNINSTALLED") {
forgetSubscriptions();
if (session) await prisma.session.deleteMany({ where: { shop } });
await prisma.shop.updateMany({ where: { domain: shop }, data: { uninstalledAt: new Date(), plan: "free" } }); // Shopify cancels app subscriptions on uninstall
await prisma.hold.deleteMany({ where: { shop: { domain: shop } } });
await prisma.reminderJob.updateMany({ where: { shop: { domain: shop }, status: "pending" }, data: { status: "cancelled" } });
}
// Managed pricing plan changes (upgrade, downgrade, cancellation), including ones made while the app is closed.
if (topic === "APP_SUBSCRIPTIONS_UPDATE") {
const sub = (payload as { app_subscription?: { name?: string; status?: string } }).app_subscription;
const plan = sub?.status?.toUpperCase() === "ACTIVE" ? planFromSubscription(sub?.name) : "free";
const row = await prisma.shop.findUnique({ where: { domain: shop }, select: { id: true, plan: true } });
if (row && row.plan !== plan) {
await prisma.shop.update({ where: { id: row.id }, data: { plan } });
await bumpConfig(row.id, shop);
}
}
if (topic === "APP_SCOPES_UPDATE" && session) {
const current = (payload as { current?: string[] }).current;
if (current) await prisma.session.update({ where: { id: session.id }, data: { scope: current.toString() } });
}
return new Response();
};