import { App, Config, Inject, Middleware } from '@midwayjs/decorator'; import { CoolUrlTagData } from '@cool-midway/core'; import { NextFunction, Context } from '@midwayjs/koa'; import { IMiddleware, IMidwayApplication, InjectClient } from '@midwayjs/core'; import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; import * as crypto from 'crypto'; import { InjectEntityModel } from '@midwayjs/typeorm'; import { MerchantEntity } from '../../payment/entity/merchant'; import { Repository } from 'typeorm'; import { CustomerEntity } from '../../payment/entity/customer'; /** * 签名验证 */ @Middleware() export class BaseAuthorityMiddleware implements IMiddleware { @Config('koa.globalPrefix') prefix; @Config('module.base') jwtConfig; @InjectClient(CachingFactory, 'default') midwayCache: MidwayCache; @InjectEntityModel(CustomerEntity) customerEntity: Repository; @InjectEntityModel(MerchantEntity) merchantEntity: Repository; @Inject() coolUrlTagData: CoolUrlTagData; @App() app: IMidwayApplication; ignoreUrls: string[] = []; resolve() { return async (ctx: Context, next: NextFunction) => { if (ctx.url.includes('/api/open/sign') || ctx.url.includes('api/open/easypay-webhook')) { await next(); return; } // 签名校验 let { header } = ctx; const { 'fusionpay-key': vaKey = '', 'fusionpay-timestamp': vaTimestamp = '', 'fusionpay-nonce': vaNonce = '', 'fusionpay-sign': vaSign = '', } = header; if (!vaKey || !vaTimestamp || !vaNonce || !vaSign) { ctx.status = 401; ctx.body = { code: ctx.status, message: '当前认证信息不存在,请确认之后重新发送', }; return; } const params = ctx?.req.method === 'GET' || ctx.url.includes('/v1/upload') ? /* ctx?.request.query */ '' : ctx?.request.body; const merchantInfo = await this.merchantEntity.findOne({ where: [ { apikey: `${vaKey}`, }, { apiSecret: `${vaKey}`, }, ], }); let customer = await this.customerEntity.findOne({ where: { merchantId: merchantInfo.mchId, out_user_id: null, }, }); // TODO 后续在中间件加入对指定用户的权限验证 // let merchantCustomer = '' // 商户不存在或者说商户被禁用都提示失败 // if (!merchantInfo || merchantInfo.status != 1 || !vaKey || !customer || customer.status !== 'ACTIVE') { if (!merchantInfo || merchantInfo.status != 1 || !vaKey || !customer) { let message = ''; if (!vaKey) { message = 'API Secret 不存在!'; } if (!merchantInfo && !message) { message = '当前商户不存在'; } if (merchantInfo?.status != 1 && !message) { message = '当前商户已锁定'; } if (!customer && !message && !ctx.url.includes('/api/open/')) { message = '当前商户暂无认证'; } /*if (customer?.status === 'PENDDING' && !message) { message = '当前商户认证还在审核中'; } if (customer?.status === 'FAILED' && !message) { message = '当前商户认证还在审核被驳回'; } if (customer?.status === 'SUSPENDED' && !message) { message = '当前商户认证资格被冻结'; } if (customer?.status === 'CLOSED' && !message) { message = '当前商户认证资格被回收'; }*/ if (message) { ctx.status = 401; ctx.body = { code: ctx.status, message: message, }; return; } } // 2. 生成签名 const sign = this.generateSignature( `${vaTimestamp}`, `${vaNonce}`, JSON.stringify(params), `${merchantInfo.apiSecret}` ); console.log('9999-=-=',`${sign}`.toLocaleUpperCase()) console.log('1000-=-=',`${vaSign}`.toLocaleUpperCase()) if (`${sign}`.toLocaleUpperCase() !== `${vaSign}`.toLocaleUpperCase()) { ctx.status = 401; ctx.body = { code: ctx.status, message: '签名不匹配,认证失败', }; return; } /*如果说商户没有认证,我们需要提示商户进行认证*/ // this.customerEntity /*// TODO 本地测试用 const merchantInfo = await this.merchantEntity.findOne({ where: { mchId: `ddddd`, }, });*/ ctx.admin = { ...ctx.admin, merchant: merchantInfo, openApi: true, }; await next(); }; } match(ctx: Context): boolean { // 下面的匹配到的路由会执行此中间件 if (ctx.url.includes('/api/v1/') || ctx.url.includes('/api/open/')) { return true; } } static getName(): string { return 'openApi'; } /** * 生成签名 * @see https://docs-merchant.sunpay.pro/guide */ generateSignature(timestamp, nonce, body = '', secret = '') { const payload = `${timestamp}${nonce}${body}`; const signature = crypto .createHmac('sha256', secret) .update(payload) .digest('hex') .toUpperCase(); return signature; } }