Skip to content

baaxl9vh/nest-wechat

Repository files navigation

nest-wechat

微信公众号、微信程序开、微信小游戏、微信支付以及企业微信等服务端API nestjs 模块封装。也可以直接当工具类使用。

nest-wechat是自用业务的封装,如果你需要用到的api还没有,可以提 issues 给我,我会尽快补上。


快速开始

安装

npm i --save nest-wechat

nestjs 模块引入

  • register方法注册
import { Module } from '@nestjs/common';

import { WeChatModule } from 'nest-wechat';

@Module({
  imports: [WeChatModule.register({appId: 'your app id', secret: 'your secret'})],
})
export class AppModule {
}
  • forRoot配置注册
import { CACHE_MANAGER, Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Cache } from 'cache-manager';
import { RedisCache, WeChatModule } from 'nest-wechat';

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: '.env.test.local',
    }),
    WeChatModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService, CACHE_MANAGER],
      useFactory: (configService: ConfigService, cache: Cache) => ({
        appId: configService.get('WX_APPID'),
        secret: configService.get('WX_SECRET'),
        token: configService.get('WX_TOKEN'),
        encodingAESKey: configService.get('WX_AESKEY'),
        cacheAdapter: new RedisCache(cache),
        debug: true,
      }),
    }),
  ]
})
export class AppModule {
}

工具类引入

import { WeChatService } from 'nest-wechat';
const service = new WeChatService({ appId: 'your app id', secret: 'your secret'});

全局接口

ICache

/**
 * 缓存接口,需要自定义缓存,请实现该接口
 * 
 * cache interface, please implement this interface if you need.
 * 
 */
export interface ICache {
  get<T> (key: string): Promise<T>;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  set (key: string, value: any): void;
  remove (key: string): boolean;
  close (): void;
}

WeChatService 属性与方法

config: WeChatModuleOptions

配置读写属性,类型:WeChatModuleOptions

cacheAdapter: ICache

缓存适配器读写属性,类型:ICache,默认是一个Map实现的缓存

微信公众号API

网页授权

getAccessTokenByCode

public async getAccessTokenByCode (code: string, _appId?: string, _secret?: string): Promise<UserAccessTokenResult>;

正确返回

{
  "access_token":"ACCESS_TOKEN",
  "expires_in":7200,
  "refresh_token":"REFRESH_TOKEN",
  "openid":"OPENID",
  "scope":"SCOPE" 
}

错误返回

{
    "errcode": 40029,
    "errmsg": "invalid code"
}

参考文档

getUserInfo

公众号拉取用户信息

public async getUserInfo (accessToken: string, openid: string, lang: 'zh_CN' | 'zh_TW' | 'en' = 'zh_CN'): Promise<UserInfoResult>;

正确返回

{   
  "openid": "OPENID",
  "nickname": NICKNAME,
  "sex": 1,
  "province":"PROVINCE",
  "city":"CITY",
  "country":"COUNTRY",
  "headimgurl":"https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
  "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
  "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

错误返回

{
  "errcode":40003,
  "errmsg":" invalid openid "
}

获取Access token

getAccountAccessToken

public async getAccountAccessToken (_appId?: string, _secret?: string): Promise<AccountAccessTokenResult>;

正确返回

{
  "access_token": "52_s0Mcl3E3DBKs12rthjxG8_DOvsIC4puV9A34WQR6Bhb_30TW9W9BjhUxDRkyph-hY9Ab2QS03Q8wZBe5UkA1k0q0hc17eUDZ7vAWItl4iahnhq_57dCoKc1dQ3AfiHUKGCCMJ2NcQ0BmbBRIKBEgAAAPGJ",
  "expires_in": 7200
}

错误返回

{
  "errcode": 40013,
  "errmsg": "invalid appid"
}

参考文档

获取稳定版接口调用凭据

getStableAccessToken

public async getStableAccessToken (_appId?: string, _secret?: string, force = false): Promise<AccountAccessTokenResult>;

参考文档

获取jsapi_ticket

getJSApiTicket

public async getJSApiTicket (_appId?: string, _secret?: string): Promise<TicketResult>;

返回数据

{
  "errcode": 0,
  "errmsg": "ok",
  "ticket": "bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
  "expires_in": 7200
}

参考文档

JS-SDK使用权限签名

jssdkSignature

public async jssdkSignature (url: string): Promise<SignatureResult>;
public async jssdkSignature (url: string, appId: string, secret:string): Promise<SignatureResult>;

参考文档

发送模板消息

sendTemplateMessage

public async sendTemplateMessage (message: TemplateMessage, appId?: string, secret?: string): Promise<DefaultRequestResult & { msgid: string }>;

参考文档

生成带参数的二维码

createQRCode

public async createQRCode (data: AccountCreateQRCode, appId?: string, secret?: string): Promise<AccountCreateQRCodeResult>;

参考文档

通过ticket换取二维码

showQRCode

public showQRCode (ticket: string): Promis<Buffer>;

参考文档

填写服务器配置

checkSignatureExpress

WeChatService.checkSignatureExpress (req: Request, res: Response);

Usage:

@Get('push')
async pushTest (@Req() req: Request, @Res() res: Response) {
  this.service.checkSignatureExpress(req, res);
}

参考文档

接收事件推送

messagePushExpressHandler

// 加密模式
WeChatService.messagePushExpressHandler (req: Request, res?: Response, resText?: string);
// 明文模式
WeChatService.plainMessagePushExpressHandler (req: Request, res?: Response, resText?: string);

Usage:

@Post('push')
async officialPushTest (@Req() req: Request, @Res() res: Response) {
  const decrypt = await this.service.messagePushExpressHandler(req, res);
}

参考文档

微信小程序

获取接口调用凭据

public getAccessToken (appId?: string, secret?: string): Promise<AccessTokenResult>;
const service = new WeChatService({ appId: 'your app id', secret: 'your secret'});
const res = await service.mp.getAccessToken();
console.log(res.data.access_token);

查询rid信息

public async getRid (rid: string, accessToken: string): Promise<RidInfo>;

获取插件用户openpid

public async getPluginOpenPId (code: string, accessToken: string): Promise<DefaultRequestResult & { openpid: string }>;

登录code2Session

public async code2Session (code: string, appId?: string, secret?: string): Promise<SessionResult>;

返回数据

{
  "openid": "openid",
  "session_key": "key",
  "unionid": "unionid",
  "errcode": 0,
  "errmsg": "ok",
}

参考文档

获取手机号码

public async getPhoneNumber (code: string, accessToken: string);

参考文档

获取小程序码

public async getQRCode (params: QRCode, accessToken: string): Promise<DefaultRequestResult & { contentType: string, buffer: Buffer }>;

获取不限制的小程序码

public getUnlimitedQRCode (params: GetUnlimitedQRCode, accessToken: string): Promise<DefaultRequestResult & { buffer: Buffer }>;

获取小程序二维码

public async createQRCode (params: CreateQRCode, accessToken: string): Promise<DefaultRequestResult & { contentType: string, buffer: Buffer }>;

查询scheme码

public async queryScheme (scheme: string, accessToken: string): Promise<DefaultRequestResult & { scheme_info: SchemeInfo, scheme_quota: SchemeQuota }>;

获取scheme码

public generateScheme (params: GenerateScheme, accessToken: string): Promise<DefaultRequestResult & { openlink: string >;

获取NFC的小程序scheme

public generateNFCScheme (params: GenerateNFCScheme, accessToken: string): Promise<DefaultRequestResult & { openlink: string }>;

获取URLLink

public generateUrlLink (params: GenerateUrlLink, accessToken: string): Promise<DefaultRequestResult & { url_link: string }>;

查询URLLink

public queryUrlLink (urlLink: string, accessToken: string): Promise<UrlLinkResult>;

获取ShortLink

public generateShortLink (params: GenerateShortLink, accessToken: string): Promise<DefaultRequestResult & { link: string }>;

下发统一消息

public sendUniformMessage (params: SendUniformMessage, accessToken: string): Promise<DefaultRequestResult>;

创建activity_id

public createActivityId (params: CreateActivityId, accessToken: string): Promise<ActivityIdResult>;

修改动态消息

public setUpdatableMsg (params: UpdatableMsg, accessToken: string): Promise<DefaultRequestResult>;

删除模板

public deleteMessageTemplate (priTmplId: string, accessToken: string): Promise<DefaultRequestResult>;

获取类目

public getCategory (accessToken: string): Promise<DefaultRequestResult & { data: {id: number, name: string}[] }>;

获取关键词列表

public getPubTemplateKeyWordsById (tid: number, accessToken: string): Promise<PubTemplateKeyWords>;

获取所属类目下的公共模板

public getPubTemplateTitleList (params: PubTemplateTitleList, accessToken: string): Promise<PubTemplateTitleListResult>;

获取个人模板列表

public getMessageTemplateList (accessToken: string): Promise<MessageTemplateListResult>;

发送订阅消息

public sendMessage (params: SendMessage, accessToken: string): Promise<DefaultRequestResult>;

添加模板

public addMessageTemplate (params: MessageTemplate, accessToken: string): Promise<DefaultRequestResult & { priTmplId: string }>;

移动应用

Module导入

import { Module } from '@nestjs/common';

import { WeChatMobileModule } from 'nest-wechat';

@Module({
  imports: [WeChatMobileModule.register()],
})
export class AppModule {
}

工具类引入

import { MobileService } from 'nest-wechat';
const service = new MobileService();

通过code获取access_token

public getAccessToken (code: string, appId: string, secret: string): Promise<AxiosResponse<MobileAppAccessTokenResult, any>>;

刷新或续期access_token

public refreshAccessToken (appId: string, refreshToken: string): Promise<AxiosResponse<MobileAppAccessTokenResult, any>>;

检验access_token

public checkAccessToken (openId: string, accessToken: string): Promise<AxiosResponse<DefaultRequestResult, any>>;

微信支付

小程序

JSAPI下单

pay.jsapi (order: TransactionOrder, serialNo: string, privateKey: Buffer | string): Promise<{prepay_id: string}>;

商户订单号查询订单

pay.getTransactionById (id: string, mchId: string, serialNo: string, privateKey: Buffer | string): Promise<Trade>;

微信支付订单号查询订单

pay.getTransactionByOutTradeNo (outTradeNo: string, mchId: string, serialNo: string, privateKey: Buffer | string): Promise<Trade>;

关闭订单

pay.close (outTradeNo: string, mchId: string, serialNo: string, privateKey: Buffer | string);

申请请退

pay.refund (refund: RequireOnlyOne<RefundParameters, 'transaction_id' | 'out_trade_no'>, mchId: string, serialNo: string, privateKey: Buffer | string): Promise<RefundResult>;

查询单笔退款

pay.getRefund (outRefundNo: string, mchId: string, serialNo: string, privateKey: Buffer | string): Promise<RefundResult>;

构造小程序调起支付参数

pay.buildMiniProgramPayment (appId: string, prepayId: string, privateKey: Buffer | string): MiniProgramPaymentParameters;

支付通知处理程序

pay.paidCallback (publicKey: Buffer | string, apiKey: string, req: Request, res: Response): Promise<Trade>;

退款通知处理程序

pay.refundedCallback (certs: Map<string, string>, apiKey: string, req: Request, res: Response): Promise<RefundNotifyResult>;

电子发票

配置开发选项

pay.fapiaoDevConfig (data: DevelopmentConfigRequest, mchId: string, serialNo: string, privateKey: Buffer | string);

查询商户配置的开发选项

pay.getFapiaoDevConfig (mchId: string, serialNo: string, privateKey: Buffer | string);

创建电子发票卡券模板

pay.createCardTemplate (data: CreateCardTemplateRequest, mchId: string, serialNo: string, privateKey: Buffer | string);

微信发票通知

pay.fapiaoCallback (certs: Map<string, string>, apiKey: string, req: Request, res: Response): Promise<FapiaoNotifyResult>;

获取用户填写的抬头

pay.getUserTitle (params: GetUserTitleParams, mchId: string, serialNo: string, privateKey: Buffer | string);

开具电子发票

pay.issueFapiao (data: IssueFapiaoRequest, mchId: string, serialNo: string, privateKey: Buffer | string);

查询电子发票

pay.getIssueFapiao (fapiaoApplyId: string, fapiaoId: string, mchId: string, serialNo: string, privateKey: Buffer | string);

冲红电子发票

pay.reverseFapiao (fapiaoApplyId: string, data: ReverseFapiaoRequest, mchId: string, serialNo: string, privateKey: Buffer | string);

敏感信息加解密

加密

pay.rsaEncryptOAEP (text: string, publicKey: Buffer | string);

解密

pay.rsaDecryptOAEP (cipherText: string, privateKey: Buffer | string);

现金红包

没有可用测试商户,未做成功测试,请自行测试,有问题请提issue。

测试可自行执行单元测试(需要修改配置)

npm run test lib/wepay.hb.spec.ts

发放红包

pay.sendRedPack(redPack: RedPackData, appId: string, mchId: string, apiKey: string, publicKey: Buffer | string, privateKey: Buffer | string, group = false): Promise<AxiosResponse<string, any>>;

发放裂变红包

pay.sendGroupRedPack(redPack: GroupRedPackData, appId: string, mchId: string, apiKey: string, publicKey: Buffer | string, privateKey: Buffer | string): Promise<AxiosResponse<string, any>>;

查询红包记录

pay.getHbInfo(billNO: string, appId: string, mchId: string, apiKey: string, publicKey: Buffer | string, privateKey: Buffer | string): Promise<AxiosResponse<string, any>>;

微信支付服务商

服务商JSAPI下单

参考文档

jsapiOfPartner (order: TransactionOrderOfPartner, serialNo: string, privateKey: Buffer | string);

服务商JSAPI调起支付

参考文档

构建调起微信支付参数接口

buildJSAPIParameters (appId: string, prepayId: string, privateKey: Buffer | string): MiniProgramPaymentParameters;

服务商支付通知

参考文档

paidCallbackOfPartner (certs: Map<string, string>, apiKey: string, req: Request, res: Response): Promise<TradeOfPartner>;

服务商关闭订单

参考文档

closeOfPartner (outTradeNo: string, spMchId: string, subMchId: string, serialNo: string, privateKey: Buffer | string);

服务商微信支付订单号查询订单

参考文档

getTransactionByIdOfPartner (id: string, spMchId: string, subMchid: string, serialNo: string, privateKey: Buffer | string);

服务商商户订单号查询订单

参考文档

getTransactionByOutTradeNoOfPartner (outTradeNo: string, spMchId: string, subMchid: string, serialNo: string, privateKey: Buffer | string);

服务商退款申请

参考文档

refundOfPartner (refund: RequireOnlyOne<RefundParametersOfPartner, 'transaction_id' | 'out_trade_no'>, spMchId: string, serialNo: string, privateKey: Buffer | string);

服务商查询单笔退款

参考文档

getRefundOfPartner (outRefundNo: string, spMchId: string, subMchId: string, serialNo: string, privateKey: Buffer | string);

服务商退款结果通知

参考文档

refundedCallbackOfPartner (certs: Map<string, string>, apiKey: string, req: Request, res: Response): Promise<RefundNotifyResultOfPartner>;

服务商电子发票

服务商创建电子发票卡券模板

参考文档

createCardTemplateOfPartner (data: CreateCardTemplateRequestOfPartner, spMchId: string, serialNo: string, privateKey: Buffer | string);
服务商配置开发选项

参考文档

fapiaoDevConfigOfPartner (data: DevelopmentConfigRequestOfPartner, spMchId: string, serialNo: string, privateKey: Buffer | string);
服务商查询商户配置的开发选项

参考文档

getFapiaoDevConfigOfPartner (spMchId: string, subMchId: string, serialNo: string, privateKey: Buffer | string);
服务商微信发票通知

参考文档

fapiaoCallbackOfPartner (certs: Map<string, string>, apiKey: string, req: Request, res: Response): Promise<FapiaoNotifyResultOfPartner>;
服务商获取用户填写的抬头

参考文档

getUserTitleOfPartner (params: GetUserTitleParams, spMchId: string, subMchId: string, serialNo: string, privateKey: Buffer | string);
服务商开具电子发票

参考文档

issueFapiaoOfPartner (data: IssueFapiaoRequestOfPartner, spMchId: string, serialNo: string, privateKey: Buffer | string, platformSerial: string);
服务商查询电子发票

参考文档

getIssueFapiaoOfPartner (fapiaoApplyId: string, fapiaoId: string | null | undefined, spMchId: string, subMchid: string, serialNo: string, privateKey: Buffer | string);
服务商冲红电子发票

参考文档

reverseFapiaoOfPartner (fapiaoApplyId: string, data: ReverseFapiaoRequestOfPartner, spMchId: string, serialNo: string, privateKey: Buffer | string);

微信支付工具函数

报文解密

decryptCipherText

decryptCipherText<T> (apiKey: string, cipher: string, associatedData: string, nonce: string): T | string;

参考文档

微信消息加解密签名工具类

import { MessageCrypto } from 'nest-wechat';
const sha1 = MessageCrypto.sha1('string to hash');

静态方法:

  • sha1 (...args: string[]): string;
  • md5 (text: string): string;
  • getAESKey (encodingAESKey: string): Buffer;
  • getAESKeyIV (aesKey: Buffer): Buffer;
  • PKCS7Encoder (buff: Buffer): Buffer;
  • PKCS7Decoder (buff: Buffer): Buffer;
  • decrypt (aesKey: Buffer, iv: Buffer, str: string): string;
  • encrypt (aesKey: Buffer, iv: Buffer, msg: string, appId: string): string;
  • createNonceStr (length = 16): string;
  • encryptMessage (appId: string, token: string, encodingAESKey: string, message: string, timestamp: string, nonce: string): string;
  • decryptMessage (token: string, encodingAESKey: string, signature: string, timestamp: string, nonce: string, encryptXml: string);
  • decryptMessage (token: string, encodingAESKey: string, signature: string, timestamp: string, nonce: string, encryptXml: string);
  • checkSignature (signature: string, timestamp: string, nonce: string, token: string);

Run Test

Create .env.test.local file, and save your test appid and secret in the file.

TEST_APPID=your/test/appid
TEST_SECRET=your/test/secret
TEST_JSSDK_URL=https://your/website/url
TEST_TOKEN=your/token
TEST_AESKEY=your/aeskey

REDIS_HOST=your/redis/host
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
REDIS_TTL=600

Run e2e test.

npm run test:e2e

Run unit test.

npm run test wechat.service.userinfo.spec.ts

消息推送测试

  • 开启服务测试
npx ts-node -T tests/e2e/wechat-app.main.ts

body

{
  "grant_type": "client_credential", 
  "appid": "your/appid", 
  "secret": "your/secret", 
  "force_refresh": false
}