微信支付方式有很多,这里主要记录网页微信支付唤起以及退款的主要流程,微信JSAPI支付必传openid,这里需要实现网页授权获取,网页授权先获取用户授权的code,然后拿code去换取access_token,基本实现可以参考PHP实现获取微信网页授权,获取用户信息
开发前准备
1.微信公众号后台配置业务域名和网页授权域名
配置业务域名和网页授权域名
2.微信支付平台的产品中心绑定APPID和开发配置域名
绑定APPID和开发配置域名
3.下载微信退款的证书工具
微信退款的校验证书
4.使用证书工具生成相应证书(1年有效期,注意及时更换)
证书文件
5.下载Java的SDK与DEMO下载
SDK与DEMO下载
以下附上开发代码
前端默认页面访问路径
https://open.weixin.qq.com/connect/oauth2/authorize?appid=你的appId&redirect_uri=URLEncoder.encode(你的网页地址&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;
}
/**
* 元转分(微信支付以分为单位)
* @param yuan
* @return
*/
public static String Yuan2Fen(String yuan) {
return new BigDecimal(yuan).movePointRight(2).toString();
}
/**
* 分转元
* @param fen
* @return
*/
public static String Fen2Yuan(String fen) {
return new BigDecimal(fen).movePointLeft(2).toString();
}
}
微信统一下单
/**
* 获取微信支付参数
*/
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", "你的微信支付服务器回调地址");
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));
//新增返回值,方便模拟微信回调
jsonObject.put("out_trade_no", data.get("out_trade_no"));
jsonObject.put("spbill_create_ip", data.get("spbill_create_ip"));
//新增支付等待有效期,方便定时任务扫描滤除缓存中过期未支付订单
jsonObject.put("time_expire",System.currentTimeMillis());
//新增订单编号,方便定时任务扫描滤除数据库中过期未支付订单
jsonObject.put("out_trade_no", data.get("out_trade_no"));
return jsonObject;
}
后台将微信统一下单的返回值拼接成前段调用的参数(注意:微信统一下单的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();
//获取抢购的库存总数
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中读取对应的值
if (RedisUtil.getMapKey("second_kill_pay", user_id) != null) {
JSONObject pay_detail = JSONObject.parseObject(RedisUtil.getMapKey("second_kill_pay", user_id).toString());
//根据微信支付参数增添返回值
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");
//调用微信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", "抢购失败,抢购已结束!");
}
return jsonObject;
}
获取微信支付设备的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转为map
*
* @param request
* @return
*/
public static Map<String, Object> getParameterMap(HttpServletRequest request) {
// 参数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;
}
}
微信支付回调处理
/**
* 处理微信付款回调
* 1.获取微信的回调参数 return_code 返回状态码 SUCCESS
* 2.获取second_kill_pay中对应user_id的参数
* 3.根据回调结果判断是否订单成功
* 4.成功——添加到second_kill_order表
* 5.失败——触发退款机制
* 6.删除second_kill_pay队列数据
* 7.返回给微信回调应答
* @return
*/
@RequestMapping("second_kill_notify.htm")
public void second_kill_notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
//响应微信回调,默认失败
String notify_xml_response = resFailXml;
// 1.获取微信支付回调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("微信支付回调:----result----:" + resultXml);
WechatPayUtil config = new WechatPayUtil();
WXPay wxpay = new WXPay(config);
Map<String, String> notifyMap = WXPayUtil.xmlToMap(resultXml); // 转换成map
// 2.获取second_kill_pay中对应user_id的参数,user_id对应notifyMap中的device_info,下单时用device_info存放user_id
String user_id = notifyMap.get("device_info");
// 获取对应订单out_trade_no的订单second_order_pay值
JSONObject pay_detail = JSONObject.parseObject(RedisUtil.getMapKey("second_kill_pay", user_id).toString());
// 签名正确并且是首次调用
if (wxpay.isPayResultNotifySignatureValid(notifyMap) && pay_detail.getString("out_trade_no").equals(notifyMap.get("out_trade_no"))) {
//获取second_kill_activity_goods_info中的对应的秒杀价格参数
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.根据回调结果判断是否订单成功(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.失败——触发退款机制,并将失败订单写入订单表
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);
// 失败的是后删除排队列表的信息,方便再次发起抢购
RedisUtil.delMapKey("second_kill_queue_data", user_id);
logger.info("订单支付回调删除second_kill_queue_data:user_id="+user_id);
}
// 6.无论成功与否,删除second_kill_pay队列数据
RedisUtil.delMapKey("second_kill_pay", user_id);
logger.info("订单支付回调删除second_kill_pay:user_id="+user_id);
// 7.响应微信回调,支付成功
notify_xml_response = resSuccessXml;
}
// 7.返回给微信回调应答
response.setContentType("text/xml");
response.getWriter().write(notify_xml_response);
response.flushBuffer();
}
微信的支付DEMO已经很齐全了,所有微信支付开放的接口都可以参照README.MD去一一实现,
这里重点说两点:
1.统一下单接口的sign不等同于唤起微信支付时的paySign,否则手机微信端会看到微信支付闪一下就没了,微信开发者工具提示chooseWXPay:fail the permission value is offline verifying
2.JAVA的SDK支付实际下单时(区别与沙箱模式)默认是使用HMAC-SHA256方式加密,
但是在微信支付回调时校验回调签名默认使用MD5,所以可以将WXPAY文件的45行改成
this.signType = SignType.MD5; // 指定为Md5加密
3.高并发生成唯一订单号的方式
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
微信统一下单接口说明
网友评论