支付

cool-admin 自带封装了微信和支付宝支付

微信支付

1、安装插件

npm install @cool-midway/pay

2、引入插件

src/configuration.ts

import { App, Configuration } from '@midwayjs/decorator';
import { ILifeCycle, IMidwayContainer } from '@midwayjs/core';
import { Application } from 'egg';
import * as orm from '@midwayjs/orm';
import * as cool from '@cool-midway/core';
import * as pay from '@cool-midway/pay';

@Configuration({
  // 注意组件顺序 cool 有依赖orm组件, 所以必须放在,orm组件之后 cool的其他组件必须放在cool 核心组件之后
  imports: [
    pay,
  ],
})
export class ContainerLifeCycle {
  @App()
  app: Application;
  // 应用启动完成
  async onReady(container?: IMidwayContainer) {}
  // 应用停止
  async onStop() {}
}

3、配置

也可以在src/config/xxx.ts配置(两种配置都存在,此种方式优先)

config.cool = {
  pay:{
    wx: {
        appid: '公众号ID',
        mchid: '微信商户号',
        partnerKey: '微信支付安全密钥',
        publicKey: require('fs').readFileSync('公钥证书文件路径'),
        privateKey: require('fs').readFileSync('私钥证书文件路径'),
        notify_url: '支付回调地址',
        key: '可选参数 APIv3密钥'
    },
  }
};

4、API

其他支付方式可以参考wechatpay-node-v3open in new window

支付宝支付

1、安装插件

@cool-midway/pay

2、引入插件

src/configuration.ts

import { App, Configuration } from '@midwayjs/decorator';
import { ILifeCycle, IMidwayContainer } from '@midwayjs/core';
import { Application } from 'egg';
import * as orm from '@midwayjs/orm';
import * as cool from '@cool-midway/core';
import * as pay from '@cool-midway/pay';

@Configuration({
  // 注意组件顺序 cool 有依赖orm组件, 所以必须放在,orm组件之后 cool的其他组件必须放在cool 核心组件之后
  imports: [
    pay,
  ],
})
export class ContainerLifeCycle {
  @App()
  app: Application;
  // 应用启动完成
  async onReady(container?: IMidwayContainer) {}
  // 应用停止
  async onStop() {}
}

3、配置

也可以在src/config/xxx.ts配置(两种配置都存在,此种方式优先)

config.cool = {
    pay: {
      ali: {
        notifyUrl: 'https://xxx/app/order/pay/aliNotify',
        appId: 'xxxxxx',
        privateKey: fs.readFileSync(
          path.resolve('./src/modules/order/pem/ali/privateKey.pem'),
          'ascii'
        ),
        keyType: 'PKCS1',
        appCertPath: path.resolve(
          './src/modules/order/pem/ali/appCertPublicKey.crt'
        ),
        alipayRootCertPath: path.resolve(
          './src/modules/order/pem/ali/alipayRootCert.crt'
        ),
        alipayPublicCertPath: path.resolve(
          './src/modules/order/pem/ali/alipayCertPublicKey_RSA2.crt'
        ),
      },
    },
}

WARNING

注意私钥需要用支付宝密钥工具转成PKCS1格式

4、API

其他支付方式可以参考alipay-sdkopen in new window

DEMO示例

Controller,需要注意开放回调接口,不要对其进行token校验

import {
  CoolController,
  BaseController,
  CoolUrlTag,
  TagTypes,
} from '@cool-midway/core';
import { Body, Inject, Post } from '@midwayjs/core';
import { OrderPayService } from '../../service/pay';

/**
 * 支付
 */
@CoolUrlTag({
  key: TagTypes.IGNORE_TOKEN,
  value: ['wxNotify', 'aliNotify'],
})
@CoolController()
export class AppOrderPayController extends BaseController {
  @Inject()
  orderPayService: OrderPayService;

  @Post('/aliNotify', { summary: '支付宝支付回调通知' })
  async aliNotify(@Body() body) {
    this.orderPayService.aliNotify(body);
    return 'success';
  }

  @Post('/aliQrcode', { summary: '支付宝扫码支付' })
  async aliQrcode(
    @Body('orderId') orderId: number,
    @Body('width') width: number
  ) {
    return this.ok(await this.orderPayService.aliQrcode(orderId, width));
  }

  @Post('/aliApp', { summary: '支付宝APP支付' })
  async aliApp(@Body('orderId') orderId: number) {
    return this.ok(await this.orderPayService.aliApp(orderId));
  }

  @Post('/wxApp', { summary: '微信APP支付' })
  async wxApp(@Body('orderId') orderId: number) {
    return this.ok(await this.orderPayService.wxApp(orderId));
  }

  @Post('/wxNotify', { summary: '微信支付回调' })
  async wxNotify(@Body() body) {
    await this.orderPayService.wxNotify(body);
    return 'success';
  }

  @Post('/wxQrcode', { summary: '微信扫码支付' })
  async wxQrcode(@Body() body) {
    await this.orderPayService.wxQrcode(body);
    return 'success';
  }
}

Service

import { Config, Inject, Provide } from '@midwayjs/decorator';
import { BaseService, CoolCommException } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { OrderInfoEntity } from '../entity/info';
// @ts-ignore
import { sign } from 'alipay-sdk/lib/util';
import { CoolWxPay, CoolAliPay } from '@cool-midway/pay';
// @ts-ignore
import AlipayFormData from 'alipay-sdk/lib/form';
// @ts-ignore
import { Decimal } from 'decimal.js';
import { UserVipService } from '../../user/service/vip';
import { SeesionSocketService } from '../../session/service/socket';

/**
 * 支付
 */
@Provide()
export class OrderPayService extends BaseService {
  @InjectEntityModel(OrderInfoEntity)
  orderInfoEntity: Repository<OrderInfoEntity>;

  @Config('appName')
  appName: string;

  // 微信支付
  @Inject()
  wxPay: CoolWxPay;

  // 支付宝支付
  @Inject()
  aliPay: CoolAliPay;

  @Inject()
  userVipService: UserVipService;

  @Inject()
  seesionSocketService: SeesionSocketService;

  @Inject()
  ctx;

  /**
   * 微信APP支付
   * @param orderId
   * @returns
   */
  async wxApp(orderId: number) {
    const info = await this.orderInfo(orderId);
    const params = {
      description: `${this.appName}-订单`,
      out_trade_no: info?.orderNum,
      notify_url: this.wxPay.coolWxPay.notify_url,
      amount: {
        total: new Decimal(info.price).times(100).toNumber(),
      },
    };
    const result = await this.wxPay.getInstance().transactions_app(params);
    return result;
  }

  /**
   * 微信二维码支付
   * @param orderId
   */
  async wxQrcode(orderId: number) {
    const info = await this.orderInfo(orderId);
    const params = {
      description: `${this.appName}-订单`,
      out_trade_no: info?.orderNum,
      notify_url: this.wxPay.coolWxPay.notify_url,
      amount: {
        total: new Decimal(info.price).times(100).toNumber(),
      },
    };
    const result = await this.wxPay.getInstance().transactions_native(params);
    return result;
  }

  /**
   * 微信退款
   * @param order
   */
  async wxRefund(order: OrderInfoEntity) {
    const params = {
      description: `${this.appName}-订单`,
      out_trade_no: order.orderNum,
      notify_url: this.wxPay.coolWxPay.notify_url,
      amount: {
        refund: new Decimal(order.refundAmount).times(100).toNumber(),
        total: new Decimal(order.price).times(100).toNumber(),
      },
    };
    const result = await this.wxPay.getInstance().transactions_app(params);
    return result.status == 'SUCCESS';
  }

  /**
   * 订单信息
   * @param orderId
   */
  async orderInfo(orderId: number) {
    const info = await this.orderInfoEntity.findOneBy({ id: orderId });
    if (!info && info?.payStatus != 0) {
      throw new CoolCommException('订单不存在或不是可支付的状态');
    }
    return info;
  }

  /**
   * 支付宝App支付
   * @param orderId
   */
  async aliApp(orderId: number) {
    const info = await this.orderInfo(orderId);
    // 返回支付链接
    const data = sign(
      'alipay.trade.app.pay',
      {
        notifyUrl: this.aliPay.coolAlipay.notifyUrl,
        bizContent: {
          subject: `${this.appName}-订单`,
          totalAmount: info.price,
          outTradeNo: info.orderNum,
          productCode: 'QUICK_MSECURITY_PAY',
          body: {},
        },
      },
      this.aliPay.getInstance().config
    );
    const payInfo = new URLSearchParams(data).toString();
    return payInfo;
  }

  /**
   * 支付宝扫码支付
   * @param orderId
   */
  async aliQrcode(orderId: number, width = 400): Promise<any> {
    const info = await this.orderInfo(orderId);
    const formData = new AlipayFormData();
    // 调用 setMethod 并传入 get,会返回可以跳转到支付页面的 url
    formData.setMethod('get');
    formData.addField('notifyUrl', this.aliPay.coolAlipay.notifyUrl);
    formData.addField('bizContent', {
      outTradeNo: info.orderNum,
      productCode: 'FAST_INSTANT_TRADE_PAY',
      totalAmount: info.price,
      subject: `${this.appName}-订单`,
      qrPayMode: 4,
      qrcodeWidth: width,
      body: JSON.stringify({
        orderId,
      }),
    });
    // 返回支付链接
    const result = await this.aliPay
      .getInstance()
      .exec('alipay.trade.page.pay', {}, { formData });
    return result;
  }

  /**
   * 支付宝支付回调通知
   * @param data
   */
  async aliNotify(data: any) {
    // 检查签名
    const check = await this.aliPay.signVerify(data);
    if (check && data.trade_status == 'TRADE_SUCCESS') {
      await this.paySuccess(data.out_trade_no, 1);
    }
  }

  /**
   * 微信支付回调通知
   */
  async wxNotify(body: any) {
    const { ciphertext, associated_data, nonce } = body.resource;
    const data: any = this.wxPay
      .getInstance()
      .decipher_gcm(ciphertext, associated_data, nonce);
    const check = await this.wxPay.signVerify(this.ctx);
    // 验签通过,处理业务逻辑
    if (check && data.trade_state == 'SUCCESS') {
      await this.paySuccess(data.out_trade_no, 0);
    }
  }

  /**
   * 支付成功
   * @param orderNum
   * @param payWay
   */
  async paySuccess(orderNum: string, payWay: number) {
    await this.orderInfoEntity.update(
      { orderNum },
      { payStatus: 1, payWay, payTime: new Date() }
    );
    await this.userVipService.paySuccess(orderNum);

    const order = await this.orderInfoEntity.findOneBy({ orderNum });

    this.seesionSocketService.sendByUserId(order.userId, 'paySuccess', order);
  }

  /**
   * 订单号
   * @param order
   */
  async aliRefund(order: OrderInfoEntity) {
    const result = await this.aliPay.getInstance().exec('alipay.trade.refund', {
      bizContent: {
        out_trade_no: order.orderNum,
        refund_amount: order.price,
        refund_reason: order.refundReason,
      },
    });
    return result.code == '10000';
  }
}

Last Updated: