java实现微信App支付服务端

 更新时间:2018年10月03日 08:37:48   作者:Andyzty  
这篇文章主要为大家详细介绍了java实现微信App支付服务端,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

微信App支付服务端的实现方法,供大家参考,具体内容如下

引言

主要实现app支付统一下单、异步通知、调起支付接口、支付订单查询、申请退款、查询退款功能;封装了https对发起退款的证书校验、签名、xml解析等。

支付流程

具体支付流程参考“微信APP”文档,文档地址

APP支付:APP端点击下单—-服务端生成订单,并调起“统一下单”,返回app支付所需参数—–APP端“调起支付接口“,发起支付—-微信服务器端调用服务端回调地址—–服务端按照“支付结果通知”,处理支付结果

app查询:调起“查询订单”

APP退款:发起退款请求,调用“申请退款”,发起退款,需双向证书验证

APP退款查询:调起“查询退款”

支付代码实现

代码实现签名、证书校验、http和https封装等,项目结构如下:

支付代码

包含支付、支付查询、异步通知、退款申请、退款查询

package org.andy.wxpay.controller;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.andy.wxpay.model.JsonResult;
import org.andy.wxpay.model.ResponseData;
import org.andy.wxpay.utils.CollectionUtil;
import org.andy.wxpay.utils.ConfigUtil;
import org.andy.wxpay.utils.FileUtil;
import org.andy.wxpay.utils.HttpUtils;
import org.andy.wxpay.utils.PayUtil;
import org.andy.wxpay.utils.SerializerFeatureUtil;
import org.andy.wxpay.utils.StringUtil;
import org.andy.wxpay.utils.WebUtil;
import org.andy.wxpay.utils.XmlUtil;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.alibaba.fastjson.JSON;

/**
 * 创建时间:2016年11月2日 下午4:16:32
 * 
 * @author andy
 * @version 2.2
 */
@Controller
@RequestMapping("/order")
public class PayController {

 private static final Logger LOG = Logger.getLogger(PayController.class);

 private static final String ORDER_PAY = "https://api.mch.weixin.qq.com/pay/unifiedorder"; // 统一下单

 private static final String ORDER_PAY_QUERY = "https://api.mch.weixin.qq.com/pay/orderquery"; // 支付订单查询

 private static final String ORDER_REFUND = "https://api.mch.weixin.qq.com/secapi/pay/refund"; // 申请退款

 private static final String ORDER_REFUND_QUERY = "https://api.mch.weixin.qq.com/pay/refundquery"; // 申请退款

 private static final String APP_ID = ConfigUtil.getProperty("wx.appid");

 private static final String MCH_ID = ConfigUtil.getProperty("wx.mchid");

 private static final String API_SECRET = ConfigUtil.getProperty("wx.api.secret");

 /**
 * 支付下订单
 * 
 * @param request
 * @param response
 * @param cashnum
 * 支付金额
 * @param mercid
 * 商品id
 * @param callback
 */
 @RequestMapping(value = "/pay", method = RequestMethod.POST)
 public void orderPay(HttpServletRequest request, HttpServletResponse response,
 @RequestParam(required = false, defaultValue = "0") Double cashnum, String mercid, String callback) {
 LOG.info("[/order/pay]");
 if (!"001".equals(mercid)) {
 WebUtil.response(response, WebUtil.packJsonp(callback, JSON
 .toJSONString(new JsonResult(-1, "商品不存在", new ResponseData()), SerializerFeatureUtil.FEATURES)));
 }

 Map<String, String> restmap = null;
 boolean flag = true; // 是否订单创建成功
 try {
 String total_fee = BigDecimal.valueOf(cashnum).multiply(BigDecimal.valueOf(100))
 .setScale(0, BigDecimal.ROUND_HALF_UP).toString();
 Map<String, String> parm = new HashMap<String, String>();
 parm.put("appid", APP_ID);
 parm.put("mch_id", MCH_ID);
 parm.put("device_info", "WEB");
 parm.put("nonce_str", PayUtil.getNonceStr());
 parm.put("body", "测试付费");
 parm.put("attach", "Andy");
 parm.put("out_trade_no", PayUtil.getTradeNo());
 parm.put("total_fee", total_fee);
 parm.put("spbill_create_ip", PayUtil.getRemoteAddrIp(request));
 parm.put("notify_url", "https://www.andy.org/wxpay/order/pay/notify.shtml");
 parm.put("trade_type", "APP");
 parm.put("sign", PayUtil.getSign(parm, API_SECRET));

 String restxml = HttpUtils.post(ORDER_PAY, XmlUtil.xmlFormat(parm, false));
 restmap = XmlUtil.xmlParse(restxml);
 } catch (Exception e) {
 LOG.error(e.getMessage(), e);
 }

 Map<String, String> payMap = new HashMap<String, String>();
 if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code"))) {
 payMap.put("appid", APP_ID);
 payMap.put("partnerid", MCH_ID);
 payMap.put("prepayid", restmap.get("prepay_id"));
 payMap.put("package", "Sign=WXPay");
 payMap.put("noncestr", PayUtil.getNonceStr());
 payMap.put("timestamp", PayUtil.payTimestamp());
 try {
 payMap.put("sign", PayUtil.getSign(payMap, API_SECRET));
 } catch (Exception e) {
 flag = false;
 }
 }

 if (flag) {
 WebUtil.response(response,
 WebUtil.packJsonp(callback,
 JSON.toJSONString(new JsonResult(1, "订单获取成功", new ResponseData(null, payMap)),
  SerializerFeatureUtil.FEATURES)));
 } else {
 if (CollectionUtil.isNotEmpty(restmap)) {
 LOG.info("订单创建失败:" + restmap.get("err_code") + ":" + restmap.get("err_code_des"));
 }
 WebUtil.response(response, WebUtil.packJsonp(callback, JSON
 .toJSONString(new JsonResult(-1, "订单获取失败", new ResponseData()), SerializerFeatureUtil.FEATURES)));
 }
 }


 /**
 * 查询支付结果
 * 
 * @param request
 * @param response
 * @param tradeid 微信交易订单号
 * @param tradeno 商品订单号
 * @param callback
 */
 @RequestMapping(value = "/pay/query", method = RequestMethod.POST)
 public void orderPayQuery(HttpServletRequest request, HttpServletResponse response, String tradeid, String tradeno,
 String callback) {
 LOG.info("[/order/pay/query]");
 if (StringUtil.isEmpty(tradeno) && StringUtil.isEmpty(tradeid)) {
 WebUtil.response(response, WebUtil.packJsonp(callback, JSON
 .toJSONString(new JsonResult(-1, "订单号不能为空", new ResponseData()), SerializerFeatureUtil.FEATURES)));
 }

 Map<String, String> restmap = null;
 try {
 Map<String, String> parm = new HashMap<String, String>();
 parm.put("appid", APP_ID);
 parm.put("mch_id", MCH_ID);
 parm.put("transaction_id", tradeid);
 parm.put("out_trade_no", tradeno);
 parm.put("nonce_str", PayUtil.getNonceStr());
 parm.put("sign", PayUtil.getSign(parm, API_SECRET));

 String restxml = HttpUtils.post(ORDER_PAY_QUERY, XmlUtil.xmlFormat(parm, false));
 restmap = XmlUtil.xmlParse(restxml);
 } catch (Exception e) {
 LOG.error(e.getMessage(), e);
 }

 if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code"))) {
 // 订单查询成功 处理业务逻辑
 LOG.info("订单查询:订单" + restmap.get("out_trade_no") + "支付成功");
 WebUtil.response(response, WebUtil.packJsonp(callback, JSON
 .toJSONString(new JsonResult(1, "订单支付成功", new ResponseData()), SerializerFeatureUtil.FEATURES)));
 } else {
 if (CollectionUtil.isNotEmpty(restmap)) {
 LOG.info("订单支付失败:" + restmap.get("err_code") + ":" + restmap.get("err_code_des"));
 }
 WebUtil.response(response, WebUtil.packJsonp(callback, JSON
 .toJSONString(new JsonResult(-1, "订单支付失败", new ResponseData()), SerializerFeatureUtil.FEATURES)));
 }
 }


 /**
 * 订单支付微信服务器异步通知
 * 
 * @param request
 * @param response
 */
 @RequestMapping("/pay/notify")
 public void orderPayNotify(HttpServletRequest request, HttpServletResponse response) {
 LOG.info("[/order/pay/notify]");
 response.setCharacterEncoding("UTF-8");
 response.setContentType("text/xml");
 try {
 ServletInputStream in = request.getInputStream();
 String resxml = FileUtil.readInputStream2String(in);
 Map<String, String> restmap = XmlUtil.xmlParse(resxml);
 LOG.info("支付结果通知:" + restmap);
 if ("SUCCESS".equals(restmap.get("result_code"))) {
 // 订单支付成功 业务处理
 String out_trade_no = restmap.get("out_trade_no"); // 商户订单号
 // 通过商户订单判断是否该订单已经处理 如果处理跳过 如果未处理先校验sign签名 再进行订单业务相关的处理

 String sing = restmap.get("sign"); // 返回的签名
 restmap.remove("sign");
 String signnow = PayUtil.getSign(restmap, API_SECRET);
 if (signnow.equals(sing)) {
 // 进行业务处理
 LOG.info("订单支付通知: 支付成功,订单号" + out_trade_no);

 // 处理成功后相应给响应xml 
 Map<String, String> respMap = new HashMap<>();
 respMap = new HashMap<String, String>(); 
 respMap.put("return_code", "SUCCESS"); //相应给微信服务器
 respMap.put("return_msg", "OK"); 
 String resXml = XmlUtil.xmlFormat(restmap, true); 
 response.getWriter().write(resXml); 
 } else {
 LOG.info("订单支付通知:签名错误");
 }
 } else {
 LOG.info("订单支付通知:支付失败," + restmap.get("err_code") + ":" + restmap.get("err_code_des"));
 }
 } catch (Exception e) {
 LOG.error(e.getMessage(), e);
 }
 }

 /**
 * 订单退款 需要双向证书验证
 * 
 * @param request
 * @param response
 * @param tradeno 微信订单号
 * @param orderno 商家订单号
 * @param callback
 */
 @RequestMapping(value = "/pay/refund", method = RequestMethod.POST)
 public void orderPayRefund(HttpServletRequest request, HttpServletResponse response, String tradeno, String orderno,
 String callback) {
 LOG.info("[/pay/refund]");
 if (StringUtil.isEmpty(tradeno) && StringUtil.isEmpty(orderno)) {
 WebUtil.response(response, WebUtil.packJsonp(callback, JSON
 .toJSONString(new JsonResult(-1, "订单号不能为空", new ResponseData()), SerializerFeatureUtil.FEATURES)));
 }

 Map<String, String> restmap = null;
 try {
 Map<String, String> parm = new HashMap<String, String>();
 parm.put("appid", APP_ID);
 parm.put("mch_id", MCH_ID);
 parm.put("nonce_str", PayUtil.getNonceStr());
 parm.put("transaction_id", tradeno); 
 parm.put("out_trade_no", orderno);//订单号
 parm.put("out_refund_no", PayUtil.getRefundNo()); //退款单号
 parm.put("total_fee", "10"); // 订单总金额 从业务逻辑获取
 parm.put("refund_fee", "10"); // 退款金额
 parm.put("op_user_id", MCH_ID);
 parm.put("refund_account", "REFUND_SOURCE_RECHARGE_FUNDS");//退款方式
 parm.put("sign", PayUtil.getSign(parm, API_SECRET));

 //String restxml = HttpUtils.posts(ORDER_REFUND, XmlUtil.xmlFormat(parm, false));
 String restxml = HttpUtils.posts(ORDER_REFUND, XmlUtil.xmlFormat(parm, false));
 restmap = XmlUtil.xmlParse(restxml);
 } catch (Exception e) {
 LOG.error(e.getMessage(), e);
 }

 Map<String, String> refundMap = new HashMap<>();
 if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code"))) {
 refundMap.put("transaction_id", restmap.get("transaction_id"));
 refundMap.put("out_trade_no", restmap.get("out_trade_no"));
 refundMap.put("refund_id", restmap.get("refund_id"));
 refundMap.put("out_refund_no", restmap.get("out_refund_no"));
 LOG.info("订单退款:订单" + restmap.get("out_trade_no") + "退款成功,商户退款单号" + restmap.get("out_refund_no") + ",微信退款单号"
 + restmap.get("refund_id"));
 WebUtil.response(response,
 WebUtil.packJsonp(callback,
 JSON.toJSONString(new JsonResult(1, "订单获取成功", new ResponseData(null, refundMap)),
  SerializerFeatureUtil.FEATURES)));
 } else {
 if (CollectionUtil.isNotEmpty(restmap)) {
 LOG.info("订单退款失败:" + restmap.get("err_code") + ":" + restmap.get("err_code_des"));
 }
 WebUtil.response(response, WebUtil.packJsonp(callback, JSON
 .toJSONString(new JsonResult(-1, "订单退款失败", new ResponseData()), SerializerFeatureUtil.FEATURES)));
 }
 }

 /**
 * 订单退款查询
 * @param request
 * @param response
 * @param tradeid 微信订单号
 * @param tradeno 商户订单号
 * @param refundid 微信退款号
 * @param refundno 商家退款号
 * @param callback
 */
 @RequestMapping(value = "/pay/refund/query", method = RequestMethod.POST)
 public void orderPayRefundQuery(HttpServletRequest request, HttpServletResponse response, String refundid,
 String refundno, String tradeid, String tradeno, String callback) {
 LOG.info("[/pay/refund/query]");
 if (StringUtil.isEmpty(tradeid) && StringUtil.isEmpty(tradeno)
 && StringUtil.isEmpty(refundno) && StringUtil.isEmpty(refundid)) {
 WebUtil.response(response, WebUtil.packJsonp(callback, JSON
 .toJSONString(new JsonResult(-1, "退单号或订单号不能为空", new ResponseData()), SerializerFeatureUtil.FEATURES)));
 }

 Map<String, String> restmap = null;
 try {
 Map<String, String> parm = new HashMap<String, String>();
 parm.put("appid", APP_ID);
 parm.put("mch_id", MCH_ID);
 parm.put("transaction_id", tradeid);
 parm.put("out_trade_no", tradeno);
 parm.put("refund_id", refundid);
 parm.put("out_refund_no", refundno);
 parm.put("nonce_str", PayUtil.getNonceStr());
 parm.put("sign", PayUtil.getSign(parm, API_SECRET));

 String restxml = HttpUtils.post(ORDER_REFUND_QUERY, XmlUtil.xmlFormat(parm, false));
 restmap = XmlUtil.xmlParse(restxml);
 } catch (Exception e) {
 LOG.error(e.getMessage(), e);
 }

 Map<String, String> refundMap = new HashMap<>();
 if (CollectionUtil.isNotEmpty(restmap) && "SUCCESS".equals(restmap.get("result_code")) && "SUCCESS".equals(restmap.get("result_code"))) {
 // 订单退款查询成功 处理业务逻辑
 LOG.info("退款订单查询:订单" + restmap.get("out_trade_no") + "退款成功,退款状态"+ restmap.get("refund_status_0"));
 refundMap.put("transaction_id", restmap.get("transaction_id"));
 refundMap.put("out_trade_no", restmap.get("out_trade_no"));
 refundMap.put("refund_id", restmap.get("refund_id_0"));
 refundMap.put("refund_no", restmap.get("out_refund_no_0"));
 refundMap.put("refund_status", restmap.get("refund_status_0"));
 WebUtil.response(response, WebUtil.packJsonp(callback, JSON
 .toJSONString(new JsonResult(1, "订单退款成功", new ResponseData(null, refundMap)), SerializerFeatureUtil.FEATURES)));
 } else {
 if (CollectionUtil.isNotEmpty(restmap)) {
 LOG.info("订单退款失败:" + restmap.get("err_code") + ":" + restmap.get("err_code_des"));
 }
 WebUtil.response(response, WebUtil.packJsonp(callback, JSON
 .toJSONString(new JsonResult(-1, "订单退款失败", new ResponseData()), SerializerFeatureUtil.FEATURES)));
 }
 }

}

微信支付接口参数含义具体参考微信APP支付文档。

微信支付工具类

包含签名、订单号、退单号、随机串、服务器ip地址、客户端ip地址等方法。

package org.andy.wxpay.utils;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

/**
 * 创建时间:2016年11月2日 下午7:12:44
 * 
 * @author andy
 * @version 2.2
 */

public class PayUtil {

 /**
 * 生成订单号
 * 
 * @return
 */
 public static String getTradeNo() {
 // 自增8位数 00000001
 return "TNO" + DatetimeUtil.formatDate(new Date(), DatetimeUtil.TIME_STAMP_PATTERN) + "00000001";
 }

 /**
 * 退款单号
 * 
 * @return
 */
 public static String getRefundNo() {
 // 自增8位数 00000001
 return "RNO" + DatetimeUtil.formatDate(new Date(), DatetimeUtil.TIME_STAMP_PATTERN) + "00000001";
 }

 /**
 * 退款单号
 * 
 * @return
 */
 public static String getTransferNo() {
 // 自增8位数 00000001
 return "TNO" + DatetimeUtil.formatDate(new Date(), DatetimeUtil.TIME_STAMP_PATTERN) + "00000001";
 }

 /**
 * 返回客户端ip
 * 
 * @param request
 * @return
 */
 public static String getRemoteAddrIp(HttpServletRequest request) {
 String ip = request.getHeader("X-Forwarded-For");
 if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
 // 多次反向代理后会有多个ip值,第一个ip才是真实ip
 int index = ip.indexOf(",");
 if (index != -1) {
 return ip.substring(0, index);
 } else {
 return ip;
 }
 }
 ip = request.getHeader("X-Real-IP");
 if (StringUtil.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
 return ip;
 }
 return request.getRemoteAddr();
 }

 /**
 * 获取服务器的ip地址
 * 
 * @param request
 * @return
 */
 public static String getLocalIp(HttpServletRequest request) {
 return request.getLocalAddr();
 }

 public static String getSign(Map<String, String> params, String paternerKey) throws UnsupportedEncodingException {
 return MD5Utils.getMD5(createSign(params, false) + "&key=" + paternerKey).toUpperCase();
 }

 /**
 * 构造签名
 * 
 * @param params
 * @param encode
 * @return
 * @throws UnsupportedEncodingException
 */
 public static String createSign(Map<String, String> params, boolean encode) throws UnsupportedEncodingException {
 Set<String> keysSet = params.keySet();
 Object[] keys = keysSet.toArray();
 Arrays.sort(keys);
 StringBuffer temp = new StringBuffer();
 boolean first = true;
 for (Object key : keys) {
 if (key == null || StringUtil.isEmpty(params.get(key))) // 参数为空不参与签名
 continue;
 if (first) {
 first = false;
 } else {
 temp.append("&");
 }
 temp.append(key).append("=");
 Object value = params.get(key);
 String valueStr = "";
 if (null != value) {
 valueStr = value.toString();
 }
 if (encode) {
 temp.append(URLEncoder.encode(valueStr, "UTF-8"));
 } else {
 temp.append(valueStr);
 }
 }
 return temp.toString();
 }

 /**
 * 创建支付随机字符串
 * @return
 */
 public static String getNonceStr(){
 return RandomUtil.randomString(RandomUtil.LETTER_NUMBER_CHAR, 32);
 }

 /**
 * 支付时间戳
 * @return
 */
 public static String payTimestamp() {
 return Long.toString(System.currentTimeMillis() / 1000);
 }
}

其他所需工具类参考项目源码

支付结果

APP支付测试完成

源代码地址:微信APP支付

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • spring boot @ResponseBody转换JSON 时 Date 类型处理方法【两种方法】

    spring boot @ResponseBody转换JSON 时 Date 类型处理方法【两种方法】

    这篇文章主要介绍了spring boot @ResponseBody转换JSON 时 Date 类型处理方法,主要给大家介绍Jackson和FastJson两种方式,每一种方法给大家介绍的都非常详细,需要的朋友可以参考下
    2018-08-08
  • Java关于桶排序的知识点总结

    Java关于桶排序的知识点总结

    这篇文章给大家总结了关于JAVA中J桶排序的相关知识点和用法分享,有兴趣的读者跟着学习下。
    2018-04-04
  • 建议你使用LocalDateTime而不是Date哦

    建议你使用LocalDateTime而不是Date哦

    这篇文章主要介绍了建议你使用LocalDateTime而不是Date,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • MyBatis使用动态SQL标签的小陷阱

    MyBatis使用动态SQL标签的小陷阱

    MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架,MyBatis越来越受大家的喜爱了。下面给大家分享MyBatis使用动态SQL标签的小陷阱,感兴趣的朋友一起看看吧
    2016-10-10
  • Mybatis逆向生成使用扩展类的实例代码详解

    Mybatis逆向生成使用扩展类的实例代码详解

    这篇文章主要介绍了Mybatis逆向生成使用扩展类的实例代码,代码简单易懂,非常不错,具有一定的参考借鉴价值 ,需要的朋友可以参考下
    2019-05-05
  • Spring中的@PostConstruct注解使用方法解析

    Spring中的@PostConstruct注解使用方法解析

    这篇文章主要介绍了Spring中的@PostConstruct注解使用方法解析,@PostConstruct注解是用来处理在@Autowired注入属性后init()方法之前,对一些零散的属性进行赋值的注解,需要的朋友可以参考下
    2023-11-11
  • Spring Aware源码设计示例解析

    Spring Aware源码设计示例解析

    这篇文章主要为大家介绍了Spring Aware源码设计示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • commons fileupload实现文件上传的实例代码

    commons fileupload实现文件上传的实例代码

    这篇文章主要介绍了commons fileupload实现文件上传的实例代码,包括文件上传的原理分析等相关知识点,本文给大家介绍的非常详细,具有参考借鉴价值,感兴趣的朋友一起看看吧
    2016-10-10
  • Java修改Integer变量值遇到的问题及解决

    Java修改Integer变量值遇到的问题及解决

    这篇文章主要介绍了Java修改Integer变量值遇到的问题及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • SpringBoot中事务失效的原因详解

    SpringBoot中事务失效的原因详解

    这篇文章主要介绍了SpringBoot中事务失效的原因详解,spring中的事务是依赖AOP的,AOP是通过动态代理实现的,只有通过代理类访问的方法才能被拦截,需要的朋友可以参考下
    2023-10-10

最新评论