payment-kit 1.27.2 → 1.29.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/__blocklet__.js +37 -0
- package/api/ocap-1.30-subpath-shims.d.ts +35 -0
- package/api/src/crons/index.ts +32 -0
- package/api/src/crons/metering-subscription-detection.ts +12 -14
- package/api/src/crons/overdue-detection.ts +51 -74
- package/api/src/crons/retry-pending-events.ts +58 -0
- package/api/src/integrations/app-store/apple-root-certs.ts +26 -0
- package/api/src/integrations/app-store/client.ts +369 -0
- package/api/src/integrations/app-store/handlers/index.ts +46 -0
- package/api/src/integrations/app-store/handlers/subscription.ts +635 -0
- package/api/src/integrations/app-store/node-apple-receipt-verify.d.ts +17 -0
- package/api/src/integrations/app-store/notification-routing.ts +18 -0
- package/api/src/integrations/app-store/signed-data-verifier.ts +150 -0
- package/api/src/integrations/arcblock/nft.ts +6 -2
- package/api/src/integrations/arcblock/stake.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +4 -4
- package/api/src/integrations/blocklet/notification.ts +1 -1
- package/api/src/integrations/ethereum/tx.ts +29 -0
- package/api/src/integrations/google-play/client.ts +276 -0
- package/api/src/integrations/google-play/handlers/index.ts +69 -0
- package/api/src/integrations/google-play/handlers/subscription.ts +565 -0
- package/api/src/integrations/google-play/handlers/voided.ts +106 -0
- package/api/src/integrations/google-play/setup.ts +43 -0
- package/api/src/integrations/google-play/verify.ts +251 -0
- package/api/src/integrations/iap-reconcile.ts +415 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +70 -53
- package/api/src/integrations/stripe/handlers/payment-intent.ts +8 -1
- package/api/src/integrations/stripe/resource.ts +8 -0
- package/api/src/libs/audit.ts +70 -24
- package/api/src/libs/auth.ts +49 -2
- package/api/src/libs/chain-error.ts +31 -0
- package/api/src/libs/entitlement.ts +399 -0
- package/api/src/libs/env.ts +2 -0
- package/api/src/libs/error.ts +15 -0
- package/api/src/libs/event.ts +42 -1
- package/api/src/libs/invoice.ts +69 -34
- package/api/src/libs/notification/template/customer-auto-recharge-daily-limit-exceeded.ts +1 -3
- package/api/src/libs/notification/template/customer-auto-recharge-failed.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-grant-granted.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +1 -3
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +1 -3
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -3
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -3
- package/api/src/libs/notification/template/one-time-payment-refund-succeeded.ts +1 -3
- package/api/src/libs/notification/template/one-time-payment-succeeded.ts +1 -3
- package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -3
- package/api/src/libs/notification/template/subscription-slippage-exceeded.ts +1 -3
- package/api/src/libs/notification/template/subscription-slippage-warning.ts +1 -3
- package/api/src/libs/notification/template/subscription-succeeded.ts +1 -1
- package/api/src/libs/pagination.ts +14 -9
- package/api/src/libs/payment.ts +25 -10
- package/api/src/libs/security.ts +51 -0
- package/api/src/libs/session.ts +1 -1
- package/api/src/libs/subscription.ts +13 -1
- package/api/src/libs/timing.ts +35 -0
- package/api/src/libs/util.ts +29 -15
- package/api/src/libs/wallet-migration.ts +72 -53
- package/api/src/queues/auto-recharge.ts +1 -1
- package/api/src/queues/credit-consume.ts +94 -12
- package/api/src/queues/credit-grant.ts +4 -0
- package/api/src/queues/event.ts +39 -21
- package/api/src/queues/invoice.ts +1 -0
- package/api/src/queues/payment.ts +83 -15
- package/api/src/queues/refund.ts +84 -71
- package/api/src/queues/subscription.ts +1 -0
- package/api/src/queues/webhook.ts +12 -2
- package/api/src/routes/checkout-sessions.ts +82 -43
- package/api/src/routes/connect/change-payment.ts +2 -0
- package/api/src/routes/connect/change-plan.ts +2 -0
- package/api/src/routes/connect/pay.ts +12 -3
- package/api/src/routes/connect/setup.ts +3 -1
- package/api/src/routes/connect/shared.ts +52 -39
- package/api/src/routes/connect/subscribe.ts +4 -1
- package/api/src/routes/credit-grants.ts +25 -17
- package/api/src/routes/donations.ts +2 -2
- package/api/src/routes/entitlements.ts +105 -0
- package/api/src/routes/events.ts +2 -2
- package/api/src/routes/index.ts +12 -2
- package/api/src/routes/integrations/app-store.ts +267 -0
- package/api/src/routes/integrations/google-play.ts +324 -0
- package/api/src/routes/meter-events.ts +16 -6
- package/api/src/routes/payment-links.ts +1 -1
- package/api/src/routes/payment-methods.ts +131 -1
- package/api/src/routes/settings.ts +1 -1
- package/api/src/routes/tax-rates.ts +1 -1
- package/api/src/store/migrations/20260526-iap-foundation.ts +105 -0
- package/api/src/store/models/customer.ts +37 -1
- package/api/src/store/models/entitlement-grant.ts +118 -0
- package/api/src/store/models/entitlement-product.ts +48 -0
- package/api/src/store/models/entitlement.ts +86 -0
- package/api/src/store/models/index.ts +9 -0
- package/api/src/store/models/invoice.ts +20 -0
- package/api/src/store/models/payment-method.ts +66 -1
- package/api/src/store/models/price.ts +23 -14
- package/api/src/store/models/refund.ts +10 -0
- package/api/src/store/models/subscription.ts +14 -0
- package/api/src/store/models/types.ts +32 -0
- package/api/tests/integrations/app-store/client.spec.ts +335 -0
- package/api/tests/integrations/app-store/handlers.spec.ts +480 -0
- package/api/tests/integrations/app-store/notifications.spec.ts +381 -0
- package/api/tests/integrations/app-store/signed-data-verifier.spec.ts +72 -0
- package/api/tests/integrations/app-store/webhook-routing.spec.ts +27 -0
- package/api/tests/integrations/google-play/handlers.spec.ts +341 -0
- package/api/tests/integrations/google-play/verify.spec.ts +215 -0
- package/api/tests/integrations/iap-reconcile.spec.ts +237 -0
- package/api/tests/libs/entitlement.spec.ts +347 -0
- package/api/tests/libs/wallet-migration.spec.ts +4 -4
- package/api/tests/queues/credit-consume-batch.spec.ts +5 -2
- package/api/tests/queues/credit-consume.spec.ts +8 -4
- package/api/tests/routes/credit-grants.spec.ts +1 -0
- package/blocklet.yml +1 -1
- package/cloudflare/MIGRATION-CHALLENGES.md +676 -0
- package/cloudflare/MIGRATION-RUNBOOK.md +777 -0
- package/cloudflare/README.md +499 -0
- package/cloudflare/STAGING-MIGRATION-GUIDE.md +602 -0
- package/cloudflare/build.ts +151 -0
- package/cloudflare/did-connect-auth.ts +527 -0
- package/cloudflare/docs/2026-04-22-sdk-1.30.9-upgrade-retro.md +324 -0
- package/cloudflare/docs/2026-04-24-queue-ops-followup.md +218 -0
- package/cloudflare/docs/cf-queues-ops-alert-analysis.md +663 -0
- package/cloudflare/docs/cf-workers-local-dev-and-fixes.md +284 -0
- package/cloudflare/docs/cleanup-tasks-2026-05.md +62 -0
- package/cloudflare/docs/payment-kit-platform-analysis-2026-04-20.md +354 -0
- package/cloudflare/frontend-shims/buffer-polyfill.ts +9 -0
- package/cloudflare/frontend-shims/js-sdk.ts +43 -0
- package/cloudflare/frontend-shims/mime-types.ts +46 -0
- package/cloudflare/frontend-shims/session.ts +24 -0
- package/cloudflare/frontend-shims/vite-plugin-noop.ts +6 -0
- package/cloudflare/index.html +40 -0
- package/cloudflare/migrate-to-d1.js +252 -0
- package/cloudflare/migrations/0001_initial_schema.sql +82 -0
- package/cloudflare/migrations/0002_indexes.sql +75 -0
- package/cloudflare/migrations/0003_locks_and_constraints.sql +18 -0
- package/cloudflare/migrations/0004_iap_foundation.sql +72 -0
- package/cloudflare/migrations/0005_iap_tenant_backfill.sql +112 -0
- package/cloudflare/run-build.js +391 -0
- package/cloudflare/scripts/test-decrypt.js +102 -0
- package/cloudflare/shims/arcblock-ws.ts +20 -0
- package/cloudflare/shims/axios-http-adapter.ts +4 -0
- package/cloudflare/shims/axios-lite.ts +117 -0
- package/cloudflare/shims/blocklet-sdk/auth-service.ts +33 -0
- package/cloudflare/shims/blocklet-sdk/cdn.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/component-api.ts +35 -0
- package/cloudflare/shims/blocklet-sdk/component.ts +18 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/did.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/env.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/eventbus.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/fallback.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/index.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/logger.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/middlewares.ts +15 -0
- package/cloudflare/shims/blocklet-sdk/notification.ts +11 -0
- package/cloudflare/shims/blocklet-sdk/security.ts +53 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/verify-session.ts +44 -0
- package/cloudflare/shims/blocklet-sdk/verify-sign.ts +38 -0
- package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +3 -0
- package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +6 -0
- package/cloudflare/shims/blocklet-sdk/wallet.ts +103 -0
- package/cloudflare/shims/cookie-parser.ts +3 -0
- package/cloudflare/shims/cors.ts +21 -0
- package/cloudflare/shims/cron.ts +189 -0
- package/cloudflare/shims/crypto-js-warn.ts +7 -0
- package/cloudflare/shims/did-space-js.ts +17 -0
- package/cloudflare/shims/did-space.ts +11 -0
- package/cloudflare/shims/error.ts +18 -0
- package/cloudflare/shims/express-compat/index.ts +80 -0
- package/cloudflare/shims/express-compat/types.ts +41 -0
- package/cloudflare/shims/fastq.ts +105 -0
- package/cloudflare/shims/lock.ts +115 -0
- package/cloudflare/shims/mime-types.ts +56 -0
- package/cloudflare/shims/nedb-storage.ts +9 -0
- package/cloudflare/shims/node-child-process.ts +9 -0
- package/cloudflare/shims/node-fs.ts +20 -0
- package/cloudflare/shims/node-http.ts +13 -0
- package/cloudflare/shims/node-https.ts +4 -0
- package/cloudflare/shims/node-misc.ts +15 -0
- package/cloudflare/shims/node-net.ts +8 -0
- package/cloudflare/shims/node-os.ts +14 -0
- package/cloudflare/shims/node-tty.ts +8 -0
- package/cloudflare/shims/node-zlib.ts +17 -0
- package/cloudflare/shims/noop.ts +26 -0
- package/cloudflare/shims/payment-vendor.ts +14 -0
- package/cloudflare/shims/querystring.ts +12 -0
- package/cloudflare/shims/queue.ts +611 -0
- package/cloudflare/shims/rolldown-runtime.ts +43 -0
- package/cloudflare/shims/sequelize-d1/datatypes.ts +24 -0
- package/cloudflare/shims/sequelize-d1/helpers.ts +46 -0
- package/cloudflare/shims/sequelize-d1/index.ts +34 -0
- package/cloudflare/shims/sequelize-d1/model.ts +1176 -0
- package/cloudflare/shims/sequelize-d1/operators.ts +306 -0
- package/cloudflare/shims/sequelize-d1/retry.ts +85 -0
- package/cloudflare/shims/sequelize-d1/sequelize-class.ts +119 -0
- package/cloudflare/shims/sequelize-d1/timing.ts +81 -0
- package/cloudflare/shims/sequelize-d1/types.ts +35 -0
- package/cloudflare/shims/stripe-cf.ts +29 -0
- package/cloudflare/shims/ws-lite.ts +103 -0
- package/cloudflare/shims/xss.ts +3 -0
- package/cloudflare/tests/shims/cron.spec.ts +210 -0
- package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +87 -0
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +186 -0
- package/cloudflare/vite.config.ts +162 -0
- package/cloudflare/worker.ts +1608 -0
- package/cloudflare/wrangler.json +63 -0
- package/cloudflare/wrangler.jsonc +75 -0
- package/cloudflare/wrangler.staging.json +67 -0
- package/cloudflare/wrangler.toml +28 -0
- package/jest.config.js +4 -12
- package/package.json +30 -22
- package/scripts/seed-google-play.ts +79 -0
- package/src/app.tsx +62 -4
- package/src/components/customer/link.tsx +9 -13
- package/src/components/customer/notification-preference.tsx +3 -2
- package/src/components/filter-toolbar.tsx +4 -0
- package/src/components/invoice/list.tsx +9 -1
- package/src/components/invoice-pdf/utils.ts +2 -1
- package/src/components/layout/admin.tsx +39 -5
- package/src/components/layout/user-cf.tsx +77 -0
- package/src/components/payment-intent/actions.tsx +23 -3
- package/src/components/payment-method/app-store.tsx +103 -0
- package/src/components/payment-method/form.tsx +7 -1
- package/src/components/payment-method/google-play.tsx +85 -0
- package/src/components/safe-did-address.tsx +75 -0
- package/src/components/subscription/list.tsx +20 -0
- package/src/libs/patch-user-card.ts +25 -0
- package/src/libs/util.ts +5 -7
- package/src/locales/en.tsx +63 -0
- package/src/locales/zh.tsx +63 -0
- package/src/pages/admin/billing/meter-events/index.tsx +4 -0
- package/src/pages/admin/billing/subscriptions/detail.tsx +80 -0
- package/src/pages/admin/customers/customers/detail.tsx +8 -2
- package/src/pages/admin/customers/customers/index.tsx +2 -2
- package/src/pages/admin/overview.tsx +3 -1
- package/src/pages/admin/settings/payment-methods/create.tsx +12 -0
- package/src/pages/admin/settings/payment-methods/index.tsx +1 -1
- package/src/pages/customer/subscription/detail.tsx +4 -4
- package/tsconfig.api.json +1 -6
- package/tsconfig.json +3 -4
- package/tsconfig.types.json +2 -1
- package/vite.config.ts +6 -1
package/__blocklet__.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Polyfill global for browser
|
|
2
|
+
if (typeof globalThis !== 'undefined' && typeof global === 'undefined') {
|
|
3
|
+
window.global = globalThis;
|
|
4
|
+
}
|
|
5
|
+
if (typeof globalThis !== 'undefined' && typeof Buffer === 'undefined') {
|
|
6
|
+
// Buffer polyfill will be loaded by vite-plugin-node-polyfills if configured
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// CF Workers shim for window.blocklet
|
|
10
|
+
// This file replaces the auto-generated __blocklet__.js from Blocklet Server
|
|
11
|
+
window.blocklet = window.blocklet || {};
|
|
12
|
+
Object.assign(window.blocklet, {
|
|
13
|
+
prefix: '',
|
|
14
|
+
groupPrefix: '',
|
|
15
|
+
did: 'did:abt:test',
|
|
16
|
+
componentId: null,
|
|
17
|
+
serverVersion: '3.0.0',
|
|
18
|
+
languages: [
|
|
19
|
+
{ code: 'en', name: 'English' },
|
|
20
|
+
{ code: 'zh', name: '简体中文' }
|
|
21
|
+
],
|
|
22
|
+
appPid: 'payment-kit-cf',
|
|
23
|
+
appId: 'payment-kit-cf',
|
|
24
|
+
appName: 'Payment Kit',
|
|
25
|
+
appLogo: '',
|
|
26
|
+
appDescription: 'Decentralized Payment System',
|
|
27
|
+
appUrl: window.location.origin,
|
|
28
|
+
GA_MEASUREMENT_ID: '',
|
|
29
|
+
theme: { prefer: 'light' },
|
|
30
|
+
navigation: [{ title: 'Home', link: '/', icon: '' }],
|
|
31
|
+
webWalletUrl: '',
|
|
32
|
+
componentMountPoints: [],
|
|
33
|
+
sessionPermissions: { canReadAll: true, canWriteAll: true },
|
|
34
|
+
settings: {
|
|
35
|
+
session: { cacheTtl: 3600 },
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Type shims for @ocap/* / @arcblock/* 1.30.x CBOR-only subpath entries.
|
|
2
|
+
//
|
|
3
|
+
// These packages ship runtime + `.d.mts` type files under the package.json
|
|
4
|
+
// `exports` map. tsc under `moduleResolution: node` cannot resolve `.d.mts`
|
|
5
|
+
// through the exports map, producing:
|
|
6
|
+
// error TS2307: Cannot find module '@arcblock/did-util/cbor' or its
|
|
7
|
+
// corresponding type declarations.
|
|
8
|
+
//
|
|
9
|
+
// Switching moduleResolution to `bundler`/`node16` would fix resolution but
|
|
10
|
+
// require matching `module: esnext`, which changes the emitted JS format for
|
|
11
|
+
// api/dist runtime. Not worth the blast radius.
|
|
12
|
+
//
|
|
13
|
+
// Instead: declare loose types here so tsc is satisfied. Runtime resolution
|
|
14
|
+
// still uses the real packages via Node/esbuild's own module loader (they
|
|
15
|
+
// handle .cjs + exports map correctly).
|
|
16
|
+
|
|
17
|
+
declare module '@arcblock/did-util/cbor' {
|
|
18
|
+
// biome-ignore lint/suspicious/noExplicitAny: loose shim; real types live in .d.mts
|
|
19
|
+
type AnyFn = (...args: any[]) => any;
|
|
20
|
+
export const toAssetAddress: AnyFn;
|
|
21
|
+
export const toDelegateAddress: AnyFn;
|
|
22
|
+
export const toFactoryAddress: AnyFn;
|
|
23
|
+
export const toItxAddress: AnyFn;
|
|
24
|
+
export const toRollupAddress: AnyFn;
|
|
25
|
+
export const toStakeAddress: AnyFn;
|
|
26
|
+
export const toTokenAddress: AnyFn;
|
|
27
|
+
export const toTokenFactoryAddress: AnyFn;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare module '@ocap/asset/mint/client' {
|
|
31
|
+
// biome-ignore lint/suspicious/noExplicitAny: loose shim; real types live in .d.mts
|
|
32
|
+
type AnyFn = (...args: any[]) => any;
|
|
33
|
+
export const formatFactoryState: AnyFn;
|
|
34
|
+
export const preMintFromFactory: AnyFn;
|
|
35
|
+
}
|
package/api/src/crons/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Cron from '@abtnode/cron';
|
|
2
2
|
|
|
3
3
|
import { checkStakeRevokeTx } from '../integrations/arcblock/stake';
|
|
4
|
+
import { runIapReconcile } from '../integrations/iap-reconcile';
|
|
4
5
|
import {
|
|
5
6
|
batchHandleStripeInvoices,
|
|
6
7
|
batchHandleStripePayments,
|
|
@@ -9,7 +10,9 @@ import {
|
|
|
9
10
|
import {
|
|
10
11
|
creditConsumptionCronTime,
|
|
11
12
|
depositVaultCronTime,
|
|
13
|
+
eventRetryCronTime,
|
|
12
14
|
expiredSessionCleanupCronTime,
|
|
15
|
+
iapReconcileCronTime,
|
|
13
16
|
notificationCronTime,
|
|
14
17
|
overdueDetectionCronTime,
|
|
15
18
|
paymentStatCronTime,
|
|
@@ -24,11 +27,13 @@ import {
|
|
|
24
27
|
import logger from '../libs/logger';
|
|
25
28
|
import { startCreditConsumeQueue } from '../queues/credit-consume';
|
|
26
29
|
import { startDepositVaultQueue } from '../queues/payment';
|
|
30
|
+
import { startRefundQueue } from '../queues/refund';
|
|
27
31
|
import { startSubscriptionQueue } from '../queues/subscription';
|
|
28
32
|
import { startVendorStatusCheckSchedule } from '../queues/vendors/status-check';
|
|
29
33
|
import { CheckoutSession } from '../store/models';
|
|
30
34
|
import { createOverdueDetection } from './overdue-detection';
|
|
31
35
|
import { createPaymentStat } from './payment-stat';
|
|
36
|
+
import { retryPendingEvents } from './retry-pending-events';
|
|
32
37
|
import { SubscriptionTrialWillEndSchedule } from './subscription-trial-will-end';
|
|
33
38
|
import { SubscriptionWillCanceledSchedule } from './subscription-will-canceled';
|
|
34
39
|
import { SubscriptionWillRenewSchedule } from './subscription-will-renew';
|
|
@@ -85,6 +90,15 @@ function init() {
|
|
|
85
90
|
fn: startSubscriptionQueue,
|
|
86
91
|
options: { runOnInit: false },
|
|
87
92
|
},
|
|
93
|
+
{
|
|
94
|
+
// Recover pending refunds that were missed (e.g. after deploy or failed dispatch).
|
|
95
|
+
// Previously invoked unconditionally on every CF scheduled tick; now throttled
|
|
96
|
+
// to every 5 minutes to stay within CF Queue free-tier ops budget.
|
|
97
|
+
name: 'refund.recovery',
|
|
98
|
+
time: '0 */5 * * * *',
|
|
99
|
+
fn: startRefundQueue,
|
|
100
|
+
options: { runOnInit: false },
|
|
101
|
+
},
|
|
88
102
|
{
|
|
89
103
|
name: 'checkoutSession.cleanup.expired',
|
|
90
104
|
time: expiredSessionCleanupCronTime,
|
|
@@ -118,6 +132,24 @@ function init() {
|
|
|
118
132
|
fn: checkStakeRevokeTx,
|
|
119
133
|
options: { runOnInit: false },
|
|
120
134
|
},
|
|
135
|
+
{
|
|
136
|
+
// Backup for App Store / Google Play webhooks — pulls authoritative
|
|
137
|
+
// subscription state and patches local drift (refunds/renewals the
|
|
138
|
+
// webhook missed). See blocklets/core/api/src/integrations/iap-reconcile.ts.
|
|
139
|
+
name: 'iap.reconcile',
|
|
140
|
+
time: iapReconcileCronTime,
|
|
141
|
+
fn: () => runIapReconcile(),
|
|
142
|
+
options: { runOnInit: false },
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
// Backstop for the fire-and-forget event → webhook path. Rescans events
|
|
146
|
+
// with pending_webhooks>0 older than 60s and re-invokes handleEvent.
|
|
147
|
+
// See blocklets/core/api/src/crons/retry-pending-events.ts.
|
|
148
|
+
name: 'event.retry',
|
|
149
|
+
time: eventRetryCronTime,
|
|
150
|
+
fn: () => retryPendingEvents(),
|
|
151
|
+
options: { runOnInit: false },
|
|
152
|
+
},
|
|
121
153
|
{
|
|
122
154
|
name: 'payment.stat',
|
|
123
155
|
time: paymentStatCronTime,
|
|
@@ -119,20 +119,18 @@ export class MeteringSubscriptionDetectionTemplate implements BaseEmailTemplate<
|
|
|
119
119
|
});
|
|
120
120
|
|
|
121
121
|
const meteringInvoices: any[] = [];
|
|
122
|
-
await
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
})
|
|
135
|
-
);
|
|
122
|
+
const prices = (await Price.findAll()).map((x) => x.toJSON());
|
|
123
|
+
for (const invoice of invoices) {
|
|
124
|
+
const invoiceJson = invoice.toJSON();
|
|
125
|
+
// @ts-ignore
|
|
126
|
+
(invoiceJson.lines || []).forEach((item) => {
|
|
127
|
+
item.price = prices.find((x) => x.id === item.price_id);
|
|
128
|
+
});
|
|
129
|
+
// @ts-ignore
|
|
130
|
+
if ((invoiceJson.lines || []).some((line) => line.price?.recurring?.usage_type === 'metered')) {
|
|
131
|
+
meteringInvoices.push(invoice);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
136
134
|
const meteringInvoiceIds = Array.from(new Set(meteringInvoices.map((invoice) => invoice.id)));
|
|
137
135
|
const meteringSubscriptionIds = Array.from(
|
|
138
136
|
new Set(meteringInvoices.map((invoice) => invoice?.subscription_id).filter(Boolean) as string[])
|
|
@@ -102,19 +102,19 @@ export class HealthReportTemplate implements BaseEmailTemplate<HealthReportConte
|
|
|
102
102
|
const startTime = dayjs.unix(start).toISOString();
|
|
103
103
|
const endTime = dayjs.unix(end).toISOString();
|
|
104
104
|
|
|
105
|
-
// 1. Get all currency configurations
|
|
106
|
-
const allCurrencies = await
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
[Op.between]: [startTime, endTime],
|
|
105
|
+
// 1. Get all currency configurations + pending meter events in parallel
|
|
106
|
+
const [allCurrencies, events] = await Promise.all([
|
|
107
|
+
PaymentCurrency.findAll(),
|
|
108
|
+
MeterEvent.findAll({
|
|
109
|
+
where: {
|
|
110
|
+
status: ['pending', 'requires_capture', 'requires_action'],
|
|
111
|
+
created_at: {
|
|
112
|
+
[Op.between]: [startTime, endTime],
|
|
113
|
+
},
|
|
115
114
|
},
|
|
116
|
-
},
|
|
117
|
-
|
|
115
|
+
}),
|
|
116
|
+
]);
|
|
117
|
+
const currencyMap = new Map(allCurrencies.map((c) => [c.id, c]));
|
|
118
118
|
|
|
119
119
|
if (events.length === 0) {
|
|
120
120
|
return { customerCount: 0, pendingAmounts: '0', exceedsThreshold: false };
|
|
@@ -209,25 +209,21 @@ export class HealthReportTemplate implements BaseEmailTemplate<HealthReportConte
|
|
|
209
209
|
const startTime = dayjs.unix(start).toISOString();
|
|
210
210
|
const endTime = dayjs.unix(end).toISOString();
|
|
211
211
|
|
|
212
|
-
// Get all successful payments
|
|
213
|
-
const payments = await
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
[Op.between]: [startTime, endTime],
|
|
212
|
+
// Get all successful payments + refunds in parallel
|
|
213
|
+
const [payments, refunds] = await Promise.all([
|
|
214
|
+
PaymentIntent.findAll({
|
|
215
|
+
where: {
|
|
216
|
+
status: 'succeeded',
|
|
217
|
+
created_at: { [Op.between]: [startTime, endTime] },
|
|
218
218
|
},
|
|
219
|
-
},
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
where: {
|
|
225
|
-
status: 'succeeded',
|
|
226
|
-
created_at: {
|
|
227
|
-
[Op.between]: [startTime, endTime],
|
|
219
|
+
}),
|
|
220
|
+
Refund.findAll({
|
|
221
|
+
where: {
|
|
222
|
+
status: 'succeeded',
|
|
223
|
+
created_at: { [Op.between]: [startTime, endTime] },
|
|
228
224
|
},
|
|
229
|
-
},
|
|
230
|
-
|
|
225
|
+
}),
|
|
226
|
+
]);
|
|
231
227
|
|
|
232
228
|
// Aggregate total revenue by currency
|
|
233
229
|
const revenueByCurrency = new Map<string, BN>();
|
|
@@ -282,33 +278,18 @@ export class HealthReportTemplate implements BaseEmailTemplate<HealthReportConte
|
|
|
282
278
|
const startTime = dayjs.unix(start).toISOString();
|
|
283
279
|
const endTime = dayjs.unix(end).toISOString();
|
|
284
280
|
|
|
285
|
-
//
|
|
286
|
-
const newSubscriptions = await
|
|
287
|
-
|
|
288
|
-
created_at: {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
canceled_at: {
|
|
298
|
-
[Op.between]: [start, end],
|
|
299
|
-
},
|
|
300
|
-
},
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
// Subscriptions that became past_due in the last 24 hours
|
|
304
|
-
const pastDueSubscriptions = await Subscription.count({
|
|
305
|
-
where: {
|
|
306
|
-
status: 'past_due',
|
|
307
|
-
updated_at: {
|
|
308
|
-
[Op.between]: [startTime, endTime],
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
});
|
|
281
|
+
// All 3 counts are independent — run in parallel
|
|
282
|
+
const [newSubscriptions, canceledSubscriptions, pastDueSubscriptions] = await Promise.all([
|
|
283
|
+
Subscription.count({
|
|
284
|
+
where: { created_at: { [Op.between]: [startTime, endTime] } },
|
|
285
|
+
}),
|
|
286
|
+
Subscription.count({
|
|
287
|
+
where: { canceled_at: { [Op.between]: [start, end] } },
|
|
288
|
+
}),
|
|
289
|
+
Subscription.count({
|
|
290
|
+
where: { status: 'past_due', updated_at: { [Op.between]: [startTime, endTime] } },
|
|
291
|
+
}),
|
|
292
|
+
]);
|
|
312
293
|
|
|
313
294
|
return {
|
|
314
295
|
newSubscriptions,
|
|
@@ -405,26 +386,22 @@ export class HealthReportTemplate implements BaseEmailTemplate<HealthReportConte
|
|
|
405
386
|
const startTime = dayjs.unix(start).toISOString();
|
|
406
387
|
const endTime = dayjs.unix(end).toISOString();
|
|
407
388
|
|
|
408
|
-
// Successful payments
|
|
409
|
-
const succeededPayments = await PaymentIntent.findAll({
|
|
410
|
-
where: {
|
|
411
|
-
status: 'succeeded',
|
|
412
|
-
created_at: {
|
|
413
|
-
[Op.between]: [startTime, endTime],
|
|
414
|
-
},
|
|
415
|
-
},
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
// Failed payments (canceled status)
|
|
389
|
+
// Successful + failed payments in parallel
|
|
419
390
|
const failedStatuses = ['canceled', 'requires_payment_method', 'requires_action'];
|
|
420
|
-
const failedPayments = await
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
[Op.between]: [startTime, endTime],
|
|
391
|
+
const [succeededPayments, failedPayments] = await Promise.all([
|
|
392
|
+
PaymentIntent.findAll({
|
|
393
|
+
where: {
|
|
394
|
+
status: 'succeeded',
|
|
395
|
+
created_at: { [Op.between]: [startTime, endTime] },
|
|
425
396
|
},
|
|
426
|
-
},
|
|
427
|
-
|
|
397
|
+
}),
|
|
398
|
+
PaymentIntent.findAll({
|
|
399
|
+
where: {
|
|
400
|
+
status: { [Op.in]: failedStatuses },
|
|
401
|
+
created_at: { [Op.between]: [startTime, endTime] },
|
|
402
|
+
},
|
|
403
|
+
}),
|
|
404
|
+
]);
|
|
428
405
|
|
|
429
406
|
const succeededCount = succeededPayments.length;
|
|
430
407
|
const failedCount = failedPayments.length;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Backstop for the event → webhook fire-and-forget path.
|
|
2
|
+
//
|
|
3
|
+
// createEvent emits 'event.created' (sync) → an async listener calls
|
|
4
|
+
// handleEvent → schedules HTTP webhook delivery. On CF Workers the listener's
|
|
5
|
+
// microtask can lose the worker before its waitUntil registers if it fires
|
|
6
|
+
// from the last line of an HTTP handler (e.g. ingestVerifiedGooglePlayPurchase
|
|
7
|
+
// ends with `createEvent('subscription.started', ...).catch(...)` right before
|
|
8
|
+
// returning — the response flushes and runtime may stop draining new
|
|
9
|
+
// waitUntils). Result: events row exists with pending_webhooks>0 but no
|
|
10
|
+
// webhook_attempt is ever written, so downstream apps silently miss the
|
|
11
|
+
// notification.
|
|
12
|
+
//
|
|
13
|
+
// This cron rescans for pending events older than the realtime grace window
|
|
14
|
+
// and re-invokes handleEvent. Stripe et al ship the same belt-and-suspenders.
|
|
15
|
+
|
|
16
|
+
import { Op } from 'sequelize';
|
|
17
|
+
|
|
18
|
+
import logger from '../libs/logger';
|
|
19
|
+
import { eventQueue } from '../queues/event';
|
|
20
|
+
import { Event } from '../store/models/event';
|
|
21
|
+
|
|
22
|
+
const REALTIME_GRACE_SECONDS = 60;
|
|
23
|
+
// Each cron tick only enqueues, does NOT inline await handleEvent. CF Workers
|
|
24
|
+
// scheduled handler has a tight CPU budget (~30s) — inline handling N events
|
|
25
|
+
// blew past it after ~7 iterations and was cut off mid-batch, so the tail
|
|
26
|
+
// never got processed. Pushing to eventQueue lets the consumer (which has a
|
|
27
|
+
// looser per-message budget) work through them asynchronously.
|
|
28
|
+
//
|
|
29
|
+
// BATCH_LIMIT must stay small: even push-only, the scheduled handler awaits
|
|
30
|
+
// flushPendingJobs() at the end which await's each push's enqueue promise
|
|
31
|
+
// (D1 addJob + CF Queue send). 50 was too many — pushes never finished
|
|
32
|
+
// sending, jobs table never got rows, handleEvent never ran. 5 leaves
|
|
33
|
+
// generous headroom and we still drain the backlog over a few minutes.
|
|
34
|
+
const BATCH_LIMIT = 5;
|
|
35
|
+
|
|
36
|
+
export async function retryPendingEvents(): Promise<void> {
|
|
37
|
+
const threshold = new Date(Date.now() - REALTIME_GRACE_SECONDS * 1000);
|
|
38
|
+
const docs = await Event.findAll({
|
|
39
|
+
where: {
|
|
40
|
+
pending_webhooks: { [Op.gt]: 0 },
|
|
41
|
+
created_at: { [Op.lt]: threshold },
|
|
42
|
+
},
|
|
43
|
+
attributes: ['id'],
|
|
44
|
+
order: [['created_at', 'ASC']],
|
|
45
|
+
limit: BATCH_LIMIT,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (docs.length === 0) return;
|
|
49
|
+
logger.info(`event.retry: enqueuing ${docs.length} pending events older than ${REALTIME_GRACE_SECONDS}s`);
|
|
50
|
+
|
|
51
|
+
for (const doc of docs) {
|
|
52
|
+
try {
|
|
53
|
+
eventQueue.push({ id: doc.id, job: { eventId: doc.id }, persist: false });
|
|
54
|
+
} catch (err: any) {
|
|
55
|
+
logger.error('event.retry enqueue failed', { eventId: doc.id, error: err?.message });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Apple Root Certificates for App Store JWS verification.
|
|
2
|
+
//
|
|
3
|
+
// Vendored as base64 constants so `tsc` compiles cleanly without copying
|
|
4
|
+
// non-ts assets into dist/. Source files (DER-encoded `.cer`) live under
|
|
5
|
+
// `./certs/` and were downloaded from https://www.apple.com/certificateauthority/
|
|
6
|
+
//
|
|
7
|
+
// Apple's IAP JWS signing chain currently terminates at one of these roots.
|
|
8
|
+
// Apple rotates roots roughly every 25 years — refresh when they do.
|
|
9
|
+
// - Apple Inc. Root Certificate (RSA 2048, valid 2006-04 → 2035-02)
|
|
10
|
+
// - Apple Root CA - G2 (RSA 4096, valid 2014-04 → 2039-04)
|
|
11
|
+
// - Apple Root CA - G3 (ECC P-384, valid 2014-04 → 2039-04)
|
|
12
|
+
|
|
13
|
+
const APPLE_INC_ROOT_B64 =
|
|
14
|
+
'MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJBzewdXUh';
|
|
15
|
+
|
|
16
|
+
const APPLE_ROOT_CA_G2_B64 =
|
|
17
|
+
'MIIFkjCCA3qgAwIBAgIIAeDltYNno+AwDQYJKoZIhvcNAQEMBQAwZzEbMBkGA1UEAwwSQXBwbGUgUm9vdCBDQSAtIEcyMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcNMTQwNDMwMTgxMDA5WhcNMzkwNDMwMTgxMDA5WjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzIxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANgREkhI2imKScUcx+xuM23+TfvgHN6sXuI2pyT5f1BrTM65MFQn5bPW7SXmMLYFN14UIhHF6Kob0vuy0gmVOKTvKkmMXT5xZgM4+xb1hYjkWpIMBDLyyED7Ul+f9sDx47pFoFDVEovy3d6RhiPw9bZyLgHaC/YuOQhfGaFjQQscp5TBhsRTL3b2CtcM0YM/GlMZ81fVJ3/8E7j4ko380yhDPLVoACVdJ2LT3VXdRCCQgzWTxb+4Gftr49wIQuavbfqeQMpOhYV4SbHXw8EwOTKrfl+q04tvny0aIWhwZ7Oj8ZhBbZF8+NfbqOdfIRqMM78xdLe40fTgIvS/cjTf94FNcX1RoeKz8NMoFnNvzcytN31O661A4T+B/fc9Cj6i8b0xlilZ3MIZgIxbdMYs0xBTJh0UT8TUgWY8h2czJxQI6bR3hDRSj4n4aJgXv8O7qhOTH11UL6jHfPsNFL4VPSQ08prcdUFmIrQB1guvkJ4M6mL4m1k8COKWNORj3rw31OsMiANDC1CvoDTdUE0V+1ok2Az6DGOeHwOx4e7hqkP0ZmUoNwIx7wHHHtHMn23KVDpA287PT0aLSmWaasZobNfMmRtHsHLDd4/E92GcdB/O/WuhwpyUgquUoue9G7q5cDmVF8Up8zlYNPXEpMZ7YLlmQ1A/bmH8DvmGqmAMQ0uVAgMBAAGjQjBAMB0GA1UdDgQWBBTEmRNsGAPCe8CjoA1/coB6HHcmjTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAUabz4vS4PZO/Lc4Pu1vhVRROTtHlznldgX/+tvCHM/jvlOV+3Gp5pxy+8JS3ptEwnMgNCnWefZKVfhidfsJxaXwU6s+DDuQUQp50DhDNqxq6EWGBeNjxtUVAeKuowM77fWM3aPbn+6/Gw0vsHzYmE1SGlHKy6gLti23kDKaQwFd1z4xCfVzmMX3zybKSaUYOiPjjLUKyOKimGY3xn83uamW8GrAlvacp/fQ+onVJv57byfenHmOZ4VxG/5IFjPoeIPmGlFYl5bRXOJ3riGQUIUkhOb9iZqmxospvPyFgxYnURTbImHy99v6ZSYA7LNKmp4gDBDEZt7Y6YUX6yfIjyGNzv1aJMbDZfGKnexWoiIqrOEDCzBL/FePwN983csvMmOa/orz6JopxVtfnJBtIRD6e/J/JzBrsQzwBvDR4yGn1xuZW7AYJNpDrFEobXsmII9oDMJELuDY++ee1KG++P+w8j2Ud5cAeh6Squpj9kuNsJnfdBrRkBof0Tta6SqoWqPQFZ2aWuuJVecMsXUmPgEkrihLHdoBR37q9ZV0+N0djMenl9MU/S60EinpxLK8JQzcPqOMyT/RFtm2XNuyE9QoB6he7hY1Ck3DDUOUUi78/w0EP3SIEIwiKum1xRKtzCTrJ+VKACd+66eYWyi4uTLLT3OUEVLLUNIAytbwPF+E=';
|
|
18
|
+
|
|
19
|
+
const APPLE_ROOT_CA_G3_B64 =
|
|
20
|
+
'MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwSQXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcNMTQwNDMwMTgxOTA2WhcNMzkwNDMwMTgxOTA2WjBnMRswGQYDVQQDDBJBcHBsZSBSb290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzB2MBAGByqGSM49AgEGBSuBBAAiA2IABJjpLz1AcqTtkyJygRMc3RCV8cWjTnHcFBbZDuWmBSp3ZHtfTjjTuxxEtX/1H7YyYl3J6YRbTzBPEVoA/VhYDKX1DyxNB0cTddqXl5dvMVztK517IDvYuVTZXpmkOlEKMaNCMEAwHQYDVR0OBBYEFLuw3qFYM4iapIqZ3r6966/ayySrMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMQCD6cHEFl4aXTQY2e3v9GwOAEZLuN+yRhHFD/3meoyhpmvOwgPUnPWTxnS4at+qIxUCMG1mihDK1A3UT82NQz60imOlM27jbdoXt2QfyFMm+YhidDkLF1vLUagM6BgD56KyKA==';
|
|
21
|
+
|
|
22
|
+
export const APPLE_ROOT_CERTS: Buffer[] = [
|
|
23
|
+
Buffer.from(APPLE_INC_ROOT_B64, 'base64'),
|
|
24
|
+
Buffer.from(APPLE_ROOT_CA_G2_B64, 'base64'),
|
|
25
|
+
Buffer.from(APPLE_ROOT_CA_G3_B64, 'base64'),
|
|
26
|
+
];
|