@arcblock/payment-service 1.29.6 → 1.29.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -360,7 +360,6 @@ __export(env_exports, {
360
360
  creditConsumptionCronTime: () => creditConsumptionCronTime,
361
361
  creditLowBalanceThresholdPercentage: () => creditLowBalanceThresholdPercentage,
362
362
  creditQueueConcurrency: () => creditQueueConcurrency,
363
- daysUntilCancel: () => daysUntilCancel,
364
363
  default: () => env_default,
365
364
  depositVaultCronTime: () => depositVaultCronTime,
366
365
  enableDevFakeAuth: () => enableDevFakeAuth,
@@ -437,7 +436,7 @@ function hasConfig(key) {
437
436
  const v = readConfig(key);
438
437
  return v !== void 0 && v !== "";
439
438
  }
440
- var import_env, activeConfig, numConfig, paymentStatCronTime, subscriptionCronTime, notificationCronTime, expiredSessionCleanupCronTime, notificationCronConcurrency, stripeInvoiceCronTime, stripePaymentCronTime, stripeSubscriptionCronTime, revokeStakeCronTime, daysUntilCancel, meteringSubscriptionDetectionCronTime, overdueDetectionCronTime, overdueThreshold, depositVaultCronTime, creditConsumptionCronTime, vendorStatusCheckCronTime, vendorReturnScanCronTime, iapReconcileCronTime, eventRetryCronTime, quoteCleanupCronTime, vendorTimeoutMinutes, webhookAlertWindowMinutes, webhookAlertMinFailures, shortUrlApiKey, shortUrlDomain, sequelizeOptionsPoolMin, sequelizeOptionsPoolMax, sequelizeOptionsPoolIdle, updateDataConcurrency, stopAcceptingOrders, exchangeRateCacheTTLSeconds, systemMaxPendingAmount, allowChangeLockedPrice, blockletMode, isProduction, nodeEnv, isTestEnv, isDevelopmentEnv, enableDevFakeAuth, tenantModeRaw, blockletAppPid, blockletAppId, blockletAppName, blockletAppUrl, blockletAppHost, blockletAppDir, blockletPort, blockletMountPoints, appStoreWriteEnabled, appStoreSkipSignatureVerify, googlePubsubSkipSignatureVerify, googlePubsubPushServiceAccount, googlePubsubAllowUnverifiedSender, googlePlayWebhookUrl, stripeWebhookSecret, iapReconcileBatchSize, paymentBillingThreshold, paymentMinStakeAmount, paymentDaysUntilDue, paymentDaysUntilCancel, paymentReloadSubscriptionJobs, paymentRateVolatilityThreshold, paymentLivemode, creditLowBalanceThresholdPercentage, creditBatchSize, creditBatchWindowMs, creditQueueConcurrency, exchangeRateCacheTTLFromEnv, sqlLog, sqlBenchmark, cfEnv, isCfWorker, isBlockletServer, env_default;
439
+ var import_env, activeConfig, numConfig, TRUTHY, FALSY, parseBool, readConfigAny, paymentStatCronTime, subscriptionCronTime, notificationCronTime, expiredSessionCleanupCronTime, notificationCronConcurrency, stripeInvoiceCronTime, stripePaymentCronTime, stripeSubscriptionCronTime, revokeStakeCronTime, meteringSubscriptionDetectionCronTime, overdueDetectionCronTime, overdueThreshold, depositVaultCronTime, creditConsumptionCronTime, vendorStatusCheckCronTime, vendorReturnScanCronTime, iapReconcileCronTime, eventRetryCronTime, quoteCleanupCronTime, vendorTimeoutMinutes, webhookAlertWindowMinutes, webhookAlertMinFailures, shortUrlApiKey, shortUrlDomain, sequelizeOptionsPoolMin, sequelizeOptionsPoolMax, sequelizeOptionsPoolIdle, updateDataConcurrency, stopAcceptingOrders, exchangeRateCacheTTLSeconds, systemMaxPendingAmount, allowChangeLockedPrice, blockletMode, isProduction, nodeEnv, isTestEnv, isDevelopmentEnv, enableDevFakeAuth, tenantModeRaw, blockletAppPid, blockletAppId, blockletAppName, blockletAppUrl, blockletAppHost, blockletAppDir, blockletPort, blockletMountPoints, appStoreWriteEnabled, appStoreSkipSignatureVerify, googlePubsubSkipSignatureVerify, googlePubsubPushServiceAccount, googlePubsubAllowUnverifiedSender, googlePlayWebhookUrl, stripeWebhookSecret, iapReconcileBatchSize, paymentBillingThreshold, paymentMinStakeAmount, paymentDaysUntilDue, paymentDaysUntilCancel, paymentReloadSubscriptionJobs, paymentRateVolatilityThreshold, paymentLivemode, creditLowBalanceThresholdPercentage, creditBatchSize, creditBatchWindowMs, creditQueueConcurrency, exchangeRateCacheTTLFromEnv, sqlLog, sqlBenchmark, cfEnv, isCfWorker, isBlockletServer, env_default;
441
440
  var init_env = __esm({
442
441
  "../../blocklets/core/api/src/libs/env.ts"() {
443
442
  "use strict";
@@ -446,6 +445,22 @@ var init_env = __esm({
446
445
  const v = readConfig(key);
447
446
  return v ? +v : fallback;
448
447
  };
448
+ TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
449
+ FALSY = /* @__PURE__ */ new Set(["0", "false", "no", "off"]);
450
+ parseBool = (value, fallback = false) => {
451
+ if (value === void 0 || value === "") return fallback;
452
+ const v = value.trim().toLowerCase();
453
+ if (TRUTHY.has(v)) return true;
454
+ if (FALSY.has(v)) return false;
455
+ return fallback;
456
+ };
457
+ readConfigAny = (...keys) => {
458
+ for (const key of keys) {
459
+ const v = readConfig(key);
460
+ if (v !== void 0 && v !== "") return v;
461
+ }
462
+ return void 0;
463
+ };
449
464
  paymentStatCronTime = () => "0 1 0 * * *";
450
465
  subscriptionCronTime = () => readConfig("SUBSCRIPTION_CRON_TIME") || "0 */30 * * * *";
451
466
  notificationCronTime = () => readConfig("NOTIFICATION_CRON_TIME") || "0 5 */6 * * *";
@@ -455,7 +470,6 @@ var init_env = __esm({
455
470
  stripePaymentCronTime = () => readConfig("STRIPE_PAYMENT_CRON_TIME") || "0 */20 * * * *";
456
471
  stripeSubscriptionCronTime = () => readConfig("STRIPE_SUBSCRIPTION_CRON_TIME") || "0 10 */8 * * *";
457
472
  revokeStakeCronTime = () => readConfig("REVOKE_STAKE_CRON_TIME") || "0 */5 * * * *";
458
- daysUntilCancel = () => readConfig("DAYS_UNTIL_CANCEL");
459
473
  meteringSubscriptionDetectionCronTime = () => readConfig("METERING_SUBSCRIPTION_DETECTION_CRON_TIME") || "0 0 10 * * *";
460
474
  overdueDetectionCronTime = () => readConfig("OVERDUE_DETECTION_CRON_TIME") || "0 0 10 * * *";
461
475
  overdueThreshold = () => numConfig("OVERDUE_THRESHOLD", 5);
@@ -475,16 +489,19 @@ var init_env = __esm({
475
489
  sequelizeOptionsPoolMax = () => numConfig("SEQUELIZE_OPTIONS_POOL_MAX", 5);
476
490
  sequelizeOptionsPoolIdle = () => numConfig("SEQUELIZE_OPTIONS_POOL_IDLE", 10 * 1e3);
477
491
  updateDataConcurrency = () => numConfig("UPDATE_DATA_CONCURRENCY", 5);
478
- stopAcceptingOrders = () => readConfig("PAYMENT_KIT_STOP_ACCEPTING_ORDERS") === "true" || readConfig("PAYMENT_KIT_STOP_ACCEPTING_ORDERS") === "1";
492
+ stopAcceptingOrders = () => parseBool(readConfigAny("PAYMENT_STOP_ACCEPTING_ORDERS", "PAYMENT_KIT_STOP_ACCEPTING_ORDERS"));
479
493
  exchangeRateCacheTTLSeconds = () => numConfig("EXCHANGE_RATE_CACHE_TTL_SECONDS", 10 * 60);
480
- systemMaxPendingAmount = () => numConfig("PAYMENT_KIT_MAX_PENDING_AMOUNT", 5);
481
- allowChangeLockedPrice = () => readConfig("PAYMENT_CHANGE_LOCKED_PRICE") === "1";
494
+ systemMaxPendingAmount = () => {
495
+ const v = readConfigAny("PAYMENT_MAX_PENDING_AMOUNT", "PAYMENT_KIT_MAX_PENDING_AMOUNT");
496
+ return v ? +v : 5;
497
+ };
498
+ allowChangeLockedPrice = () => parseBool(readConfig("PAYMENT_CHANGE_LOCKED_PRICE"));
482
499
  blockletMode = () => readConfig("BLOCKLET_MODE");
483
500
  isProduction = () => blockletMode() === "production";
484
501
  nodeEnv = () => readConfig("NODE_ENV");
485
502
  isTestEnv = () => nodeEnv() === "test";
486
503
  isDevelopmentEnv = () => nodeEnv() === "development";
487
- enableDevFakeAuth = () => readConfig("ENABLE_DEV_FAKE_AUTH") === "1";
504
+ enableDevFakeAuth = () => parseBool(readConfig("ENABLE_DEV_FAKE_AUTH"));
488
505
  tenantModeRaw = () => readConfig("PAYMENT_TENANT_MODE");
489
506
  blockletAppPid = () => readConfig("BLOCKLET_APP_PID");
490
507
  blockletAppId = () => readConfig("BLOCKLET_APP_ID");
@@ -494,11 +511,11 @@ var init_env = __esm({
494
511
  blockletAppDir = () => readConfig("BLOCKLET_APP_DIR");
495
512
  blockletPort = () => readConfig("BLOCKLET_PORT");
496
513
  blockletMountPoints = () => readConfig("BLOCKLET_MOUNT_POINTS");
497
- appStoreWriteEnabled = () => readConfig("APP_STORE_WRITE_ENABLED") === "true";
498
- appStoreSkipSignatureVerify = () => readConfig("APP_STORE_SKIP_SIGNATURE_VERIFY") === "true";
499
- googlePubsubSkipSignatureVerify = () => readConfig("GOOGLE_PUBSUB_SKIP_SIGNATURE_VERIFY") === "true";
514
+ appStoreWriteEnabled = () => parseBool(readConfig("APP_STORE_WRITE_ENABLED"));
515
+ appStoreSkipSignatureVerify = () => parseBool(readConfig("APP_STORE_SKIP_SIGNATURE_VERIFY"));
516
+ googlePubsubSkipSignatureVerify = () => parseBool(readConfig("GOOGLE_PUBSUB_SKIP_SIGNATURE_VERIFY"));
500
517
  googlePubsubPushServiceAccount = () => readConfig("GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT");
501
- googlePubsubAllowUnverifiedSender = () => readConfig("GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER") === "true";
518
+ googlePubsubAllowUnverifiedSender = () => parseBool(readConfig("GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER"));
502
519
  googlePlayWebhookUrl = () => readConfig("GOOGLE_PLAY_WEBHOOK_URL");
503
520
  stripeWebhookSecret = () => readConfig("STRIPE_WEBHOOK_SECRET");
504
521
  iapReconcileBatchSize = () => Number(readConfig("IAP_RECONCILE_BATCH_SIZE") ?? "100");
@@ -506,19 +523,19 @@ var init_env = __esm({
506
523
  paymentMinStakeAmount = () => +readConfig("PAYMENT_MIN_STAKE_AMOUNT");
507
524
  paymentDaysUntilDue = () => readConfig("PAYMENT_DAYS_UNTIL_DUE");
508
525
  paymentDaysUntilCancel = () => readConfig("PAYMENT_DAYS_UNTIL_CANCEL");
509
- paymentReloadSubscriptionJobs = () => readConfig("PAYMENT_RELOAD_SUBSCRIPTION_JOBS") === "1";
526
+ paymentReloadSubscriptionJobs = () => parseBool(readConfig("PAYMENT_RELOAD_SUBSCRIPTION_JOBS"));
510
527
  paymentRateVolatilityThreshold = () => readConfig("PAYMENT_RATE_VOLATILITY_THRESHOLD");
511
- paymentLivemode = () => readConfig("PAYMENT_LIVEMODE") !== "false";
528
+ paymentLivemode = () => parseBool(readConfig("PAYMENT_LIVEMODE"), true);
512
529
  creditLowBalanceThresholdPercentage = () => parseInt(readConfig("CREDIT_LOW_BALANCE_THRESHOLD_PERCENTAGE") || "10", 10);
513
530
  creditBatchSize = () => Math.max(1, parseInt(readConfig("CREDIT_BATCH_SIZE") || "50", 10));
514
531
  creditBatchWindowMs = () => Math.max(10, parseInt(readConfig("CREDIT_BATCH_WINDOW_MS") || "3000", 10));
515
532
  creditQueueConcurrency = () => Math.max(1, Math.min(20, parseInt(readConfig("CREDIT_QUEUE_CONCURRENCY") || "5", 10) || 5));
516
533
  exchangeRateCacheTTLFromEnv = () => hasConfig("EXCHANGE_RATE_CACHE_TTL_SECONDS");
517
- sqlLog = () => readConfig("SQL_LOG") === "1";
518
- sqlBenchmark = () => readConfig("SQL_BENCHMARK") === "1";
534
+ sqlLog = () => parseBool(readConfig("SQL_LOG"));
535
+ sqlBenchmark = () => parseBool(readConfig("SQL_BENCHMARK"));
519
536
  cfEnv = () => globalThis.__CF_ENV__;
520
537
  isCfWorker = () => !!cfEnv();
521
- isBlockletServer = () => hasConfig("BLOCKLET_APP_ID");
538
+ isBlockletServer = () => hasConfig("BLOCKLET_DID");
522
539
  env_default = {
523
540
  ...import_env.env
524
541
  };
@@ -9140,17 +9157,230 @@ var init_apple_root_certs = __esm({
9140
9157
  }
9141
9158
  });
9142
9159
 
9143
- // ../../blocklets/core/api/src/integrations/app-store/signed-data-verifier.ts
9144
- async function getVerifier(bundleId, environment) {
9145
- const key = `${bundleId}:${environment}`;
9146
- const cached = verifierCache.get(key);
9147
- if (cached) return cached;
9148
- const mod = await import("@apple/app-store-server-library");
9149
- const env12 = environment === "production" ? mod.Environment.PRODUCTION : mod.Environment.SANDBOX;
9150
- const verifier = new mod.SignedDataVerifier(APPLE_ROOT_CERTS, false, env12, bundleId);
9151
- verifierCache.set(key, verifier);
9152
- return verifier;
9160
+ // ../../blocklets/core/api/src/integrations/app-store/native-asn1.ts
9161
+ function oidToDerTag(oid) {
9162
+ const parts = oid.split(".").map((p) => Number.parseInt(p, 10));
9163
+ if (parts.length < 2 || parts.some((n) => !Number.isInteger(n) || n < 0)) {
9164
+ throw new Error(`oidToDerTag: invalid OID "${oid}"`);
9165
+ }
9166
+ const content = [40 * parts[0] + parts[1]];
9167
+ for (let i = 2; i < parts.length; i++) {
9168
+ let v = parts[i];
9169
+ const sevenBitGroups = [v & 127];
9170
+ v = Math.floor(v / 128);
9171
+ while (v > 0) {
9172
+ sevenBitGroups.unshift(v & 127 | 128);
9173
+ v = Math.floor(v / 128);
9174
+ }
9175
+ content.push(...sevenBitGroups);
9176
+ }
9177
+ if (content.length > 127) {
9178
+ throw new Error(`oidToDerTag: OID "${oid}" content too long for single-byte length`);
9179
+ }
9180
+ return Buffer.from([6, content.length, ...content]);
9181
+ }
9182
+ function certHasOid(cert, oid) {
9183
+ return Buffer.from(cert.raw).includes(oidToDerTag(oid));
9184
+ }
9185
+ var init_native_asn1 = __esm({
9186
+ "../../blocklets/core/api/src/integrations/app-store/native-asn1.ts"() {
9187
+ "use strict";
9188
+ }
9189
+ });
9190
+
9191
+ // ../../blocklets/core/api/src/integrations/app-store/native-jws.ts
9192
+ function base64UrlToJson(segment, code) {
9193
+ let text;
9194
+ try {
9195
+ text = Buffer.from(segment, "base64url").toString("utf8");
9196
+ } catch {
9197
+ throw new AppleJwsVerifyError(code, "segment is not valid base64url");
9198
+ }
9199
+ let parsed;
9200
+ try {
9201
+ parsed = JSON.parse(text);
9202
+ } catch {
9203
+ throw new AppleJwsVerifyError(code, "segment is not valid JSON");
9204
+ }
9205
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
9206
+ throw new AppleJwsVerifyError(code, "segment is not a JSON object");
9207
+ }
9208
+ const out = parsed;
9209
+ for (const key of ["__proto__", "constructor", "prototype"]) {
9210
+ if (Object.prototype.hasOwnProperty.call(out, key)) {
9211
+ delete out[key];
9212
+ }
9213
+ }
9214
+ return out;
9215
+ }
9216
+ function buildCert(b64, label) {
9217
+ try {
9218
+ return new import_node_crypto.X509Certificate(Buffer.from(b64, "base64"));
9219
+ } catch {
9220
+ throw new AppleJwsVerifyError("MALFORMED_CERT", `${label} cert is not valid DER`);
9221
+ }
9222
+ }
9223
+ function assertCertValid(cert, effectiveDate, label) {
9224
+ const validFrom = Date.parse(cert.validFrom);
9225
+ const validTo = Date.parse(cert.validTo);
9226
+ if (Number.isNaN(validFrom) || Number.isNaN(validTo)) {
9227
+ throw new AppleJwsVerifyError("CERT_VALIDITY", `${label} cert has unparseable validity dates`);
9228
+ }
9229
+ if (validFrom > effectiveDate + MAX_SKEW_MS) {
9230
+ throw new AppleJwsVerifyError("CERT_NOT_YET_VALID", `${label} cert not valid until after signedDate`);
9231
+ }
9232
+ if (validTo < effectiveDate - MAX_SKEW_MS) {
9233
+ throw new AppleJwsVerifyError("CERT_EXPIRED", `${label} cert expired before signedDate`);
9234
+ }
9235
+ }
9236
+ async function verifyAppleJws(jws, opts = {}) {
9237
+ const trustedRoots = opts.trustedRoots ?? APPLE_ROOT_CERTS;
9238
+ const parts = jws.split(".");
9239
+ if (parts.length !== 3) {
9240
+ throw new AppleJwsVerifyError("FORMAT", "expected 3 JWS segments");
9241
+ }
9242
+ const [headerSeg, payloadSeg, signatureSeg] = parts;
9243
+ const header = base64UrlToJson(headerSeg, "HEADER");
9244
+ if (header.alg !== "ES256") {
9245
+ throw new AppleJwsVerifyError("ALG", "only ES256 is accepted");
9246
+ }
9247
+ if (!Array.isArray(header.x5c) || header.x5c.length !== 3) {
9248
+ throw new AppleJwsVerifyError("INVALID_CHAIN_LENGTH", "x5c must contain exactly 3 certs");
9249
+ }
9250
+ const leaf = buildCert(header.x5c[0], "leaf");
9251
+ const intermediate = buildCert(header.x5c[1], "intermediate");
9252
+ const rootFromJws = buildCert(header.x5c[2], "root");
9253
+ const rootDer = Buffer.from(rootFromJws.raw);
9254
+ const anchored = trustedRoots.some((trusted) => trusted.equals(rootDer));
9255
+ if (!anchored) {
9256
+ throw new AppleJwsVerifyError("ANCHOR", "presented root is not a trusted Apple root");
9257
+ }
9258
+ if (!intermediate.verify(rootFromJws.publicKey) || intermediate.issuer !== rootFromJws.subject) {
9259
+ throw new AppleJwsVerifyError("CHAIN", "intermediate does not chain to root");
9260
+ }
9261
+ if (!leaf.verify(intermediate.publicKey) || leaf.issuer !== intermediate.subject) {
9262
+ throw new AppleJwsVerifyError("CHAIN", "leaf does not chain to intermediate");
9263
+ }
9264
+ if (intermediate.ca !== true || !certHasOid(intermediate, APPLE_INTERMEDIATE_OID)) {
9265
+ throw new AppleJwsVerifyError("OID", "intermediate missing CA flag or Apple OID");
9266
+ }
9267
+ if (!certHasOid(leaf, APPLE_LEAF_OID)) {
9268
+ throw new AppleJwsVerifyError("OID", "leaf missing Apple OID");
9269
+ }
9270
+ const payload = base64UrlToJson(payloadSeg, "PAYLOAD");
9271
+ const effectiveDate = opts.signedDate ?? (typeof payload.signedDate === "number" ? payload.signedDate : void 0);
9272
+ if (typeof effectiveDate !== "number") {
9273
+ throw new AppleJwsVerifyError("NO_SIGNED_DATE", "payload has no numeric signedDate to validate against");
9274
+ }
9275
+ assertCertValid(leaf, effectiveDate, "leaf");
9276
+ assertCertValid(intermediate, effectiveDate, "intermediate");
9277
+ assertCertValid(rootFromJws, effectiveDate, "root");
9278
+ const spki = leaf.publicKey.export({ type: "spki", format: "der" });
9279
+ const key = await crypto.subtle.importKey(
9280
+ "spki",
9281
+ spki,
9282
+ { name: "ECDSA", namedCurve: "P-256" },
9283
+ false,
9284
+ ["verify"]
9285
+ );
9286
+ const signature = Buffer.from(signatureSeg, "base64url");
9287
+ const signingInput = Buffer.from(`${headerSeg}.${payloadSeg}`, "ascii");
9288
+ const valid = await crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" }, key, signature, signingInput);
9289
+ if (!valid) {
9290
+ throw new AppleJwsVerifyError("SIGNATURE", "ES256 signature does not verify against the leaf key");
9291
+ }
9292
+ return payload;
9293
+ }
9294
+ var import_node_crypto, APPLE_LEAF_OID, APPLE_INTERMEDIATE_OID, MAX_SKEW_MS, AppleJwsVerifyError;
9295
+ var init_native_jws = __esm({
9296
+ "../../blocklets/core/api/src/integrations/app-store/native-jws.ts"() {
9297
+ "use strict";
9298
+ import_node_crypto = require("node:crypto");
9299
+ init_apple_root_certs();
9300
+ init_native_asn1();
9301
+ APPLE_LEAF_OID = "1.2.840.113635.100.6.11.1";
9302
+ APPLE_INTERMEDIATE_OID = "1.2.840.113635.100.6.2.1";
9303
+ MAX_SKEW_MS = 6e4;
9304
+ AppleJwsVerifyError = class extends Error {
9305
+ constructor(code, detail) {
9306
+ super(detail ? `AppStore JWS verify failed [${code}]: ${detail}` : `AppStore JWS verify failed [${code}]`);
9307
+ this.name = "AppleJwsVerifyError";
9308
+ this.code = code;
9309
+ }
9310
+ };
9311
+ }
9312
+ });
9313
+
9314
+ // ../../blocklets/core/api/src/integrations/app-store/native-api.ts
9315
+ function pemToDer(pem) {
9316
+ const body = pem.replace(/-----BEGIN [^-]+-----/g, "").replace(/-----END [^-]+-----/g, "").replace(/\s+/g, "");
9317
+ if (!body) {
9318
+ throw new Error("AppStore API: private_key_pem is empty or not PEM-armored");
9319
+ }
9320
+ return Buffer.from(body, "base64");
9321
+ }
9322
+ async function signBearerJwt(creds) {
9323
+ const header = { alg: "ES256", kid: creds.keyId, typ: "JWT" };
9324
+ const iat = Math.floor(Date.now() / 1e3);
9325
+ const payload = {
9326
+ iss: creds.issuerId,
9327
+ iat,
9328
+ exp: iat + JWT_TTL_SECONDS,
9329
+ aud: "appstoreconnect-v1",
9330
+ bid: creds.bundleId
9331
+ };
9332
+ const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
9333
+ const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
9334
+ const signingInput = `${headerB64}.${payloadB64}`;
9335
+ let key;
9336
+ try {
9337
+ key = await crypto.subtle.importKey(
9338
+ "pkcs8",
9339
+ pemToDer(creds.privateKeyPem),
9340
+ { name: "ECDSA", namedCurve: "P-256" },
9341
+ false,
9342
+ ["sign"]
9343
+ );
9344
+ } catch {
9345
+ throw new Error("AppStore API: private_key_pem is not a valid EC P-256 PKCS#8 key");
9346
+ }
9347
+ const signature = await crypto.subtle.sign(
9348
+ { name: "ECDSA", hash: "SHA-256" },
9349
+ key,
9350
+ Buffer.from(signingInput, "ascii")
9351
+ );
9352
+ return `${signingInput}.${Buffer.from(signature).toString("base64url")}`;
9353
+ }
9354
+ async function nativeGetAllSubscriptionStatuses(originalTransactionId, creds) {
9355
+ if (!creds.issuerId || !creds.keyId || !creds.privateKeyPem) {
9356
+ throw new Error("AppStore API: issuer_id/key_id/private_key_pem are required");
9357
+ }
9358
+ const host = creds.environment === "production" ? API_HOST_PRODUCTION : API_HOST_SANDBOX;
9359
+ const url = `${host}/inApps/v1/subscriptions/${encodeURIComponent(originalTransactionId)}`;
9360
+ const jwt = await signBearerJwt(creds);
9361
+ const response = await fetch(url, {
9362
+ method: "GET",
9363
+ headers: { Authorization: `Bearer ${jwt}`, Accept: "application/json" }
9364
+ });
9365
+ if (response.status === 404) {
9366
+ return { data: [] };
9367
+ }
9368
+ if (!response.ok) {
9369
+ throw new Error(`AppStore API: getAllSubscriptionStatuses failed with HTTP ${response.status}`);
9370
+ }
9371
+ return await response.json();
9153
9372
  }
9373
+ var API_HOST_PRODUCTION, API_HOST_SANDBOX, JWT_TTL_SECONDS;
9374
+ var init_native_api = __esm({
9375
+ "../../blocklets/core/api/src/integrations/app-store/native-api.ts"() {
9376
+ "use strict";
9377
+ API_HOST_PRODUCTION = "https://api.storekit.apple.com";
9378
+ API_HOST_SANDBOX = "https://api.storekit-sandbox.apple.com";
9379
+ JWT_TTL_SECONDS = 300;
9380
+ }
9381
+ });
9382
+
9383
+ // ../../blocklets/core/api/src/integrations/app-store/signed-data-verifier.ts
9154
9384
  function isSignatureVerificationSkipped() {
9155
9385
  if (!appStoreSkipSignatureVerify()) return false;
9156
9386
  if (isProduction()) {
@@ -9161,21 +9391,19 @@ function isSignatureVerificationSkipped() {
9161
9391
  }
9162
9392
  return true;
9163
9393
  }
9164
- async function verifySignedTransaction(signedTransaction, bundleId, environment) {
9394
+ async function verifySignedTransaction(signedTransaction, _bundleId, _environment) {
9165
9395
  if (isSignatureVerificationSkipped()) {
9166
9396
  logger_default.warn("app_store: signature verification skipped via APP_STORE_SKIP_SIGNATURE_VERIFY");
9167
9397
  return decodeUnsafe(signedTransaction);
9168
9398
  }
9169
- const verifier = await getVerifier(bundleId, environment);
9170
- return verifier.verifyAndDecodeTransaction(signedTransaction);
9399
+ return verifyAppleJws(signedTransaction);
9171
9400
  }
9172
- async function verifySignedNotification(signedPayload, bundleId, environment) {
9401
+ async function verifySignedNotification(signedPayload, _bundleId, _environment) {
9173
9402
  if (isSignatureVerificationSkipped()) {
9174
9403
  logger_default.warn("app_store: signature verification skipped via APP_STORE_SKIP_SIGNATURE_VERIFY");
9175
9404
  return decodeUnsafe(signedPayload);
9176
9405
  }
9177
- const verifier = await getVerifier(bundleId, environment);
9178
- return verifier.verifyAndDecodeNotification(signedPayload);
9406
+ return verifyAppleJws(signedPayload);
9179
9407
  }
9180
9408
  function decodeUnsafe(jws) {
9181
9409
  const parts = jws.split(".");
@@ -9188,41 +9416,85 @@ function decodeUnsafe(jws) {
9188
9416
  throw new Error(`AppStore JWS payload not valid JSON: ${err.message}`);
9189
9417
  }
9190
9418
  }
9191
- async function getApiClient(creds) {
9192
- const key = `${creds.bundleId}:${creds.environment}:${creds.keyId}`;
9193
- const cached = apiClientCache.get(key);
9194
- if (cached) return cached;
9195
- const mod = await import("@apple/app-store-server-library");
9196
- const env12 = creds.environment === "production" ? mod.Environment.PRODUCTION : mod.Environment.SANDBOX;
9197
- const client = new mod.AppStoreServerAPIClient(creds.privateKeyPem, creds.keyId, creds.issuerId, creds.bundleId, env12);
9198
- apiClientCache.set(key, client);
9199
- return client;
9200
- }
9201
9419
  async function getAllSubscriptionStatuses(originalTransactionId, creds) {
9202
- const client = await getApiClient(creds);
9203
- return client.getAllSubscriptionStatuses(originalTransactionId);
9420
+ return nativeGetAllSubscriptionStatuses(originalTransactionId, creds);
9204
9421
  }
9205
- var verifierCache, apiClientCache;
9206
9422
  var init_signed_data_verifier = __esm({
9207
9423
  "../../blocklets/core/api/src/integrations/app-store/signed-data-verifier.ts"() {
9208
9424
  "use strict";
9209
9425
  init_logger();
9210
9426
  init_env();
9211
- init_apple_root_certs();
9212
- verifierCache = /* @__PURE__ */ new Map();
9213
- apiClientCache = /* @__PURE__ */ new Map();
9427
+ init_native_jws();
9428
+ init_native_api();
9429
+ }
9430
+ });
9431
+
9432
+ // ../../blocklets/core/api/src/integrations/app-store/native-receipt.ts
9433
+ function toMs(v) {
9434
+ if (v === void 0 || v === null || v === "") return void 0;
9435
+ const n = Number(v);
9436
+ return Number.isNaN(n) ? void 0 : n;
9437
+ }
9438
+ async function postReceipt(host, body) {
9439
+ const response = await fetch(host, {
9440
+ method: "POST",
9441
+ headers: { "Content-Type": "application/json" },
9442
+ body: JSON.stringify(body)
9443
+ });
9444
+ if (!response.ok) {
9445
+ throw new Error(`AppStore verifyReceipt: HTTP ${response.status}`);
9446
+ }
9447
+ return await response.json();
9448
+ }
9449
+ async function verifyAppleReceiptNative({
9450
+ receipt,
9451
+ sharedSecret
9452
+ }) {
9453
+ const body = {
9454
+ "receipt-data": receipt,
9455
+ password: sharedSecret,
9456
+ // Deliberate, result-equivalent: client.ts reduces to the latest-expiry item
9457
+ // anyway, so trimming old transactions here changes nothing downstream.
9458
+ "exclude-old-transactions": true
9459
+ };
9460
+ let data = await postReceipt(PRODUCTION_HOST, body);
9461
+ if (RETRY_SANDBOX_STATUSES.has(data.status)) {
9462
+ data = await postReceipt(SANDBOX_HOST, body);
9463
+ }
9464
+ if (data.status !== 0) {
9465
+ throw new Error(`AppStore verifyReceipt: Apple returned status ${data.status}`);
9466
+ }
9467
+ const rawItems = data.latest_receipt_info ?? data.receipt?.in_app ?? [];
9468
+ const bundleId = data.receipt?.bundle_id;
9469
+ return rawItems.map((item) => ({
9470
+ productId: item.product_id ?? "",
9471
+ transactionId: item.transaction_id ?? "",
9472
+ originalTransactionId: item.original_transaction_id,
9473
+ purchaseDate: toMs(item.purchase_date_ms),
9474
+ expirationDate: toMs(item.expires_date_ms),
9475
+ bundleId,
9476
+ webOrderLineItemId: item.web_order_line_item_id
9477
+ }));
9478
+ }
9479
+ var PRODUCTION_HOST, SANDBOX_HOST, RETRY_SANDBOX_STATUSES;
9480
+ var init_native_receipt = __esm({
9481
+ "../../blocklets/core/api/src/integrations/app-store/native-receipt.ts"() {
9482
+ "use strict";
9483
+ PRODUCTION_HOST = "https://buy.itunes.apple.com/verifyReceipt";
9484
+ SANDBOX_HOST = "https://sandbox.itunes.apple.com/verifyReceipt";
9485
+ RETRY_SANDBOX_STATUSES = /* @__PURE__ */ new Set([21007, 21002]);
9214
9486
  }
9215
9487
  });
9216
9488
 
9217
9489
  // ../../blocklets/core/api/src/integrations/app-store/client.ts
9218
- var import_node_apple_receipt_verify, AppStoreClient;
9490
+ var AppStoreClient;
9219
9491
  var init_client = __esm({
9220
9492
  "../../blocklets/core/api/src/integrations/app-store/client.ts"() {
9221
9493
  "use strict";
9222
- import_node_apple_receipt_verify = require("node-apple-receipt-verify");
9223
9494
  init_env();
9224
9495
  init_logger();
9225
9496
  init_signed_data_verifier();
9497
+ init_native_receipt();
9226
9498
  AppStoreClient = class _AppStoreClient {
9227
9499
  constructor(settings, environment) {
9228
9500
  this.bundleId = settings.bundle_id;
@@ -9327,9 +9599,9 @@ var init_client = __esm({
9327
9599
  /**
9328
9600
  * Verify a StoreKit 1 (legacy) base64 receipt via Apple's verifyReceipt endpoint.
9329
9601
  *
9330
- * Calls `node-apple-receipt-verify` which POSTs to
9602
+ * Calls the native `verifyAppleReceiptNative` which POSTs to
9331
9603
  * https://buy.itunes.apple.com/verifyReceipt (production)
9332
- * https://sandbox.itunes.apple.com/verifyReceipt (sandbox, auto fallback)
9604
+ * https://sandbox.itunes.apple.com/verifyReceipt (sandbox, auto fallback on 21007/21002)
9333
9605
  *
9334
9606
  * Returns a payload shaped like a StoreKit 2 transaction so downstream code
9335
9607
  * (ingestVerifiedAppStorePurchase) can stay agnostic. Note: legacy receipts
@@ -9343,12 +9615,7 @@ var init_client = __esm({
9343
9615
  if (!this.sharedSecret) {
9344
9616
  throw new Error("AppStoreClient: shared_secret is required for legacy receipt verification");
9345
9617
  }
9346
- (0, import_node_apple_receipt_verify.config)({
9347
- secret: this.sharedSecret,
9348
- verbose: false,
9349
- environment: ["production", "sandbox"]
9350
- });
9351
- const items = await (0, import_node_apple_receipt_verify.validate)({ receipt });
9618
+ const items = await verifyAppleReceiptNative({ receipt, sharedSecret: this.sharedSecret });
9352
9619
  const filtered = options.expectedProductIds ? items.filter((i) => options.expectedProductIds.includes(i.productId)) : items;
9353
9620
  if (!filtered.length) {
9354
9621
  throw new Error("AppStoreClient: verifyReceipt returned no matching purchases");
@@ -33564,10 +33831,10 @@ async function handleSubscriptionOnPaymentFailure(subscription, eventType, clien
33564
33831
  const { interval } = subscription.pending_invoice_item_interval;
33565
33832
  const dueUnit = getDueUnit(interval);
33566
33833
  const cancelUpdates = {};
33567
- const daysUntilCancel2 = getDaysUntilCancel(subscription);
33834
+ const daysUntilCancel = getDaysUntilCancel(subscription);
33568
33835
  const cancelSubscription = shouldCancelSubscription(subscription);
33569
- if (daysUntilCancel2 > 0) {
33570
- cancelUpdates.cancel_at = subscription.current_period_start + daysUntilCancel2 * dueUnit;
33836
+ if (daysUntilCancel > 0) {
33837
+ cancelUpdates.cancel_at = subscription.current_period_start + daysUntilCancel * dueUnit;
33571
33838
  } else {
33572
33839
  cancelUpdates.cancel_at_period_end = true;
33573
33840
  }
@@ -40506,10 +40773,10 @@ var init_payment2 = __esm({
40506
40773
  return updates.terminate;
40507
40774
  }
40508
40775
  const dueUnit = getDueUnit(interval);
40509
- const daysUntilCancel2 = getDaysUntilCancel(subscription);
40776
+ const daysUntilCancel = getDaysUntilCancel(subscription);
40510
40777
  const cancelUpdates = {};
40511
- if (daysUntilCancel2 > 0) {
40512
- cancelUpdates.cancel_at = subscription.current_period_start + daysUntilCancel2 * dueUnit;
40778
+ if (daysUntilCancel > 0) {
40779
+ cancelUpdates.cancel_at = subscription.current_period_start + daysUntilCancel * dueUnit;
40513
40780
  } else {
40514
40781
  cancelUpdates.cancel_at_period_end = true;
40515
40782
  }
@@ -48902,7 +49169,7 @@ var init_subscriptions = __esm({
48902
49169
  description,
48903
49170
  metadata = {},
48904
49171
  days_until_due: daysUntilDue,
48905
- days_until_cancel: daysUntilCancel2,
49172
+ days_until_cancel: daysUntilCancel,
48906
49173
  billing_cycle_anchor: billingCycleAnchor,
48907
49174
  collection_method: collectionMethod,
48908
49175
  proration_behavior: prorationBehavior,
@@ -48994,7 +49261,7 @@ var init_subscriptions = __esm({
48994
49261
  proration_behavior: prorationBehavior,
48995
49262
  payment_behavior: "default_incomplete",
48996
49263
  days_until_due: daysUntilDue,
48997
- days_until_cancel: daysUntilCancel2,
49264
+ days_until_cancel: daysUntilCancel,
48998
49265
  metadata: formatMetadata(metadata),
48999
49266
  service_actions: serviceActions || []
49000
49267
  });
@@ -51456,10 +51723,10 @@ var init_subscriptions = __esm({
51456
51723
  updates.default_payment_method_id = arcblockMethod.id;
51457
51724
  if (body.recalculate_cancel_at !== false) {
51458
51725
  if (subscription.cancelation_details && !subscription.cancel_at) {
51459
- const daysUntilCancel2 = subscription.days_until_cancel || 0;
51460
- if (daysUntilCancel2 > 0) {
51726
+ const daysUntilCancel = subscription.days_until_cancel || 0;
51727
+ if (daysUntilCancel > 0) {
51461
51728
  const dueUnit = 24 * 60 * 60;
51462
- updates.cancel_at = subscription.current_period_start + daysUntilCancel2 * dueUnit;
51729
+ updates.cancel_at = subscription.current_period_start + daysUntilCancel * dueUnit;
51463
51730
  } else {
51464
51731
  updates.cancel_at_period_end = true;
51465
51732
  updates.cancel_at = subscription.current_period_end;
@@ -71452,9 +71719,9 @@ async function handlePostConsumptionEvents(context2, params) {
71452
71719
  const cancelUpdates = {};
71453
71720
  const { interval } = context2.subscription.pending_invoice_item_interval;
71454
71721
  const dueUnit = getDueUnit(interval);
71455
- const daysUntilCancel2 = getDaysUntilCancel(context2.subscription);
71456
- if (daysUntilCancel2 > 0) {
71457
- cancelUpdates.cancel_at = context2.subscription.current_period_start + daysUntilCancel2 * dueUnit;
71722
+ const daysUntilCancel = getDaysUntilCancel(context2.subscription);
71723
+ if (daysUntilCancel > 0) {
71724
+ cancelUpdates.cancel_at = context2.subscription.current_period_start + daysUntilCancel * dueUnit;
71458
71725
  } else {
71459
71726
  cancelUpdates.cancel_at_period_end = true;
71460
71727
  }
@@ -77671,9 +77938,52 @@ var init_service2 = __esm({
77671
77938
  }
77672
77939
  });
77673
77940
 
77941
+ // ../../blocklets/core/api/src/host-glue.ts
77942
+ var host_glue_exports = {};
77943
+ __export(host_glue_exports, {
77944
+ PAYMENT_PREFIX: () => PAYMENT_PREFIX,
77945
+ USER_HEADERS: () => USER_HEADERS,
77946
+ createTenantProvisioner: () => createTenantProvisioner,
77947
+ injectCaller: () => injectCaller
77948
+ });
77949
+ function injectCaller(headers, caller) {
77950
+ for (const h of USER_HEADERS) headers.delete(h);
77951
+ if (!caller) return;
77952
+ const canonicalDid2 = caller.did?.startsWith("did:abt:") ? caller.did : `did:abt:${caller.did}`;
77953
+ headers.set("x-user-did", canonicalDid2);
77954
+ headers.set("x-user-role", caller.role || "guest");
77955
+ headers.set("x-user-provider", caller.authMethod === "access-key" ? "access-key" : caller.authMethod || "wallet");
77956
+ headers.set("x-user-fullname", encodeURIComponent(caller.displayName || ""));
77957
+ headers.set("x-user-wallet-os", "");
77958
+ }
77959
+ function createTenantProvisioner(provision) {
77960
+ const inflight = /* @__PURE__ */ new Map();
77961
+ return (instanceDid) => {
77962
+ if (!instanceDid) return Promise.resolve();
77963
+ let p = inflight.get(instanceDid);
77964
+ if (!p) {
77965
+ p = provision(instanceDid).catch((err) => {
77966
+ inflight.delete(instanceDid);
77967
+ throw err;
77968
+ });
77969
+ inflight.set(instanceDid, p);
77970
+ }
77971
+ return p;
77972
+ };
77973
+ }
77974
+ var PAYMENT_PREFIX, USER_HEADERS;
77975
+ var init_host_glue = __esm({
77976
+ "../../blocklets/core/api/src/host-glue.ts"() {
77977
+ "use strict";
77978
+ PAYMENT_PREFIX = "/.well-known/payment";
77979
+ USER_HEADERS = ["x-user-did", "x-user-role", "x-user-provider", "x-user-fullname", "x-user-wallet-os"];
77980
+ }
77981
+ });
77982
+
77674
77983
  // src/index.ts
77675
77984
  var src_exports = {};
77676
77985
  __export(src_exports, {
77986
+ PAYMENT_PREFIX: () => PAYMENT_PREFIX2,
77677
77987
  applyPaymentCoreMigrations: () => applyPaymentCoreMigrations2,
77678
77988
  applySqlMigrations: () => applySqlMigrations2,
77679
77989
  createAuthStorage: () => createAuthStorage2,
@@ -77689,11 +77999,13 @@ __export(src_exports, {
77689
77999
  createNodeDbDriver: () => createNodeDbDriver2,
77690
78000
  createNodeDidConnectRuntime: () => createNodeDidConnectRuntime2,
77691
78001
  createNodeStaticHandler: () => createNodeStaticHandler2,
78002
+ createTenantProvisioner: () => createTenantProvisioner2,
77692
78003
  getCronDriver: () => getCronDriver2,
77693
78004
  getIdentityDriver: () => getIdentityDriver2,
77694
78005
  getPaymentCoreSqlMigrations: () => getPaymentCoreSqlMigrations,
77695
78006
  getQueueHostHooks: () => getQueueHostHooks2,
77696
78007
  getSecretsDriver: () => getSecretsDriver2,
78008
+ injectCaller: () => injectCaller2,
77697
78009
  matchesCron: () => matchesCron2,
77698
78010
  nodeQueueHostHooks: () => nodeQueueHostHooks2,
77699
78011
  scopedLockName: () => scopedLockName2,
@@ -77800,8 +78112,16 @@ function createEmbeddedPaymentService2(slots) {
77800
78112
  const core = (init_service2(), __toCommonJS(service_exports));
77801
78113
  return core.createEmbeddedPaymentService(slots);
77802
78114
  }
78115
+ var PAYMENT_PREFIX2 = "/.well-known/payment";
78116
+ function injectCaller2(headers, caller) {
78117
+ return (init_host_glue(), __toCommonJS(host_glue_exports)).injectCaller(headers, caller);
78118
+ }
78119
+ function createTenantProvisioner2(provision) {
78120
+ return (init_host_glue(), __toCommonJS(host_glue_exports)).createTenantProvisioner(provision);
78121
+ }
77803
78122
  // Annotate the CommonJS export names for ESM import in node:
77804
78123
  0 && (module.exports = {
78124
+ PAYMENT_PREFIX,
77805
78125
  applyPaymentCoreMigrations,
77806
78126
  applySqlMigrations,
77807
78127
  createAuthStorage,
@@ -77817,11 +78137,13 @@ function createEmbeddedPaymentService2(slots) {
77817
78137
  createNodeDbDriver,
77818
78138
  createNodeDidConnectRuntime,
77819
78139
  createNodeStaticHandler,
78140
+ createTenantProvisioner,
77820
78141
  getCronDriver,
77821
78142
  getIdentityDriver,
77822
78143
  getPaymentCoreSqlMigrations,
77823
78144
  getQueueHostHooks,
77824
78145
  getSecretsDriver,
78146
+ injectCaller,
77825
78147
  matchesCron,
77826
78148
  nodeQueueHostHooks,
77827
78149
  scopedLockName,