微信小游戏虚拟支付(米大师)


微信小游戏虚拟支付(米大师)

https://developers.weixin.qq.com/minigame/dev/guide/open-ability/virtual-payment.html

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});
});
});

}