@arcblock/payment-service 1.29.5 → 1.29.7

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 (3) hide show
  1. package/dist/cf.js +67 -217
  2. package/dist/index.js +292 -42
  3. package/package.json +3 -5
package/dist/index.js CHANGED
@@ -9140,17 +9140,230 @@ var init_apple_root_certs = __esm({
9140
9140
  }
9141
9141
  });
9142
9142
 
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;
9143
+ // ../../blocklets/core/api/src/integrations/app-store/native-asn1.ts
9144
+ function oidToDerTag(oid) {
9145
+ const parts = oid.split(".").map((p) => Number.parseInt(p, 10));
9146
+ if (parts.length < 2 || parts.some((n) => !Number.isInteger(n) || n < 0)) {
9147
+ throw new Error(`oidToDerTag: invalid OID "${oid}"`);
9148
+ }
9149
+ const content = [40 * parts[0] + parts[1]];
9150
+ for (let i = 2; i < parts.length; i++) {
9151
+ let v = parts[i];
9152
+ const sevenBitGroups = [v & 127];
9153
+ v = Math.floor(v / 128);
9154
+ while (v > 0) {
9155
+ sevenBitGroups.unshift(v & 127 | 128);
9156
+ v = Math.floor(v / 128);
9157
+ }
9158
+ content.push(...sevenBitGroups);
9159
+ }
9160
+ if (content.length > 127) {
9161
+ throw new Error(`oidToDerTag: OID "${oid}" content too long for single-byte length`);
9162
+ }
9163
+ return Buffer.from([6, content.length, ...content]);
9164
+ }
9165
+ function certHasOid(cert, oid) {
9166
+ return Buffer.from(cert.raw).includes(oidToDerTag(oid));
9167
+ }
9168
+ var init_native_asn1 = __esm({
9169
+ "../../blocklets/core/api/src/integrations/app-store/native-asn1.ts"() {
9170
+ "use strict";
9171
+ }
9172
+ });
9173
+
9174
+ // ../../blocklets/core/api/src/integrations/app-store/native-jws.ts
9175
+ function base64UrlToJson(segment, code) {
9176
+ let text;
9177
+ try {
9178
+ text = Buffer.from(segment, "base64url").toString("utf8");
9179
+ } catch {
9180
+ throw new AppleJwsVerifyError(code, "segment is not valid base64url");
9181
+ }
9182
+ let parsed;
9183
+ try {
9184
+ parsed = JSON.parse(text);
9185
+ } catch {
9186
+ throw new AppleJwsVerifyError(code, "segment is not valid JSON");
9187
+ }
9188
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
9189
+ throw new AppleJwsVerifyError(code, "segment is not a JSON object");
9190
+ }
9191
+ const out = parsed;
9192
+ for (const key of ["__proto__", "constructor", "prototype"]) {
9193
+ if (Object.prototype.hasOwnProperty.call(out, key)) {
9194
+ delete out[key];
9195
+ }
9196
+ }
9197
+ return out;
9198
+ }
9199
+ function buildCert(b64, label) {
9200
+ try {
9201
+ return new import_node_crypto.X509Certificate(Buffer.from(b64, "base64"));
9202
+ } catch {
9203
+ throw new AppleJwsVerifyError("MALFORMED_CERT", `${label} cert is not valid DER`);
9204
+ }
9205
+ }
9206
+ function assertCertValid(cert, effectiveDate, label) {
9207
+ const validFrom = Date.parse(cert.validFrom);
9208
+ const validTo = Date.parse(cert.validTo);
9209
+ if (Number.isNaN(validFrom) || Number.isNaN(validTo)) {
9210
+ throw new AppleJwsVerifyError("CERT_VALIDITY", `${label} cert has unparseable validity dates`);
9211
+ }
9212
+ if (validFrom > effectiveDate + MAX_SKEW_MS) {
9213
+ throw new AppleJwsVerifyError("CERT_NOT_YET_VALID", `${label} cert not valid until after signedDate`);
9214
+ }
9215
+ if (validTo < effectiveDate - MAX_SKEW_MS) {
9216
+ throw new AppleJwsVerifyError("CERT_EXPIRED", `${label} cert expired before signedDate`);
9217
+ }
9218
+ }
9219
+ async function verifyAppleJws(jws, opts = {}) {
9220
+ const trustedRoots = opts.trustedRoots ?? APPLE_ROOT_CERTS;
9221
+ const parts = jws.split(".");
9222
+ if (parts.length !== 3) {
9223
+ throw new AppleJwsVerifyError("FORMAT", "expected 3 JWS segments");
9224
+ }
9225
+ const [headerSeg, payloadSeg, signatureSeg] = parts;
9226
+ const header = base64UrlToJson(headerSeg, "HEADER");
9227
+ if (header.alg !== "ES256") {
9228
+ throw new AppleJwsVerifyError("ALG", "only ES256 is accepted");
9229
+ }
9230
+ if (!Array.isArray(header.x5c) || header.x5c.length !== 3) {
9231
+ throw new AppleJwsVerifyError("INVALID_CHAIN_LENGTH", "x5c must contain exactly 3 certs");
9232
+ }
9233
+ const leaf = buildCert(header.x5c[0], "leaf");
9234
+ const intermediate = buildCert(header.x5c[1], "intermediate");
9235
+ const rootFromJws = buildCert(header.x5c[2], "root");
9236
+ const rootDer = Buffer.from(rootFromJws.raw);
9237
+ const anchored = trustedRoots.some((trusted) => trusted.equals(rootDer));
9238
+ if (!anchored) {
9239
+ throw new AppleJwsVerifyError("ANCHOR", "presented root is not a trusted Apple root");
9240
+ }
9241
+ if (!intermediate.verify(rootFromJws.publicKey) || intermediate.issuer !== rootFromJws.subject) {
9242
+ throw new AppleJwsVerifyError("CHAIN", "intermediate does not chain to root");
9243
+ }
9244
+ if (!leaf.verify(intermediate.publicKey) || leaf.issuer !== intermediate.subject) {
9245
+ throw new AppleJwsVerifyError("CHAIN", "leaf does not chain to intermediate");
9246
+ }
9247
+ if (intermediate.ca !== true || !certHasOid(intermediate, APPLE_INTERMEDIATE_OID)) {
9248
+ throw new AppleJwsVerifyError("OID", "intermediate missing CA flag or Apple OID");
9249
+ }
9250
+ if (!certHasOid(leaf, APPLE_LEAF_OID)) {
9251
+ throw new AppleJwsVerifyError("OID", "leaf missing Apple OID");
9252
+ }
9253
+ const payload = base64UrlToJson(payloadSeg, "PAYLOAD");
9254
+ const effectiveDate = opts.signedDate ?? (typeof payload.signedDate === "number" ? payload.signedDate : void 0);
9255
+ if (typeof effectiveDate !== "number") {
9256
+ throw new AppleJwsVerifyError("NO_SIGNED_DATE", "payload has no numeric signedDate to validate against");
9257
+ }
9258
+ assertCertValid(leaf, effectiveDate, "leaf");
9259
+ assertCertValid(intermediate, effectiveDate, "intermediate");
9260
+ assertCertValid(rootFromJws, effectiveDate, "root");
9261
+ const spki = leaf.publicKey.export({ type: "spki", format: "der" });
9262
+ const key = await crypto.subtle.importKey(
9263
+ "spki",
9264
+ spki,
9265
+ { name: "ECDSA", namedCurve: "P-256" },
9266
+ false,
9267
+ ["verify"]
9268
+ );
9269
+ const signature = Buffer.from(signatureSeg, "base64url");
9270
+ const signingInput = Buffer.from(`${headerSeg}.${payloadSeg}`, "ascii");
9271
+ const valid = await crypto.subtle.verify({ name: "ECDSA", hash: "SHA-256" }, key, signature, signingInput);
9272
+ if (!valid) {
9273
+ throw new AppleJwsVerifyError("SIGNATURE", "ES256 signature does not verify against the leaf key");
9274
+ }
9275
+ return payload;
9276
+ }
9277
+ var import_node_crypto, APPLE_LEAF_OID, APPLE_INTERMEDIATE_OID, MAX_SKEW_MS, AppleJwsVerifyError;
9278
+ var init_native_jws = __esm({
9279
+ "../../blocklets/core/api/src/integrations/app-store/native-jws.ts"() {
9280
+ "use strict";
9281
+ import_node_crypto = require("node:crypto");
9282
+ init_apple_root_certs();
9283
+ init_native_asn1();
9284
+ APPLE_LEAF_OID = "1.2.840.113635.100.6.11.1";
9285
+ APPLE_INTERMEDIATE_OID = "1.2.840.113635.100.6.2.1";
9286
+ MAX_SKEW_MS = 6e4;
9287
+ AppleJwsVerifyError = class extends Error {
9288
+ constructor(code, detail) {
9289
+ super(detail ? `AppStore JWS verify failed [${code}]: ${detail}` : `AppStore JWS verify failed [${code}]`);
9290
+ this.name = "AppleJwsVerifyError";
9291
+ this.code = code;
9292
+ }
9293
+ };
9294
+ }
9295
+ });
9296
+
9297
+ // ../../blocklets/core/api/src/integrations/app-store/native-api.ts
9298
+ function pemToDer(pem) {
9299
+ const body = pem.replace(/-----BEGIN [^-]+-----/g, "").replace(/-----END [^-]+-----/g, "").replace(/\s+/g, "");
9300
+ if (!body) {
9301
+ throw new Error("AppStore API: private_key_pem is empty or not PEM-armored");
9302
+ }
9303
+ return Buffer.from(body, "base64");
9304
+ }
9305
+ async function signBearerJwt(creds) {
9306
+ const header = { alg: "ES256", kid: creds.keyId, typ: "JWT" };
9307
+ const iat = Math.floor(Date.now() / 1e3);
9308
+ const payload = {
9309
+ iss: creds.issuerId,
9310
+ iat,
9311
+ exp: iat + JWT_TTL_SECONDS,
9312
+ aud: "appstoreconnect-v1",
9313
+ bid: creds.bundleId
9314
+ };
9315
+ const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
9316
+ const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
9317
+ const signingInput = `${headerB64}.${payloadB64}`;
9318
+ let key;
9319
+ try {
9320
+ key = await crypto.subtle.importKey(
9321
+ "pkcs8",
9322
+ pemToDer(creds.privateKeyPem),
9323
+ { name: "ECDSA", namedCurve: "P-256" },
9324
+ false,
9325
+ ["sign"]
9326
+ );
9327
+ } catch {
9328
+ throw new Error("AppStore API: private_key_pem is not a valid EC P-256 PKCS#8 key");
9329
+ }
9330
+ const signature = await crypto.subtle.sign(
9331
+ { name: "ECDSA", hash: "SHA-256" },
9332
+ key,
9333
+ Buffer.from(signingInput, "ascii")
9334
+ );
9335
+ return `${signingInput}.${Buffer.from(signature).toString("base64url")}`;
9336
+ }
9337
+ async function nativeGetAllSubscriptionStatuses(originalTransactionId, creds) {
9338
+ if (!creds.issuerId || !creds.keyId || !creds.privateKeyPem) {
9339
+ throw new Error("AppStore API: issuer_id/key_id/private_key_pem are required");
9340
+ }
9341
+ const host = creds.environment === "production" ? API_HOST_PRODUCTION : API_HOST_SANDBOX;
9342
+ const url = `${host}/inApps/v1/subscriptions/${encodeURIComponent(originalTransactionId)}`;
9343
+ const jwt = await signBearerJwt(creds);
9344
+ const response = await fetch(url, {
9345
+ method: "GET",
9346
+ headers: { Authorization: `Bearer ${jwt}`, Accept: "application/json" }
9347
+ });
9348
+ if (response.status === 404) {
9349
+ return { data: [] };
9350
+ }
9351
+ if (!response.ok) {
9352
+ throw new Error(`AppStore API: getAllSubscriptionStatuses failed with HTTP ${response.status}`);
9353
+ }
9354
+ return await response.json();
9153
9355
  }
9356
+ var API_HOST_PRODUCTION, API_HOST_SANDBOX, JWT_TTL_SECONDS;
9357
+ var init_native_api = __esm({
9358
+ "../../blocklets/core/api/src/integrations/app-store/native-api.ts"() {
9359
+ "use strict";
9360
+ API_HOST_PRODUCTION = "https://api.storekit.apple.com";
9361
+ API_HOST_SANDBOX = "https://api.storekit-sandbox.apple.com";
9362
+ JWT_TTL_SECONDS = 300;
9363
+ }
9364
+ });
9365
+
9366
+ // ../../blocklets/core/api/src/integrations/app-store/signed-data-verifier.ts
9154
9367
  function isSignatureVerificationSkipped() {
9155
9368
  if (!appStoreSkipSignatureVerify()) return false;
9156
9369
  if (isProduction()) {
@@ -9161,21 +9374,19 @@ function isSignatureVerificationSkipped() {
9161
9374
  }
9162
9375
  return true;
9163
9376
  }
9164
- async function verifySignedTransaction(signedTransaction, bundleId, environment) {
9377
+ async function verifySignedTransaction(signedTransaction, _bundleId, _environment) {
9165
9378
  if (isSignatureVerificationSkipped()) {
9166
9379
  logger_default.warn("app_store: signature verification skipped via APP_STORE_SKIP_SIGNATURE_VERIFY");
9167
9380
  return decodeUnsafe(signedTransaction);
9168
9381
  }
9169
- const verifier = await getVerifier(bundleId, environment);
9170
- return verifier.verifyAndDecodeTransaction(signedTransaction);
9382
+ return verifyAppleJws(signedTransaction);
9171
9383
  }
9172
- async function verifySignedNotification(signedPayload, bundleId, environment) {
9384
+ async function verifySignedNotification(signedPayload, _bundleId, _environment) {
9173
9385
  if (isSignatureVerificationSkipped()) {
9174
9386
  logger_default.warn("app_store: signature verification skipped via APP_STORE_SKIP_SIGNATURE_VERIFY");
9175
9387
  return decodeUnsafe(signedPayload);
9176
9388
  }
9177
- const verifier = await getVerifier(bundleId, environment);
9178
- return verifier.verifyAndDecodeNotification(signedPayload);
9389
+ return verifyAppleJws(signedPayload);
9179
9390
  }
9180
9391
  function decodeUnsafe(jws) {
9181
9392
  const parts = jws.split(".");
@@ -9188,41 +9399,85 @@ function decodeUnsafe(jws) {
9188
9399
  throw new Error(`AppStore JWS payload not valid JSON: ${err.message}`);
9189
9400
  }
9190
9401
  }
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
9402
  async function getAllSubscriptionStatuses(originalTransactionId, creds) {
9202
- const client = await getApiClient(creds);
9203
- return client.getAllSubscriptionStatuses(originalTransactionId);
9403
+ return nativeGetAllSubscriptionStatuses(originalTransactionId, creds);
9204
9404
  }
9205
- var verifierCache, apiClientCache;
9206
9405
  var init_signed_data_verifier = __esm({
9207
9406
  "../../blocklets/core/api/src/integrations/app-store/signed-data-verifier.ts"() {
9208
9407
  "use strict";
9209
9408
  init_logger();
9210
9409
  init_env();
9211
- init_apple_root_certs();
9212
- verifierCache = /* @__PURE__ */ new Map();
9213
- apiClientCache = /* @__PURE__ */ new Map();
9410
+ init_native_jws();
9411
+ init_native_api();
9412
+ }
9413
+ });
9414
+
9415
+ // ../../blocklets/core/api/src/integrations/app-store/native-receipt.ts
9416
+ function toMs(v) {
9417
+ if (v === void 0 || v === null || v === "") return void 0;
9418
+ const n = Number(v);
9419
+ return Number.isNaN(n) ? void 0 : n;
9420
+ }
9421
+ async function postReceipt(host, body) {
9422
+ const response = await fetch(host, {
9423
+ method: "POST",
9424
+ headers: { "Content-Type": "application/json" },
9425
+ body: JSON.stringify(body)
9426
+ });
9427
+ if (!response.ok) {
9428
+ throw new Error(`AppStore verifyReceipt: HTTP ${response.status}`);
9429
+ }
9430
+ return await response.json();
9431
+ }
9432
+ async function verifyAppleReceiptNative({
9433
+ receipt,
9434
+ sharedSecret
9435
+ }) {
9436
+ const body = {
9437
+ "receipt-data": receipt,
9438
+ password: sharedSecret,
9439
+ // Deliberate, result-equivalent: client.ts reduces to the latest-expiry item
9440
+ // anyway, so trimming old transactions here changes nothing downstream.
9441
+ "exclude-old-transactions": true
9442
+ };
9443
+ let data = await postReceipt(PRODUCTION_HOST, body);
9444
+ if (RETRY_SANDBOX_STATUSES.has(data.status)) {
9445
+ data = await postReceipt(SANDBOX_HOST, body);
9446
+ }
9447
+ if (data.status !== 0) {
9448
+ throw new Error(`AppStore verifyReceipt: Apple returned status ${data.status}`);
9449
+ }
9450
+ const rawItems = data.latest_receipt_info ?? data.receipt?.in_app ?? [];
9451
+ const bundleId = data.receipt?.bundle_id;
9452
+ return rawItems.map((item) => ({
9453
+ productId: item.product_id ?? "",
9454
+ transactionId: item.transaction_id ?? "",
9455
+ originalTransactionId: item.original_transaction_id,
9456
+ purchaseDate: toMs(item.purchase_date_ms),
9457
+ expirationDate: toMs(item.expires_date_ms),
9458
+ bundleId,
9459
+ webOrderLineItemId: item.web_order_line_item_id
9460
+ }));
9461
+ }
9462
+ var PRODUCTION_HOST, SANDBOX_HOST, RETRY_SANDBOX_STATUSES;
9463
+ var init_native_receipt = __esm({
9464
+ "../../blocklets/core/api/src/integrations/app-store/native-receipt.ts"() {
9465
+ "use strict";
9466
+ PRODUCTION_HOST = "https://buy.itunes.apple.com/verifyReceipt";
9467
+ SANDBOX_HOST = "https://sandbox.itunes.apple.com/verifyReceipt";
9468
+ RETRY_SANDBOX_STATUSES = /* @__PURE__ */ new Set([21007, 21002]);
9214
9469
  }
9215
9470
  });
9216
9471
 
9217
9472
  // ../../blocklets/core/api/src/integrations/app-store/client.ts
9218
- var import_node_apple_receipt_verify, AppStoreClient;
9473
+ var AppStoreClient;
9219
9474
  var init_client = __esm({
9220
9475
  "../../blocklets/core/api/src/integrations/app-store/client.ts"() {
9221
9476
  "use strict";
9222
- import_node_apple_receipt_verify = require("node-apple-receipt-verify");
9223
9477
  init_env();
9224
9478
  init_logger();
9225
9479
  init_signed_data_verifier();
9480
+ init_native_receipt();
9226
9481
  AppStoreClient = class _AppStoreClient {
9227
9482
  constructor(settings, environment) {
9228
9483
  this.bundleId = settings.bundle_id;
@@ -9327,9 +9582,9 @@ var init_client = __esm({
9327
9582
  /**
9328
9583
  * Verify a StoreKit 1 (legacy) base64 receipt via Apple's verifyReceipt endpoint.
9329
9584
  *
9330
- * Calls `node-apple-receipt-verify` which POSTs to
9585
+ * Calls the native `verifyAppleReceiptNative` which POSTs to
9331
9586
  * https://buy.itunes.apple.com/verifyReceipt (production)
9332
- * https://sandbox.itunes.apple.com/verifyReceipt (sandbox, auto fallback)
9587
+ * https://sandbox.itunes.apple.com/verifyReceipt (sandbox, auto fallback on 21007/21002)
9333
9588
  *
9334
9589
  * Returns a payload shaped like a StoreKit 2 transaction so downstream code
9335
9590
  * (ingestVerifiedAppStorePurchase) can stay agnostic. Note: legacy receipts
@@ -9343,12 +9598,7 @@ var init_client = __esm({
9343
9598
  if (!this.sharedSecret) {
9344
9599
  throw new Error("AppStoreClient: shared_secret is required for legacy receipt verification");
9345
9600
  }
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 });
9601
+ const items = await verifyAppleReceiptNative({ receipt, sharedSecret: this.sharedSecret });
9352
9602
  const filtered = options.expectedProductIds ? items.filter((i) => options.expectedProductIds.includes(i.productId)) : items;
9353
9603
  if (!filtered.length) {
9354
9604
  throw new Error("AppStoreClient: verifyReceipt returned no matching purchases");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcblock/payment-service",
3
- "version": "1.29.5",
3
+ "version": "1.29.7",
4
4
  "description": "Embedded payment service factory (W2) — side-effect-free core for arc / blocklet server / standalone worker hosts",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,7 +37,6 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@abtnode/cron": "^1.17.13-beta-20260613-094425-b81920c8",
40
- "@apple/app-store-server-library": "^3.1.0",
41
40
  "@arcblock/did": "^1.30.24",
42
41
  "@arcblock/did-connect-storage-nedb": "^1.8.0",
43
42
  "@arcblock/did-util": "^1.30.24",
@@ -45,7 +44,7 @@
45
44
  "@blocklet/constant": "^1.17.13-beta-20260613-094425-b81920c8",
46
45
  "@blocklet/error": "^0.3.5",
47
46
  "@blocklet/logger": "^1.17.13-beta-20260613-094425-b81920c8",
48
- "@blocklet/payment-vendor": "1.29.5",
47
+ "@blocklet/payment-vendor": "1.29.7",
49
48
  "@blocklet/sdk": "^1.17.13-beta-20260613-094425-b81920c8",
50
49
  "@blocklet/xss": "^0.3.16",
51
50
  "@hono/node-server": "^2.0.4",
@@ -70,7 +69,6 @@
70
69
  "json-stable-stringify": "^1.3.0",
71
70
  "lodash": "^4.17.21",
72
71
  "nanoid": "^3.3.11",
73
- "node-apple-receipt-verify": "^1.15.0",
74
72
  "p-all": "3.0.0",
75
73
  "p-wait-for": "^3.2.0",
76
74
  "pretty-ms-i18n": "^1.0.3",
@@ -92,5 +90,5 @@
92
90
  "ts-jest": "^29.1.2",
93
91
  "typescript": "^5.4.3"
94
92
  },
95
- "gitHead": "b392a84d8557fd6f32f47540d76d642d62e59dd3"
93
+ "gitHead": "e1999aecfa9ce64bfd56da14bf47b255a19ef394"
96
94
  }