From ecd2a6712b773a123d43e9bac4678eda45e07251 Mon Sep 17 00:00:00 2001 From: zhuotianyuan Date: Tue, 6 Jan 2026 18:43:30 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat(webhook):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9shoppy=E5=B9=B3=E5=8F=B0webhook=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在site.entity.ts中添加webhookUrl字段 - 在auth.middleware.ts中添加/shoppy路由到白名单 - 在webhook.controller.ts中实现shoppy平台webhook处理逻辑 --- src/controller/webhook.controller.ts | 90 +++++++++++++++++++++++++++- src/entity/site.entity.ts | 3 + src/middleware/auth.middleware.ts | 1 + 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/controller/webhook.controller.ts b/src/controller/webhook.controller.ts index 69a070d..bf45f32 100644 --- a/src/controller/webhook.controller.ts +++ b/src/controller/webhook.controller.ts @@ -13,9 +13,13 @@ import * as crypto from 'crypto'; import { SiteService } from '../service/site.service'; import { OrderService } from '../service/order.service'; +import { + UnifiedOrderDTO, +} from '../dto/site-api.dto'; + @Controller('/webhook') export class WebhookController { - private secret = 'YOONE24kd$kjcdjflddd'; + private secret = '$kjYOONE24kdcdjflddd'; // 平台服务保留按需注入 @@ -116,4 +120,88 @@ export class WebhookController { console.log(error); } } + + + + @Post('/shoppy') + async handleShoppyWebhook( + @Body() body: any, + @Query('siteId') siteIdStr: string, + @Query('signature') signature: string, + @Headers() header: any + ) { + const topic = header['x-oemsaas-event-type']; + // const source = header['x-oemsaas-shop-domain']; + const siteId = Number(siteIdStr); + const bodys = new UnifiedOrderDTO(); + Object.assign(bodys, body); + // 从数据库获取站点配置 + const site = await this.siteService.get(siteId, true); + + // if (!site || !source?.includes(site.websiteUrl)) { + if (!site) { + console.log('domain not match'); + return { + code: HttpStatus.BAD_REQUEST, + success: false, + message: 'domain not match', + }; + } + + if (!signature) { + return { + code: HttpStatus.BAD_REQUEST, + success: false, + message: 'Signature missing', + }; + } + + //shopyy 无法提供加密字段校验,注释校验逻辑 + // const rawBody = this.ctx.request.rawBody; + // const hash = crypto + // .createHmac('sha256', this.secret) + // .update(rawBody) + // .digest('base64'); + try { + if (this.secret === signature) { + switch (topic) { + case 'product.created': + case 'product.updated': + // 不再写入本地,平台事件仅确认接收 + break; + case 'product.deleted': + // 不再写入本地,平台事件仅确认接收 + break; + case 'orders/create': + case 'orders/update': + await this.orderService.syncSingleOrder(siteId, bodys); + break; + case 'orders/delete': + break; + case 'customer.created': + break; + case 'customer.updated': + break; + case 'customer.deleted': + break; + default: + console.log('Unhandled event:', topic); + } + + return { + code: 200, + success: true, + message: 'Webhook processed successfully', + }; + } else { + return { + code: 403, + success: false, + message: 'Webhook verification failed', + }; + } + } catch (error) { + console.log(error); + } + } } diff --git a/src/entity/site.entity.ts b/src/entity/site.entity.ts index a579a33..08b10a5 100644 --- a/src/entity/site.entity.ts +++ b/src/entity/site.entity.ts @@ -13,6 +13,9 @@ export class Site { @Column({ name: 'website_url', length: 255, nullable: true }) websiteUrl: string; + @Column({ name: 'webhook_url', length: 255, nullable: true }) + webhookUrl: string; + @Column({ length: 255, nullable: true }) consumerKey?: string; diff --git a/src/middleware/auth.middleware.ts b/src/middleware/auth.middleware.ts index 69a1495..e0fd0cf 100644 --- a/src/middleware/auth.middleware.ts +++ b/src/middleware/auth.middleware.ts @@ -21,6 +21,7 @@ export class AuthMiddleware implements IMiddleware { whiteList = [ '/user/login', '/webhook/woocommerce', + '/webhook/shoppy', '/logistics/getTrackingNumber', '/logistics/getListByTrackingId', '/product/categories/all', -- 2.40.1 From 4af69aeb6fa1cc01fc098069482e1a0328b464e4 Mon Sep 17 00:00:00 2001 From: zhuotianyuan Date: Tue, 6 Jan 2026 18:57:26 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix(webhook):=20=E6=9B=B4=E6=96=B0webhook?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=99=A8=E4=B8=AD=E7=9A=84=E5=AF=86=E9=92=A5?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controller/webhook.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controller/webhook.controller.ts b/src/controller/webhook.controller.ts index bf45f32..41ef422 100644 --- a/src/controller/webhook.controller.ts +++ b/src/controller/webhook.controller.ts @@ -19,7 +19,7 @@ import { @Controller('/webhook') export class WebhookController { - private secret = '$kjYOONE24kdcdjflddd'; + private secret = 'YOONE24kd$kjcdjflddd'; // 平台服务保留按需注入 -- 2.40.1 From f2b1036286a2965c496e6a24f02b20b098e58447 Mon Sep 17 00:00:00 2001 From: zhuotianyuan Date: Tue, 6 Jan 2026 19:00:02 +0800 Subject: [PATCH 3/3] =?UTF-8?q?refactor(entity):=20=E5=B0=86=E5=8F=AF?= =?UTF-8?q?=E9=80=89=E5=AD=97=E6=AE=B5=E6=98=8E=E7=A1=AE=E6=A0=87=E8=AE=B0?= =?UTF-8?q?=E4=B8=BA=E5=8F=AF=E9=80=89=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entity/site.entity.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/entity/site.entity.ts b/src/entity/site.entity.ts index 08b10a5..56fce60 100644 --- a/src/entity/site.entity.ts +++ b/src/entity/site.entity.ts @@ -11,10 +11,10 @@ export class Site { apiUrl: string; @Column({ name: 'website_url', length: 255, nullable: true }) - websiteUrl: string; + websiteUrl?: string; @Column({ name: 'webhook_url', length: 255, nullable: true }) - webhookUrl: string; + webhookUrl?: string; @Column({ length: 255, nullable: true }) consumerKey?: string; -- 2.40.1