微信支付 OpenAPI SDK
Promise based and chained WeChatPay OpenAPI client SDK for NodeJS
NodeJs >= 10.15.0
$ npm install wechatpay-axios-plugin
[!NOTE] В 2024 году в третьем квартале WeChat Pay запустил замену «платформенных открытых ключей» на «сертификаты платформы». Для инициализации требуется настроить только идентификатор платформенного открытого ключа и платформенный открытый ключ. Это полностью совместимо и поддерживает, и можно пропустить загрузку сертификатов платформы с помощью CLI/API. Идентификатор платформенного открытого ключа и платформенный открытый ключ можно найти на странице WeChat Pay Merchant Platform -> Account Center -> API Security.
WeChat Pay APIv3 использует интерфейс RESTful API с обменом данными JSON через HTTP. Данные обмениваются асимметричным (RSA-OAEP) шифрованием.
API up требует закрытого ключа продавца, который можно создать с помощью специального инструмента для создания сертификатов продавца.
Для получения сертификатов платформы, необходимых для API down, используйте v3/certificates
API. Эти сертификаты были зашифрованы с помощью симметричного AES-GCM и требуют использования APIv3 key
для расшифровки.
Этот проект также предоставляет инструмент командной строки для загрузки, руководство по использованию которого приведено ниже:
wxpay crt
The WeChatPay APIv3's Certificate Downloader
cert
-m, --mchid The merchant's ID, aka mchid. [string] [required]
-s, --serialno The serial number of the merchant's certificate aka serialno. [string] [required]
-f, --privatekey The path of the merchant's private key certificate aka privatekey. [string] [required]
-k, --key The secret key string of the merchant's APIv3 aka key. [string] [required]
-o, --output Path to output the downloaded WeChatPay's platform certificate(s) [string] [default: "/tmp"]
Options:
--version Show version number [boolean]
--help Show help [boolean]
-u, --baseURL The baseURL [string] [default: "https://api.mch.weixin.qq.com/"]
Примечание: --help
выводит справочное руководство, а [required]
указывает обязательные параметры; [string]
указывает тип строки, [default]
указывает значение по умолчанию.
The WeChatPay Platform Certificate#0
serial=HEXADECIAL
notBefore=Wed, 22 Apr 2020 01:43:19 GMT
notAfter=Mon, 21 Apr 2025 01:43:19 GMT
Saved to: wechatpay_HEXADECIAL.pem
You may confirm the above infos again even if this library already did(by Rsa.verify):
openssl x509 -in wechatpay_HEXADECIAL.pem -noout -serial -dates
Примечание: после предоставления обязательных параметров и запуска на экране отображаются сведения о сертификате, включая серийный номер и время начала и окончания срока действия сертификата в формате UTC. Также отображается местоположение сохранённого сертификата.
[!IMPORTANT] После загрузки сертификата на экране отображается несколько строк информации о сертификатах. Необходимо настроить все эти строки в приложении, особенно во время перехода между старыми и новыми сертификатами платформы. Если это не сделать, приложение может дать сбой.
Командную строку можно использовать для быстрого подключения. Как это сделать: wxpay req
Запускает запросы WeChatPay OpenAPI через командную строку.
—c, --config — конфигурация [обязательный]
—b, --binary — true для массива buffer, два — без верификатора, иначе для отображения источника
—m, --method — HTTP-запрос (DELETE, GET, POST, PUT, PATCH, delete, get, post, put, patch) [по умолчанию: POST]
—h, --headers — заголовки HTTP-запроса
—d, --data — тело HTTP-запроса
—p, --params — параметры HTTP-запроса
Опции:
--version — показать номер версии [boolean]
--help — показать справку [boolean]
-u, --baseURL — базовый URL [string] [по умолчанию: https://api.mch.weixin.qq.com/]
v3版Native付
./node_modules/.bin/wxpay v3.pay.transactions.native \
-c.mchid 1230000109 \
-c.serial MCHSERIAL \
-c.privateKey /path/your/merchant/mchid.key \
-c.certs.PLATSERIAL /path/the/platform/certificates/HEXADECIAL.pem \
-d.appid wxd678efh567hg6787 \
-d.mchid 1230000109 \
-d.description 'Image形象店-深圳腾大-QQ公仔' \
-d.out_trade_no '1217752501201407033233368018' \
-d.notify_url 'https://www.weixin.qq.com/wxpay/pay.php' \
-d.amount.total 100 \
-d.amount.currency CNY
v2版付款码付
./node_modules/.bin/wxpay v2.pay.micropay \
-c.mchid 1230000109 \
-c.serial nop \
-c.privateKey any \
-c.certs.any \
-c.secret your_merchant_secret_key_string \
-d.appid wxd678efh567hg6787 \
-d.mch_id 1230000109 \
-d.device_info 013467007045764 \
-d.nonce_str 5K8264ILTKCH16CQ2502SI8ZNMTM67VS \
-d.detail 'Image形象店-深圳腾大-QQ公仔' \
-d.spbill_create_ip 8.8.8.8 \
-d.out_trade_no '1217752501201407033233368018' \
-d.total_fee 100 \
-d.fee_type CNY \
-d.auth_code 120061098828009406
v2版付款码查询openid
./node_modules/.bin/wxpay v2/tools/authcodetoopenid \
-c.mchid 1230000109 \
-c.serial nop \
-c.privateKey any \
-c.certs.any \
-c.secret your_merchant_secret_key_string \
-d.appid wxd678efh567hg6787 \
-d.mch_id 1230000109 \
-d.nonce_str 5K8264ILTKCH16CQ2502SI8ZNMTM67VS \
-d.auth_code 120061098828009406
``` ```
'/path/to/wechatpay/certificate_or_publickey.pem';
const platformPublicKeyInstance = readFileSync(platformCertificateOrPublicKeyFilePath);
const wxpay = new Wechatpay({
mchid: merchantId,
serial: merchantCertificateSerial,
privateKey: merchantPrivateKeyInstance,
certs: {
[platformCertificateSerialOrPublicKeyId]: platformPublicKeyInstance
}
});
Примечание: серийный номер сертификата (серийный номер торгового сертификата и серийный номер платформы) можно получить с помощью команды OpenSSL, например: openssl x509 -in /path/to/merchant/apiclient_cert.pem -noout -serial | awk -F= '{print $2}'
.
Инициализация словаря выглядит следующим образом:
mchid
— ваш торговый номер, обычно 10-значное число;serial
— серийный номер вашего торгового сертификата, обычно это 40-символьная строка;privateKey
— ваш API-ключ для торговца, обычно файл с именем apiclient_key.pem
, который можно создать с помощью официального инструмента для создания сертификатов. Поддерживаются как чистые строки, так и потоки файлов в формате buffer;certs{[serial_number]:string}
— загруженный сертификат платформы в формате key/value, ключ — это серийный номер или идентификатор открытого ключа платформы, значение — сертификат или открытый ключ платформы в формате pem, либо в виде чистой строки, либо потока файла в формате buffer;Обратите внимание, что часть текста запроса не удалось перевести из-за отсутствия контекста. ``` console.error(error) })
**Создание коммерческого купона**
```js
wxpay.v3.marketing.busifavor.stocks
.post({/*Условия создания коммерческого купона*/})
.then(({data}) => console.info(data))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data))
Запрос информации об одном купоне
;(async () => {
try {
const {data: detail} = await wxpay.v3.marketing.busifavor
.users.$openid$.coupons['{coupon_code}'].appids['wx233544546545989']
.get({openid: '2323dfsdf342342', coupon_code: '123446565767'})
console.info(detail)
} catch({response: {status, statusText, data}}) {
console.error(status, statusText, data)
}
})()
Режим Native продавца в рамках услуги оплаты
;(async () => {
try {
const res = await wxpay.v3.pay.partner.transactions.native({
sp_appid,
sp_mchid,
sub_mchid,
description,
out_trade_no,
time_expire: new Date( (+new Date) + 33*60*1000 ), //after 33 minutes
attach,
notify_url,
amount: {
total: 1,
}
})
console.info(res.data.code_url)
} catch (error) {
console.error(error)
}
})()
Оплата через сервис Smart Guide
;(async () => {
try {
const {status, statusText} = await wxpay.v3.smartguide.guides.$guide_id$.assign
.post({sub_mchid, out_trade_no}, {guide_id})
console.info(status, statusText)
} catch({response: {status, statusText, data}}) {
console.error(status, statusText, data)
}
})()
Перевод средств на кошелёк
const {Rsa} = require('wechatpay-axios-plugin');
;(async () => {
try {
const res = await wxpay.v3.transfer.batches.post({
appid: 'wxf636efh567hg4356',
out_batch_no: 'plfk2020042013',
batch_name: 'Отчёт о расходах за январь 2019 года для отделения в Шэньчжэне',
batch_remark: 'Отчёт о расходах за январь 2019 года для отделения в Шэньчжэне',
total_amount: 4000000,
total_num: 200,
transfer_detail_list: [
{
out_detail_no: 'x23zy545Bd5436',
transfer_amount: 200000,
transfer_remark: 'Расходы за апрель 2020 года',
openid: 'o-MYE42l80oelYMDE34nYD456Xoy',
user_name: Rsa.encrypt('Чжан Сань', platformPublicKeyInstance),
}
],
transfer_scene_id: '1001',
}, {
headers: {
'Wechatpay-Serial' => platformCertificateSerial,
},
});
} catch({response: {status, statusText, data}}) {
console.error(status, statusText, data)
}
})()
Коммерческие жалобы
;(async () => {
try {
const res = await wxpay.v3.merchantService.complaintsV2.get({
params: {
limit : 5,
offset : 0,
begin_date : '2020-03-07',
end_date : '2020-03-14',
}
})
console.info(res.data)
} catch (error) {
console.error(error)
}
})()
Загрузка изображения
const { Multipart } = require('wechatpay-axios-plugin')
const {createReadStream} = require('fs')
const imageMeta = {
filename: 'hellowechatpay.png',
sha256: '1a47b1eb40f501457eaeafb1b1417edaddfbe7a4a8f9decec2d330d1b4477fbe',
}
const imageData = new Multipart()
imageData.append('meta', JSON.stringify(imageMeta), 'meta.json')
imageData.append('file', createReadStream('./hellowechatpay.png'), 'hellowechatpay.png')
;(async () => {
try {
const res = await wxpay.v3.marketing.favor.media.imageUpload.post(imageData, {
meta: imageMeta,
headers: imageData.getHeaders()
})
console.info(res.data.media_url)
} catch (error) {
console.error(error)
}
})()
Информация о купоне
;(async () => {
try {
const res = await wxpay.v3.marketing.favor.stocks.$stock_id$.get({
params: {
stock_creator_mchid,
},
stock_id,
})
console.info(res.data)
} catch(error) {
console.error(error)
}
})()
Маркетинг купонов
(async () => {
try {
const res = await wxpay.v3.marketing.partnerships.build.post({
partner: {
type,
appid
},
authorized_data: {
business_type,
stock_id
}
}, {
headers: {
'Idempotency-Key': 12345
}
})
console.info(res.data)
} catch (error) {
console.error(error)
}
})()
Скачать записи об использовании купонов
(async () => {
try {
let res = await
``` **Корпоративные платежи через WeChat**
1. **Корпоративный платёж на кошелёк:**
```js
wxappid: 'wx8888888888888888',
send_name: '鹅企支付',
re_openid: 'oxTWIuGaIt6gTKsQRLau2M0yL16E',
total_amount: '1000',
total_num: '1',
wishing: 'HAPPY BIRTHDAY',
client_ip: '192.168.0.1',
act_name: '回馈活动',
remark: '会员回馈活动',
scene_id: 'PRODUCT_4'
})
.then(res => console.info(res.data))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data));
wxpay.v2.mmpaymkttransfers.promotion.transfers.post({
mch_appid: 'wx8888888888888888',
mchid: '1900000109',// 注意这个商户号,key是`mchid`非`mch_id`
partner_trade_no: '10000098201411111234567890',
openid: 'oxTWIuGaIt6gTKsQRLau2M0yL16E',
check_name: 'FORCE_CHECK',
re_user_name: '王小王',
amount: '10099',
desc: '理赔',
spbill_create_ip: '192.168.0.1',
nonce_str: Formatter.nonce(),
}, {
// 返回值无`sign`字段,无需数据校验
transformResponse: [Transformer.toObject],
})
.then(res => console.info(res.data))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data));
wxpay.v2.risk.getpublickey.post({
mch_id: '1900000109',
sign_type: 'MD5',
nonce_str: Formatter.nonce(),
}, {
baseURL: 'https://fraud.mch.weixin.qq.com/',
// 返回值无`sign`字段,无需数据校验
transformResponse: [Transformer.toObject],
})
.then(res => console.info(res.data))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data));
wxpay.v2.pay.downloadbill.post({
mch_id,
nonce_str: fmt.nonce(),
appid,
bill_date,
bill_type,
}, {
responseType: 'arraybuffer', // To prevent the axios:utils.stripBOM feature
transformResponse: [function detector(data) {
// 无账单时返回值为`xml`,抛到异常`catch`处理
assert.notDeepStrictEqual(data.slice(0, 5), Buffer.from('<xml>'), data.toString())
return data
}, function csvCastor(data) { return Formatter.castCsvBill(data) }]
})
.then(res => console.info(res.data.summary))
.catch(({response: {status, statusText, data}}) => console.error(status, statusText, data));
Для корпоративных платежей через WeChat необходимо добавить дополнительную подпись к запросу. Для этого нужно выполнить следующие шаги:
Получить значения agentId
и agentSecret
из рабочего стола WeChat.
Добавить функцию подписи в код запроса.
Wechatpay.client.v2.defaults.transformRequest.unshift(function workwxredpack(data, headers) {
const {act_name, mch_billno, mch_id, nonce_str, re_openid, total_amount, wxappid} = data
if (!(act_name && mch_billno && mch_id && nonce_str && re_openid && total_amount && wxappid)) {
return data
}
data.workwx_sign = Hash.md5(
Formatter.queryStringLike(Formatter.ksort({
act_name, mch_billno, mch_id, nonce_str, re_openid, total_amount, wxappid
})), agentSecret, agentId
).toUpperCase()
return data
})
wxpay.v2.mmpaymkttransfers.sendworkwxredpack.post({
mch_billno: '123456',
wxappid: 'wx8888888888888888',
sender_name: 'XX活动',
sender_header_media_id: '1G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0',
re_openid: 'oxTWIuGaIt6gTKsQRLau2M0yL16E',
total_amount: '1000',
wishing: '感谢您参加猜灯谜活动,祝您元宵节快乐!',
act_name: '猜灯谜抢红包活动',
remark: '猜越多得越多,快来抢!',
mch_id: '1900000109',
nonce_str: Formatter.nonce(),
})
.then(res => console.info(res.data))
.catch(console.error);
Wechatpay.client.v2.defaults.transformRequest.unshift(function wwsptrans2pocket(data, headers) {
const {amount, appid, desc, mch_id, nonce_str, openid, partner_trade_no, ww_msg_type} = data
if (!(amount && appid && desc && mch_id && nonce_str && openid && partner_trade_no && ww_msg_type)) {
return data
}
data.workwx_sign = Hash.md5(
Formatter.queryStringLike(Formatter.ksort({
amount, appid, desc, mch_id, nonce_str, openid, partner_trade_no, ww_msg_type
})), agentSecret, agentId
).toUpperCase()
return data
})
wxpay.v2.mmpaymkttransfers.promotion.paywwsptrans2pocket.post({
appid: 'wxe062425f740c8888',
device_info: '013467007045764',
partner_trade_no: '100000982017072019616',
``` **openid: 'ohO4Gt7wVPxIT1A9GjFaMYMiZY1s',
check_name: 'NO_CHECK',
re_user_name: '张三',
amount: '100',
desc: '六月出差报销费用',
spbill_create_ip: '10.2.3.10',
ww_msg_type: 'NORMAL_MSG',
act_name: '示例项目',
mch_id: '1900000109',
nonce_str: Formatter.nonce()**
*— данные запроса.*
## *Пользовательский вывод журнала*
```js
// APIv2 日志
Wechatpay.client.v2.defaults.transformRequest.push(data => (console.log(data), data))
Wechatpay.client.v2.defaults.transformResponse.unshift(data => (console.log(data), data))
// APIv3 日志
Wechatpay.client.v3.defaults.transformRequest.push((data, headers) => (console.log(data, headers), data))
Wechatpay.client.v3.defaults.transformResponse.unshift((data, headers) => (console.log(data, headers), data))
— Пользовательский вывод журнала.
Нестандартный адрес интерфейса, также можно вызвать таким образом:
Wechatpay.client.v2.post('https://fraud.mch.weixin.qq.com/risk/getpublickey', {
mch_id: '1900000109',
nonce_str: Formatter.nonce(),
sign_type: 'HMAC-SHA256',
}, {
// 返回值无`sign`字段,无需数据校验
transformResponse: [Transformer.toObject],
})
.then(({data}) => console.info(data))
.catch(({response}) => console.error(response))
— Получение открытого ключа RSA.
const {Transformer} = require('wechatpay-axios-plugin')
const xml = Transformer.toXml({
return_code: 'SUCCESS',
return_msg: 'OK',
})
console.info(xml)
— Уведомление в формате XML.
const {Aes: {AesEcb}, Transformer, Hash} = require('wechatpay-axios-plugin')
const secret = 'exposed_your_key_here_have_risks'
const xml = '<xml>' + ... '</xml>'
const obj = Transformer.toObject(xml)
const res = AesEcb.decrypt(obj.req_info, Hash.md5(secret))
obj.req_info = Transformer.toObject(res)
console.info(obj)
const obj = Transformer.toObject(xml)
const ciphertext = AesEcb.encrypt(obj.req_info, Hash.md5(secret))
console.assert(
obj.req_info === ciphertext,
`The notify hash digest should be matched the local one`
)
— AES-256-ECB/PKCS7Padding.
const {Hash, Formatter} = require('wechatpay-axios-plugin')
const v2Secret = 'exposed_your_key_here_have_risks'
const params = {
appId: 'wx8888888888888888',
timeStamp: `${Formatter.timestamp()}`,
nonceStr: Formatter.nonce(),
package: 'prepay_id=wx201410272009395522657a690389285100',
signType: 'HMAC-SHA256',
}
params.paySign = Hash.sign(params.signType, params, v2Secret)
console.info(params)
const {Hash, Formatter} = require('wechatpay-axios-plugi')
const v2Secret = 'exposed_your_key_here_have_risks'
const params = {
appid: 'wx8888888888888888',
partnerid: '1900000109',
prepayid: 'WX1217752501201407033233368018',
package: 'Sign=WXPay',
timestamp: `${Formatter.timestamp()}`,
noncestr: Formatter.nonce(),
}
params.sign = Hash.sign('MD5', params, v2Secret)
console.info(params)
— Подпись данных APIv2.
const {Rsa, Formatter} = require('wechatpay-axios-plugin')
const privateKey = require('fs').readFileSync('/your/merchant/priviate_key.pem')
const params = {
appId: 'wx8888888888888888',
timeStamp: `${Formatter.timestamp()}`,
nonceStr: Formatter.nonce(),
package: 'prepay_id=wx201410272009395522657a690389285100',
signType: 'RSA',
}
params.paySign = Rsa.sign(Formatter.joinedByLineFeed(
params.appId, params.timeStamp, params.nonceStr, params.package
), privateKey)
console.info(params)
— Подпись данных APIv3. ### Бизнес-купон — H5 выпуск купона v2 версия правила подписи
const {Hash, Formatter} = require('wechatpay-axios-plugin')
const v2Secret = 'exposed_your_key_here_have_risks'
const params = {
stock_id: '12111100000001',
out_request_no: '20191204550002',
send_coupon_merchant: '10016226',
open_id: 'oVvBvwEurkeUJpBzX90-6MfCHbec',
coupon_code: '75345199',
}
params.sign = Hash.sign('HMAC-SHA256', params, v2Secret)
console.info(params)
Вопрос: инструмент для загрузки сертификата платформы постоянно выдаёт исключение AssertionError [ERR_ASSERTION]: The response's Headers incomplete. В чём причина?
Ответ: исключение возникает из-за неправильного указания параметра
-s
, то есть номера последовательности сертификата продавца. Сервер фактически возвращает код состояния 401. В следующей версии инструмента загрузки будет улучшено обнаружение исключений. На данный момент необходимо проверить правильность указания номера сертификата продавца и убедиться в его корректности.
Вопрос: как расшифровать поле с шифрованием AES-256-GCM в уведомлениях APIv3?
Ответ: официальные документы содержат информацию о том, что для шифрования и дешифрования данных в платформе APIv3 используются алгоритмы AesGcm. Для расшифровки данных можно использовать инструмент cert.js, который входит в состав bin/cli. Пример использования:
AesGcm.decrypt(nonce, secret, ciphertext, aad);
Вопрос: Как правильно формировать запрос при необходимости отправки дополнительной информации или чувствительных данных?
Ответ: Первый параметр запроса DELETE или GET, а также второй параметр запросов POST, PUT или PATCH представляют собой объект AxiosRequestConfig. Этот объект позволяет добавлять дополнительные заголовки по мере необходимости. Пример:
wxpay.v3.applyment4sub.applyment.$noop$( {}, { noop: '', headers: { 'Wechatpay-Serial': '123456' } }, ).then(console.info).catch(console.error);Дополнительную информацию можно найти в выпуске №17 на GitHub.
Вопрос: Как сформировать запрос к API, если адрес интерфейса заканчивается косой чертой (/)?
Ответ: Динамические параметры uri_template или свойства property могут быть использованы для формирования запроса. Дополнительную информацию можно найти в выпуске №16 на GitHub.
npm install && npm test
Если у вас возникли проблемы или есть предложения, вы можете создать проблему на GitHub или присоединиться к группе QQ для обсуждения технических вопросов.
QQ группа: 684379275
Если вам понравилась эта библиотека, вы можете поддержать автора, отсканировав QR-код. Блог автора содержит практические примеры использования библиотеки, которые могут оказаться полезными для вашей разработки.
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.