@frigear-nu/payments 0.0.0-stage → 0.0.2
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/README.md +42 -2
- package/dist/module.d.mts +96 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +59 -0
- package/dist/runtime/app/composables/index.d.ts +1 -0
- package/dist/runtime/app/composables/index.js +1 -0
- package/dist/runtime/app/composables/usePayments.d.ts +7 -0
- package/dist/runtime/app/composables/usePayments.js +14 -0
- package/dist/runtime/i18n/lang/da.json +13 -0
- package/dist/runtime/i18n/lang/en.json +13 -0
- package/dist/runtime/server/db/schema.sqlite.d.ts +280 -0
- package/dist/runtime/server/db/schema.sqlite.js +26 -0
- package/dist/runtime/server/routes/payments/index.get.d.ts +2 -0
- package/dist/runtime/server/routes/payments/index.get.js +4 -0
- package/dist/runtime/server/routes/payments/resume.post.d.ts +2 -0
- package/dist/runtime/server/routes/payments/resume.post.js +7 -0
- package/dist/runtime/server/stripe/payment-events.d.ts +2 -0
- package/dist/runtime/server/stripe/payment-events.js +59 -0
- package/dist/runtime/server/utils/payments.d.ts +31 -0
- package/dist/runtime/server/utils/payments.js +150 -0
- package/dist/runtime/utils/index.d.ts +6 -0
- package/dist/runtime/utils/index.js +14 -0
- package/dist/types.d.mts +3 -0
- package/package.json +86 -3
package/README.md
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @frigear-nu/payments
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
One-time Stripe payments for the frigear ecosystem: Checkout sessions with inline prices, a `payments` table, refunds and
|
|
4
|
+
Nitro lifecycle hooks (`payments:paid`, `payments:failed`, `payments:expired`, `payments:refunded`). Built on
|
|
5
|
+
[`@frigear-nu/stripe`](../stripe); it has no webhook of its own and only reacts to the verified Stripe events that module
|
|
6
|
+
dispatches.
|
|
7
|
+
|
|
8
|
+
## Quick Setup
|
|
9
|
+
|
|
10
|
+
Requires `@nuxthub/core` with `hub.kv: true`, `@frigear-nu/users` and `@frigear-nu/stripe`.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
export default defineNuxtConfig({
|
|
14
|
+
modules: ['@frigear-nu/users', '@frigear-nu/stripe', '@frigear-nu/payments'],
|
|
15
|
+
})
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Generate and commit a migration for the `payments` table in your app (see the repository `AGENTS.md`).
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// In a route handler
|
|
24
|
+
const { payment, url } = await createPayment({
|
|
25
|
+
userId: user.id,
|
|
26
|
+
items: [{ name: 'Ticket', amount: 15000, currency: 'DKK' }],
|
|
27
|
+
reference: { type: 'event_ticket', id: ticketId },
|
|
28
|
+
successUrl: '/thanks',
|
|
29
|
+
cancelUrl: '/cancelled',
|
|
30
|
+
}, event)
|
|
31
|
+
|
|
32
|
+
// Anywhere in Nitro
|
|
33
|
+
nitroApp.hooks.hook('payments:paid', ({ payment }) => { /* ... */ })
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Full documentation: `apps/docs/content/4.modules/24.payments.md`.
|
|
37
|
+
|
|
38
|
+
## Development
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm --filter @frigear-nu/payments test
|
|
42
|
+
pnpm --filter @frigear-nu/payments lint
|
|
43
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
import Stripe from 'stripe';
|
|
3
|
+
import { H3Event } from 'h3';
|
|
4
|
+
|
|
5
|
+
type ModuleOptions = {
|
|
6
|
+
settings?: {
|
|
7
|
+
/** Minutes a Checkout session stays open before it expires (Stripe allows 30 - 1440). Defaults to `30`. */
|
|
8
|
+
checkoutExpiresInMinutes?: number;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
type PaymentsRuntimeConfig = {
|
|
12
|
+
checkoutExpiresInMinutes: number;
|
|
13
|
+
};
|
|
14
|
+
type PaymentStatus = 'pending' | 'paid' | 'failed' | 'expired' | 'refunded' | 'partially_refunded';
|
|
15
|
+
type PaymentItem = {
|
|
16
|
+
name: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
/** Unit amount in the currency's minor unit (e.g. cents / øre). */
|
|
19
|
+
amount: number;
|
|
20
|
+
/** Three letter ISO currency code. */
|
|
21
|
+
currency: string;
|
|
22
|
+
quantity?: number;
|
|
23
|
+
};
|
|
24
|
+
type PaymentReference = {
|
|
25
|
+
/** What the payment is for, e.g. `event_ticket`. Used by consumers to filter lifecycle hooks. */
|
|
26
|
+
type: string;
|
|
27
|
+
id: string;
|
|
28
|
+
};
|
|
29
|
+
type PaymentDto = {
|
|
30
|
+
id: string;
|
|
31
|
+
userId: number;
|
|
32
|
+
status: PaymentStatus;
|
|
33
|
+
amount: number;
|
|
34
|
+
currency: string;
|
|
35
|
+
description: string | null;
|
|
36
|
+
referenceType: string | null;
|
|
37
|
+
referenceId: string | null;
|
|
38
|
+
refundedAmount: number;
|
|
39
|
+
paidAt: string | null;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
};
|
|
42
|
+
type CreatePaymentInput = {
|
|
43
|
+
userId: number;
|
|
44
|
+
items: PaymentItem[];
|
|
45
|
+
reference?: PaymentReference;
|
|
46
|
+
/** Absolute URL, or a path on the current origin. */
|
|
47
|
+
successUrl: string;
|
|
48
|
+
cancelUrl: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
expiresInMinutes?: number;
|
|
51
|
+
metadata?: Record<string, string>;
|
|
52
|
+
};
|
|
53
|
+
type CreatePaymentResult = {
|
|
54
|
+
payment: PaymentDto;
|
|
55
|
+
url: string;
|
|
56
|
+
};
|
|
57
|
+
type PaymentHookContext = {
|
|
58
|
+
payment: PaymentDto;
|
|
59
|
+
};
|
|
60
|
+
type CheckoutBeforeContext = {
|
|
61
|
+
event?: H3Event;
|
|
62
|
+
userId: number;
|
|
63
|
+
reference?: PaymentReference;
|
|
64
|
+
/** Mutable: adjust the Checkout session params before they are sent to Stripe. */
|
|
65
|
+
params: Stripe.Checkout.SessionCreateParams;
|
|
66
|
+
};
|
|
67
|
+
type ResumeResponse = {
|
|
68
|
+
url: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
declare module '@nuxt/schema' {
|
|
72
|
+
interface NuxtConfig {
|
|
73
|
+
payments?: ModuleOptions;
|
|
74
|
+
}
|
|
75
|
+
interface RuntimeConfig {
|
|
76
|
+
payments: PaymentsRuntimeConfig;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
declare module 'nitropack' {
|
|
80
|
+
interface NitroRuntimeHooks {
|
|
81
|
+
/** Policy hook: throw to deny a checkout, or adjust `ctx.params` before it reaches Stripe. */
|
|
82
|
+
'payments:checkout:before': (ctx: CheckoutBeforeContext) => void | Promise<void>;
|
|
83
|
+
/** A payment became `paid` (fired once, from the verified Stripe event). */
|
|
84
|
+
'payments:paid': (ctx: PaymentHookContext) => void | Promise<void>;
|
|
85
|
+
/** An asynchronous payment method failed. */
|
|
86
|
+
'payments:failed': (ctx: PaymentHookContext) => void | Promise<void>;
|
|
87
|
+
/** The Checkout session expired without payment. */
|
|
88
|
+
'payments:expired': (ctx: PaymentHookContext) => void | Promise<void>;
|
|
89
|
+
/** The payment was (partially) refunded; inspect `payment.status` / `payment.refundedAmount`. */
|
|
90
|
+
'payments:refunded': (ctx: PaymentHookContext) => void | Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
94
|
+
|
|
95
|
+
export { _default as default };
|
|
96
|
+
export type { CheckoutBeforeContext, CreatePaymentInput, CreatePaymentResult, ModuleOptions, PaymentDto, PaymentHookContext, PaymentItem, PaymentReference, PaymentStatus, PaymentsRuntimeConfig, ResumeResponse };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { defineNuxtModule, createResolver, addServerHandler, addServerImportsDir, addImports } from '@nuxt/kit';
|
|
2
|
+
import { registerModule, registerLocales, registerDatabaseTable, moduleApiUrl } from '@frigear-nu/module-kit/utils';
|
|
3
|
+
import { registerStripeEventHandler } from '@frigear-nu/stripe/utils';
|
|
4
|
+
|
|
5
|
+
const MODULE_NAME = "@frigear-nu/payments";
|
|
6
|
+
const module$1 = defineNuxtModule({
|
|
7
|
+
meta: { name: MODULE_NAME, configKey: "payments" },
|
|
8
|
+
defaults: {
|
|
9
|
+
settings: { checkoutExpiresInMinutes: 30 }
|
|
10
|
+
},
|
|
11
|
+
moduleDependencies: {
|
|
12
|
+
"@nuxthub/core": {
|
|
13
|
+
version: ">=0.10",
|
|
14
|
+
defaults: { kv: true }
|
|
15
|
+
},
|
|
16
|
+
"@frigear-nu/module-kit": { version: ">=0.0.1" },
|
|
17
|
+
"@frigear-nu/users": { version: ">=0.0.1" },
|
|
18
|
+
"@frigear-nu/stripe": { version: ">=0.0.1" }
|
|
19
|
+
},
|
|
20
|
+
async setup(options, nuxt) {
|
|
21
|
+
registerModule(MODULE_NAME, nuxt);
|
|
22
|
+
const resolver = createResolver(import.meta.url);
|
|
23
|
+
registerLocales(nuxt, resolver.resolve("./runtime/i18n/lang"));
|
|
24
|
+
nuxt.options.runtimeConfig.payments = {
|
|
25
|
+
checkoutExpiresInMinutes: options.settings?.checkoutExpiresInMinutes ?? 30
|
|
26
|
+
};
|
|
27
|
+
await registerDatabaseTable(nuxt, {
|
|
28
|
+
name: "payments",
|
|
29
|
+
resolver,
|
|
30
|
+
schema: (dialect) => `./runtime/server/db/schema.${dialect}`
|
|
31
|
+
});
|
|
32
|
+
registerStripeEventHandler(nuxt, {
|
|
33
|
+
events: [
|
|
34
|
+
"checkout.session.completed",
|
|
35
|
+
"checkout.session.async_payment_succeeded",
|
|
36
|
+
"checkout.session.async_payment_failed",
|
|
37
|
+
"checkout.session.expired",
|
|
38
|
+
"charge.refunded"
|
|
39
|
+
],
|
|
40
|
+
handler: resolver.resolve("./runtime/server/stripe/payment-events")
|
|
41
|
+
});
|
|
42
|
+
addServerHandler({
|
|
43
|
+
route: moduleApiUrl("payments"),
|
|
44
|
+
method: "get",
|
|
45
|
+
handler: resolver.resolve("./runtime/server/routes/payments/index.get")
|
|
46
|
+
});
|
|
47
|
+
addServerHandler({
|
|
48
|
+
route: moduleApiUrl("payments", "resume"),
|
|
49
|
+
method: "post",
|
|
50
|
+
handler: resolver.resolve("./runtime/server/routes/payments/resume.post")
|
|
51
|
+
});
|
|
52
|
+
addServerImportsDir(resolver.resolve("./runtime/server/utils"));
|
|
53
|
+
addImports([
|
|
54
|
+
{ name: "usePayments", from: resolver.resolve("./runtime/app/composables/usePayments") }
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export { module$1 as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { usePayments } from './usePayments.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { usePayments } from "./usePayments.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function usePayments() {
|
|
2
|
+
const { loggedIn } = useUserSession();
|
|
3
|
+
const { data, refresh, status } = useFetch("/api/payments", {
|
|
4
|
+
key: "payments:mine",
|
|
5
|
+
immediate: loggedIn.value,
|
|
6
|
+
watch: false,
|
|
7
|
+
default: () => []
|
|
8
|
+
});
|
|
9
|
+
async function resume(paymentId) {
|
|
10
|
+
const { url } = await $fetch("/api/payments/resume", { method: "POST", body: { paymentId } });
|
|
11
|
+
await navigateTo(url, { external: true });
|
|
12
|
+
}
|
|
13
|
+
return { payments: data, refresh, status, resume };
|
|
14
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
export declare const payments: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
2
|
+
name: "payments";
|
|
3
|
+
schema: undefined;
|
|
4
|
+
columns: {
|
|
5
|
+
id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
6
|
+
name: "id";
|
|
7
|
+
tableName: "payments";
|
|
8
|
+
dataType: "string";
|
|
9
|
+
columnType: "SQLiteText";
|
|
10
|
+
data: string;
|
|
11
|
+
driverParam: string;
|
|
12
|
+
notNull: true;
|
|
13
|
+
hasDefault: false;
|
|
14
|
+
isPrimaryKey: true;
|
|
15
|
+
isAutoincrement: false;
|
|
16
|
+
hasRuntimeDefault: false;
|
|
17
|
+
enumValues: [string, ...string[]];
|
|
18
|
+
baseColumn: never;
|
|
19
|
+
identity: undefined;
|
|
20
|
+
generated: undefined;
|
|
21
|
+
}, {}, {
|
|
22
|
+
length: undefined;
|
|
23
|
+
}>;
|
|
24
|
+
userId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
25
|
+
name: "userId";
|
|
26
|
+
tableName: "payments";
|
|
27
|
+
dataType: "number";
|
|
28
|
+
columnType: "SQLiteInteger";
|
|
29
|
+
data: number;
|
|
30
|
+
driverParam: number;
|
|
31
|
+
notNull: true;
|
|
32
|
+
hasDefault: false;
|
|
33
|
+
isPrimaryKey: false;
|
|
34
|
+
isAutoincrement: false;
|
|
35
|
+
hasRuntimeDefault: false;
|
|
36
|
+
enumValues: undefined;
|
|
37
|
+
baseColumn: never;
|
|
38
|
+
identity: undefined;
|
|
39
|
+
generated: undefined;
|
|
40
|
+
}, {}, {}>;
|
|
41
|
+
status: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
42
|
+
name: "status";
|
|
43
|
+
tableName: "payments";
|
|
44
|
+
dataType: "string";
|
|
45
|
+
columnType: "SQLiteText";
|
|
46
|
+
data: string;
|
|
47
|
+
driverParam: string;
|
|
48
|
+
notNull: true;
|
|
49
|
+
hasDefault: true;
|
|
50
|
+
isPrimaryKey: false;
|
|
51
|
+
isAutoincrement: false;
|
|
52
|
+
hasRuntimeDefault: false;
|
|
53
|
+
enumValues: [string, ...string[]];
|
|
54
|
+
baseColumn: never;
|
|
55
|
+
identity: undefined;
|
|
56
|
+
generated: undefined;
|
|
57
|
+
}, {}, {
|
|
58
|
+
length: undefined;
|
|
59
|
+
}>;
|
|
60
|
+
amount: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
61
|
+
name: "amount";
|
|
62
|
+
tableName: "payments";
|
|
63
|
+
dataType: "number";
|
|
64
|
+
columnType: "SQLiteInteger";
|
|
65
|
+
data: number;
|
|
66
|
+
driverParam: number;
|
|
67
|
+
notNull: true;
|
|
68
|
+
hasDefault: false;
|
|
69
|
+
isPrimaryKey: false;
|
|
70
|
+
isAutoincrement: false;
|
|
71
|
+
hasRuntimeDefault: false;
|
|
72
|
+
enumValues: undefined;
|
|
73
|
+
baseColumn: never;
|
|
74
|
+
identity: undefined;
|
|
75
|
+
generated: undefined;
|
|
76
|
+
}, {}, {}>;
|
|
77
|
+
currency: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
78
|
+
name: "currency";
|
|
79
|
+
tableName: "payments";
|
|
80
|
+
dataType: "string";
|
|
81
|
+
columnType: "SQLiteText";
|
|
82
|
+
data: string;
|
|
83
|
+
driverParam: string;
|
|
84
|
+
notNull: true;
|
|
85
|
+
hasDefault: false;
|
|
86
|
+
isPrimaryKey: false;
|
|
87
|
+
isAutoincrement: false;
|
|
88
|
+
hasRuntimeDefault: false;
|
|
89
|
+
enumValues: [string, ...string[]];
|
|
90
|
+
baseColumn: never;
|
|
91
|
+
identity: undefined;
|
|
92
|
+
generated: undefined;
|
|
93
|
+
}, {}, {
|
|
94
|
+
length: undefined;
|
|
95
|
+
}>;
|
|
96
|
+
description: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
97
|
+
name: "description";
|
|
98
|
+
tableName: "payments";
|
|
99
|
+
dataType: "string";
|
|
100
|
+
columnType: "SQLiteText";
|
|
101
|
+
data: string;
|
|
102
|
+
driverParam: string;
|
|
103
|
+
notNull: false;
|
|
104
|
+
hasDefault: false;
|
|
105
|
+
isPrimaryKey: false;
|
|
106
|
+
isAutoincrement: false;
|
|
107
|
+
hasRuntimeDefault: false;
|
|
108
|
+
enumValues: [string, ...string[]];
|
|
109
|
+
baseColumn: never;
|
|
110
|
+
identity: undefined;
|
|
111
|
+
generated: undefined;
|
|
112
|
+
}, {}, {
|
|
113
|
+
length: undefined;
|
|
114
|
+
}>;
|
|
115
|
+
referenceType: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
116
|
+
name: "referenceType";
|
|
117
|
+
tableName: "payments";
|
|
118
|
+
dataType: "string";
|
|
119
|
+
columnType: "SQLiteText";
|
|
120
|
+
data: string;
|
|
121
|
+
driverParam: string;
|
|
122
|
+
notNull: false;
|
|
123
|
+
hasDefault: false;
|
|
124
|
+
isPrimaryKey: false;
|
|
125
|
+
isAutoincrement: false;
|
|
126
|
+
hasRuntimeDefault: false;
|
|
127
|
+
enumValues: [string, ...string[]];
|
|
128
|
+
baseColumn: never;
|
|
129
|
+
identity: undefined;
|
|
130
|
+
generated: undefined;
|
|
131
|
+
}, {}, {
|
|
132
|
+
length: undefined;
|
|
133
|
+
}>;
|
|
134
|
+
referenceId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
135
|
+
name: "referenceId";
|
|
136
|
+
tableName: "payments";
|
|
137
|
+
dataType: "string";
|
|
138
|
+
columnType: "SQLiteText";
|
|
139
|
+
data: string;
|
|
140
|
+
driverParam: string;
|
|
141
|
+
notNull: false;
|
|
142
|
+
hasDefault: false;
|
|
143
|
+
isPrimaryKey: false;
|
|
144
|
+
isAutoincrement: false;
|
|
145
|
+
hasRuntimeDefault: false;
|
|
146
|
+
enumValues: [string, ...string[]];
|
|
147
|
+
baseColumn: never;
|
|
148
|
+
identity: undefined;
|
|
149
|
+
generated: undefined;
|
|
150
|
+
}, {}, {
|
|
151
|
+
length: undefined;
|
|
152
|
+
}>;
|
|
153
|
+
checkoutSessionId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
154
|
+
name: "checkoutSessionId";
|
|
155
|
+
tableName: "payments";
|
|
156
|
+
dataType: "string";
|
|
157
|
+
columnType: "SQLiteText";
|
|
158
|
+
data: string;
|
|
159
|
+
driverParam: string;
|
|
160
|
+
notNull: true;
|
|
161
|
+
hasDefault: false;
|
|
162
|
+
isPrimaryKey: false;
|
|
163
|
+
isAutoincrement: false;
|
|
164
|
+
hasRuntimeDefault: false;
|
|
165
|
+
enumValues: [string, ...string[]];
|
|
166
|
+
baseColumn: never;
|
|
167
|
+
identity: undefined;
|
|
168
|
+
generated: undefined;
|
|
169
|
+
}, {}, {
|
|
170
|
+
length: undefined;
|
|
171
|
+
}>;
|
|
172
|
+
paymentIntentId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
173
|
+
name: "paymentIntentId";
|
|
174
|
+
tableName: "payments";
|
|
175
|
+
dataType: "string";
|
|
176
|
+
columnType: "SQLiteText";
|
|
177
|
+
data: string;
|
|
178
|
+
driverParam: string;
|
|
179
|
+
notNull: false;
|
|
180
|
+
hasDefault: false;
|
|
181
|
+
isPrimaryKey: false;
|
|
182
|
+
isAutoincrement: false;
|
|
183
|
+
hasRuntimeDefault: false;
|
|
184
|
+
enumValues: [string, ...string[]];
|
|
185
|
+
baseColumn: never;
|
|
186
|
+
identity: undefined;
|
|
187
|
+
generated: undefined;
|
|
188
|
+
}, {}, {
|
|
189
|
+
length: undefined;
|
|
190
|
+
}>;
|
|
191
|
+
refundedAmount: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
192
|
+
name: "refundedAmount";
|
|
193
|
+
tableName: "payments";
|
|
194
|
+
dataType: "number";
|
|
195
|
+
columnType: "SQLiteInteger";
|
|
196
|
+
data: number;
|
|
197
|
+
driverParam: number;
|
|
198
|
+
notNull: true;
|
|
199
|
+
hasDefault: true;
|
|
200
|
+
isPrimaryKey: false;
|
|
201
|
+
isAutoincrement: false;
|
|
202
|
+
hasRuntimeDefault: false;
|
|
203
|
+
enumValues: undefined;
|
|
204
|
+
baseColumn: never;
|
|
205
|
+
identity: undefined;
|
|
206
|
+
generated: undefined;
|
|
207
|
+
}, {}, {}>;
|
|
208
|
+
metadata: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
209
|
+
name: "metadata";
|
|
210
|
+
tableName: "payments";
|
|
211
|
+
dataType: "json";
|
|
212
|
+
columnType: "SQLiteTextJson";
|
|
213
|
+
data: Record<string, string>;
|
|
214
|
+
driverParam: string;
|
|
215
|
+
notNull: false;
|
|
216
|
+
hasDefault: false;
|
|
217
|
+
isPrimaryKey: false;
|
|
218
|
+
isAutoincrement: false;
|
|
219
|
+
hasRuntimeDefault: false;
|
|
220
|
+
enumValues: undefined;
|
|
221
|
+
baseColumn: never;
|
|
222
|
+
identity: undefined;
|
|
223
|
+
generated: undefined;
|
|
224
|
+
}, {}, {
|
|
225
|
+
$type: Record<string, string>;
|
|
226
|
+
}>;
|
|
227
|
+
paidAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
228
|
+
name: "paidAt";
|
|
229
|
+
tableName: "payments";
|
|
230
|
+
dataType: "date";
|
|
231
|
+
columnType: "SQLiteTimestamp";
|
|
232
|
+
data: Date;
|
|
233
|
+
driverParam: number;
|
|
234
|
+
notNull: false;
|
|
235
|
+
hasDefault: false;
|
|
236
|
+
isPrimaryKey: false;
|
|
237
|
+
isAutoincrement: false;
|
|
238
|
+
hasRuntimeDefault: false;
|
|
239
|
+
enumValues: undefined;
|
|
240
|
+
baseColumn: never;
|
|
241
|
+
identity: undefined;
|
|
242
|
+
generated: undefined;
|
|
243
|
+
}, {}, {}>;
|
|
244
|
+
createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
245
|
+
name: "createdAt";
|
|
246
|
+
tableName: "payments";
|
|
247
|
+
dataType: "date";
|
|
248
|
+
columnType: "SQLiteTimestamp";
|
|
249
|
+
data: Date;
|
|
250
|
+
driverParam: number;
|
|
251
|
+
notNull: true;
|
|
252
|
+
hasDefault: true;
|
|
253
|
+
isPrimaryKey: false;
|
|
254
|
+
isAutoincrement: false;
|
|
255
|
+
hasRuntimeDefault: true;
|
|
256
|
+
enumValues: undefined;
|
|
257
|
+
baseColumn: never;
|
|
258
|
+
identity: undefined;
|
|
259
|
+
generated: undefined;
|
|
260
|
+
}, {}, {}>;
|
|
261
|
+
updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
262
|
+
name: "updatedAt";
|
|
263
|
+
tableName: "payments";
|
|
264
|
+
dataType: "date";
|
|
265
|
+
columnType: "SQLiteTimestamp";
|
|
266
|
+
data: Date;
|
|
267
|
+
driverParam: number;
|
|
268
|
+
notNull: true;
|
|
269
|
+
hasDefault: true;
|
|
270
|
+
isPrimaryKey: false;
|
|
271
|
+
isAutoincrement: false;
|
|
272
|
+
hasRuntimeDefault: true;
|
|
273
|
+
enumValues: undefined;
|
|
274
|
+
baseColumn: never;
|
|
275
|
+
identity: undefined;
|
|
276
|
+
generated: undefined;
|
|
277
|
+
}, {}, {}>;
|
|
278
|
+
};
|
|
279
|
+
dialect: "sqlite";
|
|
280
|
+
}>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core";
|
|
2
|
+
export const payments = sqliteTable("payments", {
|
|
3
|
+
/** `pay_` + random id. */
|
|
4
|
+
id: text().primaryKey(),
|
|
5
|
+
userId: integer().notNull(),
|
|
6
|
+
/** `pending | paid | failed | expired | refunded | partially_refunded`. */
|
|
7
|
+
status: text().notNull().default("pending"),
|
|
8
|
+
/** Total in the currency's minor unit. */
|
|
9
|
+
amount: integer().notNull(),
|
|
10
|
+
currency: text().notNull(),
|
|
11
|
+
description: text(),
|
|
12
|
+
/** What the payment is for (e.g. `event_ticket`) and the id of that thing. */
|
|
13
|
+
referenceType: text(),
|
|
14
|
+
referenceId: text(),
|
|
15
|
+
checkoutSessionId: text().notNull().unique(),
|
|
16
|
+
paymentIntentId: text(),
|
|
17
|
+
refundedAmount: integer().notNull().default(0),
|
|
18
|
+
metadata: text({ mode: "json" }).$type(),
|
|
19
|
+
paidAt: integer({ mode: "timestamp" }),
|
|
20
|
+
createdAt: integer({ mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
21
|
+
updatedAt: integer({ mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()).$onUpdateFn(() => /* @__PURE__ */ new Date())
|
|
22
|
+
}, (t) => [
|
|
23
|
+
index("payment_user_idx").on(t.userId),
|
|
24
|
+
index("payment_reference_idx").on(t.referenceType, t.referenceId),
|
|
25
|
+
index("payment_intent_idx").on(t.paymentIntentId)
|
|
26
|
+
]);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const bodySchema = z.object({ paymentId: z.string().min(1) });
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const { user } = await requireUserSession(event);
|
|
5
|
+
const { paymentId } = await readValidatedBody(event, bodySchema.parse);
|
|
6
|
+
return resumePayment(paymentId, user.id, event);
|
|
7
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { db, schema } from "@nuxthub/db";
|
|
2
|
+
import { eq } from "drizzle-orm";
|
|
3
|
+
import { defineStripeEventHandler } from "@frigear-nu/stripe/utils";
|
|
4
|
+
import { toPaymentDto } from "../utils/payments.js";
|
|
5
|
+
const hookFor = {
|
|
6
|
+
paid: "payments:paid",
|
|
7
|
+
failed: "payments:failed",
|
|
8
|
+
expired: "payments:expired",
|
|
9
|
+
refunded: "payments:refunded"
|
|
10
|
+
};
|
|
11
|
+
export default defineStripeEventHandler(async (event) => {
|
|
12
|
+
const nitro = useNitroApp();
|
|
13
|
+
async function transition(where, next, extra, allowedFrom, hook) {
|
|
14
|
+
const [row] = await db.select().from(schema.payments).where(where).limit(1);
|
|
15
|
+
if (!row) return;
|
|
16
|
+
if (row.status === next && hook !== "refunded") return;
|
|
17
|
+
if (!allowedFrom.includes(row.status)) return;
|
|
18
|
+
const [updated] = await db.update(schema.payments).set({ status: next, ...extra }).where(eq(schema.payments.id, row.id)).returning();
|
|
19
|
+
if (updated) await nitro.hooks.callHook(hookFor[hook], { payment: toPaymentDto(updated) });
|
|
20
|
+
}
|
|
21
|
+
switch (event.type) {
|
|
22
|
+
case "checkout.session.completed":
|
|
23
|
+
case "checkout.session.async_payment_succeeded": {
|
|
24
|
+
const session = event.data.object;
|
|
25
|
+
if (session.payment_status !== "paid") return;
|
|
26
|
+
await transition(
|
|
27
|
+
eq(schema.payments.checkoutSessionId, session.id),
|
|
28
|
+
"paid",
|
|
29
|
+
{ paidAt: /* @__PURE__ */ new Date(), paymentIntentId: typeof session.payment_intent === "string" ? session.payment_intent : session.payment_intent?.id ?? null },
|
|
30
|
+
["pending", "failed", "expired"],
|
|
31
|
+
"paid"
|
|
32
|
+
);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
case "checkout.session.async_payment_failed": {
|
|
36
|
+
await transition(eq(schema.payments.checkoutSessionId, event.data.object.id), "failed", {}, ["pending"], "failed");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
case "checkout.session.expired": {
|
|
40
|
+
await transition(eq(schema.payments.checkoutSessionId, event.data.object.id), "expired", {}, ["pending"], "expired");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
case "charge.refunded": {
|
|
44
|
+
const charge = event.data.object;
|
|
45
|
+
const intent = typeof charge.payment_intent === "string" ? charge.payment_intent : charge.payment_intent?.id;
|
|
46
|
+
if (!intent) return;
|
|
47
|
+
const full = charge.amount_refunded >= charge.amount;
|
|
48
|
+
const [row] = await db.select().from(schema.payments).where(eq(schema.payments.paymentIntentId, intent)).limit(1);
|
|
49
|
+
if (!row || row.refundedAmount === charge.amount_refunded) return;
|
|
50
|
+
await transition(
|
|
51
|
+
eq(schema.payments.paymentIntentId, intent),
|
|
52
|
+
full ? "refunded" : "partially_refunded",
|
|
53
|
+
{ refundedAmount: charge.amount_refunded },
|
|
54
|
+
["paid", "partially_refunded"],
|
|
55
|
+
"refunded"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { schema } from '@nuxthub/db';
|
|
2
|
+
import type { H3Event } from 'h3';
|
|
3
|
+
import type { CreatePaymentInput, CreatePaymentResult, PaymentDto } from '../../../types.js';
|
|
4
|
+
type PaymentRow = typeof schema.payments.$inferSelect;
|
|
5
|
+
export declare function toPaymentDto(row: PaymentRow): PaymentDto;
|
|
6
|
+
/**
|
|
7
|
+
* Creates a Stripe Checkout session (`mode: 'payment'`, inline `price_data`) and a `pending` payment
|
|
8
|
+
* row. The row only turns `paid` when Stripe tells us so (see `payments:paid`).
|
|
9
|
+
*
|
|
10
|
+
* Pass the request `event` to have the return URLs restricted to the current origin.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createPayment(input: CreatePaymentInput, event?: H3Event): Promise<CreatePaymentResult>;
|
|
13
|
+
export declare function getPayment(id: string): Promise<PaymentDto | null>;
|
|
14
|
+
export declare function getPaymentByReference(type: string, id: string): Promise<PaymentDto | null>;
|
|
15
|
+
export declare function listUserPayments(userId: number): Promise<PaymentDto[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Returns a checkout URL for a still-unpaid payment: the original session when it is open, otherwise
|
|
18
|
+
* a fresh session for the same amount (the payment row follows the new session). Throws for payments
|
|
19
|
+
* that are already paid/refunded.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resumePayment(paymentId: string, userId: number, event?: H3Event): Promise<{
|
|
22
|
+
url: string;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Refunds a paid payment (fully, or `amount` minor units). The status change is applied by the
|
|
26
|
+
* `charge.refunded` Stripe event, so the `payments:refunded` hook fires once from one place.
|
|
27
|
+
*/
|
|
28
|
+
export declare function refundPayment(paymentId: string, options?: {
|
|
29
|
+
amount?: number;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { db, schema } from "@nuxthub/db";
|
|
2
|
+
import { and, desc, eq } from "drizzle-orm";
|
|
3
|
+
import { resolveSameOriginUrl, sumItems } from "../../utils/index.js";
|
|
4
|
+
export function toPaymentDto(row) {
|
|
5
|
+
return {
|
|
6
|
+
id: row.id,
|
|
7
|
+
userId: row.userId,
|
|
8
|
+
status: row.status,
|
|
9
|
+
amount: row.amount,
|
|
10
|
+
currency: row.currency,
|
|
11
|
+
description: row.description,
|
|
12
|
+
referenceType: row.referenceType,
|
|
13
|
+
referenceId: row.referenceId,
|
|
14
|
+
refundedAmount: row.refundedAmount,
|
|
15
|
+
paidAt: row.paidAt?.toISOString() ?? null,
|
|
16
|
+
createdAt: row.createdAt.toISOString()
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const newPaymentId = () => `pay_${crypto.randomUUID().replaceAll("-", "")}`;
|
|
20
|
+
function safeReturnUrl(input, event, name) {
|
|
21
|
+
if (!event) return input;
|
|
22
|
+
const resolved = resolveSameOriginUrl(input, getRequestURL(event).origin);
|
|
23
|
+
if (!resolved) throw createError({ statusCode: 400, statusMessage: `${name} must be a relative path or an URL on the same origin.` });
|
|
24
|
+
return resolved;
|
|
25
|
+
}
|
|
26
|
+
function expiresAt(minutes) {
|
|
27
|
+
const clamped = Math.min(Math.max(Math.round(minutes), 30), 1440);
|
|
28
|
+
return Math.floor(Date.now() / 1e3) + clamped * 60;
|
|
29
|
+
}
|
|
30
|
+
export async function createPayment(input, event) {
|
|
31
|
+
if (input.items.length === 0) throw createError({ statusCode: 400, statusMessage: "A payment needs at least one item." });
|
|
32
|
+
const currency = input.items[0].currency.toLowerCase();
|
|
33
|
+
if (input.items.some((item) => item.currency.toLowerCase() !== currency)) {
|
|
34
|
+
throw createError({ statusCode: 400, statusMessage: "All items in a payment must share one currency." });
|
|
35
|
+
}
|
|
36
|
+
const amount = sumItems(input.items);
|
|
37
|
+
if (amount <= 0) throw createError({ statusCode: 400, statusMessage: "The payment amount must be greater than zero." });
|
|
38
|
+
const config = useRuntimeConfig().payments;
|
|
39
|
+
const stripe = useStripe();
|
|
40
|
+
const customer = await ensureStripeCustomer(input.userId);
|
|
41
|
+
const id = newPaymentId();
|
|
42
|
+
const metadata = { ...input.metadata, paymentId: id, userId: String(input.userId) };
|
|
43
|
+
const context = {
|
|
44
|
+
event,
|
|
45
|
+
userId: input.userId,
|
|
46
|
+
reference: input.reference,
|
|
47
|
+
params: {
|
|
48
|
+
mode: "payment",
|
|
49
|
+
customer,
|
|
50
|
+
client_reference_id: String(input.userId),
|
|
51
|
+
line_items: input.items.map((item) => ({
|
|
52
|
+
quantity: item.quantity ?? 1,
|
|
53
|
+
price_data: {
|
|
54
|
+
currency,
|
|
55
|
+
unit_amount: item.amount,
|
|
56
|
+
product_data: { name: item.name, ...item.description ? { description: item.description } : {} }
|
|
57
|
+
}
|
|
58
|
+
})),
|
|
59
|
+
success_url: safeReturnUrl(input.successUrl, event, "successUrl"),
|
|
60
|
+
cancel_url: safeReturnUrl(input.cancelUrl, event, "cancelUrl"),
|
|
61
|
+
expires_at: expiresAt(input.expiresInMinutes ?? config.checkoutExpiresInMinutes),
|
|
62
|
+
metadata,
|
|
63
|
+
payment_intent_data: { metadata }
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
await useNitroApp().hooks.callHook("payments:checkout:before", context);
|
|
67
|
+
const session = await stripe.checkout.sessions.create(context.params);
|
|
68
|
+
if (!session.url) throw createError({ statusCode: 502, statusMessage: "Stripe did not return a checkout URL." });
|
|
69
|
+
try {
|
|
70
|
+
const [row] = await db.insert(schema.payments).values({
|
|
71
|
+
id,
|
|
72
|
+
userId: input.userId,
|
|
73
|
+
amount,
|
|
74
|
+
currency,
|
|
75
|
+
description: input.description ?? null,
|
|
76
|
+
referenceType: input.reference?.type ?? null,
|
|
77
|
+
referenceId: input.reference?.id ?? null,
|
|
78
|
+
checkoutSessionId: session.id,
|
|
79
|
+
metadata: input.metadata ?? null
|
|
80
|
+
}).returning();
|
|
81
|
+
return { payment: toPaymentDto(row), url: session.url };
|
|
82
|
+
} catch (error) {
|
|
83
|
+
await stripe.checkout.sessions.expire(session.id).catch(() => void 0);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export async function getPayment(id) {
|
|
88
|
+
const [row] = await db.select().from(schema.payments).where(eq(schema.payments.id, id)).limit(1);
|
|
89
|
+
return row ? toPaymentDto(row) : null;
|
|
90
|
+
}
|
|
91
|
+
export async function getPaymentByReference(type, id) {
|
|
92
|
+
const [row] = await db.select().from(schema.payments).where(and(eq(schema.payments.referenceType, type), eq(schema.payments.referenceId, id))).orderBy(desc(schema.payments.createdAt)).limit(1);
|
|
93
|
+
return row ? toPaymentDto(row) : null;
|
|
94
|
+
}
|
|
95
|
+
export async function listUserPayments(userId) {
|
|
96
|
+
const rows = await db.select().from(schema.payments).where(eq(schema.payments.userId, userId)).orderBy(desc(schema.payments.createdAt));
|
|
97
|
+
return rows.map(toPaymentDto);
|
|
98
|
+
}
|
|
99
|
+
export async function resumePayment(paymentId, userId, event) {
|
|
100
|
+
const [row] = await db.select().from(schema.payments).where(and(eq(schema.payments.id, paymentId), eq(schema.payments.userId, userId))).limit(1);
|
|
101
|
+
if (!row) throw createError({ statusCode: 404, statusMessage: "Payment not found." });
|
|
102
|
+
if (row.status === "paid" || row.status === "refunded" || row.status === "partially_refunded") {
|
|
103
|
+
throw createError({ statusCode: 409, statusMessage: "This payment is already paid." });
|
|
104
|
+
}
|
|
105
|
+
const stripe = useStripe();
|
|
106
|
+
const session = await stripe.checkout.sessions.retrieve(row.checkoutSessionId);
|
|
107
|
+
if (session.status === "open" && session.url) return { url: session.url };
|
|
108
|
+
if (session.status === "complete") throw createError({ statusCode: 409, statusMessage: "This payment is already paid." });
|
|
109
|
+
const customer = await ensureStripeCustomer(userId);
|
|
110
|
+
const metadata = { ...row.metadata, paymentId: row.id, userId: String(userId) };
|
|
111
|
+
const origin = event ? getRequestURL(event).origin : void 0;
|
|
112
|
+
const successUrl = session.success_url ?? origin;
|
|
113
|
+
const cancelUrl = session.cancel_url ?? origin;
|
|
114
|
+
if (!successUrl || !cancelUrl) throw createError({ statusCode: 409, statusMessage: "Cannot resume this payment." });
|
|
115
|
+
const next = await stripe.checkout.sessions.create({
|
|
116
|
+
mode: "payment",
|
|
117
|
+
customer,
|
|
118
|
+
client_reference_id: String(userId),
|
|
119
|
+
line_items: [{
|
|
120
|
+
quantity: 1,
|
|
121
|
+
price_data: {
|
|
122
|
+
currency: row.currency,
|
|
123
|
+
unit_amount: row.amount,
|
|
124
|
+
product_data: { name: row.description ?? "Payment" }
|
|
125
|
+
}
|
|
126
|
+
}],
|
|
127
|
+
success_url: successUrl,
|
|
128
|
+
cancel_url: cancelUrl,
|
|
129
|
+
expires_at: expiresAt(useRuntimeConfig().payments.checkoutExpiresInMinutes),
|
|
130
|
+
metadata,
|
|
131
|
+
payment_intent_data: { metadata }
|
|
132
|
+
});
|
|
133
|
+
if (!next.url) throw createError({ statusCode: 502, statusMessage: "Stripe did not return a checkout URL." });
|
|
134
|
+
await db.update(schema.payments).set({ checkoutSessionId: next.id, status: "pending" }).where(eq(schema.payments.id, row.id));
|
|
135
|
+
return { url: next.url };
|
|
136
|
+
}
|
|
137
|
+
export async function refundPayment(paymentId, options = {}) {
|
|
138
|
+
const [row] = await db.select().from(schema.payments).where(eq(schema.payments.id, paymentId)).limit(1);
|
|
139
|
+
if (!row) throw createError({ statusCode: 404, statusMessage: "Payment not found." });
|
|
140
|
+
if (!row.paymentIntentId || row.status !== "paid" && row.status !== "partially_refunded") {
|
|
141
|
+
throw createError({ statusCode: 409, statusMessage: "Only paid payments can be refunded." });
|
|
142
|
+
}
|
|
143
|
+
if (options.amount !== void 0 && (options.amount <= 0 || options.amount > row.amount - row.refundedAmount)) {
|
|
144
|
+
throw createError({ statusCode: 400, statusMessage: "Invalid refund amount." });
|
|
145
|
+
}
|
|
146
|
+
await useStripe().refunds.create({
|
|
147
|
+
payment_intent: row.paymentIntentId,
|
|
148
|
+
...options.amount !== void 0 ? { amount: options.amount } : {}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PaymentItem } from '../../types.js';
|
|
2
|
+
export type { CheckoutBeforeContext, CreatePaymentInput, CreatePaymentResult, ModuleOptions, PaymentDto, PaymentHookContext, PaymentItem, PaymentReference, PaymentStatus, } from '../../types.js';
|
|
3
|
+
/** Total of a list of items in minor units. */
|
|
4
|
+
export declare function sumItems(items: PaymentItem[]): number;
|
|
5
|
+
/** Resolves a relative path against `origin`, and only accepts absolute URLs on the same origin. */
|
|
6
|
+
export declare function resolveSameOriginUrl(input: string, origin: string): string | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function sumItems(items) {
|
|
2
|
+
return items.reduce((total, item) => total + item.amount * (item.quantity ?? 1), 0);
|
|
3
|
+
}
|
|
4
|
+
export function resolveSameOriginUrl(input, origin) {
|
|
5
|
+
if (input.startsWith("/")) {
|
|
6
|
+
if (input.startsWith("//") || input.startsWith("/\\")) return null;
|
|
7
|
+
return `${origin}${input}`;
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
return new URL(input).origin === new URL(origin).origin ? input : null;
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
package/dist/types.d.mts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { default } from './module.mjs'
|
|
2
|
+
|
|
3
|
+
export { type CheckoutBeforeContext, type CreatePaymentInput, type CreatePaymentResult, type ModuleOptions, type PaymentDto, type PaymentHookContext, type PaymentItem, type PaymentReference, type PaymentStatus, type PaymentsRuntimeConfig, type ResumeResponse } from './module.mjs'
|
package/package.json
CHANGED
|
@@ -1,6 +1,89 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frigear-nu/payments",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "One-time payments (Stripe Checkout, refunds and payment lifecycle hooks) for the frigear ecosystem, built on @frigear-nu/stripe.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/frigear-nu/frigear-nu.git",
|
|
8
|
+
"directory": "packages/payments"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/module.mjs",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/types.d.mts",
|
|
16
|
+
"import": "./dist/module.mjs",
|
|
17
|
+
"default": "./dist/module.mjs"
|
|
18
|
+
},
|
|
19
|
+
"./utils": {
|
|
20
|
+
"types": "./dist/runtime/utils/index.d.ts",
|
|
21
|
+
"import": "./dist/runtime/utils/index.js",
|
|
22
|
+
"default": "./dist/runtime/utils/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"workspaces": [
|
|
29
|
+
"playground"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@frigear-nu/module-kit": "0.1.0",
|
|
33
|
+
"@frigear-nu/stripe": "0.0.2",
|
|
34
|
+
"@frigear-nu/users": "1.0.2",
|
|
35
|
+
"@libsql/client": "^0.18.0",
|
|
36
|
+
"@nuxt/kit": "^4.6.0",
|
|
37
|
+
"drizzle-kit": "^0.31.11",
|
|
38
|
+
"drizzle-orm": "^0.45.3",
|
|
39
|
+
"h3": "^1.15.11",
|
|
40
|
+
"stripe": "^23.0.0",
|
|
41
|
+
"unstorage": "^1.17.5",
|
|
42
|
+
"zod": "^4.6.5"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@nuxt/ui": "^4.10.0",
|
|
46
|
+
"@nuxthub/core": "^0.10.8"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"@nuxt/ui": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@nuxt/devtools": "^3.4.2",
|
|
55
|
+
"@nuxt/eslint-config": "^1.17.0",
|
|
56
|
+
"@nuxt/module-builder": "^1.0.3",
|
|
57
|
+
"@nuxt/schema": "^4.6.0",
|
|
58
|
+
"@nuxt/test-utils": "^4.3.2",
|
|
59
|
+
"@nuxt/ui": "^4.11.0",
|
|
60
|
+
"@nuxthub/core": "^0.10.8",
|
|
61
|
+
"@types/node": "latest",
|
|
62
|
+
"changelogen": "^0.6.2",
|
|
63
|
+
"eslint": "^10.10.0",
|
|
64
|
+
"nuxt": "^4.6.0",
|
|
65
|
+
"typescript": "^6.0.3",
|
|
66
|
+
"vitest": "^5.0.1",
|
|
67
|
+
"vue-tsc": "^3.3.11"
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"postinstall": "nuxt-module-build build --stub && nuxt prepare",
|
|
71
|
+
"build": "nuxt-module-build build",
|
|
72
|
+
"dev": "npm run dev:prepare && nuxt dev playground",
|
|
73
|
+
"dev:build": "nuxt build playground",
|
|
74
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
|
|
75
|
+
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|
|
76
|
+
"lint": "eslint .",
|
|
77
|
+
"lint:fix": "eslint . --fix",
|
|
78
|
+
"test": "vitest run",
|
|
79
|
+
"test:watch": "vitest watch",
|
|
80
|
+
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
81
|
+
},
|
|
82
|
+
"typesVersions": {
|
|
83
|
+
"*": {
|
|
84
|
+
".": [
|
|
85
|
+
"./dist/types.d.mts"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
6
89
|
}
|