Rust discount function (one automatic discount evaluating all offers in priority order, parity with the Odoo engine), React Router admin with Polaris, theme app extension and app proxy pages, webhooks incl. GDPR, managed-pricing limits, and a Docker Compose stack (Postgres + Caddy HTTPS) for a single VPS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
47 lines
2.0 KiB
TypeScript
47 lines
2.0 KiB
TypeScript
import db from "../db.server";
|
|
import { planCodeFromSubscription } from "./plans";
|
|
|
|
type GraphQL = (query: string, options?: { variables?: Record<string, unknown> }) => Promise<Response>;
|
|
export interface AdminLike { graphql: GraphQL }
|
|
|
|
export async function gql<T = any>(admin: AdminLike, query: string, variables?: Record<string, unknown>): Promise<T> {
|
|
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 { currencyCode ianaTimezone currencyFormats { moneyFormat } }
|
|
currentAppInstallation { id activeSubscriptions { name status test } }
|
|
}`;
|
|
|
|
/** Creates/refreshes the Shop row from the Admin API (currency, timezone, plan). */
|
|
export async function syncShop(admin: AdminLike, domain: string) {
|
|
const data = await gql(admin, SHOP_QUERY);
|
|
const subs = (data.currentAppInstallation?.activeSubscriptions || []) as { name: string; status: string }[];
|
|
const active = subs.find((s) => s.status === "ACTIVE");
|
|
const planCode = planCodeFromSubscription(active?.name);
|
|
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);
|
|
}
|