@porulle/plugin-giftcards 0.8.0 → 0.9.0
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/routes/admin.d.ts.map +1 -1
- package/dist/routes/admin.js +5 -4
- package/dist/services/gift-card-repository.d.ts +8 -8
- package/dist/services/gift-card-repository.d.ts.map +1 -1
- package/dist/services/gift-card-repository.js +23 -18
- package/dist/services/gift-card-service.d.ts.map +1 -1
- package/dist/services/gift-card-service.js +12 -12
- package/package.json +2 -2
- package/src/routes/admin.ts +5 -4
- package/src/services/gift-card-repository.ts +22 -15
- package/src/services/gift-card-service.ts +12 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../src/routes/admin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAG7D,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,eAAe,EACxB,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,
|
|
1
|
+
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../src/routes/admin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAG7D,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,eAAe,EACxB,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GACtE,uBAAuB,EAAE,CA0G3B"}
|
package/dist/routes/admin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { router } from "@porulle/core";
|
|
1
|
+
import { router, CommerceNotFoundError } from "@porulle/core";
|
|
2
2
|
import { z } from "@hono/zod-openapi";
|
|
3
3
|
import { formatCode } from "../code-generator.js";
|
|
4
4
|
export function buildAdminRoutes(service, ctx) {
|
|
@@ -52,8 +52,9 @@ export function buildAdminRoutes(service, ctx) {
|
|
|
52
52
|
.permission("gift-cards:admin")
|
|
53
53
|
.handler(async ({ params, orgId }) => {
|
|
54
54
|
const cardResult = await service.getById(orgId, params.id);
|
|
55
|
+
// Cross-tenant / missing card → safe 404, not a generic 500.
|
|
55
56
|
if (!cardResult.ok)
|
|
56
|
-
throw new
|
|
57
|
+
throw new CommerceNotFoundError("Gift card not found.");
|
|
57
58
|
const txnResult = await service.getTransactions(orgId, cardResult.value.id);
|
|
58
59
|
return {
|
|
59
60
|
...cardResult.value,
|
|
@@ -68,7 +69,7 @@ export function buildAdminRoutes(service, ctx) {
|
|
|
68
69
|
.handler(async ({ params, orgId }) => {
|
|
69
70
|
const result = await service.disable(orgId, params.id);
|
|
70
71
|
if (!result.ok)
|
|
71
|
-
throw new
|
|
72
|
+
throw new CommerceNotFoundError("Gift card not found.");
|
|
72
73
|
return result.value;
|
|
73
74
|
});
|
|
74
75
|
// ─── Manual Balance Adjustment ────────────────────────────────────
|
|
@@ -83,7 +84,7 @@ export function buildAdminRoutes(service, ctx) {
|
|
|
83
84
|
const body = input;
|
|
84
85
|
const result = await service.adjust(orgId, params.id, body.delta, body.note);
|
|
85
86
|
if (!result.ok)
|
|
86
|
-
throw new
|
|
87
|
+
throw new CommerceNotFoundError("Gift card not found.");
|
|
87
88
|
return result.value;
|
|
88
89
|
});
|
|
89
90
|
return r.routes();
|
|
@@ -6,25 +6,25 @@ export declare class GiftCardRepository {
|
|
|
6
6
|
create(data: GiftCardInsert, ctx?: {
|
|
7
7
|
tx?: Db;
|
|
8
8
|
}): Promise<GiftCard>;
|
|
9
|
-
findById(id: string, ctx?: {
|
|
9
|
+
findById(orgId: string, id: string, ctx?: {
|
|
10
10
|
tx?: Db;
|
|
11
11
|
}): Promise<GiftCard | undefined>;
|
|
12
|
-
findByCode(code: string, ctx?: {
|
|
12
|
+
findByCode(orgId: string, code: string, ctx?: {
|
|
13
13
|
tx?: Db;
|
|
14
14
|
}): Promise<GiftCard | undefined>;
|
|
15
|
-
list(filters?: {
|
|
15
|
+
list(orgId: string, filters?: {
|
|
16
16
|
status?: GiftCardStatus;
|
|
17
17
|
purchaserId?: string;
|
|
18
18
|
}, ctx?: {
|
|
19
19
|
tx?: Db;
|
|
20
20
|
}): Promise<GiftCard[]>;
|
|
21
|
-
disable(id: string, ctx?: {
|
|
21
|
+
disable(orgId: string, id: string, ctx?: {
|
|
22
22
|
tx?: Db;
|
|
23
23
|
}): Promise<GiftCard | undefined>;
|
|
24
|
-
findByCodeForUpdate(code: string, tx: Db): Promise<GiftCard | undefined>;
|
|
25
|
-
findByIdForUpdate(id: string, tx: Db): Promise<GiftCard | undefined>;
|
|
26
|
-
updateBalance(id: string, balance: number, status: GiftCardStatus, currentVersion: number, tx: Db): Promise<GiftCard>;
|
|
27
|
-
adjustBalance(id: string, delta: number, tx: Db): Promise<GiftCard>;
|
|
24
|
+
findByCodeForUpdate(orgId: string, code: string, tx: Db): Promise<GiftCard | undefined>;
|
|
25
|
+
findByIdForUpdate(orgId: string, id: string, tx: Db): Promise<GiftCard | undefined>;
|
|
26
|
+
updateBalance(orgId: string, id: string, balance: number, status: GiftCardStatus, currentVersion: number, tx: Db): Promise<GiftCard>;
|
|
27
|
+
adjustBalance(orgId: string, id: string, delta: number, tx: Db): Promise<GiftCard>;
|
|
28
28
|
recordTransaction(data: {
|
|
29
29
|
giftCardId: string;
|
|
30
30
|
type: TransactionType;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gift-card-repository.d.ts","sourceRoot":"","sources":["../../src/services/gift-card-repository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,mBAAmB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEtH,qBAAa,kBAAkB;IACjB,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,EAAE;IAE1B,OAAO,CAAC,KAAK;IAMP,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;IAQlE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"gift-card-repository.d.ts","sourceRoot":"","sources":["../../src/services/gift-card-repository.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,mBAAmB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEtH,qBAAa,kBAAkB;IACjB,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,EAAE;IAE1B,OAAO,CAAC,KAAK;IAMP,MAAM,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;IAQlE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAQrF,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAYzF,IAAI,CACR,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,cAAc,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,EAC3D,GAAG,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,EAAE,CAAA;KAAE,GAChB,OAAO,CAAC,QAAQ,EAAE,CAAC;IAYhB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAWpF,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IASvF,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IASnF,aAAa,CACjB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,EACtB,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,EAAE,GACL,OAAO,CAAC,QAAQ,CAAC;IAcd,aAAa,CACjB,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,EAAE,GACL,OAAO,CAAC,QAAQ,CAAC;IAYd,iBAAiB,CACrB,IAAI,EAAE;QACJ,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,eAAe,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,EACD,GAAG,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,EAAE,CAAA;KAAE,GAChB,OAAO,CAAC,mBAAmB,CAAC;IAQzB,gBAAgB,CACpB,UAAU,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,EAAE,CAAA;KAAE,GAChB,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAQ3B,yBAAyB,CAC7B,OAAO,EAAE,MAAM,EACf,GAAG,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,EAAE,CAAA;KAAE,GAChB,OAAO,CAAC,mBAAmB,EAAE,CAAC;CAOlC"}
|
|
@@ -16,22 +16,27 @@ export class GiftCardRepository {
|
|
|
16
16
|
.returning();
|
|
17
17
|
return rows[0];
|
|
18
18
|
}
|
|
19
|
-
async findById(id, ctx) {
|
|
19
|
+
async findById(orgId, id, ctx) {
|
|
20
20
|
const rows = await this.getDb(ctx)
|
|
21
21
|
.select()
|
|
22
22
|
.from(giftCards)
|
|
23
|
-
.where(eq(giftCards.id, id));
|
|
23
|
+
.where(and(eq(giftCards.id, id), eq(giftCards.organizationId, orgId)));
|
|
24
24
|
return rows[0];
|
|
25
25
|
}
|
|
26
|
-
async findByCode(code, ctx) {
|
|
26
|
+
async findByCode(orgId, code, ctx) {
|
|
27
|
+
const conditions = [eq(giftCards.code, code)];
|
|
28
|
+
// "_any" is the public bearer-token lookup (check-balance): a gift-card code
|
|
29
|
+
// is org-agnostic by design. Admin id-based lookups stay strictly org-scoped.
|
|
30
|
+
if (orgId !== "_any")
|
|
31
|
+
conditions.push(eq(giftCards.organizationId, orgId));
|
|
27
32
|
const rows = await this.getDb(ctx)
|
|
28
33
|
.select()
|
|
29
34
|
.from(giftCards)
|
|
30
|
-
.where(
|
|
35
|
+
.where(and(...conditions));
|
|
31
36
|
return rows[0];
|
|
32
37
|
}
|
|
33
|
-
async list(filters, ctx) {
|
|
34
|
-
const conditions = [];
|
|
38
|
+
async list(orgId, filters, ctx) {
|
|
39
|
+
const conditions = [eq(giftCards.organizationId, orgId)];
|
|
35
40
|
if (filters?.status)
|
|
36
41
|
conditions.push(eq(giftCards.status, filters.status));
|
|
37
42
|
if (filters?.purchaserId)
|
|
@@ -39,35 +44,35 @@ export class GiftCardRepository {
|
|
|
39
44
|
return this.getDb(ctx)
|
|
40
45
|
.select()
|
|
41
46
|
.from(giftCards)
|
|
42
|
-
.where(
|
|
47
|
+
.where(and(...conditions))
|
|
43
48
|
.orderBy(desc(giftCards.createdAt));
|
|
44
49
|
}
|
|
45
|
-
async disable(id, ctx) {
|
|
50
|
+
async disable(orgId, id, ctx) {
|
|
46
51
|
const rows = await this.getDb(ctx)
|
|
47
52
|
.update(giftCards)
|
|
48
53
|
.set({ status: "disabled", updatedAt: new Date() })
|
|
49
|
-
.where(eq(giftCards.id, id))
|
|
54
|
+
.where(and(eq(giftCards.id, id), eq(giftCards.organizationId, orgId)))
|
|
50
55
|
.returning();
|
|
51
56
|
return rows[0];
|
|
52
57
|
}
|
|
53
58
|
// ─── SELECT FOR UPDATE (Concurrency-Safe Balance Operations) ───────
|
|
54
|
-
async findByCodeForUpdate(code, tx) {
|
|
59
|
+
async findByCodeForUpdate(orgId, code, tx) {
|
|
55
60
|
const rows = await tx
|
|
56
61
|
.select()
|
|
57
62
|
.from(giftCards)
|
|
58
|
-
.where(eq(giftCards.code, code))
|
|
63
|
+
.where(and(eq(giftCards.code, code), eq(giftCards.organizationId, orgId)))
|
|
59
64
|
.for("update");
|
|
60
65
|
return rows[0];
|
|
61
66
|
}
|
|
62
|
-
async findByIdForUpdate(id, tx) {
|
|
67
|
+
async findByIdForUpdate(orgId, id, tx) {
|
|
63
68
|
const rows = await tx
|
|
64
69
|
.select()
|
|
65
70
|
.from(giftCards)
|
|
66
|
-
.where(eq(giftCards.id, id))
|
|
71
|
+
.where(and(eq(giftCards.id, id), eq(giftCards.organizationId, orgId)))
|
|
67
72
|
.for("update");
|
|
68
73
|
return rows[0];
|
|
69
74
|
}
|
|
70
|
-
async updateBalance(id, balance, status, currentVersion, tx) {
|
|
75
|
+
async updateBalance(orgId, id, balance, status, currentVersion, tx) {
|
|
71
76
|
const rows = await tx
|
|
72
77
|
.update(giftCards)
|
|
73
78
|
.set({
|
|
@@ -76,17 +81,17 @@ export class GiftCardRepository {
|
|
|
76
81
|
version: currentVersion + 1,
|
|
77
82
|
updatedAt: new Date(),
|
|
78
83
|
})
|
|
79
|
-
.where(eq(giftCards.id, id))
|
|
84
|
+
.where(and(eq(giftCards.id, id), eq(giftCards.organizationId, orgId)))
|
|
80
85
|
.returning();
|
|
81
86
|
return rows[0];
|
|
82
87
|
}
|
|
83
|
-
async adjustBalance(id, delta, tx) {
|
|
84
|
-
const card = await this.findByIdForUpdate(id, tx);
|
|
88
|
+
async adjustBalance(orgId, id, delta, tx) {
|
|
89
|
+
const card = await this.findByIdForUpdate(orgId, id, tx);
|
|
85
90
|
if (!card)
|
|
86
91
|
throw new Error("Gift card not found");
|
|
87
92
|
const newBalance = Math.max(0, Math.min(card.initialAmount, card.balance + delta));
|
|
88
93
|
const newStatus = newBalance === 0 ? "exhausted" : "active";
|
|
89
|
-
return this.updateBalance(id, newBalance, newStatus, card.version, tx);
|
|
94
|
+
return this.updateBalance(orgId, id, newBalance, newStatus, card.version, tx);
|
|
90
95
|
}
|
|
91
96
|
// ─── Transactions ───────────────────────────────────────────────────
|
|
92
97
|
async recordTransaction(data, ctx) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gift-card-service.d.ts","sourceRoot":"","sources":["../../src/services/gift-card-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGlD,OAAO,KAAK,EACV,EAAE,EACF,QAAQ,EACR,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EAEf,MAAM,aAAa,CAAC;AAErB,qBAAa,eAAe;IAOxB,OAAO,CAAC,OAAO;IANjB,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,WAAW,CAAyD;gBAG1E,EAAE,EAAE,EAAE,EACN,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,EAC7D,OAAO,EAAE,QAAQ,CAAC,qBAAqB,CAAC;IAQ5C,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;QACjC,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAuD7B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAMnE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAMvE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAClC,MAAM,CAAC,EAAE,cAAc,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC,CAAC;IAK/B,eAAe,CACnB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAOzC,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;QACpE,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;IAaH;;;OAGG;IACG,aAAa,CACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;IAwC3C;;;OAGG;IACG,cAAc,CAClB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,CAAC;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAsC5C,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAMnE,MAAM,CACV,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"gift-card-service.d.ts","sourceRoot":"","sources":["../../src/services/gift-card-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAGlD,OAAO,KAAK,EACV,EAAE,EACF,QAAQ,EACR,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EAEf,MAAM,aAAa,CAAC;AAErB,qBAAa,eAAe;IAOxB,OAAO,CAAC,OAAO;IANjB,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,WAAW,CAAyD;gBAG1E,EAAE,EAAE,EAAE,EACN,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,EAC7D,OAAO,EAAE,QAAQ,CAAC,qBAAqB,CAAC;IAQ5C,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;QACjC,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAuD7B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAMnE,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAMvE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAClC,MAAM,CAAC,EAAE,cAAc,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC,CAAC;IAK/B,eAAe,CACnB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAOzC,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;QACpE,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;IAaH;;;OAGG;IACG,aAAa,CACjB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;IAwC3C;;;OAGG;IACG,cAAc,CAClB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,CAAC;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAsC5C,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAMnE,MAAM,CACV,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;CAqCnC"}
|
|
@@ -23,7 +23,7 @@ export class GiftCardService {
|
|
|
23
23
|
let attempts = 0;
|
|
24
24
|
do {
|
|
25
25
|
code = normalizeCode(generateGiftCardCode(this.options.codeFormat));
|
|
26
|
-
const existing = await this.repo.findByCode(code);
|
|
26
|
+
const existing = await this.repo.findByCode(orgId, code);
|
|
27
27
|
if (!existing)
|
|
28
28
|
break;
|
|
29
29
|
attempts++;
|
|
@@ -60,19 +60,19 @@ export class GiftCardService {
|
|
|
60
60
|
}
|
|
61
61
|
// ─── Query ───────────────────────────────────────────────────────────
|
|
62
62
|
async getById(orgId, id) {
|
|
63
|
-
const card = await this.repo.findById(id);
|
|
63
|
+
const card = await this.repo.findById(orgId, id);
|
|
64
64
|
if (!card)
|
|
65
65
|
return Err("Gift card not found");
|
|
66
66
|
return Ok(card);
|
|
67
67
|
}
|
|
68
68
|
async getByCode(orgId, code) {
|
|
69
|
-
const card = await this.repo.findByCode(normalizeCode(code));
|
|
69
|
+
const card = await this.repo.findByCode(orgId, normalizeCode(code));
|
|
70
70
|
if (!card)
|
|
71
71
|
return Err("Gift card not found");
|
|
72
72
|
return Ok(card);
|
|
73
73
|
}
|
|
74
74
|
async list(orgId, filters) {
|
|
75
|
-
const cards = await this.repo.list(filters);
|
|
75
|
+
const cards = await this.repo.list(orgId, filters);
|
|
76
76
|
return Ok(cards);
|
|
77
77
|
}
|
|
78
78
|
async getTransactions(orgId, giftCardId) {
|
|
@@ -81,7 +81,7 @@ export class GiftCardService {
|
|
|
81
81
|
}
|
|
82
82
|
// ─── Balance Check (Public) ──────────────────────────────────────────
|
|
83
83
|
async checkBalance(orgId, code) {
|
|
84
|
-
const card = await this.repo.findByCode(normalizeCode(code));
|
|
84
|
+
const card = await this.repo.findByCode(orgId, normalizeCode(code));
|
|
85
85
|
if (!card)
|
|
86
86
|
return Err("Gift card not found");
|
|
87
87
|
return Ok({
|
|
@@ -99,7 +99,7 @@ export class GiftCardService {
|
|
|
99
99
|
if (amount <= 0)
|
|
100
100
|
return Err("Debit amount must be positive");
|
|
101
101
|
const result = await this.transaction(async (tx) => {
|
|
102
|
-
const card = await this.repo.findByCodeForUpdate(normalizeCode(code), tx);
|
|
102
|
+
const card = await this.repo.findByCodeForUpdate(orgId, normalizeCode(code), tx);
|
|
103
103
|
if (!card)
|
|
104
104
|
return Err("GIFT_CARD_NOT_FOUND");
|
|
105
105
|
if (card.status === "disabled")
|
|
@@ -114,7 +114,7 @@ export class GiftCardService {
|
|
|
114
114
|
return Err("INSUFFICIENT_BALANCE");
|
|
115
115
|
const balanceAfter = card.balance - amount;
|
|
116
116
|
const newStatus = balanceAfter === 0 ? "exhausted" : "active";
|
|
117
|
-
await this.repo.updateBalance(card.id, balanceAfter, newStatus, card.version, tx);
|
|
117
|
+
await this.repo.updateBalance(orgId, card.id, balanceAfter, newStatus, card.version, tx);
|
|
118
118
|
await this.repo.recordTransaction({
|
|
119
119
|
giftCardId: card.id,
|
|
120
120
|
type: "debit",
|
|
@@ -140,7 +140,7 @@ export class GiftCardService {
|
|
|
140
140
|
if (amount <= 0)
|
|
141
141
|
return Err("Credit amount must be positive");
|
|
142
142
|
const result = await this.transaction(async (tx) => {
|
|
143
|
-
const card = await this.repo.findByCodeForUpdate(normalizeCode(code), tx);
|
|
143
|
+
const card = await this.repo.findByCodeForUpdate(orgId, normalizeCode(code), tx);
|
|
144
144
|
if (!card)
|
|
145
145
|
return Err("GIFT_CARD_NOT_FOUND");
|
|
146
146
|
// Cap credit at initial amount (prevent inflation attack)
|
|
@@ -150,7 +150,7 @@ export class GiftCardService {
|
|
|
150
150
|
return Ok({ balanceAfter: card.balance });
|
|
151
151
|
}
|
|
152
152
|
const newStatus = balanceAfter > 0 ? "active" : card.status;
|
|
153
|
-
await this.repo.updateBalance(card.id, balanceAfter, newStatus, card.version, tx);
|
|
153
|
+
await this.repo.updateBalance(orgId, card.id, balanceAfter, newStatus, card.version, tx);
|
|
154
154
|
await this.repo.recordTransaction({
|
|
155
155
|
giftCardId: card.id,
|
|
156
156
|
type: "refund",
|
|
@@ -165,20 +165,20 @@ export class GiftCardService {
|
|
|
165
165
|
}
|
|
166
166
|
// ─── Admin Operations ────────────────────────────────────────────────
|
|
167
167
|
async disable(orgId, id) {
|
|
168
|
-
const card = await this.repo.disable(id);
|
|
168
|
+
const card = await this.repo.disable(orgId, id);
|
|
169
169
|
if (!card)
|
|
170
170
|
return Err("Gift card not found");
|
|
171
171
|
return Ok(card);
|
|
172
172
|
}
|
|
173
173
|
async adjust(orgId, id, delta, note) {
|
|
174
174
|
const result = await this.transaction(async (tx) => {
|
|
175
|
-
const card = await this.repo.findByIdForUpdate(id, tx);
|
|
175
|
+
const card = await this.repo.findByIdForUpdate(orgId, id, tx);
|
|
176
176
|
if (!card)
|
|
177
177
|
return Err("Gift card not found");
|
|
178
178
|
const newBalance = Math.max(0, Math.min(card.initialAmount, card.balance + delta));
|
|
179
179
|
const actualDelta = newBalance - card.balance;
|
|
180
180
|
const newStatus = newBalance === 0 ? "exhausted" : "active";
|
|
181
|
-
const updated = await this.repo.updateBalance(card.id, newBalance, newStatus, card.version, tx);
|
|
181
|
+
const updated = await this.repo.updateBalance(orgId, card.id, newBalance, newStatus, card.version, tx);
|
|
182
182
|
if (actualDelta !== 0) {
|
|
183
183
|
const txnType = actualDelta > 0 ? "credit" : "debit";
|
|
184
184
|
await this.repo.recordTransaction({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@porulle/plugin-giftcards",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@hono/zod-openapi": "^1.2.2",
|
|
21
21
|
"hono": "^4.12.5",
|
|
22
|
-
"@porulle/core": "0.
|
|
22
|
+
"@porulle/core": "0.9.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^24.5.2",
|
package/src/routes/admin.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { router } from "@porulle/core";
|
|
1
|
+
import { router, CommerceNotFoundError } from "@porulle/core";
|
|
2
2
|
import { z } from "@hono/zod-openapi";
|
|
3
3
|
import type { GiftCardService } from "../services/gift-card-service.js";
|
|
4
4
|
import type { PluginRouteRegistration } from "@porulle/core";
|
|
@@ -72,7 +72,8 @@ export function buildAdminRoutes(
|
|
|
72
72
|
.permission("gift-cards:admin")
|
|
73
73
|
.handler(async ({ params, orgId }) => {
|
|
74
74
|
const cardResult = await service.getById(orgId, params.id!);
|
|
75
|
-
|
|
75
|
+
// Cross-tenant / missing card → safe 404, not a generic 500.
|
|
76
|
+
if (!cardResult.ok) throw new CommerceNotFoundError("Gift card not found.");
|
|
76
77
|
|
|
77
78
|
const txnResult = await service.getTransactions(orgId, cardResult.value.id);
|
|
78
79
|
return {
|
|
@@ -89,7 +90,7 @@ export function buildAdminRoutes(
|
|
|
89
90
|
.permission("gift-cards:admin")
|
|
90
91
|
.handler(async ({ params, orgId }) => {
|
|
91
92
|
const result = await service.disable(orgId, params.id!);
|
|
92
|
-
if (!result.ok) throw new
|
|
93
|
+
if (!result.ok) throw new CommerceNotFoundError("Gift card not found.");
|
|
93
94
|
return result.value;
|
|
94
95
|
});
|
|
95
96
|
|
|
@@ -107,7 +108,7 @@ export function buildAdminRoutes(
|
|
|
107
108
|
.handler(async ({ params, input, orgId }) => {
|
|
108
109
|
const body = input as { delta: number; note: string };
|
|
109
110
|
const result = await service.adjust(orgId, params.id!, body.delta, body.note);
|
|
110
|
-
if (!result.ok) throw new
|
|
111
|
+
if (!result.ok) throw new CommerceNotFoundError("Gift card not found.");
|
|
111
112
|
return result.value;
|
|
112
113
|
});
|
|
113
114
|
|
|
@@ -19,67 +19,73 @@ export class GiftCardRepository {
|
|
|
19
19
|
return rows[0]!;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
async findById(id: string, ctx?: { tx?: Db }): Promise<GiftCard | undefined> {
|
|
22
|
+
async findById(orgId: string, id: string, ctx?: { tx?: Db }): Promise<GiftCard | undefined> {
|
|
23
23
|
const rows = await this.getDb(ctx)
|
|
24
24
|
.select()
|
|
25
25
|
.from(giftCards)
|
|
26
|
-
.where(eq(giftCards.id, id));
|
|
26
|
+
.where(and(eq(giftCards.id, id), eq(giftCards.organizationId, orgId)));
|
|
27
27
|
return rows[0];
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
async findByCode(code: string, ctx?: { tx?: Db }): Promise<GiftCard | undefined> {
|
|
30
|
+
async findByCode(orgId: string, code: string, ctx?: { tx?: Db }): Promise<GiftCard | undefined> {
|
|
31
|
+
const conditions = [eq(giftCards.code, code)];
|
|
32
|
+
// "_any" is the public bearer-token lookup (check-balance): a gift-card code
|
|
33
|
+
// is org-agnostic by design. Admin id-based lookups stay strictly org-scoped.
|
|
34
|
+
if (orgId !== "_any") conditions.push(eq(giftCards.organizationId, orgId));
|
|
31
35
|
const rows = await this.getDb(ctx)
|
|
32
36
|
.select()
|
|
33
37
|
.from(giftCards)
|
|
34
|
-
.where(
|
|
38
|
+
.where(and(...conditions));
|
|
35
39
|
return rows[0];
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
async list(
|
|
43
|
+
orgId: string,
|
|
39
44
|
filters?: { status?: GiftCardStatus; purchaserId?: string },
|
|
40
45
|
ctx?: { tx?: Db },
|
|
41
46
|
): Promise<GiftCard[]> {
|
|
42
|
-
const conditions = [];
|
|
47
|
+
const conditions = [eq(giftCards.organizationId, orgId)];
|
|
43
48
|
if (filters?.status) conditions.push(eq(giftCards.status, filters.status));
|
|
44
49
|
if (filters?.purchaserId) conditions.push(eq(giftCards.purchaserId, filters.purchaserId));
|
|
45
50
|
|
|
46
51
|
return this.getDb(ctx)
|
|
47
52
|
.select()
|
|
48
53
|
.from(giftCards)
|
|
49
|
-
.where(
|
|
54
|
+
.where(and(...conditions))
|
|
50
55
|
.orderBy(desc(giftCards.createdAt));
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
async disable(id: string, ctx?: { tx?: Db }): Promise<GiftCard | undefined> {
|
|
58
|
+
async disable(orgId: string, id: string, ctx?: { tx?: Db }): Promise<GiftCard | undefined> {
|
|
54
59
|
const rows = await this.getDb(ctx)
|
|
55
60
|
.update(giftCards)
|
|
56
61
|
.set({ status: "disabled" as const, updatedAt: new Date() })
|
|
57
|
-
.where(eq(giftCards.id, id))
|
|
62
|
+
.where(and(eq(giftCards.id, id), eq(giftCards.organizationId, orgId)))
|
|
58
63
|
.returning();
|
|
59
64
|
return rows[0];
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
// ─── SELECT FOR UPDATE (Concurrency-Safe Balance Operations) ───────
|
|
63
68
|
|
|
64
|
-
async findByCodeForUpdate(code: string, tx: Db): Promise<GiftCard | undefined> {
|
|
69
|
+
async findByCodeForUpdate(orgId: string, code: string, tx: Db): Promise<GiftCard | undefined> {
|
|
65
70
|
const rows = await tx
|
|
66
71
|
.select()
|
|
67
72
|
.from(giftCards)
|
|
68
|
-
.where(eq(giftCards.code, code))
|
|
73
|
+
.where(and(eq(giftCards.code, code), eq(giftCards.organizationId, orgId)))
|
|
69
74
|
.for("update");
|
|
70
75
|
return rows[0];
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
async findByIdForUpdate(id: string, tx: Db): Promise<GiftCard | undefined> {
|
|
78
|
+
async findByIdForUpdate(orgId: string, id: string, tx: Db): Promise<GiftCard | undefined> {
|
|
74
79
|
const rows = await tx
|
|
75
80
|
.select()
|
|
76
81
|
.from(giftCards)
|
|
77
|
-
.where(eq(giftCards.id, id))
|
|
82
|
+
.where(and(eq(giftCards.id, id), eq(giftCards.organizationId, orgId)))
|
|
78
83
|
.for("update");
|
|
79
84
|
return rows[0];
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
async updateBalance(
|
|
88
|
+
orgId: string,
|
|
83
89
|
id: string,
|
|
84
90
|
balance: number,
|
|
85
91
|
status: GiftCardStatus,
|
|
@@ -94,23 +100,24 @@ export class GiftCardRepository {
|
|
|
94
100
|
version: currentVersion + 1,
|
|
95
101
|
updatedAt: new Date(),
|
|
96
102
|
})
|
|
97
|
-
.where(eq(giftCards.id, id))
|
|
103
|
+
.where(and(eq(giftCards.id, id), eq(giftCards.organizationId, orgId)))
|
|
98
104
|
.returning();
|
|
99
105
|
return rows[0]!;
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
async adjustBalance(
|
|
109
|
+
orgId: string,
|
|
103
110
|
id: string,
|
|
104
111
|
delta: number,
|
|
105
112
|
tx: Db,
|
|
106
113
|
): Promise<GiftCard> {
|
|
107
|
-
const card = await this.findByIdForUpdate(id, tx);
|
|
114
|
+
const card = await this.findByIdForUpdate(orgId, id, tx);
|
|
108
115
|
if (!card) throw new Error("Gift card not found");
|
|
109
116
|
|
|
110
117
|
const newBalance = Math.max(0, Math.min(card.initialAmount, card.balance + delta));
|
|
111
118
|
const newStatus: GiftCardStatus = newBalance === 0 ? "exhausted" : "active";
|
|
112
119
|
|
|
113
|
-
return this.updateBalance(id, newBalance, newStatus, card.version, tx);
|
|
120
|
+
return this.updateBalance(orgId, id, newBalance, newStatus, card.version, tx);
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
// ─── Transactions ───────────────────────────────────────────────────
|
|
@@ -49,7 +49,7 @@ export class GiftCardService {
|
|
|
49
49
|
let attempts = 0;
|
|
50
50
|
do {
|
|
51
51
|
code = normalizeCode(generateGiftCardCode(this.options.codeFormat));
|
|
52
|
-
const existing = await this.repo.findByCode(code);
|
|
52
|
+
const existing = await this.repo.findByCode(orgId, code);
|
|
53
53
|
if (!existing) break;
|
|
54
54
|
attempts++;
|
|
55
55
|
} while (attempts < 10);
|
|
@@ -92,13 +92,13 @@ export class GiftCardService {
|
|
|
92
92
|
// ─── Query ───────────────────────────────────────────────────────────
|
|
93
93
|
|
|
94
94
|
async getById(orgId: string, id: string): Promise<PluginResult<GiftCard>> {
|
|
95
|
-
const card = await this.repo.findById(id);
|
|
95
|
+
const card = await this.repo.findById(orgId, id);
|
|
96
96
|
if (!card) return Err("Gift card not found");
|
|
97
97
|
return Ok(card);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
async getByCode(orgId: string, code: string): Promise<PluginResult<GiftCard>> {
|
|
101
|
-
const card = await this.repo.findByCode(normalizeCode(code));
|
|
101
|
+
const card = await this.repo.findByCode(orgId, normalizeCode(code));
|
|
102
102
|
if (!card) return Err("Gift card not found");
|
|
103
103
|
return Ok(card);
|
|
104
104
|
}
|
|
@@ -107,7 +107,7 @@ export class GiftCardService {
|
|
|
107
107
|
status?: GiftCardStatus;
|
|
108
108
|
purchaserId?: string;
|
|
109
109
|
}): Promise<PluginResult<GiftCard[]>> {
|
|
110
|
-
const cards = await this.repo.list(filters);
|
|
110
|
+
const cards = await this.repo.list(orgId, filters);
|
|
111
111
|
return Ok(cards);
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -126,7 +126,7 @@ export class GiftCardService {
|
|
|
126
126
|
currency: string;
|
|
127
127
|
status: string;
|
|
128
128
|
}>> {
|
|
129
|
-
const card = await this.repo.findByCode(normalizeCode(code));
|
|
129
|
+
const card = await this.repo.findByCode(orgId, normalizeCode(code));
|
|
130
130
|
if (!card) return Err("Gift card not found");
|
|
131
131
|
|
|
132
132
|
return Ok({
|
|
@@ -152,7 +152,7 @@ export class GiftCardService {
|
|
|
152
152
|
if (amount <= 0) return Err("Debit amount must be positive");
|
|
153
153
|
|
|
154
154
|
const result = await this.transaction(async (tx) => {
|
|
155
|
-
const card = await this.repo.findByCodeForUpdate(normalizeCode(code), tx);
|
|
155
|
+
const card = await this.repo.findByCodeForUpdate(orgId, normalizeCode(code), tx);
|
|
156
156
|
if (!card) return Err("GIFT_CARD_NOT_FOUND");
|
|
157
157
|
if (card.status === "disabled") return Err("GIFT_CARD_INACTIVE");
|
|
158
158
|
if (card.status === "exhausted") return Err("GIFT_CARD_EXHAUSTED");
|
|
@@ -163,7 +163,7 @@ export class GiftCardService {
|
|
|
163
163
|
const balanceAfter = card.balance - amount;
|
|
164
164
|
const newStatus: GiftCardStatus = balanceAfter === 0 ? "exhausted" : "active";
|
|
165
165
|
|
|
166
|
-
await this.repo.updateBalance(card.id, balanceAfter, newStatus, card.version, tx);
|
|
166
|
+
await this.repo.updateBalance(orgId, card.id, balanceAfter, newStatus, card.version, tx);
|
|
167
167
|
await this.repo.recordTransaction(
|
|
168
168
|
{
|
|
169
169
|
giftCardId: card.id,
|
|
@@ -202,7 +202,7 @@ export class GiftCardService {
|
|
|
202
202
|
if (amount <= 0) return Err("Credit amount must be positive");
|
|
203
203
|
|
|
204
204
|
const result = await this.transaction(async (tx) => {
|
|
205
|
-
const card = await this.repo.findByCodeForUpdate(normalizeCode(code), tx);
|
|
205
|
+
const card = await this.repo.findByCodeForUpdate(orgId, normalizeCode(code), tx);
|
|
206
206
|
if (!card) return Err("GIFT_CARD_NOT_FOUND");
|
|
207
207
|
|
|
208
208
|
// Cap credit at initial amount (prevent inflation attack)
|
|
@@ -215,7 +215,7 @@ export class GiftCardService {
|
|
|
215
215
|
|
|
216
216
|
const newStatus: GiftCardStatus = balanceAfter > 0 ? "active" : card.status;
|
|
217
217
|
|
|
218
|
-
await this.repo.updateBalance(card.id, balanceAfter, newStatus, card.version, tx);
|
|
218
|
+
await this.repo.updateBalance(orgId, card.id, balanceAfter, newStatus, card.version, tx);
|
|
219
219
|
await this.repo.recordTransaction(
|
|
220
220
|
{
|
|
221
221
|
giftCardId: card.id,
|
|
@@ -237,7 +237,7 @@ export class GiftCardService {
|
|
|
237
237
|
// ─── Admin Operations ────────────────────────────────────────────────
|
|
238
238
|
|
|
239
239
|
async disable(orgId: string, id: string): Promise<PluginResult<GiftCard>> {
|
|
240
|
-
const card = await this.repo.disable(id);
|
|
240
|
+
const card = await this.repo.disable(orgId, id);
|
|
241
241
|
if (!card) return Err("Gift card not found");
|
|
242
242
|
return Ok(card);
|
|
243
243
|
}
|
|
@@ -249,7 +249,7 @@ export class GiftCardService {
|
|
|
249
249
|
note: string,
|
|
250
250
|
): Promise<PluginResult<GiftCard>> {
|
|
251
251
|
const result = await this.transaction(async (tx) => {
|
|
252
|
-
const card = await this.repo.findByIdForUpdate(id, tx);
|
|
252
|
+
const card = await this.repo.findByIdForUpdate(orgId, id, tx);
|
|
253
253
|
if (!card) return Err("Gift card not found");
|
|
254
254
|
|
|
255
255
|
const newBalance = Math.max(0, Math.min(card.initialAmount, card.balance + delta));
|
|
@@ -257,6 +257,7 @@ export class GiftCardService {
|
|
|
257
257
|
const newStatus: GiftCardStatus = newBalance === 0 ? "exhausted" : "active";
|
|
258
258
|
|
|
259
259
|
const updated = await this.repo.updateBalance(
|
|
260
|
+
orgId,
|
|
260
261
|
card.id,
|
|
261
262
|
newBalance,
|
|
262
263
|
newStatus,
|