2019独角兽企业重金招聘Python工程师标准>>>
微信公众号支付【Java版】
说明:
① 本文主要讲解的是微信公众号内(商城)支付部分,如需了解其他微信公众号开发内容,请访问:http://blog.csdn.net/lyq8479/article/details/8944988 【柳峰的博客 内容为2013year的有些内容已经改变,如需交流,请留言】
② 部分内容来源于我学习时参考的博客 :http://www.oschina.net/code/snippet_1754599_49966 【非常感谢 zyjason91 】
③ 本文由个人的印象笔记导出,如你也在使用印象笔记,我可以直接把笔记分享给你,我的印象笔记:[email protected]
1.准备工作
首先登录微信公众平台,获取并配置以下微信开发配置:
- 开发者ID【AppID和AppSecret】
- 服务器配置
1.url服务器地址设置
2.Token【自己设置,必须英文或数字】
3.EncodingAESKey[自己随机生成,用于消息加解密]
然后登录微信商户平台,获取并配置以下微信支付配置:
- 商户号(mchId)
- API秘钥(key)
- API证书(java版主要使用:apiclient_cert.p12)
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 map = Maps.newHashMap();
map.put("openId",authToken.getOpenid());//用户标识openId
map.put("remoteIp",request.getRemoteAddr());//请求Ip地址
//调用统一下单service
Map 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 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参数进行传递
- 微信网页授权说明
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
微信公众号支付测试
h5方式调用微信支付接口:h5Pay.jsp
- WeixinJSBridge为微信公众号内置对象,所以必须在公众号内部网页使用
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
确认支付
微信支付订单Service:WeChatPayService.java
- 微信支付API–统一下单 使用说明
/**
*微信支付统一下单
**/
public Map unifiedOrder(Order order, Map map){
Map 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 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
*/
private SortedMap buildParamMap(WxPaySendData data) {
SortedMap paramters = new TreeMap();
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 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 parseXml(InputStream inputStream) {
SortedMap map = Maps.newTreeMap();
try {
//获取request输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
//得到xml根元素
Element root = document.getRootElement();
//得到根元素所有节点
List 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()*/
}