import { HttpStatus, ILogger, Inject, Logger } from '@midwayjs/core'; import { Controller, Post, Body, Headers, Get, Query, } from '@midwayjs/decorator'; import { Context } from '@midwayjs/koa'; 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'; // 平台服务保留按需注入 @Inject() private readonly orderService: OrderService; @Inject() ctx: Context; @Logger() logger: ILogger; @Inject() private readonly siteService: SiteService; // 移除配置中的站点数组,来源统一改为数据库 @Get('/') async test() { return 'webhook'; } // TODO 这里得检查一下是否对 SHOPYY有效,否则得另外书写 @Post('/woocommerce') async handleWooWebhook( @Body() body: any, @Query('siteId') siteIdStr: string, @Headers() header: any ) { console.log(`webhook woocommerce`, siteIdStr, body,header) const signature = header['x-wc-webhook-signature']; const topic = header['x-wc-webhook-topic']; const source = header['x-wc-webhook-source']; const siteId = Number(siteIdStr); // 从数据库获取站点配置 const site = await this.siteService.get(siteId, true); if (!site || !source?.includes(site.apiUrl)) { 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', }; } const rawBody = this.ctx.request.rawBody; const hash = crypto .createHmac('sha256', this.secret) .update(rawBody) .digest('base64'); try { if (hash === signature) { switch (topic) { case 'product.created': case 'product.updated': // 不再写入本地,平台事件仅确认接收 break; case 'product.deleted': // 不再写入本地,平台事件仅确认接收 break; case 'order.created': case 'order.updated': await this.orderService.syncSingleOrder(siteId, body); break; case 'order.deleted': break; case 'customer.created': break; case 'customer.updated': break; case 'customer.deleted': break; default: console.log('Unhandled event:', body.event); } return { code: 200, success: true, message: 'Webhook processed successfully', }; } else { return { code: 403, success: false, message: 'Webhook verification failed', }; } } catch (error) { 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); } } }