??微信支付方式有很多,這里主要記錄網(wǎng)頁微信支付喚起以及退款的主要流程澎迎,微信JSAPI支付必傳openid乱投,這里需要實現(xiàn)網(wǎng)頁授權(quán)獲取,網(wǎng)頁授權(quán)先獲取用戶授權(quán)的code铛嘱,然后拿code去換取access_token暖释,基本實現(xiàn)可以參考PHP實現(xiàn)獲取微信網(wǎng)頁授權(quán),獲取用戶信息
開發(fā)前準(zhǔn)備
1.微信公眾號后臺配置業(yè)務(wù)域名和網(wǎng)頁授權(quán)域名
2.微信支付平臺的產(chǎn)品中心綁定APPID和開發(fā)配置域名
3.下載微信退款的證書工具
4.使用證書工具生成相應(yīng)證書(1年有效期墨吓,注意及時更換)
5.下載Java的SDK與DEMO下載
以下附上開發(fā)代碼
前端默認(rèn)頁面訪問路徑
https://open.weixin.qq.com/connect/oauth2/authorize?appid=你的appId&redirect_uri=URLEncoder.encode(你的網(wǎng)頁地址&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
前端獲取url中的code通過ajax來換取openId
/**
* code換取用戶的open_id
*/
@RequestMapping(value = "getOpenid.htm")
@ResponseBody
public JSONObject getOpenid(HttpServletRequest request){
String code = request.getParameter("code");
String user_id = request.getParameter("user_id");
JSONObject jsonObject = new JSONObject();
if (RedisUtil.getString("is_web_access_token_valid" + user_id) != null) {
jsonObject = JSONObject.parseObject(RedisUtil.getString("is_web_access_token_valid" + user_id));
}else{
String app_secrect = WechatUtil.app_secrect;
String app_id = WechatUtil.app_id;
String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
jsonObject = HttpUtil.doGet(url.replace("APPID", app_id).replace("SECRET", app_secrect).replace("CODE", code));
RedisUtil.setString("is_web_access_token_valid" + user_id, jsonObject.toJSONString(), 7000);
}
return jsonObject;
}
微信繼承SDK的工共配置文件
package com.shadmin.common.util;
import com.shadmin.common.util.wxpay.IWXPayDomain;
import com.shadmin.common.util.wxpay.WXPayConfig;
import com.shadmin.common.util.wxpay.WXPayConstants;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
public class WechatPayUtil extends WXPayConfig {
private byte[] certData;
public WechatPayUtil() throws Exception {
// 獲取證書存放的路徑(放在SSM項目根目錄)
String certPath = Thread.currentThread().getContextClassLoader().getResource("1283062301_20181206_cert.p12").toString().replace("file:","");
// certPath = "/E:/myjava/sy_shadmin/target/sy_shadmin/WEB-INF/classes/1283062301_20181206_cert.p12";
File file = new File(certPath);
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
}
public String getAppID() {
return "你的AppID";
}
public String getMchID() {
return "你的商戶ID";
}
public String getKey() {
return "你的商戶支付密鑰";
}
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
public int getHttpConnectTimeoutMs() {
return 8000;
}
public int getHttpReadTimeoutMs() {
return 10000;
}
@Override
protected IWXPayDomain getWXPayDomain() {
IWXPayDomain iwxPayDomain = new IWXPayDomain() {
@Override
public void report(String domain, long elapsedTimeMillis, Exception ex) {
}
@Override
public DomainInfo getDomain(WXPayConfig config) {
return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
}
};
return iwxPayDomain;
}
/**
* 元轉(zhuǎn)分(微信支付以分為單位)
* @param yuan
* @return
*/
public static String Yuan2Fen(String yuan) {
return new BigDecimal(yuan).movePointRight(2).toString();
}
/**
* 分轉(zhuǎn)元
* @param fen
* @return
*/
public static String Fen2Yuan(String fen) {
return new BigDecimal(fen).movePointLeft(2).toString();
}
}
微信統(tǒng)一下單
/**
* 獲取微信支付參數(shù)
*/
public JSONObject getWechatPayData(JSONObject param) throws Exception {
WechatPayUtil wechatPayUtil = new WechatPayUtil();
WXPay wxpay = new WXPay(wechatPayUtil);
Map<String, String> data = new HashMap<String, String>();
data.put("body", param.getString("goods_name"));
data.put("out_trade_no", System.currentTimeMillis() + UUID.randomUUID().toString().replace("-", "").substring(0,5));
data.put("device_info", param.getString("user_id"));
data.put("fee_type", "CNY");
data.put("total_fee", WechatPayUtil.Yuan2Fen(param.getString("total_fee")));
data.put("spbill_create_ip", param.getString("spbill_create_ip"));
data.put("notify_url", "你的微信支付服務(wù)器回調(diào)地址");
data.put("trade_type", "JSAPI"); // 此處指定為JSAPI支付
data.put("openid", param.getString("open_id"));
Map<String, String> resp = wxpay.unifiedOrder(data);
JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(resp));
//新增返回值球匕,方便模擬微信回調(diào)
jsonObject.put("out_trade_no", data.get("out_trade_no"));
jsonObject.put("spbill_create_ip", data.get("spbill_create_ip"));
//新增支付等待有效期,方便定時任務(wù)掃描濾除緩存中過期未支付訂單
jsonObject.put("time_expire",System.currentTimeMillis());
//新增訂單編號帖烘,方便定時任務(wù)掃描濾除數(shù)據(jù)庫中過期未支付訂單
jsonObject.put("out_trade_no", data.get("out_trade_no"));
return jsonObject;
}
后臺將微信統(tǒng)一下單的返回值拼接成前段調(diào)用的參數(shù)(注意:微信統(tǒng)一下單的sign并不等同于前端微信支付喚起的paySign亮曹,否則會報錯chooseWXPay:fail the permission value is offline verifying)
/**
* 查詢是否有支付資格
* @param request
* @return
*/
@RequestMapping(value = "checkIsPaying.htm")
@ResponseBody
public JSONObject checkIsPaying(HttpServletRequest request) throws Exception {
String user_id = request.getParameter("user_id");
JSONObject jsonObject = new JSONObject();
//獲取搶購的庫存總數(shù)
Object store_num_object = RedisUtil.getMapKey("second_kill_activity_goods_info", "store_num");
int store_num = (int) ((store_num_object !=null)? store_num_object :0);
//判斷是否搶購失敗
if (RedisUtil.getMapSize("second_kill_order") < store_num) {
//從redis的order_to_pay的map中讀取對應(yīng)的值
if (RedisUtil.getMapKey("second_kill_pay", user_id) != null) {
JSONObject pay_detail = JSONObject.parseObject(RedisUtil.getMapKey("second_kill_pay", user_id).toString());
//根據(jù)微信支付參數(shù)增添返回值
HashMap<String, String> map = new HashMap<>();
map.put("appId", pay_detail.getString("appid"));
map.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
map.put("nonceStr", pay_detail.getString("nonce_str"));
map.put("package", "prepay_id=" + pay_detail.getString("prepay_id"));
map.put("signType", "MD5");
//調(diào)用微信SDK自帶的生成簽名的方法生成paySign
WechatPayUtil payUtil = new WechatPayUtil();
map.put("paySign", WXPayUtil.generateSignature(map, payUtil.getKey()));
map.put("out_trade_no", pay_detail.getString("out_trade_no"));
jsonObject.put("code", 1);
jsonObject.put("data", map);
jsonObject.put("message", "訂單已生成,請支付秘症!");
} else {
jsonObject.put("code", 0);
jsonObject.put("message", "搶購排隊中照卦,請稍后嘗試!");
}
}else{
jsonObject.put("code", -1);
jsonObject.put("message", "搶購失敗乡摹,搶購已結(jié)束役耕!");
}
return jsonObject;
}
獲取微信支付設(shè)備的ip地址
package com.shadmin.common.util;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* 工具類
* @author Administrator
*
*/
@SuppressWarnings("all")
public class ToolUtil {
/**
* 獲取客戶端IP
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
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.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (ip.equals("127.0.0.1") || ip.endsWith("0:0:0:0:0:0:0:1")) {
ip = "127.0.0.1";
}
if (ip != null && ip.length() > 15) { // "***.***.***.***".length()= 15
if (ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
}
return ip;
}
/**
* 把request轉(zhuǎn)為map
*
* @param request
* @return
*/
public static Map<String, Object> getParameterMap(HttpServletRequest request) {
// 參數(shù)Map
Map<?, ?> properties = request.getParameterMap();
// 返回值Map
Map<String, Object> returnMap = new HashMap<String, Object>();
Iterator<?> entries = properties.entrySet().iterator();
Map.Entry<String, Object> entry;
String name = "";
String value = "";
Object valueObj =null;
while (entries.hasNext()) {
entry = (Map.Entry<String, Object>) entries.next();
name = (String) entry.getKey();
valueObj = entry.getValue();
if (null == valueObj) {
value = "";
} else if (valueObj instanceof String[]) {
String[] values = (String[]) valueObj;
for (int i = 0; i < values.length; i++) {
value = values[i] + ",";
}
value = value.substring(0, value.length() - 1);
} else {
value = valueObj.toString();
}
returnMap.put(name, value);
}
return returnMap;
}
}
微信支付回調(diào)處理
/**
* 處理微信付款回調(diào)
* 1.獲取微信的回調(diào)參數(shù) return_code 返回狀態(tài)碼 SUCCESS
* 2.獲取second_kill_pay中對應(yīng)user_id的參數(shù)
* 3.根據(jù)回調(diào)結(jié)果判斷是否訂單成功
* 4.成功——添加到second_kill_order表
* 5.失敗——觸發(fā)退款機制
* 6.刪除second_kill_pay隊列數(shù)據(jù)
* 7.返回給微信回調(diào)應(yīng)答
* @return
*/
@RequestMapping("second_kill_notify.htm")
public void second_kill_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
//響應(yīng)微信回調(diào),默認(rèn)失敗
String notify_xml_response = resFailXml;
// 1.獲取微信支付回調(diào)xml
InputStream inputStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = inputStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, length);
}
outSteam.close();
inputStream.close();
String resultXml = new String(outSteam.toByteArray(), "utf-8");
logger.info("微信支付回調(diào):----result----:" + resultXml);
WechatPayUtil config = new WechatPayUtil();
WXPay wxpay = new WXPay(config);
Map<String, String> notifyMap = WXPayUtil.xmlToMap(resultXml); // 轉(zhuǎn)換成map
// 2.獲取second_kill_pay中對應(yīng)user_id的參數(shù),user_id對應(yīng)notifyMap中的device_info,下單時用device_info存放user_id
String user_id = notifyMap.get("device_info");
// 獲取對應(yīng)訂單out_trade_no的訂單second_order_pay值
JSONObject pay_detail = JSONObject.parseObject(RedisUtil.getMapKey("second_kill_pay", user_id).toString());
// 簽名正確并且是首次調(diào)用
if (wxpay.isPayResultNotifySignatureValid(notifyMap) && pay_detail.getString("out_trade_no").equals(notifyMap.get("out_trade_no"))) {
//獲取second_kill_activity_goods_info中的對應(yīng)的秒殺價格參數(shù)
String total_fee = RedisUtil.getMapKey("second_kill_activity_goods_info", "sale_price").toString();
HashMap<Object, Object> map = new HashMap<>();
map.put("pay_amount", WechatPayUtil.Fen2Yuan(notifyMap.get("total_fee")));
map.put("out_trade_no", notifyMap.get("out_trade_no"));
map.put("transaction_id", notifyMap.get("transaction_id"));
map.put("update_time", df.format(new Date()));
// 3.根據(jù)回調(diào)結(jié)果判斷是否訂單成功(total_fee支付金額一致聪廉,bank_type銀行類型必須為建行信用卡CCB_CREDIT)
if (notifyMap.get("bank_type").equals("CCB_CREDIT") && WechatPayUtil.Fen2Yuan(notifyMap.get("total_fee")).equals(total_fee)) {
// 4.成功——添加到second_kill_order表,并將該訂單寫入成功隊列
map.put("status", "1"); //status 1-支付成功
secondKillOrderListService.updateOrderList(map);
// 往成功訂單列表second_kill_order插入該user_id
RedisUtil.addMap("second_kill_order", user_id, map);
} else {
// 5.失敗——觸發(fā)退款機制蹄葱,并將失敗訂單寫入訂單表
Map<String, String> data = new HashMap<String, String>();
data.put("out_trade_no", notifyMap.get("out_trade_no"));
data.put("out_refund_no", System.currentTimeMillis() + UUID.randomUUID().toString().replace("-", "").substring(0,18));
data.put("total_fee", WechatPayUtil.Yuan2Fen(total_fee));
data.put("refund_fee", notifyMap.get("total_fee"));
data.put("refund_desc", "付款金額不一致或者非建行信用卡支付!");
Map<String, String> refund_result = wxpay.refund(data);
map.put("status", "2"); //status 2-已退款
map.put("out_refund_no", data.get("out_refund_no"));
map.put("pay_amount", WechatPayUtil.Fen2Yuan(notifyMap.get("total_fee")));
secondKillOrderListService.updateOrderList(map);
// 失敗的是后刪除排隊列表的信息锄列,方便再次發(fā)起搶購
RedisUtil.delMapKey("second_kill_queue_data", user_id);
logger.info("訂單支付回調(diào)刪除second_kill_queue_data:user_id="+user_id);
}
// 6.無論成功與否图云,刪除second_kill_pay隊列數(shù)據(jù)
RedisUtil.delMapKey("second_kill_pay", user_id);
logger.info("訂單支付回調(diào)刪除second_kill_pay:user_id="+user_id);
// 7.響應(yīng)微信回調(diào),支付成功
notify_xml_response = resSuccessXml;
}
// 7.返回給微信回調(diào)應(yīng)答
response.setContentType("text/xml");
response.getWriter().write(notify_xml_response);
response.flushBuffer();
}
微信的支付DEMO已經(jīng)很齊全了邻邮,所有微信支付開放的接口都可以參照README.MD去一一實現(xiàn)竣况,
這里重點說兩點:
1.統(tǒng)一下單接口的sign不等同于喚起微信支付時的paySign,否則手機微信端會看到微信支付閃一下就沒了筒严,微信開發(fā)者工具提示chooseWXPay:fail the permission value is offline verifying
2.JAVA的SDK支付實際下單時(區(qū)別與沙箱模式)默認(rèn)是使用HMAC-SHA256方式加密丹泉,
但是在微信支付回調(diào)時校驗回調(diào)簽名默認(rèn)使用MD5情萤,所以可以將WXPAY文件的45行改成
this.signType = SignType.MD5; // 指定為Md5加密
3.高并發(fā)生成唯一訂單號的方式
System.currentTimeMillis() + UUID.randomUUID().toString().replace("-", "").substring(0,18)
4.微信證書配置的位置在SSM項目的src根目錄下,獲取該路徑的方式
String certPath = Thread.currentThread().getContextClassLoader().getResource("1283062301_20181206_cert.p12").toString().replace("file:","");
// certPath = "/E:/myjava/sy_shadmin/target/sy_shadmin/WEB-INF/classes/1283062301_20181206_cert.p12";
以下附上參考鏈接
微信支付喚起報錯:the permission value is offline verifying
微信統(tǒng)一下單接口說明