回调服务器配置分三种,前两种是当服务部署在腾讯云上时的配置方式,第三种为服务部署在自己服务器上是的配置方式:
hosting应用on CVM(即应用部署在腾讯CVM服务器上):
-发货URL只需HTTP协议即可,不需要使用SSL安全协议。
-必须使用9001端口(内网端口,需开发者主动启用,用apache iis或nginx做一个web监听,端口改成9001)。
hosting应用on CEE_V2(即应用部署在腾讯CEE_V2服务器上):
-发货URL只需HTTP协议即可,不需要使用SSL安全协议。
-必须使用9001端口(内网端口,需开发者主动启用,用apache iis或nginx做一个web监听,端口改成9001)。
-路径必须以ceecloudpay开头,即支付相关代码必须都放到应用根目录下的“ceecloudpay”目录下。
-对于CEE其发货URL的IP只能填写为10.142.11.27或者10.142.52.17(详见:CEE_V2访问云支付)。
non-hosting应用(即应用部署在开发者自己的服务器上)
-发货URL必须使用HTTPS协议。
-必须使用443端口(外网端口)。
这里采用non-hosting应用配置方式,方式为在linux服务器的nginx/conf/nginx.conf文件下添加如下配置(nginx版本1.14.0):
# HTTPS server
server {
listen 443 ssl;
server_name localhost;
#"/usr/.../"为自己放证书文件的目录地址
ssl_certificate /usr/.../1**_**6.crt;
ssl_certificate_key /usr/.../1**_**6.key;
ssl_client_certificate /usr/.../ca.crt;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
}
配置完后重新加载nginx使配置生效。
服务器请求下单参数:
HashMap<String, String> params = new HashMap<>();//params类型不能是Map<String,String>会报错,需要改为HsahMap<String,String>
//必填参数
params.put("openid", "");//app登陆获取
params.put("openkey", "");//app登陆获取
params.put("appid", APP_ID);//应用id
params.put("ts", String.valueOf(System.currentTimeMillis() / 1000L));//时间戳(单位:秒)
params.put("payitem", product_id + "*" + price * 10 + "*1");//使用x*p*num的格式,x表示物品ID,p表示单价(以Q点为单位,1Q币=10Q点,单价的制定需遵循腾讯定价规范),num表示默认的购买数量
params.put("goodsmeta", "充值*金币充值");//物品信息,格式必须是name*des,批量购买套餐时也只能有1个道具名称和1个描述,即给出该套餐的名称和描述
params.put("goodsurl", "http://.../coin.png");//物品的图片url(长度<512字符)
params.put("pf", "");//app登陆获取
params.put("pfkey", "");//app登陆获取
params.put("zoneid", "1");//应用如果没有分区:传zoneid=1
//可选参数
params.put("amt", "1");//道具总价值。应用宝支持的qq支付和微信支付方式会有折扣(qq点和qq卡一般没有折扣),导致回调的amt数值与请求下单时的amt值不一致,所以回调避免用amt做金额验证,如有必要,可以在回调代码里采用Double.parseDouble(payitem.split("\\*")[1])的值做金额验证
params.put("appmode", "1");//(可选)1表示用户不可以修改物品数量,2 表示用户可以选择购买物品的数量
params.put("format", "json");//json、jsonp_$func。默认json。如果jsonp,前缀为:$func 例如:format=jsonp_sample_pay,返回格式前缀为:sample_pay()
params.put("userip", "");//用户ip
params.put("app_metadata", "");//发货时透传给应用。长度必须<=128字符,可以传商户id,便于回调后续做验证
生成签名:
//SnsSigCheck为腾讯开放平台提供的签名方法:[SDK下载](http://qzonestyle.gtimg.cn/qzone/vas/opensns/res/doc/Java_SDK_V3.0.6.zip)
params.put("sig", SnsSigCheck.makeSig("POST", "/v3/r/mpay/buy_goods_m", params,APP_KEY + "&"));//java调用签名方法必须用post请求,APP_KEY为app密钥
服务器下单请求cookie:
//不必自己生成cookie直接调用官方工具包的方法,所以直接传入HashMap<String,String>作为cookie的配置即可
HashMap<String, String> cookies = new HashMap<>();
if ("qq".equalsIgnoreCase(txOpenPayOrderPost.getLoginType())) {//这里的wx/qq需前端传入
cookies.put("session_id", "openid");
cookies.put("session_type", "kp_actoken");
cookies.put("org_loc", SnsSigCheck.encodeUrl("/v3/r/mpay/buy_goods_m"));//org_loc的值需用官方工具类调用SnsSigCheck.encodeUrl方法做urlEncode,java自带的URLEncoder.encode方法因为不符合腾讯的规范,不建议使用
} else if ("wx".equalsIgnoreCase(txOpenPayOrderPost.getLoginType())) {
cookies.put("session_id", "hy_gameid");
cookies.put("session_type", "wc_actoken");
cookies.put("org_loc", SnsSigCheck.encodeUrl("/v3/r/mpay/buy_goods_m"));
}
服务器下单请求方法:
//https://ysdktest.qq.com/mpay/buy_goods_m为测试服务器
String orderInfo = SnsNetwork.postRequest("https://ysdktest.qq.com/mpay/buy_goods_m", params, cookies,"http");
请求正确的返回值为:
{
"ret": 0,
"token": "7***2",
"url_params": "/**/***/**/mobile_goods_info?token_id=7**2",
"attach": ""
}
请求错误的返回值为:
{
"ret": **,//不为0的值
"msg": ***
}
最好做一个判断:
JSONObject jsonObject = JSON.parseObject(orderInfo);
if (jsonObject.getIntValue("ret") == 0) {
logger.error("下单成功");
} else {
logger.error("下单失败");
}
支付回调:
//支付回调的接口必须部署在前面配置了证书的linux服务器上,并在应用宝配置界面做配置
@RequestMapping(value = "/notify")
@ResponseBody
public String verify(HttpServletRequest request, HttpServletResponse response) throws Exception {
String url = request.getRequestURI();//请求uri
HashMap<String, String> params = new HashMap<>();
Enumeration em = request.getParameterNames();
String req_sig = "";
while (em.hasMoreElements()) {
String key = (String) em.nextElement();
String value = request.getParameter(key);
if (key.equalsIgnoreCase("sig")) {
req_sig = value;
} else {
params.put(key, encode(value));
}
}
logger.error(params.toString());
String appid = params.get("appid");//应用的唯一ID。可以通过appid查找APP基本信息。
String appmeta = params.get("appmeta");//透传参数 可以用来获取商户订单号
String billno = params.get("billno");//支付流水号(64个字符长度。该字段和openid合起来是唯一的)。
String token = params.get("token");//应用调用v3/pay/buy_goods接口成功返回的交易token
String payitem = params.get("payitem");//接收标准格式为ID*price*num G001*10*1
String sig = params.get("sig");//服务器签名
Long orderId = Long.parseLong(appmeta.split("\\*")[0]);//从appmeta中截取的透传参数商户支付流水号
Double amount = Double.parseDouble(payitem.split("\\*")[1]);
String sys_sig = SnsSigCheck.makeSig("GET", url, params, APP_KEY + "&");
if(req_sig != null && req_sig.equalsIgnoreCase(sys_sig)){
logger.error("验证成功");
//业务代码,自定义信息验证及数据库信息回写等
}else{
logger.error("验证失败");
return JSONObject.toJSONString(mkResultMap(-2, "签名错误"));//这里的ret值会影响之后腾讯给前端回调的错误码值,计算方式为500000-ret.例如此处ret=-2,回调错误码即为499998,ret=2,回调错误码为500002,利用这一点可以写出分类比较详细的错误验证
}
}
/**
* 生成成功回复结果map
*
* @param ret 结果code
* @param msg 结果msg
*/
private Map<String, Object> mkResultMap(int ret, String msg) {
Map<String, Object> map = new HashMap<>();
map.put("ret", ret);
map.put("msg", msg);
return map;
}
/**
* 解码腾讯编码规范
*
* @param string
* @return
*/
private String decode(String string) {
// 替换ascii码为对应字符
return string.replace("%2D", "-").replace("%2E", ".").replace("%5F", "_");
}
/**
* 编码腾讯编码规范
*
* @param string
* @return
*/
private String encode(String string) {
// 替换腾讯不建议字符为ascii码
return string.replace("-", "%2D").replace(".", "%2E").replace("_", "%5F");
}
参考资料:
1.腾讯米大师支付服务端接入流程
2.米大师介绍——道具直购模式介绍