一、流程概述
这里是微信支付APP版的支付流程概述
图片取自微信支付官方文档,
- 首先服务端需要调用统一下单接口,获取到prepay_id,然后按照指定格式对prepay_id进行封装,然后返回给客户端APP。
- 客户端APP获取参数后直接调取微信支付,输入密码支付,
- 微信支付调用服务端的通知接口, 告诉服务端支付结果
所以服务端需要些两个接口,就可以完成微信支付。
二、代码示例
创建预支付订单示例:
/**
*
* @param map ,前段传的参数, 包含总金额,等信息
* @return
* @throws Exception
*/
public String prepay(Map map) throws Exception {
try {
String totalFee = (String )map.get("totalFee");
String AXZJXM_ID = (String) map.get("AXJZXM_ID");
if(StringUtils.isAnyEmpty(totalFee,AXZJXM_ID)){
return "{\"code\":\"error\"}"; //返回信息,根据业务自行完善
}
String moneyReg= "^\\d+(\\.\\d{1,2})?$"; //金额信息判断,这里只是最简单的判断,
if(!Pattern.matches(moneyReg,totalFee)){
return "{\"code\":\"error\"}";
}
String[] totals = totalFee.split("\\."); //将金额信息转换成分为单位
if(totals.length==1){
totals[0]=totals[0]+"00";
}else if(totals.length==2){
totals[0]=totals[0]+(totals[1]+"00").substring(0,2);
}
int feeint = Integer.parseInt(totals[0]);
if(feeint==0){
return "{\"code\":\"error\"}";
}
WxPayApiConfig config = WxPayApiConfig.New();
config.setAppId(MyPro.getPro("wx.appId")).setBody("微信支付示例").setOutTradeNo(UUID.randomUUID().toString().replace("-",""));
config.setTotalFee(feeint+"");
config.setMchId(MyPro.getPro("wx.mchId")).setSpbillCreateIp(getIP());//wx.mchId :微信商户号
config.setTimeStart(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()));
config.setTimeExpire(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date(System.currentTimeMillis()+1000*60*30)));
config.setDeviceInfo("WEB");
config.setTradeType(WxPayApi.TradeType.APP);
config.setNotifyUrl(MyPro.getPro("wx.notifyUrl")); //通知接口的地址
config.setPaternerKey(MyPro.getPro("wx.partnerKey")); //微信商户号的key
String result = WxPayApi.doPost(MyPro.getPro("wx.payUrl")+"unifiedorder",config.build());
Map returnMap = PaymentKit.xmlToMap(result);
String rc =(String) returnMap.get("return_code");
if("SUCCESS".equals(rc)){
String prepay_id = (String) returnMap.get("prepay_id");
Map resMap = new TreeMap();
resMap.put("appid",config.getAppId());
resMap.put("prepayid",prepay_id);//TODO
resMap.put("partnerid",config.getMchId());//TODO
resMap.put("noncestr", HashKit.md5(System.currentTimeMillis()+"superBeyound").toUpperCase());
Long mill = System.currentTimeMillis();
mill=mill/1000; //时间搓, 以秒为最小单位
resMap.put("timestamp",mill+"");
resMap.put("package","Sign=WXPay");
String sign = PaymentKit.createSign(resMap, MyPro.getPro("wx.partnerKey"));
String nosignjson = JSON.toJSONString(resMap);
String json = nosignjson.substring(0,nosignjson.length()-1)+",\"sign\":\""+sign+"\"}";
return json;
}else{
return "{\"code\":\"error\"}";
}
} catch (Exception e) {
}
return "{\"code\":\"error\"}";
}
private static String IP;
public static String getIP(){
if(IP==null||"".equals(IP)){
String chinaz = "http://ip.chinaz.com";
StringBuilder inputLine = new StringBuilder();
String read = "";
URL url = null;
HttpURLConnection urlConnection = null;
BufferedReader in = null;
try {
url = new URL(chinaz);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedReader( new InputStreamReader(urlConnection.getInputStream(),"UTF-8"));
while((read=in.readLine())!=null){
inputLine.append(read+"\r\n");
}
//System.out.println(inputLine.toString());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(in!=null){
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Pattern p = Pattern.compile("\\<dd class\\=\"fz24\">(.*?)\\<\\/dd>");
Matcher m = p.matcher(inputLine.toString());
if(m.find()){
String ipstr = m.group(1);
IP = ipstr;
}
}
return IP;
}
详细的注释都写在代码中了。
微信支付结果通知接口示例:(示例代码中,在修改订单状态时没有判断checkFlag, 需要修改一下,可以直接将这里贴出的代码替换到示例代码的相应方法中)
/**
* 返回成功xml
*/
private String resSuccessXml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
/**
* 返回失败xml
*/
private String resFailXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[fail]]></return_msg></xml>";
/**
* 该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。
* 通知url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”
* <p>
* 支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。
* 对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。
* (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)
* 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
* 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
* 特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,防止数据泄漏导致出现“假通知”,造成资金损失。
*/
@RequestMapping("/wxnotify")
@ResponseBody
public Object wxnotify(HttpServletRequest request, HttpServletResponse response) {
String resXml = resFailXml;
InputStream inStream;
try {
inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
String result = new String(outSteam.toByteArray(), "utf-8");
WXPayUtil.getLogger().debug("----result----=" + result);
outSteam.close();
inStream.close();
Map<String, String> resultMap = WXPayUtil.xmlToMap(result); //转换成Map
Map dd = new HashMap() ;//TODO 查询数据库存储的订单信息,这个根据自己的业务逻辑,自行完善
boolean isSuccess = false;
String sign = (String) resultMap.remove("sign"); //验证签名是否正确
String resultStr = PaymentKit.createSign(resultMap, MyPro.getPro("wx.partnerKey"));
isSuccess = sign.equalsIgnoreCase(resultStr);
WXPayUtil.getLogger().debug("veryfy result: "+isSuccess );
String total_fee = resultMap.get("total_fee");
boolean checkFlag= false;
if (WXPayConstants.SUCCESS.equalsIgnoreCase(resultMap.get("return_code"))) {
if (isSuccess) {
resXml = resSuccessXml;
if(total_fee.equals(dd.get("totalFee"))){
checkFlag = true;
System.out.println("check success!!! ");
}
}
} else {
resXml = resFailXml;
}
if(checkFlag){ //TODO 示例代码中没有判断checkFlag,属于bug ,请读者自行修改一下
String DDZT = (String) dd.get("DDZT");
if(DDZT.equals("0")){
DDZT = checkFlag?"1":"2";
dd.put("DDZT",DDZT);
dd.put("ZF_ID",resultMap.get("openid"));
int updateResult =1;//TODO 更新订单状态,自行完善
}
}
} catch (Exception e) {
} finally {
return resXml;
}
}
三、踩坑经历
微信支付对参数进行签名是需要制定签名方式,我这里没有指定,默认是MD5签名方式,而在微信开发者账号上面创建应用时填的包签名方式是SHA-3,导致对接的时候一直报错,
{"code":-100,"message":"[payment微信:-1]General errors"}
试过好多种方法都不行,最后把应用的包签名改成了MD5签名之后成功掉通,所以这里推荐你直接用MD5作为微信开发者账号创建应用时的包签名,减少踩坑。