首页 > 技术文章 > 微信App支付

learning-cow 2020-04-23 17:01 原文

一、申请

1、先去微信开放平台申请一个app应用,需要注意的是选择ios和Android两个平台,而不是ios创建一个、安卓创建一个。创建好之后一般会在3~5天左右审核通过,此时去申请开通微信支付,大概需要1~2天。

https://open.weixin.qq.com/cgi-bin/applist?t=manage/list&page=0&num=20&openapptype=1&lang=zh_CN&token=e534aa3eb66664cec30a9d48f224931cc87f67ad

 

2、上面申请通过之后,会让你去商户平台验证,验证通过之后,需要你设置密钥(在API安全里面)。都通过之后,我们需要拿到以下三个数据

appID:应用的id wxe57df9967063cf2c
mchId:商户id 1509558461
appKey:密钥 在商户平台中你自己设置的
https://pay.weixin.qq.com/index.php/core/cert/api_cert

 

二、集成sdk

 WxPayAPI的Java代码

1、这是我修改后的demo,用idea打开后,maven install打成jar包然后安装到本地仓库

安装命令:mvn install:install-file -Dfile=wxpay-sdk-3.0.9.jar -DgroupId=com.github.wxpay -DartifactId=wxpay-sdk -Dversion=3.0.9 -Dpackaging=jar

或者自己定义路径也可以(个人喜欢自己定义路径 详情请看我 Maven学习笔记 中 Maven 如何引用本地Jar包 的笔记)

 

然后在pom中引用:(微信sdk需要httpclient的jar包)

<dependency>

<groupId>com.github.wxpay</groupId>

<artifactId>wxpay-sdk</artifactId>

<version>3.0.9</version>

</dependency>

<dependency>

<groupId>org.apache.httpcomponents</groupId>

<artifactId>httpclient</artifactId>

<version>4.5.3</version>

</dependency>

 

2、说明:之前下载的demo,是存在问题的

(1)、需要自定义 APPWxPayConfig

(2)、需要自己实现 IWXPayDomain接口

(3)、另外源代码中的加密方式默认为 HMACSHA256,被我改成了 默认 MD5(在某些场景下HA256加密 签名永远验证不通过)

(4)、Api中的 sign 和 nonce_str 不需要传递,已经在jar包中内置,但是二次加密还是需要自己在用工具类获取 nonce_str,下面代码中有演示(微信Api官方文档:https://pay.weixin.qq.com/wiki/doc/api/index.html)

(5)、同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 来自微信官方文档(微信回调接口 一定要留意这个问题,以下代码中没有处理回调重复的问题,请根据自己的业务和实际情况来注意和处理这个问题)

 

3、统一下单接口(以下是微信APP支付示例代码)

需要注意的是:两处签名方式要一致(我采取的都是MD5加密),且二次签名的参数要注意,参与二次签名的参数有:appid、noncestr、package(固定就是Sign=WXPay)、partnerid、prepayid、timestamp、key。全都是小写,且时间戳是秒(十位数),key在参与签名的时候放在最后。

如果提示签名不正确(要是一次就正确不得不说你的运气真滴好),请仔细查看,也可参考微信提供的验证签名:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=20_1

 

@RequestMapping(value="/wxpay_orderStr/ajax")

@ResponseBody

public Result getOrderStr(HttpServletRequest request,

@RequestParam(value="amount")Double amount,

@RequestParam(value="orderNo")String orderNo){

//校验订单并获取订单创建时间时间戳 来拼接订单号

WyPropertyCostsLog wyPropertyCostsLog = propertyCostsService.getPropertyCostsLogById(Integer.valueOf(orderNo));

String createTimeString = null;

if (wyPropertyCostsLog != null && wyPropertyCostsLog.getCosts() == amount) {

Calendar cal = Calendar.getInstance();

cal.setTime(wyPropertyCostsLog.getCreateTime());

createTimeString = String.valueOf(cal.getTimeInMillis());

}else {

return new Result(-1,"该订单不存在!");

}

AppWxPayConfig wxPayConfig = new AppWxPayConfig(appId, key, mchId);

Map<String,String> reqData = new HashMap<String,String>();

reqData.put("body", "融易社区物业缴费");

reqData.put("out_trade_no", createTimeString + "_" + orderNo);

reqData.put("fee_type", "CNY");

reqData.put("total_fee", "1");//微信以分为单位 测试用

//reqData.put("total_fee", String.valueOf(Math.round(amount * 100)));//微信以分为单位

reqData.put("spbill_create_ip", StrKit.getIpAddr(request));

reqData.put("notify_url", notify_url);

reqData.put("trade_type", "APP");//此处指定为APP支付

try {

WXPay wxpay = new WXPay(wxPayConfig);

Map<String, String> resp = wxpay.unifiedOrder(reqData);//统一下单返回结果

System.out.println("统一下单返回数据:" + resp);

if (resp != null) {

String prepayid = resp.get("prepay_id");

//若获取prepayid成功,将相关信息返回客户端

if (prepayid != null && !prepayid.equals("")) {

String noncestr = WXPayUtil.generateNonceStr();

String timestamp = String.valueOf(WXPayUtil.getCurrentTimestamp());

Map<String, String> signMap = new HashMap<String, String>();

signMap.put("appid", appId);

signMap.put("noncestr", noncestr);

signMap.put("package", "Sign=WXPay");

signMap.put("partnerid", mchId);

signMap.put("prepayid", prepayid);

signMap.put("timestamp", timestamp);

Map<String, String> resultMap = new HashMap<String, String>();

resultMap.put("prepayid", prepayid);

resultMap.put("sign", StrKit.generateSignature(signMap,key));

resultMap.put("appid", appId);

resultMap.put("timestamp", timestamp); //等于请求prepayId时的

resultMap.put("noncestr", noncestr); //与请求prepayId时值一致

resultMap.put("package", "Sign=WXPay"); //固定常量

resultMap.put("partnerid", mchId);

System.out.println("返回给app数据:" + resultMap);

return new Result(0,"请求成功",resultMap);

}

}

} catch (Exception e) {

e.printStackTrace();

}

return new Result(-1,"请求失败");

}

 

 

/**

* 微信支付:生成MD5加密

* @param data

* @param key

* @return

* @throws Exception

*/

public static String generateSignature(final Map<String, String> data,final String key) throws Exception {

Set<String> keySet = data.keySet();

String[] keyArray = keySet.toArray(new String[keySet.size()]);

Arrays.sort(keyArray);

StringBuilder sb = new StringBuilder();

for (String k : keyArray) {

if (k.equals(WXPayConstants.FIELD_SIGN)) {

continue;

}

if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名

sb.append(k).append("=").append(data.get(k).trim()).append("&");

}

String result = sb.append("key=" + key).toString();

return MD5(result).toUpperCase();

}

/**

* 生成 MD5

*

* @param data 待处理数据

* @return MD5结果

*/

public static String MD5(String data) throws Exception {

java.security.MessageDigest md = MessageDigest.getInstance("MD5");

byte[] array = md.digest(data.getBytes("UTF-8"));

StringBuilder sb = new StringBuilder();

for (byte item : array) {

sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));

}

return sb.toString().toUpperCase();

}

 

/**.

* 根据Request获取客户端的IP地址

* @param request 请求对象

* @return 返回IP地址

*/

public static String getIpAddr(HttpServletRequest request) {

String ip = request.getHeader("X-Real-IP");

if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

ip = request.getHeader("Proxy-Client-IP");

}

if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

ip = request.getHeader("WL-Proxy-Client-IP");

}

if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

ip = request.getRemoteAddr();

}

if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {

ip = request.getHeader("X-Forwarded-For");

}

return ip;

}

 

 

4、异步回调

/**

* 异步回调

* @param request

* @return

*/

@RequestMapping(value="/wxpay_notify")

@ResponseBody

public String wxpay_notify(HttpServletRequest request,HttpServletResponse response){

try{

BufferedReader reader = request.getReader();

String line = "";

StringBuffer inputString = new StringBuffer();

try{

while ((line = reader.readLine()) != null) {

inputString.append(line);

}

if(reader!=null){

reader.close();

}

System.out.println("----[微信回调]接收到的报文---"+inputString.toString());

if(!StrKit.isBlank(inputString.toString())){

AppWxPayConfig wxPayConfig = new AppWxPayConfig(appId, key, mchId);

WXPay wxpay = new WXPay(wxPayConfig);

Map<String, String> notifyMap = WXPayUtil.xmlToMap(inputString.toString());// 转换成map

if (notifyMap.get("return_code").equals("SUCCESS")) {

if (notifyMap.get("result_code").equals("SUCCESS")) {

if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {

// 签名正确 进行业务处理

String out_trade_no_resp = notifyMap.get("out_trade_no");

Double amount = Double.valueOf(notifyMap.get("total_fee"))/100;

System.out.print("订单号:" + out_trade_no_resp + "---金额:" + +amount);

String[] arr = out_trade_no_resp.split("_");

String out_trade_no = arr[1];

int result = propertyCostsService.updateRecord(out_trade_no,Constants.PAY_SUCCESS,Constants.WXPAY,String.valueOf(amount));

if (result > 0) {

redisTemplate.opsForValue().set("ORDER_STATUS_" + out_trade_no, Constants.PAY_SUCCESS, 60, TimeUnit.MINUTES);

Map<String, String> map = new HashMap<String, String>();

map.put("return_map", "SUCCESS");

map.put("return_msg", "OK");

return WXPayUtil.mapToXml(map);

}

}

}

}

}

}catch(Exception e){

e.printStackTrace();

}

}catch(Exception ex){

ex.printStackTrace();

}

return "";

}

 

 

//以下代码是微信小程序支付实例代码

public static void main(String[] args) {

AppWxPayConfig wxPayConfig = new AppWxPayConfig("小程序的AppId(一定要是小程序的APPId)", "微信支付密钥(不是小程序的密钥)", "微信商户号");

Map<String,String> reqData = new HashMap<String,String>();

reqData.put("body", "kaifacheshishuju");

reqData.put("out_trade_no", "fdfdsfsdfes");

reqData.put("fee_type", "CNY");

reqData.put("total_fee", "1");//微信以分为单位 测试用

//reqData.put("total_fee", String.valueOf(amount * 100));//微信以分为单位

reqData.put("spbill_create_ip", "172.19.1.15");

reqData.put("notify_url", " https://kuajiesaoke.natapp1.cc/order/my_app/wxpay_notify");

reqData.put("openid","oxJ_25Y7ygc9SmXp5BXhLuDLCiOY");//获取openId 是使用小程序的密钥获取openId

//JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付,不同trade_type决定了调起支付的方式

//MICROPAY--付款码支付,付款码支付有单独的支付接口,所以接口不需要上传,该字段在对账单中会出现(微信支付的核心就是 trade_type 不同的type值会集成不同的微信支付方式,其他的微信支付大同小异,只有小程序的sign加密规则不一样 所以dome单独体现出来)

reqData.put("trade_type", "JSAPI");//此处指定为小程序支付 MWEB web支付NATIVE

 

try {

WXPay wxpay = new WXPay(wxPayConfig);

Map<String, String> resp = wxpay.unifiedOrder(reqData);//统一下单返回结果

System.out.println("resp"+ resp);

if (resp != null) {

String prepayid = resp.get("prepay_id");

//若获取prepayid成功,将相关信息返回客户端

if (prepayid != null && !prepayid.equals("")) {

String noncestr = WXPayUtil.generateNonceStr();

String timestamp = String.valueOf(WXPayUtil.getCurrentTimestamp());

//此处需要注意 小程序和sign加密格式个其他支付不一样 格式为下面这种 请留意

Map<String, String> signMap = new HashMap<String, String>();

signMap.put("appId", "APPId");

signMap.put("nonceStr", noncestr);

signMap.put("package", "prepay_id="+prepayid);

signMap.put("signType", "MD5");

signMap.put("timeStamp", timestamp);

 

Map<String, String> resultMap = new HashMap<String, String>();

resultMap.put("paySign", generateSignature(signMap, "微信密钥"));

resultMap.put("timeStamp", timestamp); //等于请求prepayId时的

resultMap.put("nonceStr", noncestr); //与请求prepayId时值一致

resultMap.put("package", "prepay_id="+prepayid); //固定常量

resultMap.put("signType", "MD5");

System.out.println("resultMap"+resultMap);

}

}

} catch (Exception e) {

e.printStackTrace();

}

}

 

如上代码中 reqData 的Map添加的数据为微信官方必要参数,具体详情,请看如下链接:

https://pay.weixin.qq.com/wiki/doc/api/index.html

 

 

 

 

/**

* 微信-web支付代码

*/

@RequestMapping(value = "/wxpay_orderStr")

public ResultDTO getOrderStr(HttpServletRequest request,@RequestParam Double price,@RequestParam String orderNo) {

AppWxPayConfig wxPayConfig = wxPayConfig = new AppWxPayConfig("微信APPid","微信密钥","微信商户号");

Map<String, String> reqData = new HashMap<String, String>();

reqData.put("body", "在线订单-"+orderNo);

reqData.put("out_trade_no", orderNo);

reqData.put("fee_type", "CNY");

// reqData.put("total_fee", "1");//微信以分为单位 测试用

reqData.put("total_fee", String.valueOf(Math.round(price * 100)));//微信以分为单位

reqData.put("spbill_create_ip", StrKit.getIpAddr(request));

reqData.put("notify_url", WxPayConfig.APP_NOTIFY_URL);

//JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付,不同trade_type决定了调起支付的方式

//MICROPAY--付款码支付,付款码支付有单独的支付接口,所以接口不需要上传,该字段在对账单中会出现(由此可见 当前支付和微信APP支付大同小异,在小程序支付中也进行了说明(微信支付的核心就是 trade_type),具体详情 请参照微信官方文档(H5支付也相当类似 这里不再叙述))

reqData.put("trade_type", "NATIVE");//此处指定为WEB端支付

 

try {

WXPay wxpay = new WXPay(wxPayConfig);

Map<String, String> resp = wxpay.unifiedOrder(reqData);//统一下单返回结果

return new ResultDTO(CodeEnum.login_code.getIndex(), "请求成功", resp.get("code_url"));

} catch (Exception e) {

e.printStackTrace();

}

return new ResultDTO(CodeEnum.error_code.getIndex(), "请求失败");

}

在写微信支付的时候也参考了这篇文章:

https://www.cnblogs.com/godwhisper/p/6880060.html

推荐阅读