payment-kit 1.20.5 → 1.20.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.
- package/api/src/crons/index.ts +11 -3
- package/api/src/index.ts +18 -14
- package/api/src/libs/env.ts +7 -0
- package/api/src/libs/url.ts +77 -0
- package/api/src/libs/vendor/adapters/factory.ts +40 -0
- package/api/src/libs/vendor/adapters/launcher-adapter.ts +179 -0
- package/api/src/libs/vendor/adapters/types.ts +91 -0
- package/api/src/libs/vendor/fulfillment.ts +317 -0
- package/api/src/queues/payment.ts +14 -10
- package/api/src/queues/payout.ts +1 -0
- package/api/src/queues/vendor/commission.ts +192 -0
- package/api/src/queues/vendor/fulfillment-coordinator.ts +625 -0
- package/api/src/queues/vendor/fulfillment.ts +98 -0
- package/api/src/queues/vendor/status-check.ts +178 -0
- package/api/src/routes/checkout-sessions.ts +12 -0
- package/api/src/routes/index.ts +2 -0
- package/api/src/routes/products.ts +72 -1
- package/api/src/routes/vendor.ts +527 -0
- package/api/src/store/migrations/20250820-add-product-vendor.ts +102 -0
- package/api/src/store/migrations/20250822-add-vendor-config-to-products.ts +56 -0
- package/api/src/store/models/checkout-session.ts +84 -18
- package/api/src/store/models/index.ts +3 -0
- package/api/src/store/models/payout.ts +11 -0
- package/api/src/store/models/product-vendor.ts +118 -0
- package/api/src/store/models/product.ts +15 -0
- package/blocklet.yml +8 -2
- package/doc/vendor_fulfillment_system.md +929 -0
- package/package.json +5 -4
- package/src/components/collapse.tsx +1 -0
- package/src/components/product/edit.tsx +9 -0
- package/src/components/product/form.tsx +11 -0
- package/src/components/product/vendor-config.tsx +249 -0
- package/src/components/vendor/actions.tsx +145 -0
- package/src/locales/en.tsx +89 -0
- package/src/locales/zh.tsx +89 -0
- package/src/pages/admin/products/index.tsx +11 -1
- package/src/pages/admin/products/products/detail.tsx +79 -2
- package/src/pages/admin/products/vendors/create.tsx +418 -0
- package/src/pages/admin/products/vendors/index.tsx +313 -0
package/api/src/crons/index.ts
CHANGED
|
@@ -18,17 +18,19 @@ import {
|
|
|
18
18
|
stripePaymentCronTime,
|
|
19
19
|
stripeSubscriptionCronTime,
|
|
20
20
|
subscriptionCronTime,
|
|
21
|
+
vendorStatusCheckCronTime,
|
|
21
22
|
} from '../libs/env';
|
|
22
23
|
import logger from '../libs/logger';
|
|
24
|
+
import { startCreditConsumeQueue } from '../queues/credit-consume';
|
|
25
|
+
import { startDepositVaultQueue } from '../queues/payment';
|
|
23
26
|
import { startSubscriptionQueue } from '../queues/subscription';
|
|
27
|
+
import { startVendorStatusCheckSchedule } from '../queues/vendor/status-check';
|
|
24
28
|
import { CheckoutSession } from '../store/models';
|
|
29
|
+
import { createMeteringSubscriptionDetection } from './metering-subscription-detection';
|
|
25
30
|
import { createPaymentStat } from './payment-stat';
|
|
26
31
|
import { SubscriptionTrialWillEndSchedule } from './subscription-trial-will-end';
|
|
27
32
|
import { SubscriptionWillCanceledSchedule } from './subscription-will-canceled';
|
|
28
33
|
import { SubscriptionWillRenewSchedule } from './subscription-will-renew';
|
|
29
|
-
import { createMeteringSubscriptionDetection } from './metering-subscription-detection';
|
|
30
|
-
import { startDepositVaultQueue } from '../queues/payment';
|
|
31
|
-
import { startCreditConsumeQueue } from '../queues/credit-consume';
|
|
32
34
|
|
|
33
35
|
function init() {
|
|
34
36
|
Cron.init({
|
|
@@ -115,6 +117,12 @@ function init() {
|
|
|
115
117
|
fn: () => startCreditConsumeQueue(),
|
|
116
118
|
options: { runOnInit: true },
|
|
117
119
|
},
|
|
120
|
+
{
|
|
121
|
+
name: 'vendor.status.check',
|
|
122
|
+
time: vendorStatusCheckCronTime,
|
|
123
|
+
fn: () => startVendorStatusCheckSchedule(),
|
|
124
|
+
options: { runOnInit: false },
|
|
125
|
+
},
|
|
118
126
|
],
|
|
119
127
|
onError: (error: Error, name: string) => {
|
|
120
128
|
logger.error('run job failed', { name, error });
|
package/api/src/index.ts
CHANGED
|
@@ -8,48 +8,50 @@ import cors from 'cors';
|
|
|
8
8
|
import dotenv from 'dotenv-flow';
|
|
9
9
|
import express, { ErrorRequestHandler } from 'express';
|
|
10
10
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
11
|
-
import { xss } from '@blocklet/xss';
|
|
12
|
-
import { csrf } from '@blocklet/sdk/lib/middlewares';
|
|
13
11
|
import { CustomError, formatError, getStatusFromError } from '@blocklet/error';
|
|
12
|
+
import { csrf } from '@blocklet/sdk/lib/middlewares';
|
|
13
|
+
import { xss } from '@blocklet/xss';
|
|
14
14
|
|
|
15
|
+
import { syncCurrencyLogo } from './crons/currency';
|
|
15
16
|
import crons from './crons/index';
|
|
16
17
|
import { ensureStakedForGas } from './integrations/arcblock/stake';
|
|
17
18
|
import { initResourceHandler } from './integrations/blocklet/resource';
|
|
19
|
+
import { initUserHandler } from './integrations/blocklet/user';
|
|
18
20
|
import { ensureWebhookRegistered } from './integrations/stripe/setup';
|
|
19
21
|
import { handlers } from './libs/auth';
|
|
20
22
|
import logger, { setupAccessLogger } from './libs/logger';
|
|
21
23
|
import { contextMiddleware, ensureI18n } from './libs/middleware';
|
|
24
|
+
import { ensureCreateOverdraftProtectionPrices } from './libs/overdraft-protection';
|
|
22
25
|
import { initEventBroadcast } from './libs/ws';
|
|
23
26
|
import { startCheckoutSessionQueue } from './queues/checkout-session';
|
|
27
|
+
import { startCreditConsumeQueue } from './queues/credit-consume';
|
|
28
|
+
import { startCreditGrantQueue } from './queues/credit-grant';
|
|
24
29
|
import { startEventQueue } from './queues/event';
|
|
25
30
|
import { startInvoiceQueue } from './queues/invoice';
|
|
26
31
|
import { startNotificationQueue } from './queues/notification';
|
|
27
32
|
import { startPaymentQueue } from './queues/payment';
|
|
28
33
|
import { startPayoutQueue } from './queues/payout';
|
|
29
34
|
import { startRefundQueue } from './queues/refund';
|
|
35
|
+
import { startUploadBillingInfoListener } from './queues/space';
|
|
30
36
|
import { startSubscriptionQueue } from './queues/subscription';
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
37
|
+
import { startVendorCommissionQueue } from './queues/vendor/commission';
|
|
38
|
+
import { startVendorFulfillmentQueue } from './queues/vendor/fulfillment';
|
|
33
39
|
import routes from './routes';
|
|
40
|
+
import autoRechargeAuthorizationHandlers from './routes/connect/auto-recharge-auth';
|
|
34
41
|
import changePaymentHandlers from './routes/connect/change-payment';
|
|
35
42
|
import changePlanHandlers from './routes/connect/change-plan';
|
|
36
43
|
import collectHandlers from './routes/connect/collect';
|
|
37
44
|
import collectBatchHandlers from './routes/connect/collect-batch';
|
|
38
|
-
import rechargeHandlers from './routes/connect/recharge';
|
|
39
|
-
import payHandlers from './routes/connect/pay';
|
|
40
|
-
import setupHandlers from './routes/connect/setup';
|
|
41
|
-
import subscribeHandlers from './routes/connect/subscribe';
|
|
42
45
|
import delegationHandlers from './routes/connect/delegation';
|
|
43
46
|
import overdraftProtectionHandlers from './routes/connect/overdraft-protection';
|
|
44
|
-
import
|
|
47
|
+
import payHandlers from './routes/connect/pay';
|
|
45
48
|
import reStakeHandlers from './routes/connect/re-stake';
|
|
46
|
-
import
|
|
49
|
+
import rechargeHandlers from './routes/connect/recharge';
|
|
50
|
+
import rechargeAccountHandlers from './routes/connect/recharge-account';
|
|
51
|
+
import setupHandlers from './routes/connect/setup';
|
|
52
|
+
import subscribeHandlers from './routes/connect/subscribe';
|
|
47
53
|
import { initialize } from './store/models';
|
|
48
54
|
import { sequelize } from './store/sequelize';
|
|
49
|
-
import { initUserHandler } from './integrations/blocklet/user';
|
|
50
|
-
import { startUploadBillingInfoListener } from './queues/space';
|
|
51
|
-
import { syncCurrencyLogo } from './crons/currency';
|
|
52
|
-
import { ensureCreateOverdraftProtectionPrices } from './libs/overdraft-protection';
|
|
53
55
|
|
|
54
56
|
dotenv.config();
|
|
55
57
|
|
|
@@ -126,6 +128,8 @@ export const server = app.listen(port, (err?: any) => {
|
|
|
126
128
|
startSubscriptionQueue().then(() => logger.info('subscription queue started'));
|
|
127
129
|
startEventQueue().then(() => logger.info('event queue started'));
|
|
128
130
|
startPayoutQueue().then(() => logger.info('payout queue started'));
|
|
131
|
+
startVendorCommissionQueue().then(() => logger.info('vendor commission queue started'));
|
|
132
|
+
startVendorFulfillmentQueue().then(() => logger.info('vendor fulfillment queue started'));
|
|
129
133
|
startCheckoutSessionQueue().then(() => logger.info('checkoutSession queue started'));
|
|
130
134
|
startNotificationQueue().then(() => logger.info('notification queue started'));
|
|
131
135
|
startRefundQueue().then(() => logger.info('refund queue started'));
|
package/api/src/libs/env.ts
CHANGED
|
@@ -14,6 +14,13 @@ export const meteringSubscriptionDetectionCronTime: string =
|
|
|
14
14
|
process.env.METERING_SUBSCRIPTION_DETECTION_CRON_TIME || '0 0 10 * * *'; // 默认每天 10:00 执行
|
|
15
15
|
export const depositVaultCronTime: string = process.env.DEPOSIT_VAULT_CRON_TIME || '0 */5 * * * *'; // 默认每 5 min 执行一次
|
|
16
16
|
export const creditConsumptionCronTime: string = process.env.CREDIT_CONSUMPTION_CRON_TIME || '0 */10 * * * *'; // 默认每 10 min 执行一次
|
|
17
|
+
export const vendorStatusCheckCronTime: string = process.env.VENDOR_STATUS_CHECK_CRON_TIME || '0 */10 * * * *'; // 默认每 10 min 执行一次
|
|
18
|
+
export const vendorTimeoutMinutes: number = process.env.VENDOR_TIMEOUT_MINUTES
|
|
19
|
+
? +process.env.VENDOR_TIMEOUT_MINUTES
|
|
20
|
+
: 10; // 默认 10 分钟超时
|
|
21
|
+
|
|
22
|
+
export const shortUrlApiKey: string = process.env.SHORT_URL_API_KEY || '';
|
|
23
|
+
|
|
17
24
|
// sequelize 配置相关
|
|
18
25
|
export const sequelizeOptionsPoolMin: number = process.env.SEQUELIZE_OPTIONS_POOL_MIN
|
|
19
26
|
? +process.env.SEQUELIZE_OPTIONS_POOL_MIN
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import logger from './logger';
|
|
2
|
+
import { shortUrlApiKey } from './env';
|
|
3
|
+
|
|
4
|
+
interface ShortUrlResponse {
|
|
5
|
+
shortUrl: string;
|
|
6
|
+
longUrl: string;
|
|
7
|
+
shortCode: string;
|
|
8
|
+
dateCreated: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ShortUrlRequest {
|
|
12
|
+
longUrl: string;
|
|
13
|
+
validUntil?: string;
|
|
14
|
+
maxVisits?: number;
|
|
15
|
+
tags?: string[];
|
|
16
|
+
shortCodeLength?: number;
|
|
17
|
+
domain?: string;
|
|
18
|
+
findIfExists?: boolean;
|
|
19
|
+
validateUrl?: boolean;
|
|
20
|
+
forwardQuery?: boolean;
|
|
21
|
+
crawlable?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function formatToShortUrl({
|
|
25
|
+
url,
|
|
26
|
+
validUntil,
|
|
27
|
+
maxVisits,
|
|
28
|
+
}: {
|
|
29
|
+
url: string;
|
|
30
|
+
validUntil?: string;
|
|
31
|
+
maxVisits?: number;
|
|
32
|
+
}): Promise<string> {
|
|
33
|
+
const apiKey = shortUrlApiKey;
|
|
34
|
+
|
|
35
|
+
if (!apiKey) {
|
|
36
|
+
return url;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const requestPayload: ShortUrlRequest = {
|
|
41
|
+
longUrl: url,
|
|
42
|
+
validUntil,
|
|
43
|
+
maxVisits,
|
|
44
|
+
tags: [],
|
|
45
|
+
shortCodeLength: 8,
|
|
46
|
+
domain: 's.abtnet.io',
|
|
47
|
+
findIfExists: true,
|
|
48
|
+
validateUrl: true,
|
|
49
|
+
forwardQuery: true,
|
|
50
|
+
crawlable: true,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const response = await fetch('https://s.abtnet.io/rest/v3/short-urls', {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: {
|
|
56
|
+
'Content-Type': 'application/json',
|
|
57
|
+
'X-Api-Key': apiKey,
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify(requestPayload),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
logger.warn('Failed to create short URL, using original URL', {
|
|
64
|
+
status: response.status,
|
|
65
|
+
statusText: response.statusText,
|
|
66
|
+
url,
|
|
67
|
+
});
|
|
68
|
+
return url;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const data: ShortUrlResponse = await response.json();
|
|
72
|
+
return data.shortUrl;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
logger.error('Error creating short URL, using original URL', { error, url });
|
|
75
|
+
return url;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { BN } from '@ocap/util';
|
|
2
|
+
import { LauncherAdapter } from './launcher-adapter';
|
|
3
|
+
import { VendorAdapter, VendorConfig } from './types';
|
|
4
|
+
|
|
5
|
+
export function calculateVendorCommission(
|
|
6
|
+
totalAmount: string,
|
|
7
|
+
commissionRate: number,
|
|
8
|
+
commissionType: 'percentage' | 'fixed_amount',
|
|
9
|
+
itemAmount?: string
|
|
10
|
+
): string {
|
|
11
|
+
const amount = itemAmount || totalAmount || '0';
|
|
12
|
+
|
|
13
|
+
if (commissionType === 'percentage') {
|
|
14
|
+
return new BN(amount)
|
|
15
|
+
.mul(new BN(commissionRate || 0))
|
|
16
|
+
.div(new BN(100))
|
|
17
|
+
.toString();
|
|
18
|
+
}
|
|
19
|
+
return new BN(commissionRate).toString();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class VendorAdapterFactory {
|
|
23
|
+
static create(vendorConfig: string | VendorConfig): VendorAdapter {
|
|
24
|
+
const vendorKey = typeof vendorConfig === 'string' ? vendorConfig : vendorConfig.vendor_key;
|
|
25
|
+
switch (vendorKey) {
|
|
26
|
+
case 'launcher':
|
|
27
|
+
return new LauncherAdapter(vendorConfig);
|
|
28
|
+
default:
|
|
29
|
+
throw new Error(`Unsupported vendor: ${vendorConfig}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static getSupportedVendors(): string[] {
|
|
34
|
+
return ['launcher'];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static isSupported(vendorKey: string): boolean {
|
|
38
|
+
return this.getSupportedVendors().includes(vendorKey);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { VendorAuth } from '@blocklet/payment-vendor';
|
|
2
|
+
|
|
3
|
+
import { ProductVendor } from '../../../store/models';
|
|
4
|
+
import logger from '../../logger';
|
|
5
|
+
import { api } from '../../util';
|
|
6
|
+
import {
|
|
7
|
+
CheckOrderStatusParams,
|
|
8
|
+
CheckOrderStatusResult,
|
|
9
|
+
FulfillOrderParams,
|
|
10
|
+
FulfillOrderResult,
|
|
11
|
+
ReturnRequestParams,
|
|
12
|
+
ReturnRequestResult,
|
|
13
|
+
VendorAdapter,
|
|
14
|
+
VendorConfig,
|
|
15
|
+
} from './types';
|
|
16
|
+
|
|
17
|
+
export class LauncherAdapter implements VendorAdapter {
|
|
18
|
+
private vendorConfig: VendorConfig | null = null;
|
|
19
|
+
private vendorKey: string;
|
|
20
|
+
|
|
21
|
+
constructor(vendorInfo: VendorConfig | string) {
|
|
22
|
+
if (typeof vendorInfo === 'string') {
|
|
23
|
+
this.vendorKey = vendorInfo;
|
|
24
|
+
this.vendorConfig = null;
|
|
25
|
+
} else {
|
|
26
|
+
this.vendorKey = vendorInfo.id;
|
|
27
|
+
this.vendorConfig = vendorInfo;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getVendorConfig(): Promise<VendorConfig> {
|
|
32
|
+
if (this.vendorConfig === null) {
|
|
33
|
+
this.vendorConfig = await ProductVendor.findOne({ where: { vendor_key: this.vendorKey } });
|
|
34
|
+
if (!this.vendorConfig) {
|
|
35
|
+
throw new Error(`Vendor not found: ${this.vendorKey}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return this.vendorConfig;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async fulfillOrder(params: FulfillOrderParams): Promise<FulfillOrderResult> {
|
|
42
|
+
logger.info('Creating launcher order via real API', {
|
|
43
|
+
checkoutSessionId: params.checkoutSessionId,
|
|
44
|
+
productCode: params.productCode,
|
|
45
|
+
customerId: params.customerId,
|
|
46
|
+
description: params.description,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const vendorConfig = await this.getVendorConfig();
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const launcherApiUrl = vendorConfig.app_url;
|
|
53
|
+
const orderData = {
|
|
54
|
+
checkoutSessionId: params.checkoutSessionId,
|
|
55
|
+
description: params.description,
|
|
56
|
+
userInfo: params.userInfo,
|
|
57
|
+
deliveryParams: params.deliveryParams,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const { headers, body } = VendorAuth.signRequestWithHeaders(orderData);
|
|
61
|
+
const response = await fetch(`${launcherApiUrl}/api/vendor/deliveries`, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers,
|
|
64
|
+
body,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const errorBody = await response.text();
|
|
69
|
+
logger.error('Launcher API error', {
|
|
70
|
+
status: response.status,
|
|
71
|
+
statusText: response.statusText,
|
|
72
|
+
body: errorBody,
|
|
73
|
+
});
|
|
74
|
+
throw new Error(`Launcher API error: ${response.status} ${response.statusText}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const launcherResult = await response.json();
|
|
78
|
+
|
|
79
|
+
const result: FulfillOrderResult = {
|
|
80
|
+
orderId: launcherResult.orderId || launcherResult.vendorOrderId || `launcher_${Date.now()}`,
|
|
81
|
+
status: launcherResult.status || 'pending',
|
|
82
|
+
serviceUrl: launcherResult.serviceUrl || launcherResult.appUrl,
|
|
83
|
+
estimatedTime: launcherResult.estimatedTime || 300,
|
|
84
|
+
message: launcherResult.message || 'Launcher order created successfully',
|
|
85
|
+
vendorOrderId: launcherResult.vendorOrderId || launcherResult.orderId,
|
|
86
|
+
progress: launcherResult.progress || 0,
|
|
87
|
+
orderDetails: {
|
|
88
|
+
productCode: params.productCode,
|
|
89
|
+
customerId: params.customerId,
|
|
90
|
+
amount: params.amount,
|
|
91
|
+
currency: params.currency,
|
|
92
|
+
quantity: params.quantity,
|
|
93
|
+
paymentIntentId: params.paymentIntentId,
|
|
94
|
+
customParams: params.customParams,
|
|
95
|
+
},
|
|
96
|
+
installationInfo: {
|
|
97
|
+
appId: launcherResult.appId,
|
|
98
|
+
appUrl: launcherResult.appUrl,
|
|
99
|
+
adminUrl: launcherResult.adminUrl,
|
|
100
|
+
status: launcherResult.installationStatus || launcherResult.status,
|
|
101
|
+
estimatedCompletionTime: launcherResult.estimatedCompletionTime,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
logger.info('Launcher order created successfully', {
|
|
106
|
+
orderId: result.orderId,
|
|
107
|
+
status: result.status,
|
|
108
|
+
serviceUrl: result.serviceUrl,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return result;
|
|
112
|
+
} catch (error: any) {
|
|
113
|
+
logger.error('Failed to create launcher order', {
|
|
114
|
+
error: error.message,
|
|
115
|
+
productCode: params.productCode,
|
|
116
|
+
customerId: params.customerId,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async requestReturn(params: ReturnRequestParams): Promise<ReturnRequestResult> {
|
|
124
|
+
logger.info('Requesting return for Launcher order', {
|
|
125
|
+
orderId: params.orderId,
|
|
126
|
+
reason: params.reason,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const vendorConfig = await this.getVendorConfig();
|
|
130
|
+
const launcherApiUrl = vendorConfig.app_url;
|
|
131
|
+
const { headers, body } = VendorAuth.signRequestWithHeaders(params);
|
|
132
|
+
|
|
133
|
+
const response = await fetch(`${launcherApiUrl}/api/vendor/return`, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers,
|
|
136
|
+
body,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
const errorBody = await response.text();
|
|
141
|
+
logger.error('Launcher API error', {
|
|
142
|
+
status: response.status,
|
|
143
|
+
statusText: response.statusText,
|
|
144
|
+
body: errorBody,
|
|
145
|
+
});
|
|
146
|
+
throw new Error(`Launcher API error: ${response.status} ${response.statusText}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const launcherResult = await response.json();
|
|
150
|
+
|
|
151
|
+
return launcherResult;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async checkOrderStatus(params: CheckOrderStatusParams): Promise<CheckOrderStatusResult> {
|
|
155
|
+
const url = `${params.appUrl}/__blocklet__.js?type=json&noCache=1`;
|
|
156
|
+
try {
|
|
157
|
+
const blocklet = await api.get(url, {
|
|
158
|
+
headers: { 'Content-Type': 'application/json' },
|
|
159
|
+
});
|
|
160
|
+
const blockletInfo = blocklet.data;
|
|
161
|
+
let status: 'processing' | 'completed' | 'failed' = 'processing';
|
|
162
|
+
if (blockletInfo.status === 'running') {
|
|
163
|
+
status = 'completed';
|
|
164
|
+
} else if (blockletInfo.status === 'failed') {
|
|
165
|
+
status = 'failed';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { status };
|
|
169
|
+
} catch (error: any) {
|
|
170
|
+
logger.error('Failed to check order status', {
|
|
171
|
+
url,
|
|
172
|
+
error: error.message,
|
|
173
|
+
stack: error.stack,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export interface VendorConfig {
|
|
2
|
+
id: string;
|
|
3
|
+
vendor_key: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
app_url: string;
|
|
7
|
+
webhook_path?: string;
|
|
8
|
+
default_commission_rate: number;
|
|
9
|
+
default_commission_type: 'percentage' | 'fixed_amount';
|
|
10
|
+
order_create_params: Record<string, any>;
|
|
11
|
+
status: 'active' | 'inactive';
|
|
12
|
+
metadata: Record<string, any>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface FulfillOrderParams {
|
|
16
|
+
checkoutSessionId: string;
|
|
17
|
+
productCode: string;
|
|
18
|
+
customerId: string;
|
|
19
|
+
quantity: number;
|
|
20
|
+
paymentIntentId: string;
|
|
21
|
+
amount: string;
|
|
22
|
+
currency: string;
|
|
23
|
+
|
|
24
|
+
description: string;
|
|
25
|
+
userInfo: {
|
|
26
|
+
userDid: string;
|
|
27
|
+
email: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
};
|
|
30
|
+
deliveryParams: {
|
|
31
|
+
blockletMetaUrl: string;
|
|
32
|
+
customParams?: Record<string, any>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
customParams?: Record<string, any>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface OrderDetails {
|
|
39
|
+
productCode: string;
|
|
40
|
+
customerId: string;
|
|
41
|
+
amount: string;
|
|
42
|
+
currency: string;
|
|
43
|
+
quantity: number;
|
|
44
|
+
paymentIntentId: string;
|
|
45
|
+
customParams?: Record<string, any>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface InstallationInfo {
|
|
49
|
+
appId: string;
|
|
50
|
+
appUrl: string;
|
|
51
|
+
adminUrl: string;
|
|
52
|
+
status: 'installing' | 'completed' | 'failed';
|
|
53
|
+
estimatedCompletionTime: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface FulfillOrderResult {
|
|
57
|
+
orderId: string;
|
|
58
|
+
status: 'pending' | 'processing' | 'completed' | 'failed';
|
|
59
|
+
serviceUrl?: string;
|
|
60
|
+
estimatedTime?: number;
|
|
61
|
+
message: string;
|
|
62
|
+
vendorOrderId?: string;
|
|
63
|
+
progress?: number;
|
|
64
|
+
orderDetails?: OrderDetails;
|
|
65
|
+
installationInfo?: InstallationInfo;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ReturnRequestParams {
|
|
69
|
+
orderId: string;
|
|
70
|
+
vendorOrderId?: string;
|
|
71
|
+
reason: string;
|
|
72
|
+
paymentIntentId: string;
|
|
73
|
+
customParams?: Record<string, any>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ReturnRequestResult {
|
|
77
|
+
status: 'requested' | 'accepted' | 'rejected' | 'failed';
|
|
78
|
+
message?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface CheckOrderStatusParams extends Record<string, any> {}
|
|
82
|
+
|
|
83
|
+
export interface CheckOrderStatusResult {
|
|
84
|
+
status: 'processing' | 'completed' | 'failed';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface VendorAdapter {
|
|
88
|
+
fulfillOrder(params: FulfillOrderParams): Promise<FulfillOrderResult>;
|
|
89
|
+
requestReturn(params: ReturnRequestParams): Promise<ReturnRequestResult>;
|
|
90
|
+
checkOrderStatus(params: CheckOrderStatusParams): Promise<CheckOrderStatusResult>;
|
|
91
|
+
}
|