@voltro/plugin-economy 0.0.0-stage → 0.87.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 +24329 -0
  2. package/LICENSE +61 -0
  3. package/README.md +2719 -2
  4. package/SECURITY.md +56 -0
  5. package/THIRD-PARTY-NOTICES.md +9189 -0
  6. package/dist/amount.js +21 -0
  7. package/dist/appleIap.server.js +263 -0
  8. package/dist/appleIapNotifications.server.js +163 -0
  9. package/dist/appleIapTrust.server.js +31 -0
  10. package/dist/bundleValue.server.js +113 -0
  11. package/dist/canonicalDataJson.js +11 -0
  12. package/dist/capture.js +54 -0
  13. package/dist/codePort.fixture.js +124 -0
  14. package/dist/codeStore.server.js +440 -0
  15. package/dist/codeTables.entity.js +44 -0
  16. package/dist/codes.d.ts +150 -0
  17. package/dist/codes.js +83 -0
  18. package/dist/components.d.ts +397 -0
  19. package/dist/components.js +284 -0
  20. package/dist/consistency.server.js +116 -0
  21. package/dist/containerStore.server.js +688 -0
  22. package/dist/containerStore.testFixtures.js +76 -0
  23. package/dist/containerTables.entity.js +46 -0
  24. package/dist/containers.d.ts +311 -0
  25. package/dist/containers.fixture.js +129 -0
  26. package/dist/containers.js +215 -0
  27. package/dist/containers.server.js +40 -0
  28. package/dist/containersPort.fixture.js +75 -0
  29. package/dist/currency.js +33 -0
  30. package/dist/declarationFixtures.js +62 -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 +125 -0
  34. package/dist/errors.d.ts +14 -0
  35. package/dist/errors.js +22 -0
  36. package/dist/events.d.ts +46 -0
  37. package/dist/events.js +1 -0
  38. package/dist/googleIap.server.js +392 -0
  39. package/dist/googleIapNotifications.server.js +131 -0
  40. package/dist/googleIapSubscription.server.js +184 -0
  41. package/dist/iap.d.ts +309 -0
  42. package/dist/iap.js +304 -0
  43. package/dist/iap.testFixtures.js +145 -0
  44. package/dist/iapAccountTables.entity.js +40 -0
  45. package/dist/iapAccounts.server.js +201 -0
  46. package/dist/iapBinding.server.js +524 -0
  47. package/dist/iapCheckout.server.js +98 -0
  48. package/dist/iapCompletion.server.js +56 -0
  49. package/dist/iapCurrentState.server.js +108 -0
  50. package/dist/iapCurrentState.testFixtures.js +23 -0
  51. package/dist/iapCurrentStatePort.fixture.js +64 -0
  52. package/dist/iapEntitlements.d.ts +79 -0
  53. package/dist/iapEntitlements.js +58 -0
  54. package/dist/iapEntitlements.server.js +49 -0
  55. package/dist/iapEntitlementsBinding.server.js +106 -0
  56. package/dist/iapEntitlementsPort.fixture.js +81 -0
  57. package/dist/iapInstalledEntitlements.testFixtures.js +76 -0
  58. package/dist/iapInstalledNotifications.testFixtures.js +94 -0
  59. package/dist/iapInventoryRefunds.server.js +262 -0
  60. package/dist/iapNotificationCustodyPort.fixture.js +68 -0
  61. package/dist/iapNotificationPort.fixture.js +74 -0
  62. package/dist/iapNotificationProcessor.server.js +135 -0
  63. package/dist/iapNotificationProcessor.testFixtures.js +64 -0
  64. package/dist/iapNotificationProcessorPort.fixture.js +56 -0
  65. package/dist/iapNotificationSchedule.server.js +70 -0
  66. package/dist/iapNotificationStore.server.js +353 -0
  67. package/dist/iapNotificationStore.testFixtures.js +32 -0
  68. package/dist/iapNotificationTables.entity.js +49 -0
  69. package/dist/iapNotificationTarget.server.js +37 -0
  70. package/dist/iapNotificationTarget.testFixtures.js +30 -0
  71. package/dist/iapNotifications.d.ts +644 -0
  72. package/dist/iapNotifications.js +6 -0
  73. package/dist/iapNotifications.server.js +96 -0
  74. package/dist/iapNotificationsBinding.server.js +233 -0
  75. package/dist/iapNotificationsSystemPort.fixture.js +51 -0
  76. package/dist/iapPlugin.testFixtures.js +60 -0
  77. package/dist/iapPort.fixture.js +75 -0
  78. package/dist/iapProviderCapture.server.js +18 -0
  79. package/dist/iapRecovery.d.ts +146 -0
  80. package/dist/iapRecovery.js +90 -0
  81. package/dist/iapRefundEventDelivery.server.js +83 -0
  82. package/dist/iapRefundEvents.js +45 -0
  83. package/dist/iapRefundEventsPort.fixture.js +60 -0
  84. package/dist/iapRefundPort.fixture.js +55 -0
  85. package/dist/iapRefundTables.entity.js +56 -0
  86. package/dist/iapRefunds.d.ts +755 -0
  87. package/dist/iapRefunds.js +6 -0
  88. package/dist/iapRefunds.server.js +554 -0
  89. package/dist/iapRefunds.testFixtures.js +65 -0
  90. package/dist/iapSpendAdmission.server.js +99 -0
  91. package/dist/iapStore.server.js +741 -0
  92. package/dist/iapStore.testFixtures.js +128 -0
  93. package/dist/iapTables.entity.js +81 -0
  94. package/dist/iapVerification.server.js +121 -0
  95. package/dist/index.d.ts +2984 -0
  96. package/dist/index.js +15 -0
  97. package/dist/intentionRecovery.js +134 -0
  98. package/dist/ledger.js +78 -0
  99. package/dist/loyalty.d.ts +274 -0
  100. package/dist/loyalty.fixture.js +93 -0
  101. package/dist/loyalty.js +312 -0
  102. package/dist/loyaltyBinding.server.js +204 -0
  103. package/dist/loyaltyPort.fixture.js +87 -0
  104. package/dist/loyaltyShopSource.server.js +91 -0
  105. package/dist/loyaltyStore.server.js +553 -0
  106. package/dist/loyaltyStore.testFixtures.js +83 -0
  107. package/dist/loyaltyTables.entity.js +50 -0
  108. package/dist/offerMerchandising.fixture.js +115 -0
  109. package/dist/offerMerchandising.js +283 -0
  110. package/dist/offerPolicies.d.ts +441 -0
  111. package/dist/offerPolicies.fixture.js +54 -0
  112. package/dist/offerPolicies.js +268 -0
  113. package/dist/piggyBanks.d.ts +272 -0
  114. package/dist/piggyBanks.fixture.js +71 -0
  115. package/dist/piggyBanks.js +201 -0
  116. package/dist/piggyBinding.server.js +205 -0
  117. package/dist/piggyPort.fixture.js +73 -0
  118. package/dist/piggyStore.server.js +611 -0
  119. package/dist/piggyStore.testFixtures.js +98 -0
  120. package/dist/piggyTables.entity.js +41 -0
  121. package/dist/plugin.js +1048 -0
  122. package/dist/port.js +13 -0
  123. package/dist/price.d.ts +99 -0
  124. package/dist/price.js +144 -0
  125. package/dist/purchaseRecovery.js +56 -0
  126. package/dist/reports.server.js +115 -0
  127. package/dist/rpc.d.ts +3201 -0
  128. package/dist/rpc.js +1201 -0
  129. package/dist/sanctionsTradeAuthority.server.js +80 -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 +196 -0
  134. package/dist/shop.testFixtures.js +114 -0
  135. package/dist/shopPort.fixture.js +72 -0
  136. package/dist/shopPresentation.js +68 -0
  137. package/dist/shopStore.server.js +624 -0
  138. package/dist/shopTables.entity.js +63 -0
  139. package/dist/shopUiCopy.js +57 -0
  140. package/dist/spendLimits.d.ts +140 -0
  141. package/dist/spendLimits.fixture.js +53 -0
  142. package/dist/spendLimits.js +155 -0
  143. package/dist/spendLimitsPort.fixture.js +120 -0
  144. package/dist/subscriptionBinding.server.js +211 -0
  145. package/dist/subscriptionStore.server.js +643 -0
  146. package/dist/subscriptionStore.testFixtures.js +72 -0
  147. package/dist/subscriptionTables.entity.js +37 -0
  148. package/dist/subscriptions.d.ts +394 -0
  149. package/dist/subscriptions.fixture.js +97 -0
  150. package/dist/subscriptions.js +213 -0
  151. package/dist/subscriptionsPort.fixture.js +76 -0
  152. package/dist/systemFixtures.js +89 -0
  153. package/dist/tables.entity.js +83 -0
  154. package/dist/testing.d.ts +4615 -0
  155. package/dist/testing.js +29 -0
  156. package/dist/tradeAuthority.server.js +172 -0
  157. package/dist/tradeBinding.server.js +201 -0
  158. package/dist/tradeComponents.js +342 -0
  159. package/dist/tradeEscrow.server.js +232 -0
  160. package/dist/tradeEscrow.testFixtures.js +133 -0
  161. package/dist/tradeExpiry.server.js +43 -0
  162. package/dist/tradeRecovery.js +48 -0
  163. package/dist/tradeStore.server.js +750 -0
  164. package/dist/tradeStore.testFixtures.js +73 -0
  165. package/dist/tradeTables.entity.js +66 -0
  166. package/dist/tradeUiCopy.js +79 -0
  167. package/dist/trades.d.ts +246 -0
  168. package/dist/trades.fixture.js +103 -0
  169. package/dist/trades.js +289 -0
  170. package/dist/tradesPort.fixture.js +94 -0
  171. package/dist/walletEscrow.js +106 -0
  172. package/dist/walletEscrow.server.js +376 -0
  173. package/dist/walletKeys.server.js +5 -0
  174. package/dist/walletStore.js +571 -0
  175. package/dist/web.d.ts +2257 -0
  176. package/dist/web.js +9 -0
  177. package/package.json +161 -4
package/dist/iap.js ADDED
@@ -0,0 +1,304 @@
1
+ import { economyError as e } from "./errors.js";
2
+ import { captureBoundMethods as t, dataList as n, dataObject as r, identifier as i } from "./capture.js";
3
+ import { nonNegativeAmount as a, positiveAmount as o } from "./amount.js";
4
+ import { defineShopOffer as s, shopInteger as c } from "./shop.js";
5
+ import { Context as l } from "effect";
6
+ //#region src/iap.ts
7
+ var u = (t, a, o = !1) => {
8
+ if (typeof o != "boolean") throw e("invalid_input");
9
+ let c = n(t, 4096);
10
+ if (!o && c.length !== 0) throw e("real_money_disabled");
11
+ let l = /* @__PURE__ */ new Set(), u = /* @__PURE__ */ new Set();
12
+ return Object.freeze(c.map((t) => {
13
+ let n = r(t, [
14
+ "offer",
15
+ "channel",
16
+ "kind"
17
+ ]), c = d(n.channel), p = f(n.kind), m = s(n.offer, a, o, { grantless: p === "subscription" }), h = (m.price.kind === "real" ? m.price : m.price.kind === "mixed" ? m.price.real : null)?.storeProductIds[c];
18
+ if (h === void 0) throw e("invalid_price");
19
+ let g = i(h), _ = `${c}:${g}`, v = `${c}:${m.id}`;
20
+ if (l.has(_) || u.has(v)) throw e("conflict");
21
+ if (p !== "consumable" && m.maxQuantity !== 1 || p === "non-consumable" && m.limits.perPlayer !== 1) throw e("invalid_input");
22
+ return l.add(_), u.add(v), Object.freeze({
23
+ offer: m,
24
+ channel: c,
25
+ kind: p,
26
+ storeProductId: g
27
+ });
28
+ }));
29
+ }, d = (t) => {
30
+ if (t !== "ios" && t !== "android" && t !== "steam" && t !== "web") throw e("invalid_input");
31
+ return t;
32
+ }, f = (t) => {
33
+ if (t !== "consumable" && t !== "non-consumable" && t !== "subscription") throw e("invalid_input");
34
+ return t;
35
+ }, p = (t) => {
36
+ if (t !== "sandbox" && t !== "production") throw e("invalid_input");
37
+ return t;
38
+ }, m = (t) => {
39
+ let n = r(t, [
40
+ "channel",
41
+ "offerId",
42
+ "offerVersion",
43
+ "requestId",
44
+ "receipt"
45
+ ]);
46
+ if (typeof n.receipt != "string" || n.receipt.length === 0 || n.receipt.length > 65536 || /[\u0000-\u0020\u007f]/.test(n.receipt)) throw e("invalid_input");
47
+ return Object.freeze({
48
+ channel: d(n.channel),
49
+ offerId: i(n.offerId),
50
+ offerVersion: i(n.offerVersion),
51
+ requestId: i(n.requestId),
52
+ receipt: n.receipt
53
+ });
54
+ }, h = (t) => {
55
+ let n = r(t, [
56
+ "channel",
57
+ "offerId",
58
+ "offerVersion",
59
+ "requestId",
60
+ "receiptDigest"
61
+ ]);
62
+ if (typeof n.receiptDigest != "string" || !/^[a-f0-9]{64}$/.test(n.receiptDigest)) throw e("invalid_input");
63
+ return Object.freeze({
64
+ channel: d(n.channel),
65
+ offerId: i(n.offerId),
66
+ offerVersion: i(n.offerVersion),
67
+ requestId: i(n.requestId),
68
+ receiptDigest: n.receiptDigest
69
+ });
70
+ }, g = async (e) => {
71
+ let t = m(e), n = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify(t.receipt))), r = Array.from(new Uint8Array(n), (e) => e.toString(16).padStart(2, "0")).join("");
72
+ return h({
73
+ channel: t.channel,
74
+ offerId: t.offerId,
75
+ offerVersion: t.offerVersion,
76
+ requestId: t.requestId,
77
+ receiptDigest: r
78
+ });
79
+ }, _ = (e) => {
80
+ let t = r(e, ["tenantId", "ownerId"]);
81
+ return Object.freeze({
82
+ tenantId: i(t.tenantId),
83
+ ownerId: i(t.ownerId)
84
+ });
85
+ }, v = (t) => {
86
+ try {
87
+ if (t === null) return null;
88
+ let n = r(t, [
89
+ "currency",
90
+ "units",
91
+ "scale"
92
+ ], "invalid_record");
93
+ if (typeof n.currency != "string" || !/^[A-Z]{3}$/.test(n.currency) || typeof n.units != "string" || a(n.units).toString() !== n.units) throw e("invalid_record");
94
+ return Object.freeze({
95
+ currency: n.currency,
96
+ units: n.units,
97
+ scale: c(n.scale, 0, 9)
98
+ });
99
+ } catch {
100
+ throw e("invalid_record");
101
+ }
102
+ }, y = (t) => {
103
+ try {
104
+ let a = r(t, [
105
+ "orderId",
106
+ "offerId",
107
+ "offerVersion",
108
+ "quantity",
109
+ "at",
110
+ "pricePaid",
111
+ "transfers",
112
+ "grants"
113
+ ], "invalid_record"), s = E(a.at), l = Object.freeze(n(a.transfers, 16, "invalid_record").map((t) => {
114
+ let n = r(t, [
115
+ "txId",
116
+ "currency",
117
+ "amount",
118
+ "at"
119
+ ], "invalid_record");
120
+ if (typeof n.amount != "string" || o(n.amount).toString() !== n.amount) throw e("invalid_record");
121
+ return Object.freeze({
122
+ txId: i(n.txId, "invalid_record"),
123
+ currency: i(n.currency, "invalid_record"),
124
+ amount: n.amount,
125
+ at: c(n.at, 0, s)
126
+ });
127
+ })), u = Object.freeze(n(a.grants, 16, "invalid_record").map((t) => {
128
+ let a = r(t, [
129
+ "operationId",
130
+ "kind",
131
+ "changes",
132
+ "at"
133
+ ], "invalid_record");
134
+ if (a.kind !== "grant") throw e("invalid_record");
135
+ let o = Object.freeze(n(a.changes, 1e5, "invalid_record").map((e) => {
136
+ let t = r(e, ["instanceId", "delta"], "invalid_record");
137
+ return Object.freeze({
138
+ instanceId: i(t.instanceId, "invalid_record"),
139
+ delta: c(t.delta, 1, 1e6)
140
+ });
141
+ }));
142
+ if (o.length === 0 || new Set(o.map((e) => e.instanceId)).size !== o.length) throw e("invalid_record");
143
+ return Object.freeze({
144
+ operationId: i(a.operationId, "invalid_record"),
145
+ kind: "grant",
146
+ changes: o,
147
+ at: c(a.at, 0, s)
148
+ });
149
+ }));
150
+ if (new Set(l.map((e) => e.txId)).size !== l.length || new Set(u.map((e) => e.operationId)).size !== u.length) throw e("invalid_record");
151
+ return Object.freeze({
152
+ orderId: i(a.orderId, "invalid_record"),
153
+ offerId: i(a.offerId, "invalid_record"),
154
+ offerVersion: i(a.offerVersion, "invalid_record"),
155
+ quantity: c(a.quantity, 1, 1e3),
156
+ at: s,
157
+ pricePaid: v(a.pricePaid),
158
+ transfers: l,
159
+ grants: u
160
+ });
161
+ } catch {
162
+ throw e("invalid_record");
163
+ }
164
+ }, b = (t) => {
165
+ try {
166
+ let n = r(t, [
167
+ "orderId",
168
+ "channel",
169
+ "operation",
170
+ "observedAt"
171
+ ], "invalid_record");
172
+ if (n.operation !== "acknowledge" && n.operation !== "consume" && n.operation !== "finalize" && n.operation !== "finish") throw e("invalid_record");
173
+ return Object.freeze({
174
+ orderId: i(n.orderId, "invalid_record"),
175
+ channel: d(n.channel),
176
+ operation: n.operation,
177
+ observedAt: E(n.observedAt)
178
+ });
179
+ } catch {
180
+ throw e("invalid_record");
181
+ }
182
+ }, x = (t) => {
183
+ try {
184
+ let n = r(t, [
185
+ "sequence",
186
+ "state",
187
+ "refundNumerator",
188
+ "refundDenominator",
189
+ "observedAt"
190
+ ], "invalid_record"), i = c(n.refundDenominator, 1, 1e5), a = c(n.refundNumerator, 0, i), o = a === 0 ? "active" : a === i ? "revoked" : "partially-refunded";
191
+ if (n.state !== o) throw e("invalid_record");
192
+ return Object.freeze({
193
+ sequence: c(n.sequence, 1, 1e5),
194
+ state: o,
195
+ refundNumerator: a,
196
+ refundDenominator: i,
197
+ observedAt: E(n.observedAt)
198
+ });
199
+ } catch {
200
+ throw e("invalid_record");
201
+ }
202
+ }, S = (t) => {
203
+ try {
204
+ let n = r(t, [
205
+ "requestId",
206
+ "state",
207
+ "receipt",
208
+ "refund"
209
+ ], "invalid_record"), a = i(n.requestId, "invalid_record");
210
+ if (n.state === "not-observed" || n.state === "pending") {
211
+ if (Object.hasOwn(n, "receipt") || Object.hasOwn(n, "refund")) throw e("invalid_record");
212
+ return Object.freeze({
213
+ requestId: a,
214
+ state: n.state
215
+ });
216
+ }
217
+ if (n.state !== "fulfilled" && n.state !== "partially-refunded" && n.state !== "refunded") throw e("invalid_record");
218
+ let o = y(n.receipt), s = Object.hasOwn(n, "refund") ? x(n.refund) : void 0;
219
+ if (s && s.observedAt < o.at || (n.state === "fulfilled" ? s !== void 0 && s.state !== "active" : !s || s.state !== (n.state === "refunded" ? "revoked" : "partially-refunded"))) throw e("invalid_record");
220
+ if (n.state === "fulfilled") return Object.freeze({
221
+ requestId: a,
222
+ state: "fulfilled",
223
+ receipt: o,
224
+ ...s ? { refund: s } : {}
225
+ });
226
+ if (!s) throw e("invalid_record");
227
+ return Object.freeze({
228
+ requestId: a,
229
+ state: n.state,
230
+ receipt: o,
231
+ refund: s
232
+ });
233
+ } catch {
234
+ throw e("invalid_record");
235
+ }
236
+ }, C = (t) => {
237
+ try {
238
+ let n = r(t, [
239
+ "orderId",
240
+ "offerId",
241
+ "offerVersion",
242
+ "channel",
243
+ "startsAt",
244
+ "expiresAt",
245
+ "refund"
246
+ ], "invalid_record"), a = E(n.startsAt), o = E(n.expiresAt), s = n.refund === null ? null : x(n.refund);
247
+ if (o <= a || s && s.observedAt < a) throw e("invalid_record");
248
+ return Object.freeze({
249
+ orderId: i(n.orderId, "invalid_record"),
250
+ offerId: i(n.offerId, "invalid_record"),
251
+ offerVersion: i(n.offerVersion, "invalid_record"),
252
+ channel: d(n.channel),
253
+ startsAt: a,
254
+ expiresAt: o,
255
+ refund: s
256
+ });
257
+ } catch {
258
+ throw e("invalid_record");
259
+ }
260
+ }, w = class extends l.Service()("@voltro/IapPort") {}, T = (e) => t(e, [
261
+ "redeem",
262
+ "receipt",
263
+ "exportSubject",
264
+ "eraseSubject"
265
+ ]), E = (e) => c(e, 0, 864e13), D = (e) => [[
266
+ "channel",
267
+ "storeProductId",
268
+ "offerId",
269
+ "offerVersion",
270
+ "kind",
271
+ "titleKey"
272
+ ], ...n(e, 4096).map((e) => {
273
+ let t = r(e, [
274
+ "offer",
275
+ "channel",
276
+ "kind",
277
+ "storeProductId"
278
+ ]), n = r(t.offer, [
279
+ "id",
280
+ "version",
281
+ "titleKey",
282
+ "price",
283
+ "items",
284
+ "currencies",
285
+ "from",
286
+ "until",
287
+ "segment",
288
+ "flag",
289
+ "countries",
290
+ "limits",
291
+ "oneTime",
292
+ "maxQuantity"
293
+ ]);
294
+ return [
295
+ d(t.channel),
296
+ i(t.storeProductId),
297
+ i(n.id),
298
+ i(n.version),
299
+ f(t.kind),
300
+ i(n.titleKey)
301
+ ];
302
+ }).sort((e, t) => e.join("\0") < t.join("\0") ? -1 : +(e.join("\0") > t.join("\0")))].map((e) => e.map((e) => `"${e.replaceAll("\"", "\"\"")}"`).join(",")).join("\r\n") + "\r\n";
303
+ //#endregion
304
+ export { w as IapPort, b as captureIapCompletionReceipt, S as captureIapObservation, y as captureIapOrderReceipt, v as captureIapPaidAmount, x as captureIapRefundDisposition, _ as captureIapScope, T as captureIapService, C as captureIapSubscriptionPeriod, g as createIapReceiptReference, u as defineIapCatalog, d as iapChannel, p as iapEnvironment, f as iapProductKind, D as iapProductsCsv, m as iapReceiptIntent, h as iapReceiptReference, E as iapTimestamp };
@@ -0,0 +1,145 @@
1
+ import { economyError as e } from "./errors.js";
2
+ import { defineCurrency as t } from "./currency.js";
3
+ import { defineIapCatalog as n } from "./iap.js";
4
+ import { Effect as r } from "effect";
5
+ import { randomBytes as i, randomUUID as a } from "node:crypto";
6
+ //#region src/iap.testFixtures.ts
7
+ var o = () => {
8
+ let o = Date.UTC(2026, 0, 1), s = a(), c = i(32).toString("base64url"), l = [t({
9
+ id: "test.gems",
10
+ kind: "hard",
11
+ cap: "10000",
12
+ premium: !0
13
+ })], u = {
14
+ channel: "ios",
15
+ kind: "consumable",
16
+ offer: {
17
+ id: "test.pack",
18
+ version: "1",
19
+ titleKey: "test.pack.title",
20
+ price: {
21
+ kind: "real",
22
+ prices: { EUR: 999 },
23
+ storeProductIds: {
24
+ ios: "test.pack.ios",
25
+ android: "test.pack.android"
26
+ }
27
+ },
28
+ currencies: [{
29
+ currency: "test.gems",
30
+ amount: "10"
31
+ }],
32
+ maxQuantity: 3
33
+ }
34
+ }, d = {
35
+ tenantId: "test.tenant",
36
+ ownerId: "test.player"
37
+ }, f = {
38
+ channel: "ios",
39
+ offerId: "test.pack",
40
+ offerVersion: "1",
41
+ requestId: "test.request",
42
+ receipt: c
43
+ }, p = {
44
+ realMoney: !0,
45
+ channel: "ios",
46
+ environment: "sandbox",
47
+ applicationId: "test.application",
48
+ products: [u],
49
+ currencies: l,
50
+ accounts: {
51
+ read: () => r.succeed(s),
52
+ prepareBinding: () => r.fail(e("unavailable"))
53
+ },
54
+ now: () => o
55
+ }, m = {
56
+ realMoney: p.realMoney,
57
+ environment: p.environment,
58
+ applicationId: p.applicationId,
59
+ products: p.products,
60
+ currencies: p.currencies,
61
+ accounts: p.accounts,
62
+ now: () => o
63
+ }, h = {
64
+ scope: d,
65
+ intent: f,
66
+ product: n([u], l, !0)[0],
67
+ accountToken: s,
68
+ at: o
69
+ }, g = {
70
+ bundleId: "test.application",
71
+ environment: "Sandbox",
72
+ appAccountToken: s,
73
+ inAppOwnershipType: "PURCHASED",
74
+ type: "Consumable",
75
+ transactionId: "100001",
76
+ originalTransactionId: "100001",
77
+ productId: "test.pack.ios",
78
+ quantity: 2,
79
+ purchaseDate: o - 1e3,
80
+ signedDate: o,
81
+ currency: "EUR",
82
+ price: 9990
83
+ }, _ = {
84
+ channel: "ios",
85
+ environment: "sandbox",
86
+ applicationId: "test.application",
87
+ transactionId: "100001",
88
+ originalTransactionId: "100001",
89
+ storeProductId: "test.pack.ios",
90
+ kind: "consumable",
91
+ quantity: 2,
92
+ purchasedAt: o - 1e3,
93
+ expiresAt: null,
94
+ paid: {
95
+ currency: "EUR",
96
+ units: "9990",
97
+ scale: 3
98
+ },
99
+ acknowledgement: "none",
100
+ consumption: "none"
101
+ }, v = {
102
+ ...u,
103
+ channel: "android"
104
+ };
105
+ return {
106
+ at: o,
107
+ accountToken: s,
108
+ receipt: c,
109
+ currencies: l,
110
+ definition: u,
111
+ scope: d,
112
+ intent: f,
113
+ options: p,
114
+ providerOptions: m,
115
+ context: h,
116
+ apple: g,
117
+ facts: _,
118
+ androidDefinition: v,
119
+ googleContext: {
120
+ ...h,
121
+ intent: {
122
+ ...f,
123
+ channel: "android"
124
+ },
125
+ product: n([v], l, !0)[0]
126
+ },
127
+ google: {
128
+ purchaseStateContext: { purchaseState: "PURCHASED" },
129
+ testPurchaseContext: { fopType: "TEST" },
130
+ obfuscatedExternalAccountId: s,
131
+ purchaseCompletionTime: (/* @__PURE__ */ new Date(o - 1e3)).toISOString(),
132
+ acknowledgementState: "ACKNOWLEDGEMENT_STATE_PENDING",
133
+ productLineItem: [{
134
+ productId: "test.pack.android",
135
+ productOfferDetails: {
136
+ quantity: 2,
137
+ refundableQuantity: 2,
138
+ consumptionState: "CONSUMPTION_STATE_YET_TO_BE_CONSUMED"
139
+ }
140
+ }]
141
+ }
142
+ };
143
+ };
144
+ //#endregion
145
+ export { o as iapTestFixture };
@@ -0,0 +1,40 @@
1
+ import { boolean as e, id as t, integer as n, table as r, text as i } from "@voltro/database";
2
+ //#region src/iapAccountTables.entity.ts
3
+ var a = () => i().maxLength(120).serverOnly(), o = () => i().maxLength(16).oneOf([
4
+ "ios",
5
+ "android",
6
+ "steam",
7
+ "web"
8
+ ]).serverOnly(), s = () => i().maxLength(16).oneOf(["sandbox", "production"]).serverOnly(), c = r("_voltro_eco_iap_accounts", {
9
+ id: t({ prefix: "eia" }),
10
+ applicationId: a(),
11
+ channel: o(),
12
+ environment: s(),
13
+ tenantId: i().maxLength(120).externalId().sensitive("pii"),
14
+ ownerId: i().maxLength(120).nullable().externalId().sensitive("pii"),
15
+ token: i().maxLength(128).nullable().serverOnly().sensitive("pii"),
16
+ tokenKey: i().maxLength(64).nullable().serverOnly(),
17
+ erased: e().serverOnly(),
18
+ admission: n().serverOnly()
19
+ }).index("voltro_eco_iap_accounts_owner_idx", [
20
+ "applicationId",
21
+ "channel",
22
+ "environment",
23
+ "tenantId",
24
+ "ownerId"
25
+ ]), l = r("_voltro_eco_iap_account_tokens", {
26
+ id: t({ prefix: "eit" }),
27
+ applicationId: a(),
28
+ channel: o(),
29
+ environment: s(),
30
+ tenantId: i().maxLength(120).externalId().sensitive("pii"),
31
+ accountKey: i().maxLength(64).serverOnly(),
32
+ erased: e().serverOnly()
33
+ }).index("voltro_eco_iap_account_tokens_scope_idx", [
34
+ "applicationId",
35
+ "channel",
36
+ "environment",
37
+ "tenantId"
38
+ ]), u = [c, l];
39
+ //#endregion
40
+ export { u as iapAccountTables, l as iapAccountTokensTable, c as iapAccountsTable };
@@ -0,0 +1,201 @@
1
+ import { EconomyError as e, economyError as t } from "./errors.js";
2
+ import { captureBoundMethods as n, dataObject as r, identifier as i } from "./capture.js";
3
+ import { captureIapScope as a, iapChannel as o, iapEnvironment as s } from "./iap.js";
4
+ import { iapAccountTokensTable as c, iapAccountsTable as l } from "./iapAccountTables.entity.js";
5
+ import { Context as u, Effect as d } from "effect";
6
+ import { createHash as f, randomUUID as p } from "node:crypto";
7
+ import { eq as m, queryFor as h } from "@voltro/database";
8
+ import { prepareSqlMutation as g } from "@voltro/database/sql";
9
+ //#region src/iapAccounts.server.ts
10
+ var _ = class extends u.Service()("@voltro/IapAccountsPort") {}, v = (e) => n(e, [
11
+ "read",
12
+ "observe",
13
+ "prepareBinding",
14
+ "exportSubject",
15
+ "eraseSubject"
16
+ ]), y = (e, t) => e + f("sha256").update(JSON.stringify(t)).digest("hex").slice(0, 56), b = (e) => {
17
+ if (typeof e != "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(e)) throw t("invalid_input");
18
+ return e.toLowerCase();
19
+ }, x = (n) => n instanceof e ? n : t("unavailable"), S = (e) => {
20
+ try {
21
+ return e();
22
+ } catch (e) {
23
+ return d.fail(x(e));
24
+ }
25
+ }, C = (e, n) => {
26
+ let u = r(n, [
27
+ "realMoney",
28
+ "applicationId",
29
+ "channel",
30
+ "environment"
31
+ ]);
32
+ if (u.realMoney !== !0) throw t(u.realMoney === !1 ? "real_money_disabled" : "invalid_input");
33
+ let f = i(u.applicationId), _ = o(u.channel), v = s(u.environment), C = (e) => y("eia_", [
34
+ f,
35
+ _,
36
+ v,
37
+ e.tenantId,
38
+ e.ownerId
39
+ ]), w = (e) => y("eit_", [
40
+ f,
41
+ _,
42
+ v,
43
+ e
44
+ ]), T = (e, n, r) => {
45
+ let i = a(e);
46
+ if (o(n) !== _ || s(r) !== v) throw t("forbidden");
47
+ return i;
48
+ }, E = (t) => e.insertIgnore(l.tableName, {
49
+ id: C(t),
50
+ applicationId: f,
51
+ channel: _,
52
+ environment: v,
53
+ tenantId: t.tenantId,
54
+ ownerId: t.ownerId,
55
+ token: null,
56
+ tokenKey: null,
57
+ erased: !1,
58
+ admission: 0
59
+ }, { conflictColumns: ["id"] }), D = async (e, n, r = !1) => {
60
+ let i = (await e.query(h(l).where(m("id", C(n))).limit(1).descriptor))[0];
61
+ if (!i) return;
62
+ if (i.applicationId !== f || i.channel !== _ || i.environment !== v || i.tenantId !== n.tenantId || i.admission !== 0 || i.erased !== !0 && i.erased !== !1 || (i.erased ? i.ownerId !== null || i.token !== null : i.ownerId !== n.ownerId)) throw t("invalid_record");
63
+ if (i.erased && !r) throw t("erased");
64
+ if (i.tokenKey === null) {
65
+ if (i.token !== null) throw t("invalid_record");
66
+ return i;
67
+ }
68
+ if (typeof i.tokenKey != "string" || !/^eit_[a-f0-9]{56}$/.test(i.tokenKey) || !i.erased && w(b(i.token)) !== i.tokenKey) throw t("invalid_record");
69
+ let a = (await e.query(h(c).where(m("id", i.tokenKey)).limit(1).descriptor))[0];
70
+ if (!a || a.applicationId !== f || a.channel !== _ || a.environment !== v || a.tenantId !== n.tenantId || a.accountKey !== i.id || a.erased !== i.erased) throw t("invalid_record");
71
+ return i;
72
+ }, O = async (e, n, r = !1) => {
73
+ if (await e.updateMany(l.tableName, { admission: 0 }, { where: m("id", C(n)) }) !== 1) throw t("unavailable");
74
+ let i = await D(e, n, r);
75
+ if (!i) throw t("invalid_record");
76
+ return i;
77
+ }, k = (n, r, i) => S(() => {
78
+ let a = T(n, r, i);
79
+ return d.tryPromise({
80
+ try: async () => {
81
+ await E(a);
82
+ let n = p(), r = w(n);
83
+ return e.transactional(async (e) => {
84
+ let i = await O(e, a);
85
+ if (i.token !== null) return b(i.token);
86
+ if (i.tokenKey !== null) throw t("invalid_record");
87
+ if ((await e.query(h(c).where(m("id", r)).limit(1).descriptor))[0]) throw t("conflict");
88
+ return await e.insert(c.tableName, {
89
+ id: r,
90
+ applicationId: f,
91
+ channel: _,
92
+ environment: v,
93
+ tenantId: a.tenantId,
94
+ accountKey: i.id,
95
+ erased: !1
96
+ }), await e.update(l.tableName, String(i.id), {
97
+ token: n,
98
+ tokenKey: r
99
+ }), n;
100
+ });
101
+ },
102
+ catch: x
103
+ });
104
+ }), A = (n, r, i) => S(() => {
105
+ let a = T(n, r, i);
106
+ return d.tryPromise({
107
+ try: () => e.transactional(async (e) => {
108
+ let n = await D(e, a);
109
+ if (!n) throw t("not_found");
110
+ if (n.token === null || n.tokenKey === null) throw t("forbidden");
111
+ return b(n.token);
112
+ }),
113
+ catch: x
114
+ });
115
+ }), j = (n, r, i, a) => S(() => {
116
+ let o = T(n, r, i), s = b(a);
117
+ return d.succeed(g(e, async (e) => {
118
+ let n = await O(e, o);
119
+ if (n.token !== s || n.tokenKey !== w(s)) throw t("forbidden");
120
+ return null;
121
+ }));
122
+ }), M = (e) => {
123
+ let n = r(e, [
124
+ "tenantId",
125
+ "subjectId",
126
+ "kind"
127
+ ]);
128
+ if (n.kind !== "user" && n.kind !== "guest") throw t("invalid_input");
129
+ return Object.freeze({
130
+ tenantId: i(n.tenantId),
131
+ ownerId: i(n.subjectId)
132
+ });
133
+ };
134
+ return Object.freeze({
135
+ read: k,
136
+ observe: A,
137
+ prepareBinding: j,
138
+ exportSubject: (t) => S(() => {
139
+ let n = M(t);
140
+ return d.tryPromise({
141
+ try: () => e.transactional(async (e) => {
142
+ let t = await D(e, n, !0);
143
+ return { bindings: !t || t.erased ? [] : [{
144
+ applicationId: f,
145
+ channel: _,
146
+ environment: v,
147
+ token: t.token
148
+ }] };
149
+ }),
150
+ catch: x
151
+ });
152
+ }),
153
+ eraseSubject: (t) => S(() => {
154
+ let n = M(t);
155
+ return d.tryPromise({
156
+ try: async () => {
157
+ await E(n), await e.transactional(async (e) => {
158
+ let t = await O(e, n, !0);
159
+ t.tokenKey !== null && await e.update(c.tableName, String(t.tokenKey), { erased: !0 }), await e.update(l.tableName, String(t.id), {
160
+ ownerId: null,
161
+ token: null,
162
+ erased: !0
163
+ });
164
+ });
165
+ },
166
+ catch: x
167
+ });
168
+ })
169
+ });
170
+ }, w = (e, n) => {
171
+ let a = r(n, [
172
+ "realMoney",
173
+ "applicationId",
174
+ "channel",
175
+ "environment"
176
+ ]);
177
+ if (a.realMoney !== !0) throw t(a.realMoney === !1 ? "real_money_disabled" : "invalid_input");
178
+ let u = i(a.applicationId), f = o(a.channel), p = s(a.environment);
179
+ return Object.freeze({ owner: (t) => S(() => {
180
+ let n = b(t), r = y("eit_", [
181
+ u,
182
+ f,
183
+ p,
184
+ n
185
+ ]);
186
+ return d.tryPromise({
187
+ try: () => e.transactional(async (e) => {
188
+ let t = (await e.query(h(c).where(m("id", r)).limit(1).descriptor))[0];
189
+ if (!t || t.erased !== !1 || t.applicationId !== u || t.channel !== f || t.environment !== p) return null;
190
+ let a = (await e.query(h(l).where(m("id", String(t.accountKey))).limit(1).descriptor))[0];
191
+ return !a || a.erased !== !1 || a.token !== n || a.tokenKey !== r || a.tenantId !== t.tenantId || typeof a.ownerId != "string" ? null : Object.freeze({
192
+ tenantId: i(a.tenantId),
193
+ ownerId: i(a.ownerId)
194
+ });
195
+ }),
196
+ catch: x
197
+ });
198
+ }) });
199
+ };
200
+ //#endregion
201
+ export { _ as IapAccountsPort, v as captureIapAccountBindingService, w as dataStoreIapAccountOwners, C as dataStoreIapAccounts };