@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.
- package/CHANGELOG.md +24329 -0
- package/LICENSE +61 -0
- package/README.md +2719 -2
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +9189 -0
- package/dist/amount.js +21 -0
- package/dist/appleIap.server.js +263 -0
- package/dist/appleIapNotifications.server.js +163 -0
- package/dist/appleIapTrust.server.js +31 -0
- package/dist/bundleValue.server.js +113 -0
- package/dist/canonicalDataJson.js +11 -0
- package/dist/capture.js +54 -0
- package/dist/codePort.fixture.js +124 -0
- package/dist/codeStore.server.js +440 -0
- package/dist/codeTables.entity.js +44 -0
- package/dist/codes.d.ts +150 -0
- package/dist/codes.js +83 -0
- package/dist/components.d.ts +397 -0
- package/dist/components.js +284 -0
- package/dist/consistency.server.js +116 -0
- package/dist/containerStore.server.js +688 -0
- package/dist/containerStore.testFixtures.js +76 -0
- package/dist/containerTables.entity.js +46 -0
- package/dist/containers.d.ts +311 -0
- package/dist/containers.fixture.js +129 -0
- package/dist/containers.js +215 -0
- package/dist/containers.server.js +40 -0
- package/dist/containersPort.fixture.js +75 -0
- package/dist/currency.js +33 -0
- package/dist/declarationFixtures.js +62 -0
- package/dist/economy-app/events/economy.refundReversed.event.ts +1 -0
- package/dist/economy-app/events/economy.refunded.event.ts +1 -0
- package/dist/economyPort.fixture.js +125 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +22 -0
- package/dist/events.d.ts +46 -0
- package/dist/events.js +1 -0
- package/dist/googleIap.server.js +392 -0
- package/dist/googleIapNotifications.server.js +131 -0
- package/dist/googleIapSubscription.server.js +184 -0
- package/dist/iap.d.ts +309 -0
- package/dist/iap.js +304 -0
- package/dist/iap.testFixtures.js +145 -0
- package/dist/iapAccountTables.entity.js +40 -0
- package/dist/iapAccounts.server.js +201 -0
- package/dist/iapBinding.server.js +524 -0
- package/dist/iapCheckout.server.js +98 -0
- package/dist/iapCompletion.server.js +56 -0
- package/dist/iapCurrentState.server.js +108 -0
- package/dist/iapCurrentState.testFixtures.js +23 -0
- package/dist/iapCurrentStatePort.fixture.js +64 -0
- package/dist/iapEntitlements.d.ts +79 -0
- package/dist/iapEntitlements.js +58 -0
- package/dist/iapEntitlements.server.js +49 -0
- package/dist/iapEntitlementsBinding.server.js +106 -0
- package/dist/iapEntitlementsPort.fixture.js +81 -0
- package/dist/iapInstalledEntitlements.testFixtures.js +76 -0
- package/dist/iapInstalledNotifications.testFixtures.js +94 -0
- package/dist/iapInventoryRefunds.server.js +262 -0
- package/dist/iapNotificationCustodyPort.fixture.js +68 -0
- package/dist/iapNotificationPort.fixture.js +74 -0
- package/dist/iapNotificationProcessor.server.js +135 -0
- package/dist/iapNotificationProcessor.testFixtures.js +64 -0
- package/dist/iapNotificationProcessorPort.fixture.js +56 -0
- package/dist/iapNotificationSchedule.server.js +70 -0
- package/dist/iapNotificationStore.server.js +353 -0
- package/dist/iapNotificationStore.testFixtures.js +32 -0
- package/dist/iapNotificationTables.entity.js +49 -0
- package/dist/iapNotificationTarget.server.js +37 -0
- package/dist/iapNotificationTarget.testFixtures.js +30 -0
- package/dist/iapNotifications.d.ts +644 -0
- package/dist/iapNotifications.js +6 -0
- package/dist/iapNotifications.server.js +96 -0
- package/dist/iapNotificationsBinding.server.js +233 -0
- package/dist/iapNotificationsSystemPort.fixture.js +51 -0
- package/dist/iapPlugin.testFixtures.js +60 -0
- package/dist/iapPort.fixture.js +75 -0
- package/dist/iapProviderCapture.server.js +18 -0
- package/dist/iapRecovery.d.ts +146 -0
- package/dist/iapRecovery.js +90 -0
- package/dist/iapRefundEventDelivery.server.js +83 -0
- package/dist/iapRefundEvents.js +45 -0
- package/dist/iapRefundEventsPort.fixture.js +60 -0
- package/dist/iapRefundPort.fixture.js +55 -0
- package/dist/iapRefundTables.entity.js +56 -0
- package/dist/iapRefunds.d.ts +755 -0
- package/dist/iapRefunds.js +6 -0
- package/dist/iapRefunds.server.js +554 -0
- package/dist/iapRefunds.testFixtures.js +65 -0
- package/dist/iapSpendAdmission.server.js +99 -0
- package/dist/iapStore.server.js +741 -0
- package/dist/iapStore.testFixtures.js +128 -0
- package/dist/iapTables.entity.js +81 -0
- package/dist/iapVerification.server.js +121 -0
- package/dist/index.d.ts +2984 -0
- package/dist/index.js +15 -0
- package/dist/intentionRecovery.js +134 -0
- package/dist/ledger.js +78 -0
- package/dist/loyalty.d.ts +274 -0
- package/dist/loyalty.fixture.js +93 -0
- package/dist/loyalty.js +312 -0
- package/dist/loyaltyBinding.server.js +204 -0
- package/dist/loyaltyPort.fixture.js +87 -0
- package/dist/loyaltyShopSource.server.js +91 -0
- package/dist/loyaltyStore.server.js +553 -0
- package/dist/loyaltyStore.testFixtures.js +83 -0
- package/dist/loyaltyTables.entity.js +50 -0
- package/dist/offerMerchandising.fixture.js +115 -0
- package/dist/offerMerchandising.js +283 -0
- package/dist/offerPolicies.d.ts +441 -0
- package/dist/offerPolicies.fixture.js +54 -0
- package/dist/offerPolicies.js +268 -0
- package/dist/piggyBanks.d.ts +272 -0
- package/dist/piggyBanks.fixture.js +71 -0
- package/dist/piggyBanks.js +201 -0
- package/dist/piggyBinding.server.js +205 -0
- package/dist/piggyPort.fixture.js +73 -0
- package/dist/piggyStore.server.js +611 -0
- package/dist/piggyStore.testFixtures.js +98 -0
- package/dist/piggyTables.entity.js +41 -0
- package/dist/plugin.js +1048 -0
- package/dist/port.js +13 -0
- package/dist/price.d.ts +99 -0
- package/dist/price.js +144 -0
- package/dist/purchaseRecovery.js +56 -0
- package/dist/reports.server.js +115 -0
- package/dist/rpc.d.ts +3201 -0
- package/dist/rpc.js +1201 -0
- package/dist/sanctionsTradeAuthority.server.js +80 -0
- package/dist/server.d.ts +3856 -0
- package/dist/server.js +43 -0
- package/dist/shop.d.ts +279 -0
- package/dist/shop.js +196 -0
- package/dist/shop.testFixtures.js +114 -0
- package/dist/shopPort.fixture.js +72 -0
- package/dist/shopPresentation.js +68 -0
- package/dist/shopStore.server.js +624 -0
- package/dist/shopTables.entity.js +63 -0
- package/dist/shopUiCopy.js +57 -0
- package/dist/spendLimits.d.ts +140 -0
- package/dist/spendLimits.fixture.js +53 -0
- package/dist/spendLimits.js +155 -0
- package/dist/spendLimitsPort.fixture.js +120 -0
- package/dist/subscriptionBinding.server.js +211 -0
- package/dist/subscriptionStore.server.js +643 -0
- package/dist/subscriptionStore.testFixtures.js +72 -0
- package/dist/subscriptionTables.entity.js +37 -0
- package/dist/subscriptions.d.ts +394 -0
- package/dist/subscriptions.fixture.js +97 -0
- package/dist/subscriptions.js +213 -0
- package/dist/subscriptionsPort.fixture.js +76 -0
- package/dist/systemFixtures.js +89 -0
- package/dist/tables.entity.js +83 -0
- package/dist/testing.d.ts +4615 -0
- package/dist/testing.js +29 -0
- package/dist/tradeAuthority.server.js +172 -0
- package/dist/tradeBinding.server.js +201 -0
- package/dist/tradeComponents.js +342 -0
- package/dist/tradeEscrow.server.js +232 -0
- package/dist/tradeEscrow.testFixtures.js +133 -0
- package/dist/tradeExpiry.server.js +43 -0
- package/dist/tradeRecovery.js +48 -0
- package/dist/tradeStore.server.js +750 -0
- package/dist/tradeStore.testFixtures.js +73 -0
- package/dist/tradeTables.entity.js +66 -0
- package/dist/tradeUiCopy.js +79 -0
- package/dist/trades.d.ts +246 -0
- package/dist/trades.fixture.js +103 -0
- package/dist/trades.js +289 -0
- package/dist/tradesPort.fixture.js +94 -0
- package/dist/walletEscrow.js +106 -0
- package/dist/walletEscrow.server.js +376 -0
- package/dist/walletKeys.server.js +5 -0
- package/dist/walletStore.js +571 -0
- package/dist/web.d.ts +2257 -0
- package/dist/web.js +9 -0
- 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 };
|