1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
| const crypto = require('crypto'); const got = require("got");
const hostname = 'api.weixin.qq.com'; const basepath = '/cgi-bin/midas/'; const basepathSandBox = '/cgi-bin/midas/sandbox/';
const appid = '111111111111111111'; // 微信AppID const secret = "222222222222222222222222222222222222"; // 微信支付安全密钥 const secretSandBox = "11111111111111111111111111111111"; // 微信支付沙箱安全密钥 const appsecret = "444444444444444444444444444444444444"; // const offer_id = '3333333333'; // 支付应用ID 米大师分配的offer_id const zone_id = '1'; // 游戏服务器大区id,游戏不分大区则默认zoneId ="1",String类型。如过应用选择支持角色,则角色ID接在分区ID号后用"_"连接。 const pf = 'android'; // 平台 安卓:android
// 测试用 const isSandBox = true;
let getAccessToken = async function () { try { // TODO: 缓存access_token let result = await sendHttpsGetRequest("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + appsecret); if (result.success && result.data) { return result.data.access_token; } } catch (error) { Log.error('getAccessToken error:' + error); } return null; }
/** * 获取游戏币余额。开通了虚拟支付的小游戏,可以通过本接口查看某个用户的游戏币余额 * @param {*} openid */ let getBalanceMidas = async function (openid) { try { let access_token = await getAccessToken(); if (!access_token) { return null; } let secr = secret; let base = basepath; if (isSandBox) { secr = secretSandBox; base = basepathSandBox; } if (!openid) { openid = openidTest; } let params = { "openid": openid,// string 是 用户唯一标识符 "appid": appid,// string 是 小程序 appId "offer_id": offer_id,// string 是 米大师分配的offer_id "ts": parseInt(new Date().getTime() / 1000),// number 是 UNIX 时间戳,单位是秒 "zone_id": zone_id,// string 是 游戏服务器大区id,游戏不分大区则默认zoneId ="1",String类型。如过应用选择支持角色,则角色ID接在分区ID号后用"_"连接。 "pf": pf,// string 是 平台 安卓:android // user_ip: '',// string 否 用户外网 IP }; let pSignString = ""; var pRes = Object.keys(params).sort(); for (var sKey in pRes) { let tKey = pRes[sKey]; if (pSignString.length > 0) { pSignString += "&"; } pSignString += (tKey + '=' + params[tKey]); } pSignString += "&org_loc=" + base + "getbalance&method=POST&secret=" + secr; params.sig = crypto.createHmac('sha256', secr).update(pSignString, 'utf8').digest('hex');// string 是 以上所有参数(含可选最多7个)+uri+米大师密钥,用 HMAC-SHA256签名,详见 签名计算算法 params.access_token = access_token;// string 是 接口调用凭证 const response = await got.default("https://api.weixin.qq.com" + base + 'getbalance?access_token=' + access_token, { method: 'POST', searchParams: { access_token: access_token, }, json: params, }).json(); if (response && response.errcode == 0) { /** * errcode number 错误码 errmsg string 错误信息 balance number 游戏币个数(包含赠送) gen_balance number 赠送游戏币数量(赠送游戏币数量) first_save boolean 是否满足历史首次充值 save_amt number 累计充值金额的游戏币数量 save_sum number 历史总游戏币金额 cost_sum number 历史总消费游戏币金额 present_sum number 历史累计收到赠送金额 */ return response; } } catch (error) { Log.error('getBalanceMidas error:' + error); } return null; }
/** * 扣除游戏币。开通了虚拟支付的小游戏,可以通过本接口扣除某个用户的游戏币。 * 由于可能存在接口调用超时或返回系统失败,但是游戏币实际已经扣除的情况,所以当该接口返回系统失败时, * 可以用相同的bill_no再次调用本接口,直到返回非系统失败为止,不会重复扣款,也可以调用取消支付接口取消本次扣款。 * @param {*} openid */ let payMidas = async function (openid, amt, bill_no, items) { try { let access_token = await getAccessToken(); if (!access_token) { return null; } let secr = secret; let base = basepath; if (isSandBox) { secr = secretSandBox; base = basepathSandBox; } if (!openid) { openid = openidTest; } let params = { "openid": openid,// string 是 用户唯一标识符 "appid": appid,// string 是 小程序 appId "offer_id": offer_id,// string 是 米大师分配的offer_id "ts": parseInt(new Date().getTime() / 1000),// number 是 UNIX 时间戳,单位是秒 "zone_id": zone_id,// string 是 游戏服务器大区id,游戏不分大区则默认zoneId ="1",String类型。如过应用选择支持角色,则角色ID接在分区ID号后用"_"连接。 "pf": pf,// string 是 平台 安卓:android "amt": amt,// number 是 扣除游戏币数量,不能为 0 "bill_no": bill_no,// string 是 订单号,业务需要保证全局唯一;相同的订单号不会重复扣款。长度不超过63,只能是数字、大小写字母_- "pay_item": items,// string 否 道具名称 "app_remark": items,// string 否 备注。会写到账户流水 // user_ip: '',// string 否 用户外网 IP }; let pSignString = ""; var pRes = Object.keys(params).sort(); for (var sKey in pRes) { let tKey = pRes[sKey]; if (pSignString.length > 0) { pSignString += "&"; } pSignString += (tKey + '=' + params[tKey]); } pSignString += "&org_loc=" + base + "pay&method=POST&secret=" + secr; params.sig = crypto.createHmac('sha256', secr).update(pSignString, 'utf8').digest('hex');// string 是 以上所有参数(含可选最多7个)+uri+米大师密钥,用 HMAC-SHA256签名,详见 签名计算算法 params.access_token = access_token;// string 是 接口调用凭证 Log.error('WeixinService payMidas params:' + JSON.stringify(params)); const response = await got.default("https://api.weixin.qq.com" + base + 'pay?access_token=' + access_token, { method: 'POST', searchParams: { access_token: access_token, }, json: params, }).json(); Log.error('WeixinService payMidas response:' + (response?JSON.stringify(response):response)); if (response && response.errcode == 0) { /** * errcode number 错误码 errmsg string 错误信息 bill_no string 订单号,有效期是 48 小时 balance number 预扣后的余额 used_gen_amt number 本次扣的赠送币的金额 */ return response; } } catch (error) { Log.error('payMidas error:' + error); } return null; }
let sendHttpsGetRequest = function (url) { return new Promise(function (resolve, reject) { https.get(url, (ress) => { let datas = []; let size = 0; ress.on('data', function (chunk) { datas.push(chunk); size += chunk.length; }); ress.on("end", function () { let buff = Buffer.concat(datas, size); //var data = iconv.decode(buff, "utf8");//转码 let data = buff.toString();//不需要转编码,直接tostring data = JSON.parse(data); resolve({success: true, data: data}); }); }).on('error', (e) => { resolve({success: false, errmsg: e.message}); }); });
}
|