@voltro/plugin-economy 0.0.0-stage → 0.88.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.
Files changed (177) hide show
  1. package/CHANGELOG.md +24492 -0
  2. package/LICENSE +61 -0
  3. package/README.md +2719 -2
  4. package/SECURITY.md +56 -0
  5. package/THIRD-PARTY-NOTICES.md +9841 -0
  6. package/dist/amount.js +20 -0
  7. package/dist/appleIap.server.js +262 -0
  8. package/dist/appleIapNotifications.server.js +162 -0
  9. package/dist/appleIapTrust.server.js +30 -0
  10. package/dist/bundleValue.server.js +112 -0
  11. package/dist/canonicalDataJson.js +10 -0
  12. package/dist/capture.js +53 -0
  13. package/dist/codePort.fixture.js +123 -0
  14. package/dist/codeStore.server.js +439 -0
  15. package/dist/codeTables.entity.js +43 -0
  16. package/dist/codes.d.ts +150 -0
  17. package/dist/codes.js +82 -0
  18. package/dist/components.d.ts +397 -0
  19. package/dist/components.js +283 -0
  20. package/dist/consistency.server.js +115 -0
  21. package/dist/containerStore.server.js +687 -0
  22. package/dist/containerStore.testFixtures.js +75 -0
  23. package/dist/containerTables.entity.js +45 -0
  24. package/dist/containers.d.ts +311 -0
  25. package/dist/containers.fixture.js +128 -0
  26. package/dist/containers.js +214 -0
  27. package/dist/containers.server.js +39 -0
  28. package/dist/containersPort.fixture.js +74 -0
  29. package/dist/currency.js +32 -0
  30. package/dist/declarationFixtures.js +61 -0
  31. package/dist/economy-app/events/economy.refundReversed.event.ts +1 -0
  32. package/dist/economy-app/events/economy.refunded.event.ts +1 -0
  33. package/dist/economyPort.fixture.js +124 -0
  34. package/dist/errors.d.ts +14 -0
  35. package/dist/errors.js +21 -0
  36. package/dist/events.d.ts +46 -0
  37. package/dist/events.js +1 -0
  38. package/dist/googleIap.server.js +391 -0
  39. package/dist/googleIapNotifications.server.js +130 -0
  40. package/dist/googleIapSubscription.server.js +183 -0
  41. package/dist/iap.d.ts +309 -0
  42. package/dist/iap.js +303 -0
  43. package/dist/iap.testFixtures.js +144 -0
  44. package/dist/iapAccountTables.entity.js +39 -0
  45. package/dist/iapAccounts.server.js +200 -0
  46. package/dist/iapBinding.server.js +523 -0
  47. package/dist/iapCheckout.server.js +97 -0
  48. package/dist/iapCompletion.server.js +55 -0
  49. package/dist/iapCurrentState.server.js +107 -0
  50. package/dist/iapCurrentState.testFixtures.js +22 -0
  51. package/dist/iapCurrentStatePort.fixture.js +63 -0
  52. package/dist/iapEntitlements.d.ts +79 -0
  53. package/dist/iapEntitlements.js +57 -0
  54. package/dist/iapEntitlements.server.js +48 -0
  55. package/dist/iapEntitlementsBinding.server.js +105 -0
  56. package/dist/iapEntitlementsPort.fixture.js +80 -0
  57. package/dist/iapInstalledEntitlements.testFixtures.js +75 -0
  58. package/dist/iapInstalledNotifications.testFixtures.js +93 -0
  59. package/dist/iapInventoryRefunds.server.js +261 -0
  60. package/dist/iapNotificationCustodyPort.fixture.js +67 -0
  61. package/dist/iapNotificationPort.fixture.js +73 -0
  62. package/dist/iapNotificationProcessor.server.js +134 -0
  63. package/dist/iapNotificationProcessor.testFixtures.js +63 -0
  64. package/dist/iapNotificationProcessorPort.fixture.js +55 -0
  65. package/dist/iapNotificationSchedule.server.js +69 -0
  66. package/dist/iapNotificationStore.server.js +352 -0
  67. package/dist/iapNotificationStore.testFixtures.js +31 -0
  68. package/dist/iapNotificationTables.entity.js +48 -0
  69. package/dist/iapNotificationTarget.server.js +36 -0
  70. package/dist/iapNotificationTarget.testFixtures.js +29 -0
  71. package/dist/iapNotifications.d.ts +644 -0
  72. package/dist/iapNotifications.js +6 -0
  73. package/dist/iapNotifications.server.js +95 -0
  74. package/dist/iapNotificationsBinding.server.js +232 -0
  75. package/dist/iapNotificationsSystemPort.fixture.js +50 -0
  76. package/dist/iapPlugin.testFixtures.js +59 -0
  77. package/dist/iapPort.fixture.js +74 -0
  78. package/dist/iapProviderCapture.server.js +17 -0
  79. package/dist/iapRecovery.d.ts +146 -0
  80. package/dist/iapRecovery.js +89 -0
  81. package/dist/iapRefundEventDelivery.server.js +82 -0
  82. package/dist/iapRefundEvents.js +44 -0
  83. package/dist/iapRefundEventsPort.fixture.js +59 -0
  84. package/dist/iapRefundPort.fixture.js +54 -0
  85. package/dist/iapRefundTables.entity.js +55 -0
  86. package/dist/iapRefunds.d.ts +755 -0
  87. package/dist/iapRefunds.js +6 -0
  88. package/dist/iapRefunds.server.js +553 -0
  89. package/dist/iapRefunds.testFixtures.js +64 -0
  90. package/dist/iapSpendAdmission.server.js +98 -0
  91. package/dist/iapStore.server.js +740 -0
  92. package/dist/iapStore.testFixtures.js +127 -0
  93. package/dist/iapTables.entity.js +80 -0
  94. package/dist/iapVerification.server.js +120 -0
  95. package/dist/index.d.ts +2984 -0
  96. package/dist/index.js +15 -0
  97. package/dist/intentionRecovery.js +133 -0
  98. package/dist/ledger.js +77 -0
  99. package/dist/loyalty.d.ts +274 -0
  100. package/dist/loyalty.fixture.js +92 -0
  101. package/dist/loyalty.js +311 -0
  102. package/dist/loyaltyBinding.server.js +203 -0
  103. package/dist/loyaltyPort.fixture.js +86 -0
  104. package/dist/loyaltyShopSource.server.js +90 -0
  105. package/dist/loyaltyStore.server.js +552 -0
  106. package/dist/loyaltyStore.testFixtures.js +82 -0
  107. package/dist/loyaltyTables.entity.js +49 -0
  108. package/dist/offerMerchandising.fixture.js +114 -0
  109. package/dist/offerMerchandising.js +282 -0
  110. package/dist/offerPolicies.d.ts +441 -0
  111. package/dist/offerPolicies.fixture.js +53 -0
  112. package/dist/offerPolicies.js +267 -0
  113. package/dist/piggyBanks.d.ts +272 -0
  114. package/dist/piggyBanks.fixture.js +70 -0
  115. package/dist/piggyBanks.js +200 -0
  116. package/dist/piggyBinding.server.js +204 -0
  117. package/dist/piggyPort.fixture.js +72 -0
  118. package/dist/piggyStore.server.js +610 -0
  119. package/dist/piggyStore.testFixtures.js +97 -0
  120. package/dist/piggyTables.entity.js +40 -0
  121. package/dist/plugin.js +1047 -0
  122. package/dist/port.js +12 -0
  123. package/dist/price.d.ts +99 -0
  124. package/dist/price.js +143 -0
  125. package/dist/purchaseRecovery.js +55 -0
  126. package/dist/reports.server.js +114 -0
  127. package/dist/rpc.d.ts +3201 -0
  128. package/dist/rpc.js +1200 -0
  129. package/dist/sanctionsTradeAuthority.server.js +79 -0
  130. package/dist/server.d.ts +3856 -0
  131. package/dist/server.js +43 -0
  132. package/dist/shop.d.ts +279 -0
  133. package/dist/shop.js +195 -0
  134. package/dist/shop.testFixtures.js +113 -0
  135. package/dist/shopPort.fixture.js +71 -0
  136. package/dist/shopPresentation.js +67 -0
  137. package/dist/shopStore.server.js +623 -0
  138. package/dist/shopTables.entity.js +62 -0
  139. package/dist/shopUiCopy.js +56 -0
  140. package/dist/spendLimits.d.ts +140 -0
  141. package/dist/spendLimits.fixture.js +52 -0
  142. package/dist/spendLimits.js +154 -0
  143. package/dist/spendLimitsPort.fixture.js +119 -0
  144. package/dist/subscriptionBinding.server.js +210 -0
  145. package/dist/subscriptionStore.server.js +642 -0
  146. package/dist/subscriptionStore.testFixtures.js +71 -0
  147. package/dist/subscriptionTables.entity.js +36 -0
  148. package/dist/subscriptions.d.ts +394 -0
  149. package/dist/subscriptions.fixture.js +96 -0
  150. package/dist/subscriptions.js +212 -0
  151. package/dist/subscriptionsPort.fixture.js +75 -0
  152. package/dist/systemFixtures.js +88 -0
  153. package/dist/tables.entity.js +82 -0
  154. package/dist/testing.d.ts +4615 -0
  155. package/dist/testing.js +29 -0
  156. package/dist/tradeAuthority.server.js +171 -0
  157. package/dist/tradeBinding.server.js +200 -0
  158. package/dist/tradeComponents.js +341 -0
  159. package/dist/tradeEscrow.server.js +231 -0
  160. package/dist/tradeEscrow.testFixtures.js +132 -0
  161. package/dist/tradeExpiry.server.js +42 -0
  162. package/dist/tradeRecovery.js +47 -0
  163. package/dist/tradeStore.server.js +749 -0
  164. package/dist/tradeStore.testFixtures.js +72 -0
  165. package/dist/tradeTables.entity.js +65 -0
  166. package/dist/tradeUiCopy.js +78 -0
  167. package/dist/trades.d.ts +246 -0
  168. package/dist/trades.fixture.js +102 -0
  169. package/dist/trades.js +288 -0
  170. package/dist/tradesPort.fixture.js +93 -0
  171. package/dist/walletEscrow.js +105 -0
  172. package/dist/walletEscrow.server.js +375 -0
  173. package/dist/walletKeys.server.js +4 -0
  174. package/dist/walletStore.js +570 -0
  175. package/dist/web.d.ts +2257 -0
  176. package/dist/web.js +8 -0
  177. package/package.json +161 -4
@@ -0,0 +1,6 @@
1
+ import { IapRefundEventPayloadSchema as e, IapRefundEventsPort as t, economyRefundReversed as n, economyRefunded as r } from "./iapRefundEvents.js";
2
+ import { iapRefundAdjustmentsTable as i, iapRefundNamespacesTable as a, iapRefundOrdersTable as o, iapRefundSubjectsTable as s, iapRefundTables as c } from "./iapRefundTables.entity.js";
3
+ import { IapRefundPort as l, captureIapRefundService as u, dataStoreIapRefunds as d } from "./iapRefunds.server.js";
4
+ import { inventoryIapRefundAssets as f } from "./iapInventoryRefunds.server.js";
5
+ import { deliverIapRefundEvents as p, iapRefundEventSchedule as m, noRefundAbuseSignal as h } from "./iapRefundEventDelivery.server.js";
6
+ export { e as IapRefundEventPayloadSchema, t as IapRefundEventsPort, l as IapRefundPort, u as captureIapRefundService, d as dataStoreIapRefunds, p as deliverIapRefundEvents, n as economyRefundReversed, r as economyRefunded, i as iapRefundAdjustmentsTable, m as iapRefundEventSchedule, a as iapRefundNamespacesTable, o as iapRefundOrdersTable, s as iapRefundSubjectsTable, c as iapRefundTables, f as inventoryIapRefundAssets, h as noRefundAbuseSignal };
@@ -0,0 +1,553 @@
1
+ import { EconomyError as e, economyError as t } from "./errors.js";
2
+ import { captureBoundMethods as n, dataList as r, dataObject as i, identifier as a } from "./capture.js";
3
+ import { economyAmountString as o, nonNegativeAmount as s } from "./amount.js";
4
+ import { shopInteger as c } from "./shop.js";
5
+ import { captureIapRefundDisposition as l, iapTimestamp as u } from "./iap.js";
6
+ import { IapRefundEventPayloadSchema as d } from "./iapRefundEvents.js";
7
+ import { canonicalDataJson as f } from "./canonicalDataJson.js";
8
+ import { readVerifiedIapCurrentState as p } from "./iapCurrentState.server.js";
9
+ import { readEconomyTransferEvidence as m } from "./walletStore.js";
10
+ import { iapRefundAdjustmentsTable as h, iapRefundNamespacesTable as g, iapRefundOrdersTable as _, iapRefundSubjectsTable as v } from "./iapRefundTables.entity.js";
11
+ import { Context as y, Effect as b, Result as x, Schema as S } from "effect";
12
+ import { systemSubject as C } from "@voltro/protocol";
13
+ import { createHash as w } from "node:crypto";
14
+ import { and as T, eq as E, queryFor as D } from "@voltro/database";
15
+ import { executeSqlMutations as O, prepareSqlMutation as k, prepareSqlMutationGroup as A } from "@voltro/database/sql";
16
+ // src/iapRefunds.server.ts
17
+ var j = class extends y.Service()("@voltro/IapRefundPort") {}, M = (e) => n(e, [
18
+ "reconcile",
19
+ "rebuild",
20
+ "observe",
21
+ "exportSubject",
22
+ "eraseSubject"
23
+ ]), N = (e) => w("sha256").update(f(e)).digest("hex"), P = (e, t) => e + N(t).slice(0, 56), F = (n) => n instanceof e ? n : t("unavailable"), I = (e) => b.tryPromise({
24
+ try: e,
25
+ catch: F
26
+ }), L = async (e) => {
27
+ let t = await b.runPromise(e.pipe(b.result));
28
+ if (x.isFailure(t)) throw t.failure;
29
+ return t.success;
30
+ }, R = (e) => {
31
+ let t = i(e, ["tenantId", "ownerId"]);
32
+ return Object.freeze({
33
+ tenantId: a(t.tenantId),
34
+ ownerId: a(t.ownerId)
35
+ });
36
+ }, ee = /* @__PURE__ */ new WeakMap(), z = (e) => {
37
+ let n = typeof e == "object" && e ? ee.get(e) : void 0;
38
+ if (!n) throw t("forbidden");
39
+ return n;
40
+ }, B = (e, t) => Object.freeze(e.receipt.transfers.map((e) => Object.freeze({
41
+ currency: e.currency,
42
+ original: e.amount,
43
+ refunded: (BigInt(e.amount) * BigInt(t.refundNumerator) / BigInt(t.refundDenominator)).toString()
44
+ }))), V = (e) => {
45
+ let n = i(e, [
46
+ "orderId",
47
+ "sequence",
48
+ "state",
49
+ "refundNumerator",
50
+ "refundDenominator",
51
+ "observedAt",
52
+ "noticeId",
53
+ "fingerprint",
54
+ "debtPolicy",
55
+ "currencies",
56
+ "transfers",
57
+ "assets"
58
+ ], "invalid_record"), o = c(n.refundDenominator, 1, 1e5), l = c(n.refundNumerator, 0, o);
59
+ if (n.state !== (l === 0 ? "active" : l === o ? "revoked" : "partially-refunded") || n.debtPolicy !== "allowNegative" && n.debtPolicy !== "blockSpend" || typeof n.fingerprint != "string" || !/^[a-f0-9]{64}$/.test(n.fingerprint) || typeof n.noticeId != "string" || !n.noticeId.length || n.noticeId.length > 128) throw t("invalid_record");
60
+ let d = Object.freeze(r(n.currencies, 16, "invalid_record").map((e) => {
61
+ let n = i(e, [
62
+ "currency",
63
+ "original",
64
+ "refunded"
65
+ ], "invalid_record"), r = s(n.original).toString(), c = s(n.refunded).toString();
66
+ if (r === "0" || c !== (BigInt(r) * BigInt(l) / BigInt(o)).toString()) throw t("invalid_record");
67
+ return Object.freeze({
68
+ currency: a(n.currency),
69
+ original: r,
70
+ refunded: c
71
+ });
72
+ })), f = Object.freeze(r(n.transfers, 16, "invalid_record").map((e) => {
73
+ let n = i(e, [
74
+ "txId",
75
+ "currency",
76
+ "amount",
77
+ "at"
78
+ ], "invalid_record"), r = s(n.amount).toString();
79
+ if (r === "0") throw t("invalid_record");
80
+ return Object.freeze({
81
+ txId: a(n.txId),
82
+ currency: a(n.currency),
83
+ amount: r,
84
+ at: u(n.at)
85
+ });
86
+ }));
87
+ if (new Set(d.map((e) => e.currency)).size !== d.length || new Set(f.map((e) => e.txId)).size !== f.length) throw t("invalid_record");
88
+ return Object.freeze({
89
+ orderId: a(n.orderId),
90
+ sequence: c(n.sequence, 1, 1e5),
91
+ state: n.state,
92
+ refundNumerator: l,
93
+ refundDenominator: o,
94
+ observedAt: u(n.observedAt),
95
+ noticeId: n.noticeId,
96
+ fingerprint: n.fingerprint,
97
+ debtPolicy: n.debtPolicy,
98
+ currencies: d,
99
+ transfers: f,
100
+ assets: structuredClone(n.assets)
101
+ });
102
+ }, H = (e, r) => {
103
+ let s = i(r, [
104
+ "realMoney",
105
+ "namespace",
106
+ "debtPolicy",
107
+ "orders",
108
+ "economy",
109
+ "assets",
110
+ "assetsVersion",
111
+ "maxHistoryRows"
112
+ ]);
113
+ if (s.realMoney !== !0) throw t(s.realMoney === !1 ? "real_money_disabled" : "invalid_input");
114
+ if (s.debtPolicy !== "allowNegative" && s.debtPolicy !== "blockSpend") throw t("invalid_input");
115
+ let y = a(s.namespace), x = s.debtPolicy, w = c(s.maxHistoryRows ?? 1e4, 1, 1e5), j = n(s.orders, ["prepareCurrentState"]), M = n(s.economy, ["prepareTransfer", "prepareIapRefund"]), z = s.assets === void 0 ? void 0 : n(s.assets, [
116
+ "prepare",
117
+ "validate",
118
+ "audit"
119
+ ]), H = z ? a(s.assetsVersion) : null;
120
+ if (!z && s.assetsVersion !== void 0) throw t("invalid_input");
121
+ let U = N([x, H]), W = P("ifn_", y), te = C("economy.iap-refunds"), G = (e) => P("ifs_", [
122
+ y,
123
+ e.tenantId,
124
+ e.ownerId
125
+ ]), K = (e, t) => P("ifo_", t), q = async (t) => {
126
+ let n = [[g, {
127
+ id: W,
128
+ namespace: y,
129
+ admission: 0,
130
+ configurationDigest: U,
131
+ rowsCount: 0
132
+ }], [v, {
133
+ id: G(t),
134
+ namespace: y,
135
+ tenantId: t.tenantId,
136
+ ownerId: t.ownerId,
137
+ erased: !1,
138
+ admission: 0,
139
+ rowsCount: 0
140
+ }]];
141
+ for (let [t, r] of n) try {
142
+ await e.insertIgnore(t.tableName, r, { conflictColumns: ["id"] });
143
+ } catch (n) {
144
+ if (!(await e.query(D(t).where(E("id", r.id)).limit(1).descriptor))[0]) throw n;
145
+ }
146
+ }, ne = async (e, n, r = !1, i = !1) => {
147
+ let o = (await e.query(D(g).where(E("id", W)).limit(1).descriptor))[0], s = (await e.query(D(v).where(E("id", G(n))).limit(1).descriptor))[0], l = await e.query(D(h).where(E("namespace", y)).limit(w + 1).descriptor);
148
+ if (l.length > w) throw t("limit_exceeded");
149
+ if (i && !s) {
150
+ if ((o ? o.namespace !== y || o.admission !== 0 || o.configurationDigest !== U || l.length !== c(o.rowsCount, 0, w) : l.length !== 0) || l.some((e) => e.tenantId === n.tenantId && e.ownerKey === G(n))) throw t("invalid_record");
151
+ } else {
152
+ if (!o || !s || o.namespace !== y || s.namespace !== y || s.tenantId !== n.tenantId || o.admission !== 0 || s.admission !== 0 || !r && o.configurationDigest !== U) throw t("conflict");
153
+ if (c(o.rowsCount, 0, w), c(s.rowsCount, 0, w), s.erased === !0) {
154
+ if (!r || s.ownerId !== null) throw t("erased");
155
+ } else if (s.erased !== !1 || s.ownerId !== n.ownerId) throw t("invalid_record");
156
+ if (l.length !== o.rowsCount || l.filter((e) => e.tenantId === n.tenantId && e.ownerKey === G(n)).length !== s.rowsCount) throw t("invalid_record");
157
+ for (let e of l) if (c(e.sequence, 1, w), e.namespace !== y || typeof e.ownerKey != "string" || !/^ifs_[a-f0-9]{56}$/.test(e.ownerKey) || e.id !== P("ifa_", [K({
158
+ tenantId: a(e.tenantId),
159
+ ownerId: e.ownerId === null ? n.ownerId : a(e.ownerId)
160
+ }, a(e.orderId)), String(e.sequence)]) && e.ownerId !== null) throw t("invalid_record");
161
+ return {
162
+ header: o,
163
+ person: s
164
+ };
165
+ }
166
+ }, J = async (e, n, r = !1) => {
167
+ if (await e.updateMany(g.tableName, { admission: 0 }, { where: E("id", W) }) !== 1 || await e.updateMany(v.tableName, { admission: 0 }, { where: E("id", G(n)) }) !== 1) throw t("unavailable");
168
+ let i = await ne(e, n, r);
169
+ if (!i) throw t("invalid_record");
170
+ return i;
171
+ }, Y = async (e, n, r, i, a = !1) => {
172
+ let s = K(n, r), c = (await e.query(D(_).where(E("id", s)).limit(1).descriptor))[0];
173
+ if (!c) return {
174
+ row: void 0,
175
+ receipts: [],
176
+ latest: null
177
+ };
178
+ if (c.namespace !== y || c.tenantId !== n.tenantId || c.ownerKey !== G(n) || c.orderId !== r || c.ownerId !== n.ownerId || c.erased !== !1 && c.erased !== 0 || c.admission !== 0 || typeof c.configurationDigest != "string") throw t(c.erased === !0 || c.erased === 1 ? "erased" : "invalid_record");
179
+ let l = await e.query(D(h).where(T(E("namespace", y), E("orderId", r))).orderBy("sequence", "asc").limit(w + 1).descriptor);
180
+ if (l.length > w) throw t("limit_exceeded");
181
+ if (l.length !== c.rowsCount) throw t("invalid_record");
182
+ let d = null, p = [];
183
+ for (let a of l) {
184
+ let c;
185
+ try {
186
+ c = V(a.receipt);
187
+ } catch {
188
+ throw t("invalid_record");
189
+ }
190
+ if (a.id !== P("ifa_", [s, String(c.sequence)]) || a.tenantId !== n.tenantId || a.ownerKey !== G(n) || a.ownerId !== n.ownerId || a.orderId !== r || a.sequence !== p.length + 1 || c.sequence !== a.sequence || c.orderId !== r || c.observedAt < (d?.observedAt ?? 0) || c.debtPolicy !== x || u(a.at instanceof Date ? a.at.getTime() : Date.parse(String(a.at))) !== c.observedAt || d && f(c.currencies.map((e) => [e.currency, e.original])) !== f(d.currencies.map((e) => [e.currency, e.original])) || i && f(c.currencies) !== f(B(i, c))) throw t("invalid_record");
191
+ let l = c.currencies.map((e) => ({
192
+ ...e,
193
+ delta: BigInt(e.refunded) - BigInt(d?.currencies.find((t) => t.currency === e.currency)?.refunded ?? "0")
194
+ })).filter((e) => e.delta !== 0n);
195
+ if (l.length !== c.transfers.length) throw t("invalid_record");
196
+ for (let [a, s] of l.entries()) {
197
+ let l = c.transfers[a], u = (s.delta < 0n ? -s.delta : s.delta).toString();
198
+ if (l.currency !== s.currency || l.amount !== u) throw t("invalid_record");
199
+ let d = (await m(e, n.tenantId, [l.txId], s.currency)).get(l.txId), p = d.pair.find((e) => BigInt(String(e.debit)) > 0n), h = d.pair.find((e) => BigInt(String(e.credit)) > 0n), g = s.delta > 0n ? p : h, _ = s.delta > 0n ? h : p;
200
+ if (i) {
201
+ let r = i.receipt.transfers.find((e) => e.currency === s.currency), a = (await m(e, n.tenantId, [r.txId], s.currency)).get(r.txId).pair.find((e) => BigInt(String(e.debit)) > 0n);
202
+ if (_.systemAccount !== a.systemAccount) throw t("invalid_record");
203
+ }
204
+ if (f(d.receipt.receipt) !== f(l) || d.receipt.state !== "committed" || d.receipt.id !== l.txId || g.ownerId !== n.ownerId || g.ownerKind !== "player" || g.accountKind !== "wallet" || _.accountKind !== "system" || p.reason !== (s.delta > 0n ? "iap.refund" : "iap.refund-reversed") || p.refKind !== "iap-order" || p.refId !== r || o(s.delta > 0n ? p.debit : h.credit) !== u) throw t("invalid_record");
205
+ }
206
+ if (i && i.receipt.grants.length) {
207
+ if (!z) throw t("unavailable");
208
+ let n = {
209
+ original: i,
210
+ state: {
211
+ orderId: r,
212
+ channel: i.facts.channel,
213
+ state: c.state,
214
+ refundNumerator: c.refundNumerator,
215
+ refundDenominator: c.refundDenominator,
216
+ observedAt: c.observedAt,
217
+ revokedAt: null
218
+ },
219
+ previous: d,
220
+ sequence: c.sequence
221
+ };
222
+ z.validate(n, c.assets), await z.audit(e, n, c.assets);
223
+ } else if (i && c.assets !== null) throw t("invalid_record");
224
+ p.push(c), d = c;
225
+ }
226
+ if (!a && f(c.receipt) !== f(d)) throw t("invalid_record");
227
+ return {
228
+ row: c,
229
+ receipts: Object.freeze(p),
230
+ latest: d
231
+ };
232
+ }, re = async (e, n) => {
233
+ let r = R(n.scope), i = a(n.orderId);
234
+ if (n.receipt.orderId !== i) throw t("invalid_record");
235
+ let o = await ne(e, r, !1, !0), s = await Y(e, r, i, n);
236
+ if (!o && s.row) throw t("invalid_record");
237
+ let c = s.latest;
238
+ if (!c) {
239
+ if (s.row) throw t("invalid_record");
240
+ return null;
241
+ }
242
+ if (c.observedAt < n.receipt.at) throw t("invalid_record");
243
+ return l({
244
+ sequence: c.sequence,
245
+ state: c.state,
246
+ refundNumerator: c.refundNumerator,
247
+ refundDenominator: c.refundDenominator,
248
+ observedAt: c.observedAt
249
+ });
250
+ }, X = (n, r, i) => {
251
+ let a = Object.freeze(structuredClone(n)), o = f(n), s = R(i.scope);
252
+ return ee.set(a, Object.freeze({
253
+ scope: s,
254
+ orderId: n.orderId,
255
+ noticeId: n.noticeId,
256
+ fingerprint: n.fingerprint,
257
+ prepare: () => j.prepareCurrentState(r).pipe(b.map((r) => {
258
+ let a = k(e, async (e) => {
259
+ await J(e, s);
260
+ let r = (await Y(e, s, i.orderId, i)).receipts.find((e) => e.sequence === n.sequence);
261
+ if (!r || f(r) !== o) throw t("conflict");
262
+ return null;
263
+ });
264
+ return A(e, [r, a], async () => null);
265
+ }))
266
+ })), a;
267
+ }, ie = (n) => p(n).pipe(b.flatMap((r) => I(async () => {
268
+ let { original: i, state: o, assertFresh: s } = r, c = R(i.scope);
269
+ if (i.receipt.grants.length && !z) throw t("unavailable");
270
+ let l = await L(j.prepareCurrentState(n));
271
+ await q(c);
272
+ let u = (await e.transactional(async (e) => (await J(e, c), Y(e, c, i.orderId, i)))).latest;
273
+ if (u && o.observedAt < u.observedAt) return X(u, n, i);
274
+ let d = B(i, o);
275
+ if (u && o.observedAt === u.observedAt && o.refundNumerator * u.refundDenominator !== u.refundNumerator * o.refundDenominator) throw t("conflict");
276
+ if (u && f(d) === f(u.currencies) && o.state === u.state && o.observedAt === u.observedAt) return X(u, n, i);
277
+ let p = (u?.sequence ?? 0) + 1, b = K(c, i.orderId), S = N([
278
+ i.receipt,
279
+ i.product,
280
+ i.currencies,
281
+ U
282
+ ]), C = Object.freeze({
283
+ original: i,
284
+ state: o,
285
+ previous: u,
286
+ sequence: p
287
+ }), T = [l, k(e, async (e) => {
288
+ s();
289
+ let { header: n, person: r } = await J(e, c), a = await Y(e, c, i.orderId, i);
290
+ if (f(a.latest) !== f(u)) throw t("conflict");
291
+ if (Number(n.rowsCount) >= w || Number(r.rowsCount) >= w || p > w) throw t("limit_exceeded");
292
+ if (!a.row) await e.insert(_.tableName, {
293
+ id: b,
294
+ namespace: y,
295
+ tenantId: c.tenantId,
296
+ ownerKey: G(c),
297
+ ownerId: c.ownerId,
298
+ orderId: i.orderId,
299
+ admission: 0,
300
+ configurationDigest: S,
301
+ receipt: null,
302
+ erased: !1,
303
+ rowsCount: 0
304
+ });
305
+ else if (a.row.configurationDigest !== S) throw t("conflict");
306
+ return null;
307
+ })], E = d.map((e) => ({
308
+ ...e,
309
+ delta: BigInt(e.refunded) - BigInt(u?.currencies.find((t) => t.currency === e.currency)?.refunded ?? "0")
310
+ })).filter((e) => e.delta !== 0n);
311
+ for (let t of E) {
312
+ let r = P("ifr_", [
313
+ b,
314
+ String(p),
315
+ t.currency
316
+ ]), o = (t.delta < 0n ? -t.delta : t.delta).toString();
317
+ if (t.delta > 0n) T.push(await L(M.prepareIapRefund(te, n, {
318
+ requestId: r,
319
+ currency: t.currency,
320
+ amount: o,
321
+ debtPolicy: x
322
+ })));
323
+ else {
324
+ let n = i.receipt.transfers.find((e) => e.currency === t.currency), s = (await e.transactional(async (e) => (await m(e, c.tenantId, [n.txId], t.currency)).get(n.txId))).pair.find((e) => BigInt(String(e.debit)) > 0n);
325
+ T.push(await L(M.prepareTransfer(te, {
326
+ tenantId: c.tenantId,
327
+ requestId: r,
328
+ currency: t.currency,
329
+ amount: o,
330
+ from: {
331
+ kind: "system",
332
+ id: a(s.systemAccount)
333
+ },
334
+ to: {
335
+ kind: "wallet",
336
+ ownerKind: "player",
337
+ ownerId: c.ownerId
338
+ },
339
+ reason: "iap.refund-reversed",
340
+ ref: {
341
+ kind: "iap-order",
342
+ id: i.orderId
343
+ }
344
+ })));
345
+ }
346
+ }
347
+ i.receipt.grants.length && T.push(await L(z.prepare(C)));
348
+ let D = A(e, T, async (e, t) => {
349
+ s();
350
+ let n = t.slice(2, 2 + E.length), r = i.receipt.grants.length ? z.validate(C, t[t.length - 1]) : null, a = V({
351
+ orderId: i.orderId,
352
+ sequence: p,
353
+ state: o.state,
354
+ refundNumerator: o.refundNumerator,
355
+ refundDenominator: o.refundDenominator,
356
+ observedAt: o.observedAt,
357
+ noticeId: i.notice.noticeId,
358
+ fingerprint: i.fingerprint,
359
+ debtPolicy: x,
360
+ currencies: d,
361
+ transfers: n,
362
+ assets: r
363
+ }), { header: l, person: u } = await J(e, c);
364
+ await e.insert(h.tableName, {
365
+ id: P("ifa_", [b, String(p)]),
366
+ namespace: y,
367
+ tenantId: c.tenantId,
368
+ ownerKey: G(c),
369
+ ownerId: c.ownerId,
370
+ orderId: i.orderId,
371
+ sequence: p,
372
+ receipt: a,
373
+ at: new Date(o.observedAt),
374
+ eventState: "pending",
375
+ eventDeliveryVersion: null
376
+ }), await e.update(_.tableName, b, {
377
+ receipt: a,
378
+ rowsCount: p
379
+ }), await e.update(g.tableName, W, { rowsCount: Number(l.rowsCount) + 1 }), await e.update(v.tableName, G(c), { rowsCount: Number(u.rowsCount) + 1 });
380
+ let f = await Y(e, c, i.orderId, i);
381
+ return s(), f.latest;
382
+ });
383
+ return X((await O(e, [D]))[0], n, i);
384
+ })), b.catchDefect(() => b.fail(t("unavailable")))), ae = (n) => p(n).pipe(b.flatMap((t) => j.prepareCurrentState(n).pipe(b.flatMap((n) => I(async () => {
385
+ let r = R(t.original.scope), i = k(e, async (e) => {
386
+ if (t.assertFresh(), !(await e.query(D(v).where(E("id", G(r))).limit(1).descriptor))[0]) return null;
387
+ await J(e, r);
388
+ let n = await Y(e, r, t.original.orderId, t.original, !0);
389
+ return n.row && await e.update(_.tableName, String(n.row.id), { receipt: n.latest }), t.assertFresh(), n.latest;
390
+ });
391
+ return (await O(e, [n, i]))[1];
392
+ })))), b.catchDefect(() => b.fail(t("unavailable")))), oe = (t, n) => b.try({
393
+ try: () => ({
394
+ scope: R(t),
395
+ orderId: a(n)
396
+ }),
397
+ catch: F
398
+ }).pipe(b.flatMap(({ scope: t, orderId: n }) => I(() => e.transactional(async (e) => (await e.query(D(v).where(E("id", G(t))).limit(1).descriptor))[0] ? (await J(e, t), (await Y(e, t, n)).latest) : null)))), se = (e) => {
399
+ let n = i(e, [
400
+ "tenantId",
401
+ "subjectId",
402
+ "kind"
403
+ ]);
404
+ if (n.kind !== "user" && n.kind !== "guest") throw t("invalid_input");
405
+ return R({
406
+ tenantId: n.tenantId,
407
+ ownerId: n.subjectId
408
+ });
409
+ }, ce = (n) => b.try({
410
+ try: () => se(n),
411
+ catch: F
412
+ }).pipe(b.flatMap((n) => I(async () => (await q(n), e.transactional(async (e) => {
413
+ let { person: r } = await J(e, n, !0);
414
+ if (r.erased) return { refunds: [] };
415
+ let i = await e.query(D(h).where(T(E("namespace", y), E("tenantId", n.tenantId), E("ownerKey", G(n)))).limit(w + 1).descriptor);
416
+ if (i.length > w || i.length !== r.rowsCount) throw t("invalid_record");
417
+ return { refunds: i.map((e) => V(e.receipt)) };
418
+ }))))), le = (n) => b.try({
419
+ try: () => se(n),
420
+ catch: F
421
+ }).pipe(b.flatMap((n) => I(async () => {
422
+ await q(n), await e.transactional(async (e) => {
423
+ let { person: r } = await J(e, n, !0), i = await e.query(D(h).where(T(E("namespace", y), E("tenantId", n.tenantId), E("ownerKey", G(n)))).limit(w + 1).descriptor);
424
+ if (i.length > w || i.length !== r.rowsCount) throw t("invalid_record");
425
+ let a = await e.query(D(_).where(T(E("namespace", y), E("tenantId", n.tenantId), E("ownerKey", G(n)))).limit(w + 1).descriptor);
426
+ if (a.length > w) throw t("limit_exceeded");
427
+ for (let t of i) await e.update(h.tableName, String(t.id), {
428
+ ownerId: null,
429
+ receipt: null,
430
+ eventState: "erased",
431
+ eventDeliveryVersion: null
432
+ });
433
+ for (let t of a) await e.update(_.tableName, String(t.id), {
434
+ ownerId: null,
435
+ receipt: null,
436
+ erased: !0
437
+ });
438
+ await e.update(v.tableName, G(n), {
439
+ ownerId: null,
440
+ erased: !0
441
+ });
442
+ });
443
+ }))), Z = (e) => {
444
+ let n = structuredClone(e);
445
+ if (n.type !== "system" && !(n.type === "serviceAccount" && n.scopes?.includes("economy:events:write"))) throw t("forbidden");
446
+ return n;
447
+ }, Q = (e) => {
448
+ if (e.eventState !== "pending" && e.eventState !== "delivered" && e.eventState !== "erased" || (e.eventState === "delivered" ? typeof e.eventDeliveryVersion != "string" || a(e.eventDeliveryVersion) === "default.v1" : e.eventDeliveryVersion !== null) || (e.eventState === "erased" ? e.ownerId !== null || e.receipt !== null : e.ownerId === null || e.receipt === null)) throw t("invalid_record");
449
+ }, ue = /* @__PURE__ */ new WeakMap(), $ = async (e, n, r, i = !1) => {
450
+ if (r.type === "serviceAccount" && r.tenantId !== n.scope.tenantId) throw t("forbidden");
451
+ let { person: o } = await J(e, n.scope), s = (await e.query(D(h).where(E("id", n.id)).limit(1).descriptor))[0];
452
+ if (!s) throw t("not_found");
453
+ if (Q(s), s.eventState === "erased") throw t("erased");
454
+ if (s.eventState !== "pending" && !i || s.namespace !== y || s.tenantId !== n.scope.tenantId || s.ownerId !== n.scope.ownerId || s.ownerKey !== G(n.scope) || f(s.receipt) !== n.canonical) throw t("conflict");
455
+ let c = await e.query(D(h).where(T(E("namespace", y), E("tenantId", n.scope.tenantId), E("ownerKey", G(n.scope)))).limit(w + 1).descriptor);
456
+ if (c.length > w || c.length !== o.rowsCount) throw t("invalid_record");
457
+ let l = /* @__PURE__ */ new Map();
458
+ for (let r of c) {
459
+ if (Q(r), r.ownerId !== n.scope.ownerId) throw t("invalid_record");
460
+ let i = a(r.orderId);
461
+ l.has(i) || l.set(i, (await Y(e, n.scope, i)).receipts);
462
+ }
463
+ let u = V(s.receipt), p = l.get(u.orderId)?.find((e) => e.sequence === u.sequence);
464
+ if (!p || f(p) !== n.canonical) throw t("invalid_record");
465
+ let m = S.decodeUnknownSync(d, { onExcessProperty: "error" })({
466
+ eventId: n.id,
467
+ orderId: u.orderId,
468
+ sequence: u.sequence,
469
+ state: u.state,
470
+ refundNumerator: u.refundNumerator,
471
+ refundDenominator: u.refundDenominator,
472
+ observedAt: u.observedAt
473
+ });
474
+ return Object.freeze({
475
+ namespace: y,
476
+ tenantId: n.scope.tenantId,
477
+ subjectId: n.scope.ownerId,
478
+ payload: Object.freeze(m),
479
+ observedOrders: l.size,
480
+ refundedOrders: [...l.values()].filter((e) => e.at(-1).refundNumerator > 0).length,
481
+ refundAdjustments: [...l.values()].reduce((e, t) => e + t.filter((e) => e.refundNumerator > 0).length, 0)
482
+ });
483
+ }, de = (e) => {
484
+ let n = typeof e == "object" && e ? ue.get(e) : void 0;
485
+ if (!n) throw t("forbidden");
486
+ return n;
487
+ }, fe = Object.freeze({
488
+ pending: (n) => b.try({
489
+ try: () => Z(n),
490
+ catch: F
491
+ }).pipe(b.flatMap((n) => I(() => e.transactional(async (e) => {
492
+ let r = (await e.query(D(g).where(E("id", W)).limit(1).descriptor))[0];
493
+ if (!r) return [];
494
+ if (r.namespace !== y || r.configurationDigest !== U || r.admission !== 0) throw t("conflict");
495
+ let i = await e.query(D(h).where(E("namespace", y)).limit(w + 1).descriptor);
496
+ if (i.length > w || i.length !== r.rowsCount) throw t("invalid_record");
497
+ let o = [];
498
+ for (let t of i) {
499
+ if (Q(t), t.eventState !== "pending" || n.type === "serviceAccount" && t.tenantId !== n.tenantId) continue;
500
+ let r = {
501
+ id: a(t.id),
502
+ scope: R({
503
+ tenantId: a(t.tenantId),
504
+ ownerId: a(t.ownerId)
505
+ }),
506
+ canonical: f(t.receipt)
507
+ };
508
+ await $(e, r, n);
509
+ let i = Object.freeze({ eventId: r.id });
510
+ ue.set(i, r), o.push(i);
511
+ }
512
+ return Object.freeze(o.sort((e, t) => e.eventId.localeCompare(t.eventId)));
513
+ })))),
514
+ read: (t, n) => b.try({
515
+ try: () => ({
516
+ who: Z(t),
517
+ value: de(n)
518
+ }),
519
+ catch: F
520
+ }).pipe(b.flatMap(({ who: t, value: n }) => I(() => e.transactional((e) => $(e, n, t))))),
521
+ acknowledge: (n, r, i) => b.try({
522
+ try: () => {
523
+ let e = a(i);
524
+ if (e === "default.v1") throw t("invalid_input");
525
+ return {
526
+ who: Z(n),
527
+ value: de(r),
528
+ version: e
529
+ };
530
+ },
531
+ catch: F
532
+ }).pipe(b.flatMap(({ who: n, value: r, version: i }) => I(() => e.transactional(async (e) => {
533
+ await $(e, r, n, !0);
534
+ let a = (await e.query(D(h).where(E("id", r.id)).limit(1).descriptor))[0];
535
+ if (a.eventState === "delivered") {
536
+ if (a.eventDeliveryVersion !== i) throw t("conflict");
537
+ } else await e.update(h.tableName, r.id, {
538
+ eventState: "delivered",
539
+ eventDeliveryVersion: i
540
+ });
541
+ }))))
542
+ });
543
+ return Object.freeze({
544
+ reconcile: ie,
545
+ rebuild: ae,
546
+ observe: oe,
547
+ exportSubject: ce,
548
+ eraseSubject: le,
549
+ events: fe,
550
+ readDisposition: re
551
+ });
552
+ };
553
+ export { j as IapRefundPort, M as captureIapRefundService, H as dataStoreIapRefunds, z as readAcceptedIapRefund };
@@ -0,0 +1,64 @@
1
+ import { iapCurrentStatePort as e } from "./iapCurrentState.server.js";
2
+ import { iapRefundTables as t } from "./iapRefundTables.entity.js";
3
+ import { dataStoreIapRefunds as n } from "./iapRefunds.server.js";
4
+ import { iapStoreFixture as r } from "./iapStore.testFixtures.js";
5
+ import { iapSqlNoticeFixture as i } from "./iapNotificationTarget.testFixtures.js";
6
+ import { inventoryIapRefundAssets as a } from "./iapInventoryRefunds.server.js";
7
+ import { Effect as o } from "effect";
8
+ import { ensureTableRegistered as s } from "@voltro/database";
9
+ // src/iapRefunds.testFixtures.ts
10
+ var c = async (c, l = "test.tenant", u = {}) => {
11
+ t.forEach(s);
12
+ let d = await r(c, l), f = await o.runPromise(d.iap.redeem(d.user(), d.intent)), p = i(d), m = await o.runPromise(d.iap.prepareNotification(p.options, p.evidence)), h = d.at, g = a(c, {
13
+ inventory: d.inventory,
14
+ disposition: "requireAll"
15
+ }), _ = {
16
+ realMoney: !0,
17
+ namespace: l,
18
+ debtPolicy: "blockSpend",
19
+ orders: d.iap,
20
+ economy: d.economy,
21
+ assets: g,
22
+ assetsVersion: "fixture.1",
23
+ ...u
24
+ };
25
+ return {
26
+ store: c,
27
+ f: d,
28
+ n: p,
29
+ original: f,
30
+ target: m,
31
+ options: _,
32
+ assets: g,
33
+ refunds: n(c, _),
34
+ observeProvider: async (t, n = 1) => {
35
+ h++;
36
+ let r = e({
37
+ ...p.options,
38
+ accounts: d.accounts,
39
+ now: () => h
40
+ }, {
41
+ initialize: () => o.void,
42
+ read: (e) => o.succeed({
43
+ orderId: e.orderId,
44
+ channel: "ios",
45
+ state: t === 0 ? "active" : t === n ? "revoked" : "partially-refunded",
46
+ refundNumerator: t,
47
+ refundDenominator: n,
48
+ observedAt: e.at,
49
+ revokedAt: null
50
+ })
51
+ });
52
+ return o.runPromise(r.read(m));
53
+ },
54
+ advance: (e) => {
55
+ h += e;
56
+ },
57
+ privacy: {
58
+ tenantId: l,
59
+ subjectId: d.scope.ownerId,
60
+ kind: "guest"
61
+ }
62
+ };
63
+ };
64
+ export { c as iapRefundsFixture };