authority.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. import { App, Config, Inject, Middleware } from '@midwayjs/decorator';
  2. import { CoolUrlTagData } from '@cool-midway/core';
  3. import { NextFunction, Context } from '@midwayjs/koa';
  4. import { IMiddleware, IMidwayApplication, InjectClient } from '@midwayjs/core';
  5. import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
  6. import * as crypto from 'crypto';
  7. import { InjectEntityModel } from '@midwayjs/typeorm';
  8. import { MerchantEntity } from '../../payment/entity/merchant';
  9. import { Repository } from 'typeorm';
  10. import { CustomerEntity } from '../../payment/entity/customer';
  11. /**
  12. * 签名验证
  13. */
  14. @Middleware()
  15. export class BaseAuthorityMiddleware
  16. implements IMiddleware<Context, NextFunction>
  17. {
  18. @Config('koa.globalPrefix')
  19. prefix;
  20. @Config('module.base')
  21. jwtConfig;
  22. @InjectClient(CachingFactory, 'default')
  23. midwayCache: MidwayCache;
  24. @InjectEntityModel(CustomerEntity)
  25. customerEntity: Repository<CustomerEntity>;
  26. @InjectEntityModel(MerchantEntity)
  27. merchantEntity: Repository<MerchantEntity>;
  28. @Inject()
  29. coolUrlTagData: CoolUrlTagData;
  30. @App()
  31. app: IMidwayApplication;
  32. ignoreUrls: string[] = [];
  33. resolve() {
  34. return async (ctx: Context, next: NextFunction) => {
  35. if (ctx.url.includes('/api/open/sign') || ctx.url.includes('api/open/easypay-webhook')) {
  36. await next();
  37. return;
  38. }
  39. // 签名校验
  40. let { header } = ctx;
  41. const {
  42. 'fusionpay-key': vaKey = '',
  43. 'fusionpay-timestamp': vaTimestamp = '',
  44. 'fusionpay-nonce': vaNonce = '',
  45. 'fusionpay-sign': vaSign = '',
  46. } = header;
  47. if (!vaKey || !vaTimestamp || !vaNonce || !vaSign) {
  48. ctx.status = 401;
  49. ctx.body = {
  50. code: ctx.status,
  51. message: '当前认证信息不存在,请确认之后重新发送',
  52. };
  53. return;
  54. }
  55. const params =
  56. ctx?.req.method === 'GET' || ctx.url.includes('/v1/upload')
  57. ? /* ctx?.request.query */ ''
  58. : ctx?.request.body;
  59. const merchantInfo = await this.merchantEntity.findOne({
  60. where: [
  61. {
  62. apikey: `${vaKey}`,
  63. },
  64. {
  65. apiSecret: `${vaKey}`,
  66. },
  67. ],
  68. });
  69. let customer = await this.customerEntity.findOne({
  70. where: {
  71. merchantId: merchantInfo.mchId,
  72. out_user_id: null,
  73. },
  74. });
  75. // TODO 后续在中间件加入对指定用户的权限验证
  76. // let merchantCustomer = ''
  77. // 商户不存在或者说商户被禁用都提示失败
  78. // if (!merchantInfo || merchantInfo.status != 1 || !vaKey || !customer || customer.status !== 'ACTIVE') {
  79. if (!merchantInfo || merchantInfo.status != 1 || !vaKey || !customer) {
  80. let message = '';
  81. if (!vaKey) {
  82. message = 'API Secret 不存在!';
  83. }
  84. if (!merchantInfo && !message) {
  85. message = '当前商户不存在';
  86. }
  87. if (merchantInfo?.status != 1 && !message) {
  88. message = '当前商户已锁定';
  89. }
  90. if (!customer && !message && !ctx.url.includes('/api/open/')) {
  91. message = '当前商户暂无认证';
  92. }
  93. /*if (customer?.status === 'PENDDING' && !message) {
  94. message = '当前商户认证还在审核中';
  95. }
  96. if (customer?.status === 'FAILED' && !message) {
  97. message = '当前商户认证还在审核被驳回';
  98. }
  99. if (customer?.status === 'SUSPENDED' && !message) {
  100. message = '当前商户认证资格被冻结';
  101. }
  102. if (customer?.status === 'CLOSED' && !message) {
  103. message = '当前商户认证资格被回收';
  104. }*/
  105. if (message) {
  106. ctx.status = 401;
  107. ctx.body = {
  108. code: ctx.status,
  109. message: message,
  110. };
  111. return;
  112. }
  113. }
  114. // 2. 生成签名
  115. const sign = this.generateSignature(
  116. `${vaTimestamp}`,
  117. `${vaNonce}`,
  118. JSON.stringify(params),
  119. `${merchantInfo.apiSecret}`
  120. );
  121. console.log('9999-=-=',`${sign}`.toLocaleUpperCase())
  122. console.log('1000-=-=',`${vaSign}`.toLocaleUpperCase())
  123. if (`${sign}`.toLocaleUpperCase() !== `${vaSign}`.toLocaleUpperCase()) {
  124. ctx.status = 401;
  125. ctx.body = {
  126. code: ctx.status,
  127. message: '签名不匹配,认证失败',
  128. };
  129. return;
  130. }
  131. /*如果说商户没有认证,我们需要提示商户进行认证*/
  132. // this.customerEntity
  133. /*// TODO 本地测试用
  134. const merchantInfo = await this.merchantEntity.findOne({
  135. where: {
  136. mchId: `ddddd`,
  137. },
  138. });*/
  139. ctx.admin = {
  140. ...ctx.admin,
  141. merchant: merchantInfo,
  142. openApi: true,
  143. };
  144. await next();
  145. };
  146. }
  147. match(ctx: Context): boolean {
  148. // 下面的匹配到的路由会执行此中间件
  149. if (ctx.url.includes('/api/v1/') || ctx.url.includes('/api/open/')) {
  150. return true;
  151. }
  152. }
  153. static getName(): string {
  154. return 'openApi';
  155. }
  156. /**
  157. * 生成签名
  158. * @see https://docs-merchant.sunpay.pro/guide
  159. */
  160. generateSignature(timestamp, nonce, body = '', secret = '') {
  161. const payload = `${timestamp}${nonce}${body}`;
  162. const signature = crypto
  163. .createHmac('sha256', secret)
  164. .update(payload)
  165. .digest('hex')
  166. .toUpperCase();
  167. return signature;
  168. }
  169. }