test 10 달 전
부모
커밋
eccf961ff6

+ 1 - 0
package.json

@@ -27,6 +27,7 @@
     "cache-manager-ioredis-yet": "^2.0.2",
     "decompress": "^4.2.1",
     "download": "^8.0.0",
+    "form-data": "^4.0.0",
     "ipip-ipdb": "^0.6.0",
     "jsonwebtoken": "^9.0.2",
     "lodash": "^4.17.21",

+ 9 - 0
src/comm/utils.ts

@@ -4,6 +4,7 @@ import * as ipdb from 'ipip-ipdb';
 import * as _ from 'lodash';
 import * as moment from 'moment';
 import * as NodeRSA from 'node-rsa';
+import * as crypto from 'crypto'
 
 /**
  * 帮助类
@@ -220,4 +221,12 @@ export class Utils {
     key.setOptions({ signingScheme: 'pkcs1-sha256' });
     return key.verify(data, sign);
   }
+
+  signBySha256(data, secretKey) {
+    const hmac = crypto.createHmac('sha256', secretKey);
+    // 更新hash对象与传入的数据
+    hmac.update(data);
+    // 生成哈希值
+    return hmac.digest('hex');
+  }
 }

+ 1 - 1
src/config/config.local.ts

@@ -13,7 +13,7 @@ export default {
         port: 3306,
         username: 'root',
         password: '123123',
-        database: 'cool',
+        database: 'fusion',
         // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失
         synchronize: true,
         // 打印日志

+ 2 - 2
src/config/config.prod.ts

@@ -11,9 +11,9 @@ export default {
         type: 'mysql',
         host: '127.0.0.1',
         port: 3306,
-        username: 'cool',
+        username: 'fusion',
         password: 'NW4sMHPrSMR4Z2xA',
-        database: 'cool',
+        database: 'fusion',
         // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失
         synchronize: false,
         // 打印日志

+ 1 - 1
src/modules/dj/config.ts

@@ -6,7 +6,7 @@ import { ModuleConfig } from '@cool-midway/core';
 export default () => {
   return {
     // 模块名称
-    name: 'DJ',
+    name: 'FusionPay',
     // 模块描述
     description: '业务层',
     // 中间件,只对本模块有效

+ 0 - 15
src/modules/dj/controller/admin/account.ts

@@ -1,15 +0,0 @@
-import { AccountEntity } from '../../entity/account';
-import { Provide } from '@midwayjs/decorator';
-import { CoolController, BaseController } from '@cool-midway/core';
-import { AccountService } from '../../service/account';
-
-@Provide()
-@CoolController({
-  api: ['add', 'delete', 'update', 'info', 'page', 'list'],
-  entity: AccountEntity,
-  service: AccountService,
-  listQueryOp: {
-    keyWordLikeFields: ['appId'],
-  },
-})
-export class AccountController extends BaseController {}

+ 17 - 0
src/modules/dj/controller/admin/kyc.ts

@@ -0,0 +1,17 @@
+import { Provide } from '@midwayjs/decorator';
+import { CoolController, BaseController } from '@cool-midway/core';
+import { KycService } from '../../service/kyc';
+import { KycEntity } from '../../entity/kyc';
+
+@Provide()
+@CoolController({
+  api: ['add', 'delete', 'update', 'list', 'info', 'page'],
+  entity: KycEntity,
+  service: KycService,
+  pageQueryOp: {
+    keyWordLikeFields: ['code', 'userId', 'mchId', 'customerId'],
+  },
+})
+export class KycController extends BaseController {
+ 
+}

+ 49 - 22
src/modules/dj/controller/admin/open.ts

@@ -7,6 +7,8 @@ import {
   Logger,
   Get,
   Param,
+  Files,
+  Fields,
 } from '@midwayjs/decorator';
 import {
   CoolController,
@@ -22,6 +24,7 @@ import { OrderService } from '../../service/order';
 import { ChannelService } from '../../service/channel';
 import { ILogger } from '@midwayjs/logger';
 import { WithdrawService } from '../../service/withdraw';
+import { KycService } from '../../service/kyc';
 
 @Provide()
 @CoolController()
@@ -45,6 +48,9 @@ export class PayOpenController extends BaseController {
   @Inject()
   channelService: ChannelService;
 
+  @Inject()
+  kycService: KycService;
+
   @Logger()
   logger: ILogger;
 
@@ -69,39 +75,60 @@ export class PayOpenController extends BaseController {
   }
 
   @CoolTag(TagTypes.IGNORE_TOKEN)
-  @Post('/Alipay/notifyOrder', { summary: '交易订单回调通知' })
+  @Post('/notifyOrder', { summary: '交易订单回调通知' })
   public async notifyOrder(@Body(ALL) payload: any) {
-    this.logger.error('收到交易订单回调数据', JSON.stringify(payload));
-    await this.orderService.handleNotify('Alipay', payload);
+    const headers = this.ctx.req.headers;
+    this.logger.error('收到交易订单回调数据', JSON.stringify(payload), headers);
+    await this.orderService.handleNotify('SunCard', payload, headers);
     this.ctx.body = 'success';
   }
 
   @CoolTag(TagTypes.IGNORE_TOKEN)
-  @Get('/Alipay/notifyOrder', { summary: '交易订单回调通知' })
+  @Get('/SunCard/notifyOrder', { summary: '交易订单回调通知' })
   public async notifyOrderGet() {
+    const headers = this.ctx.req.headers;
     const payload = this.ctx.query;
     this.logger.error('收到交易订单回调数据', JSON.stringify(payload));
-    await this.orderService.handleNotify('Alipay', payload);
-    this.ctx.body = 'success';
+    await this.orderService.handleNotify('SunCard', payload, headers);
+    this.ctx.body = {   
+      "is_success": "true",
+      "message": "success"
+    };
+  }
+
+  @CoolTag(TagTypes.IGNORE_TOKEN)
+  @Post('/kyc/level', { summary: 'kyc level 查询' })
+  public async kycLevel(@Body(ALL) payload: any) {
+    return this.ok(await this.kycService.getLevel(payload.orderNo));
+  }
+
+  @CoolTag(TagTypes.IGNORE_TOKEN)
+  @Get('/kyc/countries', { summary: 'kyc countries 查询' })
+  public async kycCountries() {
+    return this.ok(await this.kycService.getCountries());
   }
 
-  
   @CoolTag(TagTypes.IGNORE_TOKEN)
-  @Get('/toPay/:orderNo', { summary: '支付地址' })
-  public async toPay(@Param('orderNo') orderNo: string) {
-    this.logger.info('访问支付地址', orderNo);
-    const order = await this.orderService.getForm(orderNo);
-    if(!order) {
-      return '订单不存在!'
-    }
-    return order.form
+  @Post('/kyc/basic', { summary: 'kyc basic 保存' })
+  public async kycBasic(@Body(ALL) payload: any) {
+    return this.ok(await this.kycService.createBasic(payload));
   }
 
-  // @CoolTag(TagTypes.IGNORE_TOKEN)
-  // @Post('/Alipay/notifyWithdraw', { summary: '代付订单回调' })
-  // public async notifyWithdraw(@Body(ALL) payload: any) {
-  //   this.logger.error('收到代付订单回调数据', JSON.stringify(payload));
-  //   await this.withdrawService.handleNotify('Alipay', payload);
-  //   this.ctx.body = 'SUCCESS';
-  // }
+  @CoolTag(TagTypes.IGNORE_TOKEN)
+  @Post('/kyc/advanced', { summary: 'kyc advanced 保存' })
+  public async kycAdvanced(@Files() files, @Fields() fields) {
+    return this.ok(await this.kycService.createAdvanced(fields, files));
+  }
+
+  @CoolTag(TagTypes.IGNORE_TOKEN)
+  @Post('/kyc/premium', { summary: 'kyc basic 保存' })
+  public async kycPremium(@Files() files, @Fields() fields) {
+    return this.ok(await this.kycService.createPremium(fields, files));
+  }
+
+  @CoolTag(TagTypes.IGNORE_TOKEN)
+  @Post('/kyc/toPay', { summary: 'kyc topay' })
+  public async kycToPay(@Body(ALL) payload: any) {
+    return this.ok(await this.kycService.toPay(payload));
+  }
 }

+ 0 - 33
src/modules/dj/entity/account.ts

@@ -1,33 +0,0 @@
-import { BaseEntity } from '@cool-midway/core';
-import { Column, Entity, Index } from 'typeorm';
-
-/**
- * 字典信息
- */
-@Entity('dj_account')
-export class AccountEntity extends BaseEntity {
-  @Index({ unique: true })
-  @Column({ comment: 'APP_ID' })
-  appId: string;
-
-  @Column({ comment: '密钥', nullable: true })
-  key: string;
-
-  @Column({ comment: '私钥', type: 'text', nullable: true })
-  privateKey: string;
-
-  @Column({ comment: '公钥', type: 'text', nullable: true })
-  publicKey: string;
-
-  @Column({ comment: '状态 0-禁用 1-启用', default: 1 })
-  status: number;
-
-  @Column({ comment: '权重' })
-  weight: number;
-
-  @Column({ comment: '备注', nullable: true })
-  remark: string;
-
-  @Column({ comment: 'SMID', type: 'text', nullable: true })
-  smid: string;
-}

+ 29 - 0
src/modules/dj/entity/kyc.ts

@@ -0,0 +1,29 @@
+import { BaseEntity } from '@cool-midway/core';
+import { Column, Entity, Index } from 'typeorm';
+
+/**
+ * 描述
+ */
+@Entity('dj_kyc')
+export class KycEntity extends BaseEntity {
+  @Column({ comment: '商户号' })
+  mchId: string;
+
+  @Column({ comment: '用户ID' })
+  userId: string;
+
+  @Column({ comment: '通道码' })
+  code: string;
+
+  @Column({ comment: '平台用户ID' })
+  kycUserId: string;
+
+  @Column({ comment: 'KYC_ID' })
+  customerId: string;
+
+  @Column({ comment: 'KYC_Level' })
+  level: string;
+
+  @Column({ comment: '备注', nullable: true })
+  remark: string;
+}

+ 2 - 18
src/modules/dj/entity/order.ts

@@ -29,9 +29,6 @@ export class OrderEntity extends BaseEntity {
   @Column({ comment: '用户IP' })
   userIp: string;
 
-  @Column({ comment: '交易账号' })
-  mainId: string;
-
   @Column({ comment: '金额', type: 'decimal', precision: 10, scale: 2 })
   amount: number;
 
@@ -64,22 +61,9 @@ export class OrderEntity extends BaseEntity {
   })
   notifyStatus: number;
 
-  @Column({
-    comment: '结算状态 0-未结算 1 已确认 2 已结算',
-    type: 'tinyint',
-    default: 0,
-  })
-  settleStatus: number;
-
   @Column({ comment: '备注', nullable: true })
   remark: string;
 
-  @Column({ comment: '结算订单号', nullable: true })
-  settleNo: string;
-
-  // @Column({ comment: '支付地址', nullable: true })
-  // payUrl: string;
-
-  @Column({ comment: '支付表单', nullable: true, type: 'text' })
-  form: string;
+  @Column({ comment: '支付地址', nullable: true })
+  payUrl: string;
 }

+ 0 - 12
src/modules/dj/service/account.ts

@@ -1,12 +0,0 @@
-import { Provide } from '@midwayjs/decorator';
-import { BaseService } from '@cool-midway/core';
-import { InjectEntityModel } from '@midwayjs/typeorm';
-import { Repository } from 'typeorm';
-import * as _ from 'lodash';
-import { AccountEntity } from '../entity/account';
-
-@Provide()
-export class AccountService extends BaseService {
-  @InjectEntityModel(AccountEntity)
-  accountEntity: Repository<AccountEntity>;
-}

+ 0 - 281
src/modules/dj/service/channels/alipay.ts

@@ -1,281 +0,0 @@
-import { Inject, Logger, Provide } from '@midwayjs/decorator';
-import { BaseService } from '@cool-midway/core';
-import * as _ from 'lodash';
-import * as moment from 'moment';
-import { ILogger } from '@midwayjs/logger';
-import { AlipaySdk } from 'alipay-sdk';
-import { AccountEntity } from '../../entity/account';
-import { Repository } from 'typeorm';
-import { InjectEntityModel } from '@midwayjs/typeorm';
-import { OrderEntity } from '../../entity/order';
-import { HttpService } from '@midwayjs/axios';
-
-@Provide()
-export class AlipayService extends BaseService {
-  @Logger()
-  logger: ILogger;
-  @InjectEntityModel(AccountEntity)
-  accountEntity: Repository<AccountEntity>;
-
-  @InjectEntityModel(OrderEntity)
-  orderEntity: Repository<OrderEntity>;
-
-  @Inject()
-  httpService: HttpService;
-
-  async getRandomAccount() {
-    const accounts = await this.accountEntity.find({ where: { status: 1 } });
-    const weight = accounts.reduce((total, cur) => {
-      return +total + +cur.weight;
-    }, 0);
-    const dist = Math.floor(Math.random() * weight) + 1;
-    let preWeight = 0;
-    return accounts.find(item => {
-      preWeight = preWeight + item.weight;
-      return preWeight >= dist;
-    });
-  }
-
-  getSmid(account) {
-    const smids = account.smid.split(',');
-    return smids[Math.floor(Math.random() * smids.length)];
-  }
-
-  async order(payload) {
-    const account = await this.getRandomAccount();
-    const smid = this.getSmid(account);
-    const alipay = new AlipaySdk({
-      appId: account.appId,
-      privateKey: account.privateKey,
-      alipayPublicKey: account.publicKey,
-      gateway: 'https://openapi.alipay.com/gateway.do',
-    });
-    const bizContent = {
-      out_trade_no: payload.orderNo,
-      total_amount: payload.amount,
-      subject: 'Goods',
-      product_code: 'QUICK_WAP_WAY',
-      quit_url: payload.returnUrl,
-      business_params: `{"mc_create_trade_ip": "${payload.userIp || ''}"}`,
-      sub_merchant: {
-        merchant_id: smid,
-      },
-      notify_url: 'http://205.234.252.199/api/admin/dj/open/Alipay/notifyOrder',
-      settle_info: {
-        settle_detail_infos: [
-          {
-            amount: payload.amount,
-            trans_in_type: 'loginName',
-            trans_in: 'ywzq1916@163.com',
-          },
-        ],
-        settle_period_time: '1d',
-      },
-    };
-    const result = await alipay.pageExec('alipay.trade.wap.pay', 'GET', {
-      notify_url: 'http://205.234.252.199/api/admin/dj/open/Alipay/notifyOrder',
-      bizContent,
-    });
-    this.logger.info('下单接口返回', bizContent, JSON.stringify(result));
-    return {
-      payUrl: result,
-      mainId: account.appId,
-    };
-  }
-
-  async query(payload) {
-    const appId = payload.mainId;
-    const account = await this.accountEntity.findOne({
-      where: { appId: appId },
-    });
-    const alipay = new AlipaySdk({
-      appId: account.appId,
-      privateKey: account.privateKey,
-      alipayPublicKey: account.publicKey,
-      gateway: 'https://openapi.alipay.com/gateway.do',
-    });
-    const result = await alipay.curl('POST', '/v3/alipay/trade/query', {
-      body: {
-        out_trade_no: payload.orderNo,
-        query_options: ['trade_settle_info'],
-      },
-    });
-    this.logger.info('查询接口返回', JSON.stringify(result));
-    const { trade_status, trade_no, send_pay_date } = result.data;
-    if (trade_status === 'TRADE_SUCCESS') {
-      setTimeout(() => {
-        this.handleSettle(
-          {
-            appId,
-            orderNo: payload.orderNo,
-            traceNo: trade_no,
-            amount: payload.amount,
-          },
-          5
-        );
-      }, 60 * 1000);
-      return {
-        status: 1,
-        traceNo: trade_no,
-        date: send_pay_date,
-      };
-    } else {
-      return {
-        status: 0,
-        message: '订单未支付',
-      };
-    }
-  }
-
-  async handleOrderNotify(data) {
-    const {
-      app_id,
-      out_trade_no,
-      trade_no,
-      trade_status,
-      gmt_payment,
-      total_amount,
-    } = data;
-    const account = await this.accountEntity.findOne({
-      where: { appId: app_id },
-    });
-    const alipay = new AlipaySdk({
-      appId: account.appId,
-      privateKey: account.privateKey,
-      alipayPublicKey: account.publicKey,
-      gateway: 'https://openapi.alipay.com/gateway.do',
-    });
-    const success = alipay.checkNotifySignV2(data);
-    if (!success) {
-      throw new Error('sign error');
-    }
-    if (trade_status !== 'TRADE_SUCCESS') {
-      throw new Error('order no success');
-    }
-    setTimeout(() => {
-      this.handleSettle(
-        {
-          app_id,
-          orderNo: out_trade_no,
-          traceNo: trade_no,
-          amount: +total_amount,
-        },
-        5
-      );
-    }, 60 * 1000);
-    return {
-      date: gmt_payment,
-      amount: +total_amount,
-      orderNo: out_trade_no,
-      traceNo: trade_no,
-    };
-  }
-
-  async settle(payload) {
-    const appId = payload.appId;
-    const account = await this.accountEntity.findOne({
-      where: { appId: appId },
-    });
-    const alipay = new AlipaySdk({
-      appId: account.appId,
-      privateKey: account.privateKey,
-      alipayPublicKey: account.publicKey,
-      gateway: 'https://openapi.alipay.com/gateway.do',
-    });
-    const bizContent = {
-      out_request_no: +moment(),
-      trade_no: payload.traceNo,
-      settle_info: {
-        settle_detail_infos: [
-          {
-            amount: payload.amount,
-            trans_in_type: 'loginName',
-            trans_in: 'ywzq1916@163.com',
-          },
-        ],
-      },
-      extend_params: {
-        royalty_freeze: false,
-      },
-    };
-    const query = await alipay.sdkExecute('alipay.trade.settle.confirm', {
-      bizContent,
-    });
-    const result = await this.httpService.post(
-      `https://openapi.alipay.com/gateway.do?${query}`
-    );
-    this.logger.info('结算接口返回', query, result.data);
-    const { alipay_trade_settle_confirm_response } = result.data;
-    const { code, sub_code } = alipay_trade_settle_confirm_response;
-    if (+code === 10000 || sub_code === 'ACQ.ALREADY_CONFIRM_SETTLE') {
-      await this.orderEntity.update(
-        {
-          orderNo: payload.orderNo,
-        },
-        {
-          settleStatus: 1,
-        }
-      );
-      this.settleFinish(payload);
-      return {
-        status: 1,
-      };
-    } else {
-      return {
-        status: 0,
-      };
-    }
-  }
-
-  async handleSettle(payload, times = 0) {
-    const { status } = await this.settle(payload);
-    if (+status !== 1 && times > 0) {
-      setTimeout(() => {
-        return this.handleSettle(payload, times - 1);
-      }, 40 * 1000);
-    }
-  }
-
-  async settleFinish(payload) {
-    const appId = payload.appId;
-    const account = await this.accountEntity.findOne({
-      where: { appId: appId },
-    });
-    const alipay = new AlipaySdk({
-      appId: account.appId,
-      privateKey: account.privateKey,
-      alipayPublicKey: account.publicKey,
-      gateway: 'https://openapi.alipay.com/gateway.do',
-    });
-    const body = {
-      out_request_no: +moment(),
-      trade_no: payload.traceNo,
-      extend_params: {
-        royalty_finish: true,
-      },
-    };
-    const result = await alipay.curl('POST', '/v3/alipay/trade/order/settle', {
-      body,
-    });
-    this.logger.info('结算完成接口返回', body, result.data);
-    const { settle_no } = result.data;
-    if (settle_no) {
-      await this.orderEntity.update(
-        {
-          orderNo: payload.orderNo,
-        },
-        {
-          settleStatus: 2,
-          settleNo: settle_no,
-        }
-      );
-      return {
-        status: 1,
-      };
-    } else {
-      return {
-        status: 0,
-      };
-    }
-  }
-}

+ 0 - 280
src/modules/dj/service/channels/alipayPc.ts

@@ -1,280 +0,0 @@
-import { Inject, Logger, Provide } from '@midwayjs/decorator';
-import { BaseService } from '@cool-midway/core';
-import * as _ from 'lodash';
-import * as moment from 'moment';
-import { ILogger } from '@midwayjs/logger';
-import { AlipaySdk } from 'alipay-sdk';
-import { AccountEntity } from '../../entity/account';
-import { Repository } from 'typeorm';
-import { InjectEntityModel } from '@midwayjs/typeorm';
-import { OrderEntity } from '../../entity/order';
-import { HttpService } from '@midwayjs/axios';
-
-@Provide()
-export class AlipayPcService extends BaseService {
-  @Logger()
-  logger: ILogger;
-  @InjectEntityModel(AccountEntity)
-  accountEntity: Repository<AccountEntity>;
-
-  @InjectEntityModel(OrderEntity)
-  orderEntity: Repository<OrderEntity>;
-
-  @Inject()
-  httpService: HttpService;
-
-  async getRandomAccount() {
-    const accounts = await this.accountEntity.find({ where: { status: 1 } });
-    const weight = accounts.reduce((total, cur) => {
-      return +total + +cur.weight;
-    }, 0);
-    const dist = Math.floor(Math.random() * weight) + 1;
-    let preWeight = 0;
-    return accounts.find(item => {
-      preWeight = preWeight + item.weight;
-      return preWeight >= dist;
-    });
-  }
-
-  getSmid(account) {
-    const smids = account.smid.split(',');
-    return smids[Math.floor(Math.random() * smids.length)];
-  }
-
-  async order(payload) {
-    const account = await this.getRandomAccount();
-    const smid = this.getSmid(account);
-    const alipay = new AlipaySdk({
-      appId: account.appId,
-      privateKey: account.privateKey,
-      alipayPublicKey: account.publicKey,
-      gateway: 'https://openapi.alipay.com/gateway.do',
-    });
-    const bizContent = {
-      out_trade_no: payload.orderNo,
-      product_code: 'FAST_INSTANT_TRADE_PAY',
-      total_amount: payload.amount,
-      subject: 'Goods',
-      settle_info: {
-        settle_detail_infos: [
-          {
-            amount: payload.amount,
-            trans_in_type: 'defaultSettle',
-          },
-        ],
-      },
-      sub_merchant: {
-        merchant_id: smid,
-      },
-      request_from_url: payload.returnUrl,
-      notify_url: 'http://205.234.252.199/api/admin/dj/open/Alipay/notifyOrder',
-      // business_params: `{"mc_create_trade_ip": "${payload.userIp || ''}"}`,
-    };
-    const result = await alipay.pageExec('alipay.trade.page.pay', {
-      notify_url: 'http://205.234.252.199/api/admin/dj/open/Alipay/notifyOrder',
-      bizContent,
-    });
-    this.logger.info('下单接口返回', bizContent, JSON.stringify(result));
-    return {
-      payUrl: '',
-      form: result,
-      mainId: account.appId,
-    };
-  }
-
-  async query(payload) {
-    const appId = payload.mainId;
-    const account = await this.accountEntity.findOne({
-      where: { appId: appId },
-    });
-    const alipay = new AlipaySdk({
-      appId: account.appId,
-      privateKey: account.privateKey,
-      alipayPublicKey: account.publicKey,
-      gateway: 'https://openapi.alipay.com/gateway.do',
-    });
-    const result = await alipay.curl('POST', '/v3/alipay/trade/query', {
-      body: {
-        out_trade_no: payload.orderNo,
-        query_options: ['trade_settle_info'],
-      },
-    });
-    this.logger.info('查询接口返回', JSON.stringify(result));
-    const { trade_status, trade_no, send_pay_date } = result.data;
-    if (trade_status === 'TRADE_SUCCESS') {
-      setTimeout(() => {
-        this.handleSettle(
-          {
-            appId,
-            orderNo: payload.orderNo,
-            traceNo: trade_no,
-            amount: payload.amount,
-          },
-          5
-        );
-      }, 60 * 1000);
-      return {
-        status: 1,
-        traceNo: trade_no,
-        date: send_pay_date,
-      };
-    } else {
-      return {
-        status: 0,
-        message: '订单未支付',
-      };
-    }
-  }
-
-  async handleOrderNotify(data) {
-    const {
-      app_id,
-      out_trade_no,
-      trade_no,
-      trade_status,
-      gmt_payment,
-      total_amount,
-    } = data;
-    const account = await this.accountEntity.findOne({
-      where: { appId: app_id },
-    });
-    const alipay = new AlipaySdk({
-      appId: account.appId,
-      privateKey: account.privateKey,
-      alipayPublicKey: account.publicKey,
-      gateway: 'https://openapi.alipay.com/gateway.do',
-    });
-    const success = alipay.checkNotifySignV2(data);
-    if (!success) {
-      throw new Error('sign error');
-    }
-    if (trade_status !== 'TRADE_SUCCESS') {
-      throw new Error('order no success');
-    }
-    setTimeout(() => {
-      this.handleSettle(
-        {
-          app_id,
-          orderNo: out_trade_no,
-          traceNo: trade_no,
-          amount: +total_amount,
-        },
-        5
-      );
-    }, 60 * 1000);
-    return {
-      date: gmt_payment,
-      amount: +total_amount,
-      orderNo: out_trade_no,
-      traceNo: trade_no,
-    };
-  }
-
-  async settle(payload) {
-    const appId = payload.appId;
-    const account = await this.accountEntity.findOne({
-      where: { appId: appId },
-    });
-    const alipay = new AlipaySdk({
-      appId: account.appId,
-      privateKey: account.privateKey,
-      alipayPublicKey: account.publicKey,
-      gateway: 'https://openapi.alipay.com/gateway.do',
-    });
-    const bizContent = {
-      out_request_no: +moment(),
-      trade_no: payload.traceNo,
-      settle_info: {
-        settle_detail_infos: [
-          {
-            amount: payload.amount,
-            trans_in_type: 'loginName',
-            trans_in: 'ywzq1916@163.com',
-          },
-        ],
-      },
-      extend_params: {
-        royalty_freeze: false,
-      },
-    };
-    const query = await alipay.sdkExecute('alipay.trade.settle.confirm', {
-      bizContent,
-    });
-    const result = await this.httpService.post(
-      `https://openapi.alipay.com/gateway.do?${query}`
-    );
-    this.logger.info('结算接口返回', query, result.data);
-    const { alipay_trade_settle_confirm_response } = result.data;
-    const { code, sub_code } = alipay_trade_settle_confirm_response;
-    if (+code === 10000 || sub_code === 'ACQ.ALREADY_CONFIRM_SETTLE') {
-      await this.orderEntity.update(
-        {
-          orderNo: payload.orderNo,
-        },
-        {
-          settleStatus: 1,
-        }
-      );
-      this.settleFinish(payload);
-      return {
-        status: 1,
-      };
-    } else {
-      return {
-        status: 0,
-      };
-    }
-  }
-
-  async handleSettle(payload, times = 0) {
-    const { status } = await this.settle(payload);
-    if (+status !== 1 && times > 0) {
-      setTimeout(() => {
-        return this.handleSettle(payload, times - 1);
-      }, 40 * 1000);
-    }
-  }
-
-  async settleFinish(payload) {
-    const appId = payload.appId;
-    const account = await this.accountEntity.findOne({
-      where: { appId: appId },
-    });
-    const alipay = new AlipaySdk({
-      appId: account.appId,
-      privateKey: account.privateKey,
-      alipayPublicKey: account.publicKey,
-      gateway: 'https://openapi.alipay.com/gateway.do',
-    });
-    const body = {
-      out_request_no: +moment(),
-      trade_no: payload.traceNo,
-      extend_params: {
-        royalty_finish: true,
-      },
-    };
-    const result = await alipay.curl('POST', '/v3/alipay/trade/order/settle', {
-      body,
-    });
-    this.logger.info('结算完成接口返回', body, result.data);
-    const { settle_no } = result.data;
-    if (settle_no) {
-      await this.orderEntity.update(
-        {
-          orderNo: payload.orderNo,
-        },
-        {
-          settleStatus: 2,
-          settleNo: settle_no,
-        }
-      );
-      return {
-        status: 1,
-      };
-    } else {
-      return {
-        status: 0,
-      };
-    }
-  }
-}

+ 0 - 311
src/modules/dj/service/channels/alipay_diandianfu.ts

@@ -1,311 +0,0 @@
-import { Inject, Logger, Provide } from '@midwayjs/decorator';
-import { BaseService } from '@cool-midway/core';
-import * as _ from 'lodash';
-import * as moment from 'moment';
-import { Utils } from '../../../../comm/utils';
-import { HttpService } from '@midwayjs/axios';
-import * as md5 from 'md5';
-import { ILogger } from '@midwayjs/logger';
-
-const URL = 'https://qlapi.diandianfu.com.cn/webwt/pay/gateway.do';
-const AGT_ID = '102404122607';
-const MER_ID = '202404127037';
-const MD5_KEY = 'f9d09b3e5bd345a3a41f95cb08507423';
-const PAY_WAY = 'CH-DF-NOWAPP-001';
-
-const RSA_PRIVATE_KEY = `-----BEGIN PRIVATE KEY-----
-  MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCCaUylJ3XRMb0W7ytitzAL4kekMEan6ZQxonJS8MXK7RVkVW7gabmoR3moMum0B14Sk6OpDPRTHBqpBsZLSUPQX8/ADGdHWddWd4z0z9yUXioMrGhbKTVbWJphBKsUE5YLag6cZoQBpLjJgkctROg/74fm3WqO4rB6/GZ35OsJX2YYv8mgJZKVq4PJTd7mzAR8k9NVrdOdHqIuR3pLdsKLKtFw3n2C7kxyhynqhavimZlygTehnLZtXRXK55/x+dyees0xldjilJsyCh+Y/4Q5x2oVa/4oZGlz41v+7XFruBnTBwh/WhzlH6N2Dh1JaFUdSLi9s5RWLEDwPYb6nKrpAgMBAAECggEAd7CXJLj7u/zvc/4cjqRcl3udjwazwmZxmcBjNtY+YomH6aczdQnz2IuJD6NytMVSRLB2M7BtCV4w5dMO6YgbkkJtVPDNVUISjgx5NTMVWqK16x3NRWWjz+uY/NQhA/MZ8e3r7QV1wGBjLKbCFr04f32i+HNc2jkaCjZfAUj7ooQ5sEpQJJkRFFFahDIsARuPSr13yFHmXPkv+5ALs++bJYDnn3S3JDlrN9BBHKQmo2RYwnYAZWySTKsywhJvSyDzGIjXMFhj0/SHNhAKkkfSmmgPbJO72fye3k4FCjYh9K38Bs9y4pyFEKj6pCbv17x8mLsSUav1WvHWB5GC8ZF2kQKBgQC42cF1BwiMBolPBufRYeZEEp4HyVLt1wCq/dfLHaKwOHQYhVC1KgPuYMQJyJixrZ5ZzDLjx02xE+uMvYcqGrStPk1SuJp43T7o1qjALVkshIFCpZLonpVPwsQB/UFQJWg887XyUC8YPwXc6g27HBENwT4aoLsu+V3qN6fLb4poBQKBgQC0m18/m37zh0mscEDG7SKpxkJGY35ehRk7pTTYzKomDuNscNcxiGPPHm0k52/x3weVCDJ9MMAJ2e/tRycKLR+LDeHMM54riZU8OxAPwfy/Atjbsmq8BnfTUDlwo7SdrUrxqPSyU9L3E0uApf/hnVSJUdHalbtLmLWzR+pJSs+glQKBgBfG+tmA1Bqe5J6jtsH6I9Jxyp4ZyW8Kju1vxYg9AaZFPLnCvETxaJwLGmxfvzRrrsjCOXGPO3EhrNTntDE4SjFWqD+Ru1z8qJDuu7osmyHc0XuvLJx7eZ1O4kOXPgV/Kk9c0fSfV894m9p0yNtemtr4FnL6Zdl+/hOYoR7L9DO5AoGALHqHPpcA8ODEmB8dFOCZxM36zHL8UDLfP/0zwqDE1pKcF7zCgoRgcEXCA3Cr9RlEOMPIC2TSJprxp7H6P1tVhV1p2mxr67T6d9bj4b2YjUIaPiD2gE/nercrLj5i4OIortM5UCGstwSdd5VqKxogE6F+2SI++iMlvH1diadO8vECgYBADTA+FL7iUqsGAfsn1xQ5pe0l+vZpGSgJNEwu6SqGe3+x702PWn6yAfioyI/qgrkXEwQjJvaMLLJ9fx0z+cii/FGM/WmQODlo0nY+B9niIe0LzsRI5bJgOuJn7zKWLoaKvunDynFuXqhFpCJQX9OCWcJfKi55/cXc5oxzTsIkIA==
-  -----END PRIVATE KEY-----`;
-const RSA_PUBLIC_KEY =
-  'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyXswHJctc7UoSlxParKB5qQHiTRegntGbAhE5h+TmRvxmFqIyluKQChHViz4Hpp1d+SHsb4SooHH7LPzmHVESsRY2vDksE3iMx5dhSt5OMXnHFeUYMlXgS/CmBYsN9QSkTGo6t0URojqHnqnHxgdpo+MikamheAaftkv6naokuBHLrO02HPGRDr90Zwi7u/f3R3bGGUumWU+nbx0Gweki4ktpJhAW5VNFlV6sxn4r78P9Q3KAHSZMeV2uYuu6u5IM7idiZPL07zpFAGSzFqpjeejbgf2t98MmVnK/HRs0Gl3KLgJsaOiL1wPmMltV55W9sCZfIRt0lmcd00Y8FtdlwIDAQAB';
-
-const HOST = '127.0.0.1:9000';
-const NOTIFY_HOST = `http://${HOST}/admin/dj/open/`;
-
-@Provide()
-export class AlipayService extends BaseService {
-  @Inject()
-  utils: Utils;
-  @Inject()
-  httpService: HttpService;
-  @Logger()
-  logger: ILogger;
-
-  async order(payload) {
-    const param = {
-      agtId: AGT_ID,
-      merId: MER_ID,
-      tranCode: '4101',
-      orderAmt: (+payload.amount * 100).toFixed(0),
-      memberId: Math.floor(Math.random() * 10000) + 100000,
-      orderId: payload.orderNo,
-      orderTime: moment(payload.createTime).format('YYYYMMDD'),
-      pageReturnUrl: payload.returnUrl || 'https://www.baidu.com',
-      notifyUrl: NOTIFY_HOST + 'notifyOrder',
-      goodsName: this.utils.stringToHex('虚拟商品'),
-      goodsDetail: this.utils.stringToHex('虚拟商品'),
-    };
-    const md5Sign = md5(
-      this.utils.signSort(param) + `&key=${MD5_KEY}`
-    ).toUpperCase();
-    const sign = this.utils.signByRsa2(md5Sign, RSA_PRIVATE_KEY);
-    const data = {
-      REQ_HEAD: {
-        sign,
-      },
-      REQ_BODY: param,
-    };
-    const res = await this.httpService.post(URL, data);
-    console.log(data);
-    this.logger.info('下单接口返回', JSON.stringify(res.data));
-    const { REP_BODY } = res.data;
-    const { rspcode, rspmsg, payUrl } = REP_BODY;
-    if (rspcode === '000000' && payUrl) {
-      return payUrl;
-    } else {
-      throw new Error(this.utils.hexToString(rspmsg));
-    }
-  }
-
-  async query(payload) {
-    const param = {
-      agtId: AGT_ID,
-      merId: MER_ID,
-      tranCode: '4102',
-      orderId: payload.orderNo,
-    };
-    const md5Sign = md5(
-      this.utils.signSort(param) + `&key=${MD5_KEY}`
-    ).toUpperCase();
-    const sign = this.utils.signByRsa2(md5Sign, RSA_PRIVATE_KEY);
-    const data = {
-      REQ_HEAD: {
-        sign,
-      },
-      REQ_BODY: param,
-    };
-    const res = await this.httpService.post(URL, data);
-    console.log(data);
-    this.logger.info('查询接口返回', JSON.stringify(res.data));
-    const { REP_BODY } = res.data;
-    const { rspcode, submsg, rspmsg, orderState } = REP_BODY;
-    if (rspcode === '000000' && orderState === '01') {
-      return {
-        status: 1,
-      };
-    } else {
-      const message = submsg
-        ? this.utils.hexToString(submsg)
-        : this.utils.hexToString(rspmsg);
-      return {
-        status: 0,
-        message,
-      };
-    }
-  }
-
-  async queryBalance() {
-    const param = {
-      agtId: AGT_ID,
-      merId: MER_ID,
-      tranCode: '2103',
-      nonceStr: +moment(),
-    };
-    const md5Sign = md5(
-      this.utils.signSort(param) + `&key=${MD5_KEY}`
-    ).toUpperCase();
-    const sign = this.utils.signByRsa2(md5Sign, RSA_PRIVATE_KEY);
-    const data = {
-      REQ_HEAD: {
-        sign,
-      },
-      REQ_BODY: param,
-    };
-    const res = await this.httpService.post(URL, data);
-    console.log(data);
-    this.logger.info('查询余额接口返回', JSON.stringify(res.data));
-    const { REP_BODY } = res.data;
-    const { rspcode, acBal, rspmsg } = REP_BODY;
-    if (rspcode === '000000') {
-      return {
-        status: 1,
-        balance: +acBal,
-      };
-    } else {
-      return {
-        status: 0,
-        message: this.utils.hexToString(rspmsg),
-      };
-    }
-  }
-
-  async withdraw(payload) {
-    const param = {
-      agtId: AGT_ID,
-      merId: MER_ID,
-      tranCode: '2101',
-      orderId: payload.orderNo,
-      tranDate: moment(payload.createTime).format('YYYYMMDD'),
-      nonceStr: +moment(),
-      txnAmt: +payload.amount * 100,
-      accountNo: payload.accountNo,
-      accountType: '2',
-      certNum: '112044195412202433',
-      bankCode: payload.bankCode,
-      bankName: this.utils.stringToHex(payload.bankName),
-      accountName: this.utils.stringToHex(payload.accountName),
-      mobile: '15960566211',
-      email: '15960566211@1631.com',
-      gender: '1',
-      location: '北京市大户通路2111号',
-      notifyUrl: NOTIFY_HOST + 'notifyWithdraw',
-    };
-    const md5Sign = md5(
-      this.utils.signSort(param) + `&key=${MD5_KEY}`
-    ).toUpperCase();
-    const sign = this.utils.signByRsa2(md5Sign, RSA_PRIVATE_KEY);
-    const data = {
-      REQ_HEAD: {
-        sign,
-      },
-      REQ_BODY: param,
-    };
-    const res = await this.httpService.post(URL, data);
-    console.log(data);
-    this.logger.info('代付接口返回', JSON.stringify(res.data));
-    if (res.data.retCode === '500') {
-      return {
-        status: 1,
-        message: res.data.retMsg,
-      };
-    }
-    const { REP_BODY } = res.data;
-    const { rspcode, rspmsg, subcode, submsg } = REP_BODY;
-    if (rspcode === '000000') {
-      if (subcode === 'T010') {
-        return {
-          status: 2,
-          message: submsg
-            ? this.utils.hexToString(submsg)
-            : this.utils.hexToString(rspmsg),
-        };
-      }
-      return {
-        status: 1,
-      };
-    } else {
-      return {
-        status: 0,
-        message: this.utils.hexToString(rspmsg),
-      };
-    }
-  }
-
-  async queryWithdraw(payload) {
-    const param = {
-      agtId: AGT_ID,
-      merId: MER_ID,
-      tranCode: '2102',
-      orderId: payload.orderNo,
-      tranDate: moment(payload.createTime).format('YYYYMMDD'),
-      nonceStr: +moment(),
-    };
-    const md5Sign = md5(
-      this.utils.signSort(param) + `&key=${MD5_KEY}`
-    ).toUpperCase();
-    const sign = this.utils.signByRsa2(md5Sign, RSA_PRIVATE_KEY);
-    const data = {
-      REQ_HEAD: {
-        sign,
-      },
-      REQ_BODY: param,
-    };
-    const res = await this.httpService.post(URL, data);
-    console.log(data);
-    this.logger.info('查询接口返回', JSON.stringify(res.data));
-    const { REP_BODY } = res.data;
-    const { rspcode, submsg, rspmsg, subcode, tranId } = REP_BODY;
-    if (rspcode === '000000') {
-      if (subcode === '0000') {
-        return {
-          status: 1,
-          traceNo: tranId,
-        };
-      } else if (subcode === 'T010') {
-        return {
-          status: 2,
-          message: submsg
-            ? this.utils.hexToString(submsg)
-            : this.utils.hexToString(rspmsg),
-          traceNo: tranId,
-        };
-      } else {
-        return {
-          status: 0,
-          message: submsg
-            ? this.utils.hexToString(submsg)
-            : this.utils.hexToString(rspmsg),
-        };
-      }
-    } else {
-      const message = submsg
-        ? this.utils.hexToString(submsg)
-        : this.utils.hexToString(rspmsg);
-      return {
-        status: 0,
-        message,
-      };
-    }
-  }
-
-  async handleOrderNotify(data) {
-    const { REP_HEAD, REP_BODY } = data;
-    const { sign = '' } = REP_HEAD.sign;
-    if (!sign) {
-      throw new Error('sign null');
-    }
-    const md5Sign = md5(
-      this.utils.signSort(REP_BODY) + `&key=${MD5_KEY}`
-    ).toUpperCase();
-    const verifySign = this.utils.verifyByRsa2(md5Sign, sign, RSA_PUBLIC_KEY);
-    if (!verifySign) {
-      throw new Error('sign error');
-    }
-    if (REP_BODY.orderState !== '01') {
-      throw new Error('order no success');
-    }
-    return {
-      date: moment(REP_BODY.payTime, 'yyyyMMddHHmmss'),
-      amount: +(REP_BODY.orderAmt / 100).toFixed(2),
-      orderNo: REP_BODY.orderId,
-      traceNo: REP_BODY.tranSeqId,
-    };
-  }
-
-  async handleWithdrawNotify(data) {
-    const { REP_HEAD, REP_BODY } = data;
-    const { sign = '' } = REP_HEAD.sign;
-    if (!sign) {
-      throw new Error('sign null');
-    }
-    const md5Sign = md5(
-      this.utils.signSort(REP_BODY) + `&key=${MD5_KEY}`
-    ).toUpperCase();
-    const verifySign = this.utils.verifyByRsa2(md5Sign, sign, RSA_PUBLIC_KEY);
-    if (!verifySign) {
-      throw new Error('sign error');
-    }
-    if (REP_BODY.subcode !== '0000' || REP_BODY.subcode !== 'T010') {
-      throw new Error('withdraw no result');
-    }
-    return {
-      status: REP_BODY.subcode === '0000' ? 1 : 2,
-      amount: +(REP_BODY.orderAmt / 100).toFixed(2),
-      orderNo: REP_BODY.orderId,
-      traceNo: REP_BODY.tranId,
-      message: REP_BODY.submsg
-        ? this.utils.hexToString(REP_BODY.submsg)
-        : this.utils.hexToString(REP_BODY.rspmsg),
-    };
-  }
-}

+ 4 - 20
src/modules/dj/service/channels/dispatch.ts

@@ -1,16 +1,12 @@
 import { Inject, Provide } from '@midwayjs/decorator';
 import { BaseService } from '@cool-midway/core';
-import { AlipayService } from './alipay';
+import { SunPayService } from './sunpay';
 import { ChannelService } from '../channel';
-import { AlipayPcService } from './alipayPc';
 
 @Provide()
 export class DispatchService extends BaseService {
   @Inject()
-  alipayService: AlipayService;
-
-  @Inject()
-  alipayPcService: AlipayPcService;
+  sunPayService: SunPayService;
 
   @Inject()
   channelService: ChannelService;
@@ -23,18 +19,6 @@ export class DispatchService extends BaseService {
     }
   }
 
-  async settle(order, channel) {
-    try {
-      if (+order.settleStatus === 0) {
-        return await this[channel.service].settle(order);
-      } else {
-        return await this[channel.service].settleFinish(order);
-      }
-    } catch (e) {
-      throw new Error('请求通道接口失败,失败原因:' + e.message);
-    }
-  }
-
   async query(order) {
     try {
       const channel = await this.channelService.queryByCode(order.code);
@@ -77,10 +61,10 @@ export class DispatchService extends BaseService {
     }
   }
 
-  async handleOrderNotify(code, data) {
+  async handleOrderNotify(code, data, headers) {
     try {
       const channel = await this.channelService.queryByCode(code);
-      return await this[channel.service].handleOrderNotify(data);
+      return await this[channel.service].handleOrderNotify(data, headers);
     } catch (e) {
       throw new Error('交易回调处理接口失败,失败原因:' + e.message);
     }

+ 261 - 0
src/modules/dj/service/channels/sunpay.ts

@@ -0,0 +1,261 @@
+import { Inject, Logger, Provide } from '@midwayjs/decorator';
+import { BaseService } from '@cool-midway/core';
+import * as _ from 'lodash';
+import * as moment from 'moment';
+import { Utils } from '../../../../comm/utils';
+import { HttpService } from '@midwayjs/axios';
+import * as md5 from 'md5';
+import { ILogger } from '@midwayjs/logger';
+import * as FormData from 'form-data';
+import * as fs from 'fs';
+
+const URL = 'https://sandbox-oapi.sunpay.pro/api/v3-2/Fiat/Direct/PayIn';
+//  https://oapi.sunpay.pro/api/v3/Crypto/PayIn 
+const BASIC_KEY_URL = 'https://sandbox-oapi.sunpay.pro/api/v3-2/Fiat/Kyc/CreateBasicUser';
+// https://oapi.sunpay.pro/api/v3-2/Fiat/Kyc/CreateBasicUser 
+const ADVANCED_KEY_URL = 'https://sandbox-oapi.sunpay.pro/api/v3-2/Fiat/Kyc/CreateAdvancedUser';
+//  https://oapi.sunpay.pro/api/v3-2/Fiat/Kyc/CreateAdvancedUser 
+const PREMINUM_KEY_URL = 'https://sandbox-oapi.sunpay.pro/api/v3-2/Fiat/Kyc/CreatePremiumUser';
+// https://oapi.sunpay.pro/api/v3-2/Fiat/Kyc/CreatePremiumUser
+const COUNTRIES_URL = 'https://sandbox-oapi.sunpay.pro/api/v3-2/Fiat/Countries';
+// const COUNTRIES_URL = 'https://oapi.sunpay.pro/api/v3-2/Fiat/Countries';
+
+const API_KEY = '08dcd396-a91a-486a-8ca5-02150819d7ae';
+const API_SERECT = '8357086ffbd2cc610afe8a5f5359f7f8563bd887f9e6c376bc3b86b512e2e74e';
+const HOST = '127.0.0.1:9000';
+const NOTIFY_HOST = `http://${HOST}/api/admin/dj/open/`;
+const MER_ID = '08dcd396-a91a-486a-8ca5-02150819d7ae';
+
+@Provide()
+export class SunPayService extends BaseService {
+  @Inject()
+  utils: Utils;
+  @Inject()
+  httpService: HttpService;
+  @Logger()
+  logger: ILogger;
+
+  async createPremium(payload: any, files: any) {
+    const form = new FormData();
+    form.append('customer_id', payload.customerId);
+    form.append('residential_address', fs.createReadStream(files.residentialAddress.data));
+    form.append('income', fs.createReadStream(files.income.data));
+    const timestamp = +moment();
+    const nonce = md5(timestamp);
+    const signData = timestamp + nonce;
+    const sign = this.utils.signBySha256(signData, API_SERECT).toUpperCase();
+    const res = await this.httpService.post(PREMINUM_KEY_URL, form, {
+      headers: {
+        'SunPay-Key': API_KEY,
+        'SunPay-Timestamp': timestamp,
+        'SunPay-Nonce': nonce,
+        'SunPay-Sign': sign,
+        ...form.getHeaders()
+      }
+    });
+    this.logger.info('高级KYC接口返回', JSON.stringify(res.data));
+    const { is_success, msg, data } = res.data;
+    if (is_success && data) {
+      return {
+        customerId: payload.customerId
+      };
+    } else if (!is_success && msg === 'Server Error: Cannot premium repeatedly') {
+      return {
+        customerId: payload.customerId
+      };
+    } else {
+      throw new Error(msg);
+    }
+  }
+
+  async createAdvanced(payload: any, files: any) {
+    const form = new FormData();
+    form.append('customer_id', payload.customerId);
+    form.append('document_type', payload.documentType);
+    form.append('document_number', payload.documentNumber);
+    form.append('front_side_document', fs.createReadStream(files.frontSideDocument.data));
+    form.append('back_side_document', fs.createReadStream(files.backSideDocument.data));
+    form.append('customer_photo', fs.createReadStream(files.customerPhoto.data));
+    const timestamp = +moment();
+    const nonce = md5(timestamp);
+    const signData = timestamp + nonce;
+    const sign = this.utils.signBySha256(signData, API_SERECT).toUpperCase();
+    const res = await this.httpService.post(ADVANCED_KEY_URL, form, {
+      headers: {
+        'SunPay-Key': API_KEY,
+        'SunPay-Timestamp': timestamp,
+        'SunPay-Nonce': nonce,
+        'SunPay-Sign': sign,
+        ...form.getHeaders()
+      }
+    });
+    this.logger.info('进阶KYC接口返回', JSON.stringify(res.data));
+    const { is_success, msg, data } = res.data;
+    if (is_success && data) {
+      return {
+        customerId: payload.customerId
+      };
+    } else if (!is_success && msg === 'Server Error: Cannot advance repeatedly') {
+      return {
+        customerId: payload.customerId
+      };
+    } else {
+      throw new Error(msg);
+    }
+  }
+
+  async createBasic(payload: any) {
+    const param = {
+      "out_user_id": payload.outUserId,
+      "customer_name": payload.customerName,
+      "customer_email": payload.customerEmail,
+      "country_code": payload.countryCode,
+      "residence_country": payload.residenceCountry,
+      "birth_date": moment(payload.birthDate).format('YYYY-MM-DD HH:mm:ss'),
+    }
+    const timestamp = +moment();
+    const nonce = md5(timestamp);
+    const data = timestamp + nonce + JSON.stringify(param);
+    const sign = this.utils.signBySha256(data, API_SERECT).toUpperCase();
+    const res = await this.httpService.post(BASIC_KEY_URL, param, {
+      headers: {
+        'SunPay-Key': API_KEY,
+        'SunPay-Timestamp': timestamp,
+        'SunPay-Nonce': nonce,
+        'SunPay-Sign': sign
+      }
+    });
+    this.logger.info('基础KYC接口返回', param, JSON.stringify(res.data));
+    const { is_success, msg } = res.data;
+    if (is_success && res.data.data.customer_id) {
+      return {
+        customerId: res.data.data.customer_id
+      };
+    } else {
+      throw new Error(msg);
+    }
+  }
+
+  async getCountries() {
+    const timestamp = +moment();
+    const nonce = md5(timestamp);
+    const data = timestamp + nonce;
+    const sign = this.utils.signBySha256(data, API_SERECT).toUpperCase();
+    const res = await this.httpService.get(COUNTRIES_URL, {
+      headers: {
+        'SunPay-Key': API_KEY,
+        'SunPay-Timestamp': timestamp,
+        'SunPay-Nonce': nonce,
+        'SunPay-Sign': sign
+      }
+    });
+    const { is_success, msg } = res.data;
+    if (is_success) {
+      return res.data.data;
+    } else {
+      throw new Error(msg);
+    }
+  }
+
+  async order(payload) {
+    return {
+      payUrl: `http://localhost:8080/#/?t=${payload.orderNo}`,
+      isDone: false
+    };
+  }
+
+  async createOrder(payload) {
+    const param = {
+      out_order_no: payload.orderNo,
+      customer_id: payload.customerId,
+      currency: 'USD',
+      amount: +payload.amount,
+      payment_type: "CARD",
+      client_ip: payload.userIp || "127.0.0.0",
+      billing_address_country_code: "US",
+      webhook_url: NOTIFY_HOST + '/SunCard/notifyOrder',
+      cancel_url: payload.returnUrl || 'https://www.baidu.com',
+    };
+    const timestamp = +moment();
+    const nonce = md5(timestamp);
+    const data = timestamp + nonce + JSON.stringify(param);
+    const sign = this.utils.signBySha256(data, API_SERECT).toUpperCase();
+    const res = await this.httpService.post(URL, param, {
+      headers: {
+        'SunPay-Key': API_KEY,
+        'SunPay-Timestamp': timestamp,
+        'SunPay-Nonce': nonce,
+        'SunPay-Sign': sign
+      }
+    });
+    this.logger.info('下单接口返回', param, JSON.stringify(res.data));
+    const { is_success, msg } = res.data;
+    if (is_success && res.data.data.check_out_url) {
+      return {
+        payUrl: res.data.data.check_out_url,
+        traceNo: res.data.data.order_no
+      };
+    } else {
+      throw new Error(msg);
+    }
+  }
+
+  async query(payload) {
+    if (!payload.traceNo) {
+      return {
+        status: 0,
+        msg: 'traceNo is'
+      }
+    }
+    const param = {
+      orderNo: payload.traceNo
+    };
+    const timestamp = +moment();
+    const nonce = md5(timestamp);
+    const data = timestamp + nonce;
+    const sign = this.utils.signBySha256(data, API_SERECT).toUpperCase();
+    const res = await this.httpService.get(`${URL}/${param.orderNo}`, {
+      headers: {
+        'SunPay-Key': API_KEY,
+        'SunPay-Timestamp': timestamp,
+        'SunPay-Nonce': nonce,
+        'SunPay-Sign': sign
+      }
+    });
+    this.logger.info('查询接口返回', JSON.stringify(res.data));
+    const { is_success, msg } = res.data;
+    if (is_success && res.data.data.order_status === 'SUCCESS ') {
+      return {
+        status: 1,
+      };
+    } else {
+      return {
+        status: 0,
+        msg,
+      };
+    }
+  }
+
+  async handleOrderNotify(payload, headers) {
+    const { data } = payload;
+    const sign = headers['SunPay-Sign'];
+    const timestamp = headers['SunPay-Timestamp'];
+    const nonce = headers['SunPay-Nonce'];
+    const key = headers['SunPay-Key'];
+    const str = timestamp + nonce + JSON.stringify(data);
+    const validSign = this.utils.signBySha256(str, API_SERECT).toUpperCase();
+    if (sign !== validSign) {
+      throw new Error('sign error');
+    }
+    if (data.status !== 'SUCCESS') {
+      throw new Error('order no success');
+    }
+    return {
+      date: moment(),
+      orderNo: data.out_order_no,
+      traceNo: data.order_no,
+    };
+  }
+
+
+}

+ 168 - 0
src/modules/dj/service/kyc.ts

@@ -0,0 +1,168 @@
+import { Inject, Provide } from '@midwayjs/decorator';
+import { BaseService, CoolCommException } from '@cool-midway/core';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import * as _ from 'lodash';
+import { KycEntity } from '../entity/kyc';
+import { OrderService } from './order';
+import { SunPayService } from './channels/sunpay';
+import * as md5 from 'md5';
+
+@Provide()
+export class KycService extends BaseService {
+
+
+  @InjectEntityModel(KycEntity)
+  kycEntity: Repository<KycEntity>;
+
+  @Inject()
+  orderService: OrderService;
+
+  @Inject()
+  sunPayService: SunPayService
+
+  async toPay(payload) {
+    const { orderNo, customerId } = payload;
+    const order = await this.orderService.findByOrderNo(orderNo);
+    if(!orderNo || !order) {
+      throw new CoolCommException('Order does not exist');
+    }
+    if(order.payUrl) {
+      return {
+        payUrl: order.payUrl
+      };
+    }
+    const { payUrl, traceNo } =  await this.sunPayService.createOrder({
+      ...order,
+      customerId
+    })
+    order.payUrl = payUrl;
+    order.traceNo = traceNo;
+    await this.orderService.create(order);
+    return {
+      payUrl
+    };
+  }
+
+  async createAdvanced(payload, files){
+    const kyc = await this.kycEntity.findOneBy({customerId: payload.customerId});
+    if(!payload.customerId || !kyc) {
+      throw new CoolCommException('KYC user does not exist');
+    }
+    const fileObj = {};
+    files.forEach(item => {
+      fileObj[item.fieldName] = item
+    })  
+    const data = await this.sunPayService.createAdvanced(payload, fileObj);
+    await this.kycEntity.update(kyc.id, { level: '2' });
+    return data;
+  }
+
+  async createPremium(payload, files){
+    const kyc = await this.kycEntity.findOneBy({customerId: payload.customerId});
+    if(!payload.customerId || !kyc) {
+      throw new CoolCommException('KYC user does not exist');
+    }
+    const fileObj = {};
+    files.forEach(item => {
+      fileObj[item.fieldName] = item
+    })  
+    const data = await this.sunPayService.createPremium(payload, fileObj);
+    await this.kycEntity.update(kyc.id, { level: '3' });
+    return data;
+  }
+
+  async createBasic(payload) {
+    const { orderNo } = payload;
+    const order = await this.orderService.findByOrderNo(orderNo);
+    if(!orderNo || !order) {
+      throw new CoolCommException('Order does not exist');
+    }
+    const { customerId } = await this.sunPayService.createBasic(payload);
+    if(customerId) {
+      await this.kycEntity.insert({
+        userId: order.userId,
+        mchId: order.mchId,
+        code: order.code,
+        kycUserId: payload.outUserId,
+        customerId: customerId,
+        level: '1'
+      })
+      return { customerId }
+    } else {
+      throw new CoolCommException('Network exception, please try again later');
+    }
+  }
+
+  async getCountries() {
+    return await this.sunPayService.getCountries();
+  }
+
+  async getLevel(orderNo) {
+    const order = await this.orderService.findByOrderNo(orderNo);
+    if(!orderNo || !order) {
+      throw new CoolCommException('Order does not exist');
+    }
+    const outUserId = md5(order.mchId + '_' + order.userId + 'v1');
+    const data = {
+      outUserId,
+      level: 1,
+      levelEnd: 3,
+      customerId: ''
+    }
+    const kyc = await this.kycEntity.findOneBy({
+      userId: order.userId,
+      code: order.code,
+      mchId: order.mchId,
+    });
+    if(kyc) {
+      data.level = +kyc.level + 1;
+      data.customerId = kyc.customerId
+    }
+    if(data.level > 3) {
+      return data;
+    }
+    const dailyLevel = this.getAmountLevel(order.amount);
+    if(dailyLevel === 3) {
+      data.levelEnd = dailyLevel;
+      return data;
+    }
+    const weekly = await this.getWeekLyLevel(order);
+    if(weekly === 3) {
+      data.levelEnd = dailyLevel;
+      return data;
+    }
+    const monthly = await this.getMonthlyLevel(order);
+    data.levelEnd = Math.max(dailyLevel, weekly, monthly);
+    return data;
+  }
+
+  async getMonthlyLevel(order) {
+    const month = await this.orderService.getMonthlyAmount(order.mchId, order.userId, order.code);
+    let amount = 0;
+    if(month && month[0]) {
+      amount = +month[0].total;
+    }
+    return this.getAmountLevel(amount + +order.amount);
+  }
+
+  async getWeekLyLevel(order) {
+    const weekly = await this.orderService.getWeeklyAmount(order.mchId, order.userId, order.code);
+    let amount = 0;
+    if(weekly && weekly[0]) {
+      amount = +weekly[0].total;
+    }
+    return this.getAmountLevel(amount + +order.amount);
+  }
+
+  getAmountLevel(amount: number) {
+    if(amount <= 1000) {
+      return 1;
+    } else if(amount <= 5000) {
+      return 2;
+    } else {
+      return 3;
+    }
+  }
+
+}

+ 36 - 25
src/modules/dj/service/order.ts

@@ -39,8 +39,8 @@ export class OrderService extends BaseService {
   @Inject()
   utils: Utils;
 
-  async handleNotify(code, payload) {
-    const data = await this.dispatchService.handleOrderNotify(code, payload);
+  async handleNotify(code, payload, headers) {
+    const data = await this.dispatchService.handleOrderNotify(code, payload, headers);
     const order = await this.orderEntity.findOneBy({ orderNo: data.orderNo });
     order.date = data.date;
     order.traceNo = data.traceNo;
@@ -74,19 +74,6 @@ export class OrderService extends BaseService {
     }
   }
 
-  async settle(id) {
-    const order = await this.orderEntity.findOneBy({ id });
-    if (order && +order.status === 1) {
-      const channel = await this.channelService.getOne(order.code);
-      return await this.dispatchService.settle(order, channel);
-    }
-  }
-
-  
-  async getForm(orderNo: string) {
-    return await this.orderEntity.findOneBy({ orderNo });
-  }
-
   async queryByApi(id) {
     const order = await this.orderEntity.findOneBy({ id });
     if (!order) {
@@ -147,6 +134,10 @@ export class OrderService extends BaseService {
     return await this.orderEntity.findOneBy({ outOrderNo });
   }
 
+  async findByOrderNo(orderNo: any) {
+    return await this.orderEntity.findOneBy({ orderNo: orderNo });
+  }
+
   async dashboard(query) {
     let { mchId = '' } = query;
     const { roleIds, userId } = this.ctx.admin;
@@ -264,8 +255,6 @@ export class OrderService extends BaseService {
       status = '',
       notifyStatus = '',
       createTime = [],
-      mainId = '',
-      settleStatus = '',
     } = query;
     if (!createTime || createTime.length !== 2) {
       return {
@@ -314,10 +303,8 @@ export class OrderService extends BaseService {
           : this.setSql(mchId, 'and a.mchId like ?', [`%${mchId}%`])
       }
       ${this.setSql(code, 'and a.code = ?', [code])}
-      ${this.setSql(mainId, 'and a.mainId = ?', [mainId])}
       ${this.setSql(status, 'and a.status = ?', [status])}
       ${this.setSql(notifyStatus, 'and a.notifyStatus = ?', [notifyStatus])}
-      ${this.setSql(settleStatus, 'and a.settleStatus = ?', [settleStatus])}
       ${this.setSql(
         createTime && createTime.length === 2,
         'and (a.createTime between ? and ?)',
@@ -337,8 +324,6 @@ export class OrderService extends BaseService {
       status = '',
       notifyStatus = '',
       createTime = [],
-      mainId = '',
-      settleStatus = '',
     } = query;
     if (!createTime || createTime.length !== 2) {
       throw new CoolCommException('请选择日期范围');
@@ -355,9 +340,9 @@ export class OrderService extends BaseService {
         mchId = '-1';
       }
     }
-    const sql = `SELECT a.id, a.orderNo, a.outOrderNo, a.traceNo, a.mchId,  a.code, a.amount,${
+    const sql = `SELECT a.id, a.orderNo, a.outOrderNo, a.payUrl, a.traceNo, a.mchId,  a.code, a.amount,${
       this.utils.isMerchant(roleIds) ? '' : 'a.channelCharge,'
-    } a.charge, a.status, a.notifyStatus, a.userId, a.userIp, a.mainId, a.settleStatus, a.settleNo, a.date, a.notifyUrl, a.returnUrl, a.remark, a.createTime, a.updateTime FROM dj_order a WHERE 1=1
+    } a.charge, a.status, a.notifyStatus, a.userId, a.userIp,  a.date, a.notifyUrl, a.returnUrl, a.remark, a.createTime, a.updateTime FROM dj_order a WHERE 1=1
       ${this.setSql(orderNo, 'and a.orderNo like ?', [`%${orderNo}%`])}
       ${this.setSql(outOrderNo, 'and a.outOrderNo like ?', [`%${outOrderNo}%`])}
       ${this.setSql(traceNo, 'and a.traceNo like ?', [`%${traceNo}%`])}
@@ -367,10 +352,8 @@ export class OrderService extends BaseService {
           : this.setSql(mchId, 'and a.mchId like ?', [`%${mchId}%`])
       }
       ${this.setSql(code, 'and a.code = ?', [code])}
-      ${this.setSql(mainId, 'and a.mainId = ?', [mainId])}
       ${this.setSql(status, 'and a.status = ?', [status])}
       ${this.setSql(notifyStatus, 'and a.notifyStatus = ?', [notifyStatus])}
-      ${this.setSql(settleStatus, 'and a.settleStatus = ?', [settleStatus])}
       ${this.setSql(
         createTime && createTime.length === 2,
         'and (a.createTime between ? and ?)',
@@ -379,4 +362,32 @@ export class OrderService extends BaseService {
     `;
     return this.sqlRenderPage(sql, query);
   }
+
+  async getWeeklyAmount(mchId, userId, code) {
+    const time = [
+      moment().startOf('week').format('YYYY-MM-DD') + ' 00:00:00',
+      moment().endOf('week').format('YYYY-MM-DD') + ' 23:59:59',
+    ];
+    const sql = `SELECT SUM(IF(a.status = 1, a.amount, 0)) as total FROM dj_order a WHERE 1=1 
+    ${ this.setSql(mchId, 'and a.mchId = ?', [`${mchId}`]) }
+    ${ this.setSql(userId, 'and a.userId = ?', [`${userId}`]) }
+    ${ this.setSql(code, 'and a.code = ?', [`${code}`]) }
+    ${this.setSql(true, 'and (a.createTime between ? and ?)', time)}
+    `
+    return await this.nativeQuery(sql);
+  }
+
+  async getMonthlyAmount(mchId, userId, code) {
+    const time = [
+      moment().startOf('month').format('YYYY-MM-DD') + ' 00:00:00',
+      moment().endOf('month').format('YYYY-MM-DD') + ' 23:59:59',
+    ];
+    const sql = `SELECT SUM(IF(a.status = 1, a.amount, 0)) as total FROM dj_order a WHERE 1=1 
+    ${ this.setSql(mchId, 'and a.mchId = ?', [`${mchId}`]) }
+    ${ this.setSql(userId, 'and a.userId = ?', [`${userId}`]) }
+    ${ this.setSql(code, 'and a.code = ?', [`${code}`]) }
+    ${this.setSql(true, 'and (a.createTime between ? and ?)', time)}
+    `
+    return await this.nativeQuery(sql);
+  }
 }

+ 9 - 5
src/modules/dj/service/pay.ts

@@ -86,7 +86,6 @@ export class PayService extends BaseService {
         traceNo: '',
         code: channel.code,
         mchId: merchant.mchId,
-        mainId: '',
         amount: payload.amount,
         channelCharge: (+payload.amount * +channel.rate) / 100,
         charge: (+payload.amount * +merchant.rate) / 100,
@@ -100,13 +99,15 @@ export class PayService extends BaseService {
         userIp: payload.userIp || '',
       };
       await this.orderService.create(order);
-      const { payUrl, mainId } = await this.dispatchService.order(
+      const { isDone = true, payUrl, traceNo } = await this.dispatchService.order(
         order,
         channel
       );
-      order.mainId = mainId;
-      order.form = payUrl;
-      await this.orderService.create(order);
+      if(isDone) {
+        order.payUrl = payUrl;
+        order.traceNo = traceNo;
+        await this.orderService.create(order);
+      }
       return {
         orderNo: order.orderNo,
         outOrderNo: payload.outOrderNo,
@@ -142,6 +143,9 @@ export class PayService extends BaseService {
     if (+payload.amount <= 0) {
       throw new Error('支付金额【amount】必须大于0');
     }
+    if (_.isEmpty(payload.userId)) {
+      throw new Error('商户平台用户ID【userId】不能为空');
+    }
     // if (!Number.isInteger(+payload.amount)) {
     //   throw new Error('支付金额【amount】必须为整数');
     // }