@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 +1 -1
- package/dist/module.mjs +13 -2
- package/dist/runtime/app/widgets/RecentTransactions.d.vue.ts +3 -0
- package/dist/runtime/app/widgets/RecentTransactions.vue +54 -0
- package/dist/runtime/app/widgets/RecentTransactions.vue.d.ts +3 -0
- package/dist/runtime/app/widgets/RevenueStat.d.vue.ts +3 -0
- package/dist/runtime/app/widgets/RevenueStat.vue +21 -0
- package/dist/runtime/app/widgets/RevenueStat.vue.d.ts +3 -0
- package/dist/runtime/i18n/lang/da.json +5 -1
- package/dist/runtime/i18n/lang/en.json +5 -1
- package/dist/runtime/server/mcp/tools/my-payments.js +5 -2
- package/dist/runtime/server/routes/admin/recent.get.d.ts +2 -0
- package/dist/runtime/server/routes/admin/recent.get.js +23 -0
- package/package.json +9 -9
package/dist/module.json
CHANGED
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 }) => ({
|
|
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,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.
|
|
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.
|
|
34
|
-
"@frigear-nu/mcp": "0.0.
|
|
35
|
-
"@frigear-nu/module-kit": "0.
|
|
36
|
-
"@frigear-nu/stripe": "0.0.
|
|
37
|
-
"@frigear-nu/ui": "0.0.
|
|
38
|
-
"@frigear-nu/users": "1.0.
|
|
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.
|
|
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.
|
|
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",
|