payment-kit 1.28.0 → 1.29.1
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/api/src/crons/index.ts +22 -0
- 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/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/libs/audit.ts +38 -8
- package/api/src/libs/entitlement.ts +399 -0
- package/api/src/libs/env.ts +2 -0
- package/api/src/libs/security.ts +51 -0
- package/api/src/libs/subscription.ts +13 -1
- package/api/src/libs/util.ts +13 -0
- package/api/src/queues/event.ts +25 -19
- package/api/src/queues/webhook.ts +12 -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/payment-methods.ts +130 -0
- package/api/src/store/migrations/20260526-iap-foundation.ts +105 -0
- package/api/src/store/models/customer.ts +14 -0
- 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 +62 -1
- 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/blocklet.yml +1 -1
- package/cloudflare/docs/2026-06-10-bundle-size-analysis.md +288 -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 +23 -1
- package/cloudflare/shims/blocklet-sdk/verify-session.ts +44 -0
- package/cloudflare/shims/node-fetch.ts +35 -0
- package/cloudflare/shims/queue.ts +28 -2
- package/cloudflare/shims/sequelize-d1/model.ts +19 -0
- package/cloudflare/shims/sequelize-d1/operators.ts +14 -1
- package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +87 -0
- package/cloudflare/worker.ts +59 -4
- package/cloudflare/wrangler.jsonc +7 -1
- package/cloudflare/wrangler.staging.json +2 -1
- package/package.json +10 -6
- package/scripts/seed-google-play.ts +79 -0
- 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/subscription/list.tsx +20 -0
- package/src/locales/en.tsx +63 -0
- package/src/locales/zh.tsx +63 -0
- package/src/pages/admin/billing/subscriptions/detail.tsx +80 -0
- package/src/pages/admin/customers/customers/detail.tsx +6 -0
- package/src/pages/admin/settings/payment-methods/create.tsx +12 -0
- package/src/pages/admin/settings/payment-methods/index.tsx +1 -1
package/src/locales/en.tsx
CHANGED
|
@@ -1310,6 +1310,55 @@ export default flat({
|
|
|
1310
1310
|
tip: 'Number of blocks required since transaction execution',
|
|
1311
1311
|
},
|
|
1312
1312
|
},
|
|
1313
|
+
google_play: {
|
|
1314
|
+
package_name: {
|
|
1315
|
+
label: 'Package Name',
|
|
1316
|
+
tip: 'e.g. com.example.app, configured in Play Console',
|
|
1317
|
+
},
|
|
1318
|
+
service_account_json: {
|
|
1319
|
+
label: 'Service Account JSON',
|
|
1320
|
+
tip: 'Paste the full service account credentials JSON downloaded from Google Cloud Console',
|
|
1321
|
+
invalidJson: 'Not valid JSON',
|
|
1322
|
+
missingFields: 'JSON is missing client_email or private_key',
|
|
1323
|
+
detectedClient: 'Detected client',
|
|
1324
|
+
},
|
|
1325
|
+
pubsub_topic_name: {
|
|
1326
|
+
label: 'Pub/Sub Topic (optional)',
|
|
1327
|
+
tip: 'projects/<project-id>/topics/<topic-name>, for receiving RTDN',
|
|
1328
|
+
},
|
|
1329
|
+
},
|
|
1330
|
+
app_store: {
|
|
1331
|
+
bundle_id: {
|
|
1332
|
+
label: 'Bundle ID',
|
|
1333
|
+
tip: 'e.g. com.example.app, configured in App Store Connect',
|
|
1334
|
+
},
|
|
1335
|
+
environment: {
|
|
1336
|
+
label: 'Environment',
|
|
1337
|
+
tip: 'StoreKit 2 JWS environment must match this setting',
|
|
1338
|
+
production: 'Production',
|
|
1339
|
+
sandbox: 'Sandbox',
|
|
1340
|
+
},
|
|
1341
|
+
shared_secret: {
|
|
1342
|
+
label: 'Shared Secret (for StoreKit 1)',
|
|
1343
|
+
tip: 'App-Specific Shared Secret for legacy receipt verification. Not needed for StoreKit 2 JWS. Find it at App Store Connect → App Information → App-Specific Shared Secret',
|
|
1344
|
+
},
|
|
1345
|
+
serverApi: {
|
|
1346
|
+
heading: 'Server API Credentials (optional)',
|
|
1347
|
+
tip: 'Not required for StoreKit 2 JWS verification. Only needed when querying App Store Server API for subscription status. Provide all three or none.',
|
|
1348
|
+
},
|
|
1349
|
+
issuer_id: {
|
|
1350
|
+
label: 'Issuer ID',
|
|
1351
|
+
tip: 'App Store Connect API Issuer ID',
|
|
1352
|
+
},
|
|
1353
|
+
key_id: {
|
|
1354
|
+
label: 'Key ID',
|
|
1355
|
+
tip: 'App Store Connect API Key ID',
|
|
1356
|
+
},
|
|
1357
|
+
private_key_pem: {
|
|
1358
|
+
label: 'Private Key (.p8 contents)',
|
|
1359
|
+
tip: 'Paste the contents of the .p8 file downloaded from App Store Connect',
|
|
1360
|
+
},
|
|
1361
|
+
},
|
|
1313
1362
|
},
|
|
1314
1363
|
paymentCurrency: {
|
|
1315
1364
|
name: 'Payment Currency',
|
|
@@ -1484,7 +1533,19 @@ export default flat({
|
|
|
1484
1533
|
attention: 'Past due subscriptions',
|
|
1485
1534
|
product: 'Product',
|
|
1486
1535
|
collectionMethod: 'Billing',
|
|
1536
|
+
channel: 'Channel',
|
|
1487
1537
|
currentPeriod: 'Current Period',
|
|
1538
|
+
iap: {
|
|
1539
|
+
googlePlayTitle: 'Google Play Purchase',
|
|
1540
|
+
appStoreTitle: 'App Store Purchase',
|
|
1541
|
+
purchaseToken: 'Purchase Token',
|
|
1542
|
+
orderId: 'Order ID',
|
|
1543
|
+
productId: 'Product ID',
|
|
1544
|
+
originalTransactionId: 'Original Transaction ID',
|
|
1545
|
+
transactionId: 'Transaction ID',
|
|
1546
|
+
expiryTime: 'Expires At',
|
|
1547
|
+
environment: 'Environment',
|
|
1548
|
+
},
|
|
1488
1549
|
trialingPeriod: 'Trial Period',
|
|
1489
1550
|
trialEnd: 'Trial ends {prefix} {date}',
|
|
1490
1551
|
willEnd: 'Will end {prefix} {date}',
|
|
@@ -1605,6 +1666,8 @@ export default flat({
|
|
|
1605
1666
|
email: 'Email',
|
|
1606
1667
|
phone: 'Phone',
|
|
1607
1668
|
invoicePrefix: 'Invoice Prefix',
|
|
1669
|
+
googlePlayUuid: 'Google Play UUID',
|
|
1670
|
+
appStoreUuid: 'App Store UUID',
|
|
1608
1671
|
balance: 'Balance ({currency})',
|
|
1609
1672
|
summary: {
|
|
1610
1673
|
refund: 'Refunds',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -1265,6 +1265,55 @@ export default flat({
|
|
|
1265
1265
|
tip: '交易标记为确认需要的区块数',
|
|
1266
1266
|
},
|
|
1267
1267
|
},
|
|
1268
|
+
google_play: {
|
|
1269
|
+
package_name: {
|
|
1270
|
+
label: '应用包名',
|
|
1271
|
+
tip: '例如 com.example.app,在 Play Console 中配置',
|
|
1272
|
+
},
|
|
1273
|
+
service_account_json: {
|
|
1274
|
+
label: '服务账号 JSON',
|
|
1275
|
+
tip: '从 Google Cloud Console 下载的 service account credentials JSON,整段粘贴',
|
|
1276
|
+
invalidJson: '不是合法的 JSON',
|
|
1277
|
+
missingFields: 'JSON 缺少 client_email 或 private_key 字段',
|
|
1278
|
+
detectedClient: '识别到客户端',
|
|
1279
|
+
},
|
|
1280
|
+
pubsub_topic_name: {
|
|
1281
|
+
label: 'Pub/Sub 主题名(可选)',
|
|
1282
|
+
tip: 'projects/<project-id>/topics/<topic-name>,用于接收 RTDN',
|
|
1283
|
+
},
|
|
1284
|
+
},
|
|
1285
|
+
app_store: {
|
|
1286
|
+
bundle_id: {
|
|
1287
|
+
label: '应用 Bundle ID',
|
|
1288
|
+
tip: '例如 com.example.app,在 App Store Connect 中配置',
|
|
1289
|
+
},
|
|
1290
|
+
environment: {
|
|
1291
|
+
label: '环境',
|
|
1292
|
+
tip: 'StoreKit 2 JWS 中携带的 environment 必须与此匹配',
|
|
1293
|
+
production: '正式环境',
|
|
1294
|
+
sandbox: '沙盒环境',
|
|
1295
|
+
},
|
|
1296
|
+
shared_secret: {
|
|
1297
|
+
label: 'Shared Secret(StoreKit 1 用)',
|
|
1298
|
+
tip: 'App-Specific Shared Secret,用于 legacy receipt 校验。StoreKit 2 JWS 不需要。在 App Store Connect → App Information → App-Specific Shared Secret',
|
|
1299
|
+
},
|
|
1300
|
+
serverApi: {
|
|
1301
|
+
heading: 'Server API 凭据(可选)',
|
|
1302
|
+
tip: 'StoreKit 2 JWS 校验不需要凭据;只在调用 App Store Server API 查询订阅状态时使用。三项需同时填写。',
|
|
1303
|
+
},
|
|
1304
|
+
issuer_id: {
|
|
1305
|
+
label: 'Issuer ID',
|
|
1306
|
+
tip: 'App Store Connect API 的 Issuer ID',
|
|
1307
|
+
},
|
|
1308
|
+
key_id: {
|
|
1309
|
+
label: 'Key ID',
|
|
1310
|
+
tip: 'App Store Connect API 的 Key ID',
|
|
1311
|
+
},
|
|
1312
|
+
private_key_pem: {
|
|
1313
|
+
label: '私钥 (.p8 内容)',
|
|
1314
|
+
tip: '从 App Store Connect 下载的 .p8 文件内容,整段粘贴',
|
|
1315
|
+
},
|
|
1316
|
+
},
|
|
1268
1317
|
},
|
|
1269
1318
|
paymentCurrency: {
|
|
1270
1319
|
name: '支付货币',
|
|
@@ -1452,7 +1501,19 @@ export default flat({
|
|
|
1452
1501
|
product: '产品',
|
|
1453
1502
|
attention: '将过期的订阅',
|
|
1454
1503
|
collectionMethod: '计费',
|
|
1504
|
+
channel: '渠道',
|
|
1455
1505
|
currentPeriod: '当前周期',
|
|
1506
|
+
iap: {
|
|
1507
|
+
googlePlayTitle: 'Google Play 订阅详情',
|
|
1508
|
+
appStoreTitle: 'App Store 订阅详情',
|
|
1509
|
+
purchaseToken: '购买令牌',
|
|
1510
|
+
orderId: '订单号',
|
|
1511
|
+
productId: '产品 ID',
|
|
1512
|
+
originalTransactionId: '原始交易 ID',
|
|
1513
|
+
transactionId: '本次交易 ID',
|
|
1514
|
+
expiryTime: '过期时间',
|
|
1515
|
+
environment: '环境',
|
|
1516
|
+
},
|
|
1456
1517
|
trialingPeriod: '试用期',
|
|
1457
1518
|
trialEnd: '试用期结束于 {date}',
|
|
1458
1519
|
willEnd: '将于 {date} 结束',
|
|
@@ -1569,6 +1630,8 @@ export default flat({
|
|
|
1569
1630
|
email: '电子邮件',
|
|
1570
1631
|
phone: '电话',
|
|
1571
1632
|
invoicePrefix: '账单前缀',
|
|
1633
|
+
googlePlayUuid: 'Google Play UUID',
|
|
1634
|
+
appStoreUuid: 'App Store UUID',
|
|
1572
1635
|
balance: '余额 ({currency})',
|
|
1573
1636
|
summary: {
|
|
1574
1637
|
refund: '退款金额',
|
|
@@ -335,6 +335,86 @@ export default function SubscriptionDetail(props: { id: string }) {
|
|
|
335
335
|
)}
|
|
336
336
|
</InfoRowGroup>
|
|
337
337
|
</Box>
|
|
338
|
+
|
|
339
|
+
{/* IAP details — Google Play / App Store */}
|
|
340
|
+
{(data.payment_details?.google_play || data.payment_details?.app_store) && (
|
|
341
|
+
<>
|
|
342
|
+
<Divider />
|
|
343
|
+
<Box className="section" sx={{ containerType: 'inline-size' }}>
|
|
344
|
+
<SectionHeader
|
|
345
|
+
title={
|
|
346
|
+
data.payment_details?.google_play
|
|
347
|
+
? t('admin.subscription.iap.googlePlayTitle')
|
|
348
|
+
: t('admin.subscription.iap.appStoreTitle')
|
|
349
|
+
}
|
|
350
|
+
/>
|
|
351
|
+
<InfoRowGroup
|
|
352
|
+
sx={{
|
|
353
|
+
'.info-row-value': { wordBreak: 'break-all' },
|
|
354
|
+
}}>
|
|
355
|
+
{(data as any).channel && (
|
|
356
|
+
<InfoRow label={t('admin.subscription.channel')} value={(data as any).channel} />
|
|
357
|
+
)}
|
|
358
|
+
{data.payment_details?.google_play && (
|
|
359
|
+
<>
|
|
360
|
+
<InfoRow
|
|
361
|
+
label={t('admin.subscription.iap.purchaseToken')}
|
|
362
|
+
value={data.payment_details.google_play.purchase_token}
|
|
363
|
+
/>
|
|
364
|
+
<InfoRow
|
|
365
|
+
label={t('admin.subscription.iap.orderId')}
|
|
366
|
+
value={data.payment_details.google_play.order_id || '—'}
|
|
367
|
+
/>
|
|
368
|
+
<InfoRow
|
|
369
|
+
label={t('admin.subscription.iap.productId')}
|
|
370
|
+
value={data.payment_details.google_play.product_id}
|
|
371
|
+
/>
|
|
372
|
+
<InfoRow
|
|
373
|
+
label={t('admin.subscription.iap.expiryTime')}
|
|
374
|
+
value={
|
|
375
|
+
data.payment_details.google_play.expiry_time_millis
|
|
376
|
+
? formatTime(Number(data.payment_details.google_play.expiry_time_millis))
|
|
377
|
+
: '—'
|
|
378
|
+
}
|
|
379
|
+
/>
|
|
380
|
+
<InfoRow
|
|
381
|
+
label={t('admin.subscription.iap.environment')}
|
|
382
|
+
value={data.payment_details.google_play.environment || (data as any).environment || '—'}
|
|
383
|
+
/>
|
|
384
|
+
</>
|
|
385
|
+
)}
|
|
386
|
+
{data.payment_details?.app_store && (
|
|
387
|
+
<>
|
|
388
|
+
<InfoRow
|
|
389
|
+
label={t('admin.subscription.iap.originalTransactionId')}
|
|
390
|
+
value={data.payment_details.app_store.original_transaction_id}
|
|
391
|
+
/>
|
|
392
|
+
<InfoRow
|
|
393
|
+
label={t('admin.subscription.iap.transactionId')}
|
|
394
|
+
value={data.payment_details.app_store.transaction_id || '—'}
|
|
395
|
+
/>
|
|
396
|
+
<InfoRow
|
|
397
|
+
label={t('admin.subscription.iap.productId')}
|
|
398
|
+
value={data.payment_details.app_store.product_id}
|
|
399
|
+
/>
|
|
400
|
+
<InfoRow
|
|
401
|
+
label={t('admin.subscription.iap.expiryTime')}
|
|
402
|
+
value={
|
|
403
|
+
data.payment_details.app_store.expires_at
|
|
404
|
+
? formatTime(data.payment_details.app_store.expires_at * 1000)
|
|
405
|
+
: '—'
|
|
406
|
+
}
|
|
407
|
+
/>
|
|
408
|
+
<InfoRow
|
|
409
|
+
label={t('admin.subscription.iap.environment')}
|
|
410
|
+
value={data.payment_details.app_store.environment || (data as any).environment || '—'}
|
|
411
|
+
/>
|
|
412
|
+
</>
|
|
413
|
+
)}
|
|
414
|
+
</InfoRowGroup>
|
|
415
|
+
</Box>
|
|
416
|
+
</>
|
|
417
|
+
)}
|
|
338
418
|
<Divider />
|
|
339
419
|
{/* Discount Information */}
|
|
340
420
|
{(data as any).discountStats && (
|
|
@@ -349,6 +349,12 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
349
349
|
<InfoRow label={t('common.createdAt')} value={formatTime(data.customer.created_at)} />
|
|
350
350
|
|
|
351
351
|
<InfoRow label={t('admin.customer.invoicePrefix')} value={data.customer.invoice_prefix} />
|
|
352
|
+
{(data.customer as any).google_play_uuid && (
|
|
353
|
+
<InfoRow label={t('admin.customer.googlePlayUuid')} value={(data.customer as any).google_play_uuid} />
|
|
354
|
+
)}
|
|
355
|
+
{(data.customer as any).app_store_uuid && (
|
|
356
|
+
<InfoRow label={t('admin.customer.appStoreUuid')} value={(data.customer as any).app_store_uuid} />
|
|
357
|
+
)}
|
|
352
358
|
<InfoRow label={t('common.updatedAt')} value={formatTime(data.customer.updated_at)} />
|
|
353
359
|
{state.editing.customer && (
|
|
354
360
|
<EditCustomer
|
|
@@ -52,6 +52,18 @@ export default function PaymentMethodCreate() {
|
|
|
52
52
|
explorer_host: '',
|
|
53
53
|
logo: '',
|
|
54
54
|
},
|
|
55
|
+
google_play: {
|
|
56
|
+
package_name: '',
|
|
57
|
+
service_account_json: '',
|
|
58
|
+
pubsub_topic_name: '',
|
|
59
|
+
},
|
|
60
|
+
app_store: {
|
|
61
|
+
bundle_id: '',
|
|
62
|
+
environment: 'sandbox',
|
|
63
|
+
issuer_id: '',
|
|
64
|
+
key_id: '',
|
|
65
|
+
private_key_pem: '',
|
|
66
|
+
},
|
|
55
67
|
},
|
|
56
68
|
},
|
|
57
69
|
});
|
|
@@ -453,7 +453,7 @@ export default function PaymentMethods() {
|
|
|
453
453
|
addons={
|
|
454
454
|
<>
|
|
455
455
|
<Switch checked={method.active} disabled sx={{ cursor: 'default' }} />
|
|
456
|
-
{
|
|
456
|
+
{!['arcblock', 'google_play', 'app_store'].includes(method.type) && (
|
|
457
457
|
<IconButton
|
|
458
458
|
onClick={(e) => {
|
|
459
459
|
e.stopPropagation();
|