@frigear-nu/payments 0.0.8 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@frigear-nu/payments",
3
3
  "configKey": "payments",
4
- "version": "0.0.8",
4
+ "version": "0.0.9",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { defineNuxtModule, createResolver, addServerHandler, addServerImportsDir, addServerPlugin, addImports } from '@nuxt/kit';
2
- import { registerModule, registerTailwindSource, registerLocales, registerDatabaseTable, moduleApiUrl } from '@frigear-nu/module-kit/utils';
1
+ import { defineNuxtModule, createResolver, addServerHandler, hasNuxtModule, addServerImportsDir, addServerPlugin, addImports } from '@nuxt/kit';
2
+ import { registerModule, registerTailwindSource, registerLocales, registerDatabaseTable, moduleApiUrl, inlineModuleRuntime } from '@frigear-nu/module-kit/utils';
3
3
  import { addAuthScope } from '@frigear-nu/auth/utils';
4
4
  import { extendUsersAdmin } from '@frigear-nu/users/utils';
5
5
  import { addMcpDefinitions } from '@frigear-nu/mcp/utils';
@@ -80,7 +80,18 @@ const module$1 = defineNuxtModule({
80
80
  span: 2
81
81
  }
82
82
  });
83
+ addServerHandler({
84
+ route: moduleApiUrl("payments", "admin/recent"),
85
+ method: "get",
86
+ handler: resolver.resolve("./runtime/server/routes/admin/recent.get")
87
+ });
88
+ if (hasNuxtModule("@frigear-nu/admin") && hasNuxtModule("@frigear-nu/widget")) {
89
+ const { extendWidgets } = await import('@frigear-nu/widget/utils');
90
+ extendWidgets(nuxt, "admin.dashboard.top", { id: "payments-revenue-stat", component: resolver.resolve("./runtime/app/widgets/RevenueStat.vue"), order: 30 });
91
+ extendWidgets(nuxt, "admin.dashboard.side", { id: "payments-recent", component: resolver.resolve("./runtime/app/widgets/RecentTransactions.vue"), order: 20 });
92
+ }
83
93
  addServerImportsDir(resolver.resolve("./runtime/server/utils"));
94
+ inlineModuleRuntime(nuxt, resolver);
84
95
  addServerPlugin(resolver.resolve("./runtime/server/plugins/invoice-payments"));
85
96
  if (options.settings?.mcp === true) addMcpDefinitions(nuxt, resolver.resolve("./runtime/server/mcp"));
86
97
  addImports([
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,54 @@
1
+ <script setup>
2
+ import { formatMoney } from "@frigear-nu/ui/money";
3
+ defineOptions({ name: "PaymentsRecentTransactions" });
4
+ const t = useModuleT("payments.admin");
5
+ const status = useModuleT("payments.status");
6
+ const usersPage = useRuntimeConfig().public.users?.pages?.admin;
7
+ const { data, status: fetchStatus } = useLazyFetch("/api/payments/admin/recent", { key: "admin-payments-recent" });
8
+ const date = (value) => value ? new Date(value).toLocaleDateString() : "\u2013";
9
+ const color = (value) => value === "paid" ? "success" : value === "failed" ? "error" : "neutral";
10
+ const userTo = (id) => usersPage ? `${usersPage.replace(/\/+$/, "")}/${id}` : void 0;
11
+ </script>
12
+
13
+ <template>
14
+ <AdminListCard
15
+ :title="t('recent', 'Recent transactions')"
16
+ :loading="fetchStatus === 'pending' && !data"
17
+ :is-empty="!data?.items.length"
18
+ :empty="t('empty', 'No payments yet.')"
19
+ >
20
+ <li
21
+ v-for="payment in data?.items"
22
+ :key="payment.id"
23
+ class="flex items-center justify-between gap-3"
24
+ >
25
+ <div class="min-w-0">
26
+ <NuxtLink
27
+ v-if="userTo(payment.userId)"
28
+ :to="userTo(payment.userId)"
29
+ class="text-highlighted block truncate font-medium hover:underline"
30
+ >
31
+ {{ payment.userName ?? payment.description ?? t("defaultDescription", "Payment") }}
32
+ </NuxtLink>
33
+ <span
34
+ v-else
35
+ class="text-highlighted block truncate font-medium"
36
+ >{{ payment.userName ?? payment.description ?? t("defaultDescription", "Payment") }}</span>
37
+ <p class="text-muted truncate text-xs">
38
+ {{ date(payment.paidAt ?? payment.createdAt) }}<template v-if="payment.description && payment.userName">
39
+ · {{ payment.description }}
40
+ </template>
41
+ </p>
42
+ </div>
43
+ <div class="flex shrink-0 flex-col items-end gap-1">
44
+ <span class="text-highlighted font-medium">{{ formatMoney(payment.amount, payment.currency) }}</span>
45
+ <UBadge
46
+ size="sm"
47
+ variant="subtle"
48
+ :color="color(payment.status)"
49
+ :label="status(payment.status, payment.status)"
50
+ />
51
+ </div>
52
+ </li>
53
+ </AdminListCard>
54
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,21 @@
1
+ <script setup>
2
+ import { formatMoney } from "@frigear-nu/ui/money";
3
+ defineOptions({ name: "PaymentsRevenueStat" });
4
+ const t = useModuleT("payments.admin");
5
+ const { data, status } = useLazyFetch("/api/payments/admin/recent", { key: "admin-payments-recent" });
6
+ const main = computed(() => data.value?.revenue[0]);
7
+ </script>
8
+
9
+ <template>
10
+ <AdminStatCard
11
+ :label="t('revenue', 'Revenue, 30 days')"
12
+ icon="i-lucide-circle-dollar-sign"
13
+ :loading="status === 'pending' && !data"
14
+ :empty="t('noRevenue', 'No payments yet.')"
15
+ >
16
+ <template v-if="main">
17
+ <span>{{ formatMoney(main.amount, main.currency) }}</span>
18
+ <span class="text-muted text-sm font-normal">{{ t("transactions", "{count} transactions").replace("{count}", String(main.count)) }}</span>
19
+ </template>
20
+ </AdminStatCard>
21
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -20,7 +20,11 @@
20
20
  "statusColumn": "Status",
21
21
  "refunded": "Refunderet",
22
22
  "reference": "Reference",
23
- "defaultDescription": "Betaling"
23
+ "defaultDescription": "Betaling",
24
+ "revenue": "Omsætning, 30 dage",
25
+ "noRevenue": "Ingen betalinger endnu.",
26
+ "transactions": "{count} transaktioner",
27
+ "recent": "Seneste transaktioner"
24
28
  }
25
29
  }
26
30
  }
@@ -20,7 +20,11 @@
20
20
  "statusColumn": "Status",
21
21
  "refunded": "Refunded",
22
22
  "reference": "Reference",
23
- "defaultDescription": "Payment"
23
+ "defaultDescription": "Payment",
24
+ "revenue": "Revenue, 30 days",
25
+ "noRevenue": "No payments yet.",
26
+ "transactions": "{count} transactions",
27
+ "recent": "Recent transactions"
24
28
  }
25
29
  }
26
30
  }
@@ -1,8 +1,11 @@
1
1
  import { z } from "zod";
2
+ import { fieldsFromCents } from "@frigear-nu/mcp/utils";
2
3
  export default defineScopedMcpTool({
3
- description: "List your own payments, newest first: tickets, memberships and other charges with amount, status, refunds and what each was for.",
4
+ description: "List your own payments, newest first: tickets, memberships and other charges with amount, status, refunds and what each was for. Amounts are in whole currency units (52.2 = 52.20), not cents.",
4
5
  scopes: ["payments:read"],
5
6
  annotations: { readOnlyHint: true },
6
7
  inputSchema: { limit: z.number().int().min(1).max(100).optional().describe("How many payments to return (default 50).") },
7
- handler: async ({ limit }, { user }) => ({ payments: await listUserPayments(user.id, { limit: limit ?? 50 }) })
8
+ handler: async ({ limit }, { user }) => ({
9
+ payments: (await listUserPayments(user.id, { limit: limit ?? 50 })).map((payment) => fieldsFromCents({ ...payment, currency: payment.currency.toUpperCase() }, ["amount", "refundedAmount"], payment.currency))
10
+ })
8
11
  });
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,23 @@
1
+ import { db, schema } from "@nuxthub/db";
2
+ import { and, count, desc, eq, gte, inArray, sql } from "drizzle-orm";
3
+ const DAYS = 30;
4
+ const LIMIT = 8;
5
+ export default defineEventHandler(async (event) => {
6
+ await authorizeUsersAdmin(event, "view");
7
+ const since = new Date(Date.now() - DAYS * 24 * 60 * 60 * 1e3);
8
+ const settled = inArray(schema.payments.status, ["paid", "partially_refunded"]);
9
+ const when = sql`coalesce(${schema.payments.paidAt}, ${schema.payments.createdAt})`;
10
+ const [revenue, rows] = await Promise.all([
11
+ db.select({
12
+ currency: schema.payments.currency,
13
+ amount: sql`sum(${schema.payments.amount} - ${schema.payments.refundedAmount})`,
14
+ count: count()
15
+ }).from(schema.payments).where(and(settled, gte(schema.payments.paidAt, since))).groupBy(schema.payments.currency).orderBy(desc(count())),
16
+ db.select({ payment: schema.payments, userName: schema.users.name }).from(schema.payments).leftJoin(schema.users, eq(schema.users.id, schema.payments.userId)).where(inArray(schema.payments.status, ["paid", "partially_refunded", "refunded", "failed"])).orderBy(desc(when)).limit(LIMIT)
17
+ ]);
18
+ return {
19
+ days: DAYS,
20
+ revenue: revenue.map((row) => ({ currency: row.currency, amount: Number(row.amount), count: row.count })),
21
+ items: rows.map((row) => ({ ...toPaymentDto(row.payment), userName: row.userName }))
22
+ };
23
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frigear-nu/payments",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "One-time payments (Stripe Checkout, refunds and payment lifecycle hooks) for the frigear ecosystem, built on @frigear-nu/stripe.",
5
5
  "homepage": "https://docs.frigear.dev/modules/payments",
6
6
  "repository": {
@@ -30,12 +30,12 @@
30
30
  "playground"
31
31
  ],
32
32
  "dependencies": {
33
- "@frigear-nu/auth": "0.0.3",
34
- "@frigear-nu/mcp": "0.0.3",
35
- "@frigear-nu/module-kit": "0.1.7",
36
- "@frigear-nu/stripe": "0.0.8",
37
- "@frigear-nu/ui": "0.0.7",
38
- "@frigear-nu/users": "1.0.9",
33
+ "@frigear-nu/auth": "0.0.4",
34
+ "@frigear-nu/mcp": "0.0.4",
35
+ "@frigear-nu/module-kit": "1.0.0",
36
+ "@frigear-nu/stripe": "0.0.9",
37
+ "@frigear-nu/ui": "0.0.8",
38
+ "@frigear-nu/users": "1.0.10",
39
39
  "@libsql/client": "^0.18.0",
40
40
  "@nuxt/kit": "^4.6.0",
41
41
  "drizzle-kit": "^0.31.11",
@@ -46,7 +46,7 @@
46
46
  "zod": "^4.6.5"
47
47
  },
48
48
  "peerDependencies": {
49
- "@frigear-nu/widget": "0.0.2",
49
+ "@frigear-nu/widget": "0.0.3",
50
50
  "@nuxt/ui": "^4.10.0",
51
51
  "@nuxthub/core": "^0.10.8"
52
52
  },
@@ -59,7 +59,7 @@
59
59
  }
60
60
  },
61
61
  "devDependencies": {
62
- "@frigear-nu/widget": "0.0.2",
62
+ "@frigear-nu/widget": "0.0.3",
63
63
  "@nuxt/devtools": "^3.4.2",
64
64
  "@nuxt/eslint-config": "^1.17.0",
65
65
  "@nuxt/module-builder": "^1.0.3",