import db from "../db.server"; import { planCodeFromSubscription, type PlanCode } from "./plans"; import { fetchActiveSubscription, partnerApiConfigured } from "./partner.server"; type GraphQL = (query: string, options?: { variables?: Record }) => Promise; export interface AdminLike { graphql: GraphQL } export async function gql(admin: AdminLike, query: string, variables?: Record): Promise { const res = await admin.graphql(query, variables ? { variables } : undefined); const json = (await res.json()) as { data?: T; errors?: unknown }; if (json.errors) throw new Error(`GraphQL error: ${JSON.stringify(json.errors)}`); return json.data as T; } const SHOP_QUERY = `#graphql query BundleOffersShop { shop { id currencyCode ianaTimezone currencyFormats { moneyFormat } } currentAppInstallation { id activeSubscriptions { name status test } } }`; /** Plan code from Shopify App Pricing (Partner API). Without Partner API credentials (local dev) it falls back * to the Admin API's activeSubscriptions; if the Partner API fails it keeps the stored plan. */ async function resolvePlanCode(data: any, domain: string, fresh: boolean): Promise { if (partnerApiConfigured()) { try { const sub = await fetchActiveSubscription(data.shop.id, { fresh }); return planCodeFromSubscription(sub?.handles.join(" ")); } catch (e) { console.error("plan lookup failed, keeping the stored plan", e); const row = await getShop(domain); return (row?.planCode as PlanCode) || "free"; } } const subs = (data.currentAppInstallation?.activeSubscriptions || []) as { name: string; status: string }[]; return planCodeFromSubscription(subs.find((s) => s.status === "ACTIVE")?.name); } /** Creates/refreshes the Shop row from the Admin API (currency, timezone) and the plan from App Pricing. */ export async function syncShop(admin: AdminLike, domain: string, { fresh = false } = {}) { const data = await gql(admin, SHOP_QUERY); const planCode = await resolvePlanCode(data, domain, fresh); const values = { currency: data.shop.currencyCode, timezone: data.shop.ianaTimezone || "UTC", moneyFormat: data.shop.currencyFormats?.moneyFormat || "${{amount}}", appInstallationId: data.currentAppInstallation?.id, planCode, uninstalledAt: null, }; return db.shop.upsert({ where: { domain }, create: { domain, ...values }, update: values }); } export async function getShop(domain: string) { return db.shop.findUnique({ where: { domain } }); } /** Shop row for an admin request; syncs from Shopify the first time (or when forced). */ export async function ensureShop(admin: AdminLike, domain: string, force = false) { const shop = await getShop(domain); if (shop && !force && shop.appInstallationId && !shop.uninstalledAt) return shop; return syncShop(admin, domain); }