1.准备工作
首先登录微信公众平台,获取并配置以下微信开发配置:
- 开发者ID【AppID和AppSecret】
- 服务器配置
1.url服务器地址设置
2.Token【自己设置,必须英文或数字】
3.EncodingAESKey[自己随机生成,用于消息加解密]
然后登录微信商户平台,获取并配置以下微信支付配置:
- 商户号(mchId)
- API秘钥(key)
- API证书(java版主要使用:apiclient_cert.p12)
PS:需要微信公众号支付银行对私接口,请联系电话/微信17605918869
2.代码展示
提醒:此处粘贴出的代码为方便初学者比较直观的了解、学习微信公众号支付,部分代码并未按照编码规范封装成方法、工具类
将微信支付所有参数定义为 WeChatConfig.java
-
public class WeChatConfig {
-
/**公众号AppId*/
-
public static final APP_ID = "";
-
/**公众号AppSecret*/
-
public static final APP_SECRET = "";
-
/**微信支付商户号*/
-
public static final String MCH_ID = "";
-
/**微信支付API秘钥*/
-
public static final String KEY = "";
-
/**微信支付api证书路径*/
-
public static final String CERT_PATH = "***/apiclient_cert.p12";
-
/**微信统一下单url*/
-
public static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
-
/**微信申请退款url*/
-
public static final String REFUND_URL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
-
/**微信支付通知url*/
-
public static final String NOTIFY_URL = "此处url用于接收微信服务器发送的支付通知,并处理商家的业务";
-
/**微信交易类型:公众号支付*/
-
public static final String TRADE_TYPE_JSAPI = "JSAPI";
-
/**微信交易类型:原生扫码支付*/
-
public static final String TRADE_TYPE_NATIVE = "NATIVE";
-
/**微信甲乙类型:APP支付*/
-
public static final String TRADE_TYPE_APP = "APP";
-
}
处理微信公众号支付请求的Controller:WeChatOrderController.java
-
@RequestMapping(value="/m/weChat/")
-
@Controller("weChatOrderController")
-
public class WeChatOrderController{
-
@Autowired
-
private OrderService orderService;
-
@Autowired
-
private WechatPayService wechatPayService;
-
@Autowired
-
private NotifyReturnService notifyReturnService;
-
@RequestMapping(value = "unifiedOrder")
-
public String unifiedOrder(HttpServletRequest request,Model model){
-
//用户同意授权,获得的code
-
String code = request.getParameter("code");
-
//请求授权携带的参数【根据自己需要设定值,此处我传的是订单id】
-
String state = request.getParameter("state");
-
Order order = orderService.get(state);//订单信息
-
//通过code获取网页授权access_token
-
AuthToken authToken = WeChatUtils.getTokenByAuthCode(code);
-
//构建微信统一下单需要的参数
-
Map<String,Object> map = Maps.newHashMap();
-
map.put("openId",authToken.getOpenid());//用户标识openId
-
map.put("remoteIp",request.getRemoteAddr());//请求Ip地址
-
//调用统一下单service
-
Map<String,Object> resultMap = WeChatPayService.unifiedOrder(order,map);
-
String returnCode = (String) resultMap.get("return_code");//通信标识
-
String resultCode = (String) resultMap.get("result_code");//交易标识
-
//只有当returnCode与resultCode均返回“success”,才代表微信支付统一下单成功
-
if (WeChatConstant.RETURN_SUCCESS.equals(resultCode)&&WeChatConstant.RETURN_SUCCESS.equals(returnCode)){
-
String appId = (String) resultMap.get("appid");//微信公众号AppId
-
String timeStamp = WeChatUtils.getTimeStamp();//当前时间戳
-
String prepayId = "prepay_id="+resultMap.get("prepay_id");//统一下单返回的预支付id
-
String nonceStr = WeChatUtils.getRandomStr(20);//不长于32位的随机字符串
-
SortedMap<String,Object> signMap = Maps.newTreeMap();//自然升序map
-
signMap.put("appId",appId);
-
signMap.put("package",prepayId);
-
signMap.put("timeStamp",timeStamp);
-
signMap.put("nonceStr",nonceStr);
-
signMap.put("signType","MD5");
-
model.addAttribute("appId",appId);
-
model.addAttribute("timeStamp",timeStamp);
-
model.addAttribute("nonceStr",nonceStr);
-
model.addAttribute("prepayId",prepayId);
-
model.addAttribute("paySign",WeChatUtils.getSign(signMap));//获取签名
-
}else {
-
logger.error("微信统一下单失败,订单编号:"+order.getOrderNumber()+",失败原因:"+resultMap.get("err_code_des"));
-
return "redirect:/m/orderList";//支付下单失败,重定向至订单列表
-
}
-
//将支付需要参数返回至页面,采用h5方式调用支付接口
-
return "/mobile/order/h5Pay";
-
}
-
}
微信支付前端发起页面: weChatPayTest.jsp
- 支付按钮href中的redirect_uri= http://自己服务的ip或者域名/m/weChat/unifiedOrder 强调部分需要进行uriEncode
- 此处代码为在微信公众号内网页调用,故使用的是微信网页授权方式,将订单id通过支付接口中state参数进行传递
- 微信网页授权说明
-
<!DOCTYPE HTML>
-
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
<html>
-
<head>
-
<meta charset="UTF-8">
-
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">
-
<meta name="screen-orientation" content="portrait">
-
<meta name="x5-orientation" content="portrait">
-
<link rel="stylesheet" href="/static/weui/dist/style/weui.min.css">
-
<title>微信公众号支付测试</title>
-
</head>
-
<body>
-
<div class="container" id="container">
-
<a href="https://open.weixin.qq.com/connect/oauth2/authorizeappid=wx67e9c91f0bac335d&redirect_uri=http%3a%2f%2f***%2fm%2fweChat%2funifiedOrder&response_type=code&scope=snsapi_base&state=${order.id}#wechat_redirect" class="weui_btn weui_btn_primary">立即支付</a>
-
</div>
-
</body>
-
</html>
h5方式调用微信支付接口:h5Pay.jsp
- WeixinJSBridge为微信公众号内置对象,所以必须在公众号内部网页使用
-
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
-
<html>
-
<head>
-
<title>确认支付</title>
-
<script type="text/javascript" src="/static/jquery/jquery-1.11.3.min.js"></script>
-
<script type="text/javascript" src="/static/jquery-plugin/jquery.form.js"></script>
-
</head>
-
<body>
-
<input type="hidden" name="appId" value="${appId}">
-
<input type="hidden" name="nonceStr" value="${nonceStr}">
-
<input type="hidden" name="prepayId" value="${prepayId}">
-
<input type="hidden" name="paySign" value="${paySign}">
-
<input type="hidden" name="timeStamp" value="${timeStamp}">
-
</body>
-
<script>
-
function onBridgeReady(){
-
var appId = $("input[name='appId']").val();
-
var nonceStr = $("input[name='nonceStr']").val();
-
var prepayId = $("input[name='prepayId']").val();
-
var paySign = $("input[name='paySign']").val();
-
var timeStamp = $("input[name='timeStamp']").val();
-
WeixinJSBridge.invoke(
-
'getBrandWCPayRequest', {
-
"appId":appId,
-
"timeStamp":timeStamp,
-
"nonceStr":nonceStr,
-
"package":prepayId,
-
"signType":"MD5",
-
"paySign":paySign
-
},
-
function(res){
-
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
-
location.href="支付成功返回商家自定义页面";
-
}else {//这里支付失败和支付取消统一处理
-
alert("支付取消");
-
location.href="支付失败返回商家自定义页面";
-
}
-
}
-
);
-
}
-
$(document).ready(function () {
-
if (typeof WeixinJSBridge == "undefined"){
-
if (document.addEventListener){
-
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
-
}else if (document.attachEvent){
-
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
-
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
-
}
-
}else {
-
onBridgeReady();
-
}
-
});
-
</script>
-
</html>
微信支付订单Service:WeChatPayService.java
-
/**
-
*微信支付统一下单
-
**/
-
public Map<String,Object> unifiedOrder(Order order, Map<String,Object> map){
-
Map<String,Object> resultMap;
-
try {
-
WxPaySendData paySendData = new WxPaySendData();
-
//构建微信支付请求参数集合
-
paySendData.setAppId(WeChatConstant.APP_ID);
-
paySendData.setAttach("微信订单支付:"+order.getOrderNumber());
-
paySendData.setBody("商品描述");
-
paySendData.setMchId(WeChatConfig.MCH_ID);
-
paySendData.setNonceStr(WeChatUtils.getRandomStr(32));
-
paySendData.setNotifyUrl(WeChatConfig.NOTIFY_URL);
-
paySendData.setDeviceInfo("WEB");
-
paySendData.setOutTradeNo(order.getOrderNumber());
-
paySendData.setTotalFee(order.getSumFee());
-
paySendData.setTradeType(WeChatConfig.TRADE_TYPE_JSAPI);
-
paySendData.setSpBillCreateIp((String) map.get("remoteIp"));
-
paySendData.setOpenId((String) map.get("openId"));
-
//将参数拼成map,生产签名
-
SortedMap<String,Object> params = buildParamMap(paySendData);
-
paySendData.setSign(WeChatUtils.getSign(params));
-
//将请求参数对象转换成xml
-
String reqXml = WeChatUtils.sendDataToXml(paySendData);
-
//发送请求
-
byte[] xmlData = reqXml.getBytes();
-
URL url = new URL(WeChatConfig.UNIFIED_ORDER_URL);
-
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
-
urlConnection.setDoOutput(true);
-
urlConnection.setDoInput(true);
-
urlConnection.setUseCaches(false);
-
urlConnection.setRequestProperty("Content_Type","text/xml");
-
urlConnection.setRequestProperty("Content-length",String.valueOf(xmlData.length));
-
DataOutputStream outputStream = new DataOutputStream(urlConnection.getOutputStream());
-
outputStream.write(xmlData);
-
outputStream.flush();
-
outputStream.close();
-
resultMap = WeChatUtils.parseXml(urlConnection.getInputStream());
-
} catch (Exception e) {
-
throw new ServiceException("微信支付统一下单异常",e);
-
}
-
return resultMap;
-
/**
-
* 构建统一下单参数map 用于生成签名
-
* @param data
-
* @return SortedMap<String,Object>
-
*/
-
private SortedMap<String,Object> buildParamMap(WxPaySendData data) {
-
SortedMap<String,Object> paramters = new TreeMap<String, Object>();
-
if (null != data){
-
if (StringUtils.isNotEmpty(data.getAppId())){
-
paramters.put("appid",data.getAppId());
-
}
-
if (StringUtils.isNotEmpty(data.getAttach())){
-
paramters.put("attach",data.getAttach());
-
}
-
if (StringUtils.isNotEmpty(data.getBody())){
-
paramters.put("body",data.getBody());
-
}
-
if (StringUtils.isNotEmpty(data.getDetail())){
-
paramters.put("detail",data.getDetail());
-
}
-
if (StringUtils.isNotEmpty(data.getDeviceInfo())){
-
paramters.put("device_info",data.getDeviceInfo());
-
}
-
if (StringUtils.isNotEmpty(data.getFeeType())){
-
paramters.put("fee_type",data.getFeeType());
-
}
-
if (StringUtils.isNotEmpty(data.getGoodsTag())){
-
paramters.put("goods_tag",data.getGoodsTag());
-
}
-
if (StringUtils.isNotEmpty(data.getLimitPay())){
-
paramters.put("limit_pay",data.getLimitPay());
-
}
-
if (StringUtils.isNotEmpty(data.getMchId())){
-
paramters.put("mch_id",data.getMchId());
-
}
-
if (StringUtils.isNotEmpty(data.getNonceStr())){
-
paramters.put("nonce_str",data.getNonceStr());
-
}
-
if (StringUtils.isNotEmpty(data.getNotifyUrl())){
-
paramters.put("notify_url",data.getNotifyUrl());
-
}
-
if (StringUtils.isNotEmpty(data.getOpenId())){
-
paramters.put("openid",data.getOpenId());
-
}
-
if (StringUtils.isNotEmpty(data.getOutTradeNo())){
-
paramters.put("out_trade_no",data.getOutTradeNo());
-
}
-
if (StringUtils.isNotEmpty(data.getSign())){
-
paramters.put("sign",data.getSign());
-
}
-
if (StringUtils.isNotEmpty(data.getSpBillCreateIp())){
-
paramters.put("spbill_create_ip",data.getSpBillCreateIp());
-
}
-
if (StringUtils.isNotEmpty(data.getTimeStart())){
-
paramters.put("time_start",data.getTimeStart());
-
}
-
if (StringUtils.isNotEmpty(data.getTimeExpire())){
-
paramters.put("time_expire",data.getTimeExpire());
-
}
-
if (StringUtils.isNotEmpty(data.getProductId())){
-
paramters.put("product_id",data.getProductId());
-
}
-
if (data.getTotalFee()>0){
-
paramters.put("total_fee",data.getTotalFee());
-
}
-
if (StringUtils.isNotEmpty(data.getTradeType())){
-
paramters.put("trade_type",data.getTradeType());
-
}
-
//申请退款参数
-
if (StringUtils.isNotEmpty(data.getTransactionId())){
-
paramters.put("transaction_id",data.getTransactionId());
-
}
-
if (StringUtils.isNotEmpty(data.getOutRefundNo())){
-
paramters.put("out_refund_no",data.getOutRefundNo());
-
}
-
if (StringUtils.isNotEmpty(data.getOpUserId())){
-
paramters.put("op_user_id",data.getOpUserId());
-
}
-
if (StringUtils.isNotEmpty(data.getRefundFeeType())){
-
paramters.put("refund_fee_type",data.getRefundFeeType());
-
}
-
if (null != data.getRefundFee() && data.getRefundFee()>0){
-
paramters.put("refund_fee",data.getRefundFee());
-
}
-
}
-
return paramters;
-
}
-
}
微信工具类 WeChatUtils.java
-
public class WeChatUtils {
-
/**
-
* 根据code获取微信授权access_token
-
* @param request
-
*/
-
public static AuthToken getTokenByAuthCode(String code){
-
AuthToken authToken;
-
StringBuilder json = new StringBuilder();
-
try {
-
URL url = new URL(WeChatConstant.GET_AUTHTOKEN_URL+"appid="+ WeChatConstant.APP_ID+"&secret="+ WeChatConstant.APP_SECRET+"&code="+code+"&grant_type=authorization_code");
-
URLConnection uc = url.openConnection();
-
BufferedReader in = new BufferedReader(new InputStreamReader(uc.getInputStream()));
-
String inputLine ;
-
while((inputLine=in.readLine())!=null){
-
json.append(inputLine);
-
}
-
in.close();
-
//将json字符串转成javaBean
-
ObjectMapper om = new ObjectMapper();
-
authToken = readValue(json.toString(),AuthToken.class);
-
} catch (Exception e) {
-
throw new ServiceException("微信工具类:根据授权code获取access_token异常",e);
-
}
-
return authToken;
-
}
-
/**
-
* 获取微信签名
-
* @param map 请求参数集合
-
* @return 微信请求签名串
-
*/
-
public static String getSign(SortedMap<String,Object> map){
-
StringBuffer sb = new StringBuffer();
-
Set set = map.entrySet();
-
Iterator iterator = set.iterator();
-
while (iterator.hasNext()){
-
Map.Entry entry = (Map.Entry) iterator.next();
-
String k = (String) entry.getKey();
-
Object v = entry.getValue();
-
//参数中sign、key不参与签名加密
-
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){
-
sb.append(k + "=" + v + "&");
-
}
-
}
-
sb.append("key=" + WeChatPayConfig.KEY);
-
String sign = MD5.MD5Encode(sb.toString()).toUpperCase();
-
return sign;
-
}
-
/**
-
* 解析微信服务器发来的请求
-
* @param inputStream
-
* @return 微信返回的参数集合
-
*/
-
public static SortedMap<String,Object> parseXml(InputStream inputStream) {
-
SortedMap<String,Object> map = Maps.newTreeMap();
-
try {
-
//获取request输入流
-
SAXReader reader = new SAXReader();
-
Document document = reader.read(inputStream);
-
//得到xml根元素
-
Element root = document.getRootElement();
-
//得到根元素所有节点
-
List<Element> elementList = root.elements();
-
//遍历所有子节点
-
for (Element element:elementList){
-
map.put(element.getName(),element.getText());
-
}
-
//释放资源
-
inputStream.close();
-
} catch (Exception e) {
-
throw new ServiceException("微信工具类:解析xml异常",e);
-
}
-
return map;
-
}
-
/**
-
* 扩展xstream,使其支持name带有"_"的节点
-
*/
-
public static XStream xStream = new XStream(new DomDriver("UTF-8",new XmlFriendlyNameCoder("-_","_")));
-
/**
-
* 请求参数转换成xml
-
* @param data
-
* @return xml字符串
-
*/
-
public static String sendDataToXml(WxPaySendData data){
-
xStream.autodetectAnnotations(true);
-
xStream.alias("xml", WxPaySendData.class);
-
return xStream.toXML(data);
-
}
-
/**
-
* 获取当前时间戳
-
* @return 当前时间戳字符串
-
*/
-
public static String getTimeStamp(){
-
return String.valueOf(System.currentTimeMillis()/1000);
-
}
-
/**
-
* 获取指定长度的随机字符串
-
* @param length
-
* @return 随机字符串
-
*/
-
public static String getRandomStr(int length){
-
String base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
-
Random random = new Random();
-
StringBuffer sb = new StringBuffer();
-
for (int i = 0; i < length; i++) {
-
int number = random.nextInt(base.length());
-
sb.append(base.charAt(number));
-
}
-
return sb.toString();
-
}
-
}
微信常量类 WeChatConstant.java
-
public class WeChatConstant {
-
/**Token*/
-
public static final String TOKEN = "";
-
/**EncodingAESKey*/
-
public static final String AES_KEY = "";
-
/**消息类型:文本消息*/
-
public static final String MESSAGE_TYPE_TEXT = "text";
-
/**消息类型:音乐*/
-
public static final String MESSAGE_TYPE_MUSIC = "music";
-
/**消息类型:图文*/
-
public static final String MESSAGE_TYPE_NEWS = "news";
-
/**消息类型:图片*/
-
public static final String MESSAGE_TYPE_IMAGE = "image";
-
/**消息类型:视频*/
-
public static final String MESSAGE_TYPE_VIDEO = "video";
-
/**消息类型:小视频*/
-
public static final String MESSAGE_TYPE_SHORTVIDEO = "shortvideo";
-
/**消息类型:链接*/
-
public static final String MESSAGE_TYPE_LINK = "link";
-
/**消息类型:地理位置*/
-
public static final String MESSAGE_TYPE_LOCATION = "location";
-
/**消息类型:音频*/
-
public static final String MESSAGE_TYPE_VOICE = "voice";
-
/**消息类型:事件推送*/
-
public static final String MESSAGE_TYPE_EVENT = "event";
-
/**事件类型:subscribe(订阅)*/
-
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
-
/**事件类型:unsubscribe(取消订阅)*/
-
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
-
/**事件类型:CLICK(自定义菜单点击事件)*/
-
public static final String EVENT_TYPE_CLICK = "CLICK";
-
/**返回消息类型:转发客服*/
-
public static final String TRANSFER_CUSTOMER_SERVICE="transfer_customer_service";
-
/**ACCESS_TOKEN*/
-
public static final String ACCESS_TOKEN_ENAME = "access_token";
-
/**返回成功字符串*/
-
public static final String RETURN_SUCCESS = "SUCCESS";
-
/**主动发送消息url*/
-
public static final String SEND_MESSAGE_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=";
-
/**通过code获取授权access_token的URL*/
-
public static final String GET_AUTHTOKEN_URL = " https://api.weixin.qq.com/sns/oauth2/access_token?";
-
}
其他微信对象:
封装微信授权返回的信息,此处属性均为小写【微信返回的是小写很不友好】
-
public class AuthToken implements Serializable {
-
/**授权access_token*/
-
private String access_token;
-
/**有效期*/
-
private String expires_in;
-
/**刷新access_token*/
-
private String refresh_token;
-
/**用户OPENID*/
-
private String openid;
-
/**授权方式Scope*/
-
private String scope;
-
/**错误码*/
-
private String errcode;
-
/**错误消息*/
-
private String errmsg;
-
/**getter() and setter()*/
-
}
微信请求参数对象【下单与退款均可使用此对象】
-
public class WxPaySendData {
-
/**公众账号ID 必须*/
-
@XStreamAlias("appid")
-
private String appId;
-
/**商户号 必须*/
-
@XStreamAlias("mch_id")
-
private String mchId;
-
/**设备号*/
-
@XStreamAlias("device_info")
-
private String deviceInfo;
-
/**随机字符串 必须*/
-
@XStreamAlias("nonce_str")
-
private String nonceStr;
-
/**签名 必须*/
-
@XStreamAlias("sign")
-
private String sign;
-
/**商品描述 必须*/
-
@XStreamAlias("body")
-
private String body;
-
/**商品详情*/
-
@XStreamAlias("detail")
-
private String detail;
-
/**附加数据*/
-
@XStreamAlias("attach")
-
private String attach;
-
/**商户订单号 必须*/
-
@XStreamAlias("out_trade_no")
-
private String outTradeNo;
-
/**货币类型*/
-
@XStreamAlias("fee_type")
-
private String feeType;
-
/**交易金额 必须[JSAPI,NATIVE,APP]*/
-
@XStreamAlias("total_fee")
-
private int totalFee;
-
/**交易类型 [必须]*/
-
@XStreamAlias("trade_type")
-
private String tradeType;
-
/**通知地址 [必须]*/
-
@XStreamAlias("notify_url")
-
private String notifyUrl;
-
/**终端Ip [必须]*/
-
@XStreamAlias("spbill_create_ip")
-
private String spBillCreateIp;
-
/**订单生成时间yyyyMMddHHmmss*/
-
@XStreamAlias("time_start")
-
private String timeStart;
-
/**订单失效时间yyyyMMddHHmmss 间隔>5min*/
-
@XStreamAlias("time_expire")
-
private String timeExpire;
-
/**用户标识 tradeType=JSAPI时必须*/
-
@XStreamAlias("openid")
-
private String openId;
-
/**商品标记*/
-
@XStreamAlias("goods_tag")
-
private String goodsTag;
-
/**商品ID tradeType=NATIVE时必须*/
-
@XStreamAlias("product_id")
-
private String productId;
-
/**指定支付方式*/
-
@XStreamAlias("limit_pay")
-
private String limitPay;
-
/**
-
*以下属性为申请退款参数
-
*/
-
/**微信订单号 [商户订单号二选一]*/
-
@XStreamAlias("transaction_id")
-
private String transactionId;
-
/**商户退款单号 [必须]*/
-
@XStreamAlias("out_refund_no")
-
private String outRefundNo;
-
/**退款金额 [必须]*/
-
@XStreamAlias("refund_fee")
-
private Integer refundFee;
-
/**货币种类*/
-
@XStreamAlias("refund_fee_type")
-
private String refundFeeType;
-
/**操作员账号:默认为商户号 [必须]*/
-
@XStreamAlias("op_user_id")
-
private String opUserId;
-
/**getter() and setter()*/
-
}