public WechatUserVo getAccessToken(String authCode) {
// snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo
//(弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息
// code说明 : code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
// 公众号可通过下述接口来获取网页授权access_token。
// 如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。
// 获取code后,请求以下链接获取access_token
try {
JSONObject json_token = JSONObject.parseObject(HttpClientUtil.get(WeChatContants.OAUTH2_TOKEN_URL + "&code=" + authCode + "&grant_type=authorization_code"));
WechatMembershipVO wechatMembershipVO = new WechatMembershipVO();
String accessToken = json_token.get("access_token").toString();
String refreshToken = json_token.get("refresh_token").toString();
String getOpenid = json_token.get("openid").toString();
}catch (Exception e){
throw new BusinessException("使用code换取access_token出错");
}
}
//获取微信用户信息
try {
// 拉取用户信息(需scope为 snsapi_userinfo)
// 如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。
JSONObject json_user = JSONObject.parseObject(HttpClientUtil.get("https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN"));
if(null == json_user){
throw new BusinessException("获取微信用户信息失败");
}
logger.info("微信用户信息:" + json_user.toJSONString());
String openid = json_user.get("openid").toString();
String nickname = json_user.get("nickname").toString();
String sex = json_user.get("sex").toString();
String province = json_user.get("province").toString();
String city = json_user.get("city").toString();
String country = json_user.get("country").toString();
String headimgurl = json_user.get("headimgurl").toString();
//数据库存取
}catch (Exception e){
throw new BusinessException("获取微信用户信息失败");
}
支付需要的参数:
mch_appid: 公众号appid
mch_id: 商户号
mch_key: 商户秘钥
app_secret: 公众号开发者秘钥
notify_url: http://xxx/pay/notify(自定义微信支付异步回调地址)
prepay_url: https://api.mch.weixin.qq.com/pay/unifiedorder(获取预支付id)
下面几张图是需要的配置或获取参数位置.
//获取预支付id
public ResponseEntity createPrepayOrder(String orderCode,Long userId,String userName,HttpServletRequest request) {
try{
OrderVO order = orderService.selectOrderByOrderCode(orderCode);
//验证订单相关信息
if(null == order){
return ResponseEntity.failed("订单记录不存在");
}
if(null != order.getUserId()){
if(userId.longValue() != order.getUserId().longValue()){
return ResponseEntity.failed("该订单所属人不是你,请勿支付");
}
}else{
return ResponseEntity.failed("该订单所属人不是你,请勿支付");
}
if(OrderStauts.ORDER_STAUTS2.getIndex() == order.getOrderStatus()){
return ResponseEntity.failed("该订单已经支付,请勿支付");
}
if(OrderStauts.ORDER_STAUTS3.getIndex() == order.getOrderStatus()){
return ResponseEntity.failed("该订单已经完成,请勿支付");
}
if(OrderStauts.ORDER_STAUTS4.getIndex() == order.getOrderStatus()){
return ResponseEntity.failed("该订单已经取消,请勿支付");
}
BigDecimal orderAmount = order.getOrderAmount();
if(-1 == orderAmount.compareTo(BigDecimal.ZERO) || 0 == orderAmount.compareTo(BigDecimal.ZERO) ){
return ResponseEntity.failed("订单金额不能小于0");
}
//验证用户信息
Membership membership = membershipService.selectById(userId);
if(null == membership){
return ResponseEntity.failed("用户不存在");
}
if(StringUtils.isBlank(membership.getOpenid())){
return ResponseEntity.failed("您还没有进行微信授权,请授权后再进行操作");
}
//支付金额 单位:分
Integer totalFee = orderAmount.multiply(new BigDecimal(100)).intValue();
String body = order.getParameters();
// 生成请求预支付ID XML
String prepayXML = WeiXinUtil.buildOrderPrepayXML(
mch_appid,
mch_id,
totalFee,
body,
orderCode,
notify_url,
TRADE_TYPE,
getIp(request),
membership.getOpenid(),
mch_key
);
// 请求微信预支付ID
String prepayId;
try {
prepayId = getWeChatPrepayId(prepayXML);
} catch (IOException e) {
return ResponseEntity.failed("获取预支付订单失败");
}
// 为前端生成支付XML
String orderPayParams = JsonUtil.jsonify(WeiXinUtil.buildOrdePay(mch_appid, mch_key, prepayId));
return ResponseEntity.ok(orderPayParams);
}catch (Exception e){
}
return ResponseEntity.failed("支付失败");
}
private String getWeChatPrepayId(String prepayXML) throws IOException {
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
HttpPost httpPost = new HttpPost(prepay_url);
httpPost.setEntity(new StringEntity(prepayXML, "UTF-8"));
ResponseHandler responseHandler = response -> {
int status = response.getStatusLine().getStatusCode();
if (status >= 200 && status < 300) {
HttpEntity entity = response.getEntity();
return entity != null ? EntityUtils.toString(entity,"UTF-8") : null;
} else {
throw new ClientProtocolException("Unexpected response status: " + status);
}
};
String responseBody = httpclient.execute(httpPost, responseHandler);
if(responseBody == null) {
throw new RuntimeException("微信回传的值不正确");
}
logger.info(responseBody);
Map responseMap = new HashMap<>();
try{
responseMap = WeiXinUtil.xmlToMap(responseBody);
// WeiXinUtil.checkSign(responseMap, mch_key);
}catch (Exception e){
}
if (!StringUtils.equals(responseMap.get("appid"),mch_appid)) {
throw new RuntimeException("微信APP_ID回传不正确");
}
if (!StringUtils.equals(responseMap.get("mch_id"), mch_id)) {
throw new RuntimeException("微信MCH_ID回传不正确");
}
return responseMap.get("prepay_id");
}
}
//微信支付工具类
package com.uitech.official.sales.util;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;
import com.uitech.common.core.util.digest.MD5;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.UnsupportedEncodingException;
public class WeiXinUtil {
public static String getSign(Map map, String mchKey) {
List keys = new ArrayList<>(map.keySet());
// key排序
Collections.sort(keys);
StringBuilder info = new StringBuilder();
for (String key : keys) {
if (!"sign".equals(key) && !"key".equals(key)) {
String value = map.get(key);
if (!StringUtils.isBlank(value)) {
info.append(buildKeyValue(key, value));
info.append("&");
}
}
}
info.append(buildKeyValue("key", mchKey));
try {
return MD5.getMessageDigest(info.toString().getBytes("UTF-8")).toUpperCase();
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
private static String buildKeyValue(String key, String value) {
return key + "=" + value;
}
/**
* 封装预支付xml
* @param app_id
* @param mch_id
* @param totalFee
* @param body
* @param out_trade_no
* @param notify_url
* @param trade_type
* @param spbill_create_ip
* @return
*/
public static String buildOrderPrepayXML(String app_id,String mch_id,
Integer totalFee, String body, String out_trade_no,
String notify_url,String trade_type,String spbill_create_ip,String open_id,String mch_key) {
SortedMap packageParams = new TreeMap<>();
String nonce_str = getNonceStr();
packageParams.put("appid", app_id);
packageParams.put("mch_id", mch_id);
packageParams.put("nonce_str", nonce_str);
packageParams.put("body", body);
packageParams.put("out_trade_no", out_trade_no);
packageParams.put("total_fee", totalFee.toString());
packageParams.put("spbill_create_ip", spbill_create_ip);
packageParams.put("notify_url", notify_url);
packageParams.put("trade_type", trade_type);
packageParams.put("openid", open_id);
String sign = getSign(packageParams,mch_key);
packageParams.put("sign",sign);
return getRequestXml(packageParams);
}
/**
* 获取随机字符串
*
* @return
*/
public static String getNonceStr() {
// 随机数
String currTime = getCurrTime();
// 8位日期
String strTime = currTime.substring(8, currTime.length());
// 四位随机数
String strRandom = buildRandom(4) + "";
// 10位序列号,可以自行调整。
return strTime + strRandom;
}
public static Map buildOrdePay(String app_id, String mch_key,String prepayId){
Map payMap = new HashMap<>();
payMap.put("appId",app_id);
payMap.put("timeStamp", String.valueOf(System.currentTimeMillis()/1000));
payMap.put("nonceStr", getNonceStr());
payMap.put("package","prepay_id="+prepayId);
payMap.put("signType","MD5");
payMap.put("paySign",getSign(payMap,mch_key));
return payMap;
}
/**
* Map转xml数据
*/
public static String GetMapToXML(Map param){
StringBuffer sb = new StringBuffer();
sb.append("");
for (Map.Entry entry : param.entrySet()) {
sb.append("<"+ entry.getKey() +">");
sb.append(entry.getValue());
sb.append(""+ entry.getKey() +">");
}
sb.append(" ");
return sb.toString();
}
public static Map xmlToMap(String strXML) throws Exception {
try {
Map data = new HashMap();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
}
return data;
} catch (Exception ex) {
throw ex;
}
}
//请求支付xml组装
public static String getRequestXml(SortedMap parameters){
StringBuffer sb = new StringBuffer();
sb.append("");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String key = (String)entry.getKey();
String value = (String)entry.getValue();
if ("attach".equalsIgnoreCase(key)||"body".equalsIgnoreCase(key)||"sign".equalsIgnoreCase(key)) {
sb.append("<"+key+">"+""+key+">");
}else {
sb.append("<"+key+">"+value+""+key+">");
}
}
sb.append(" ");
return sb.toString();
}
/**
* 验证回调签名
* @return
*/
public static boolean isTenpaySign(Map map,String mch_key) {
String characterEncoding="utf-8";
String charset = "utf-8";
String signFromAPIResponse = map.get("sign");
if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {
System.out.println("API返回的数据签名数据不存在,有可能被第三方篡改!!!");
return false;
}
System.out.println("服务器回包里面的签名是:" + signFromAPIResponse);
//过滤空 设置 TreeMap
SortedMap packageParams = new TreeMap();
for (String parameter : map.keySet()) {
String parameterValue = map.get(parameter);
String v = "";
if (null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + mch_key);
//将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较
//算出签名
String resultSign = "";
String tobesign = sb.toString();
if (null == charset || "".equals(charset)) {
resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase();
}else{
try{
resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase();
}catch (Exception e) {
resultSign = MD5Util.MD5Encode(tobesign, characterEncoding).toUpperCase();
}
}
String tenpaySign = ((String)packageParams.get("sign")).toUpperCase();
return tenpaySign.equals(resultSign);
}
/**
* 获取当前时间 yyyyMMddHHmmss
* @return String
*/
public static String getCurrTime() {
Date now = new Date();
SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String s = outFormat.format(now);
return s;
}
/**
* 取出一个指定长度大小的随机正整数.
*
* @param length
* int 设定所取出随机数的长度。length小于11
* @return int 返回生成的随机数。
*/
public static int buildRandom(int length) {
int num = 1;
double random = Math.random();
if (random < 0.1) {
random = random + 0.1;
}
for (int i = 0; i < length; i++) {
num = num * 10;
}
return (int) ((random * num));
}
}
//MD5工具类
package com.uitech.official.sales.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util {
/**
* 生成MD5加密密文
*
* @param text
* 明文
* @return String 密文
*/
public static String md5(String text) {
MessageDigest msgDigest = null;
try {
msgDigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(
"System doesn't support MD5 algorithm.");
}
msgDigest.update(text.getBytes());
byte[] bytes = msgDigest.digest();
byte tb;
char low;
char high;
char tmpChar;
String md5Str = new String();
for (int i = 0; i < bytes.length; i++) {
tb = bytes[i];
tmpChar = (char) ((tb >>> 4) & 0x000f);
if (tmpChar >= 10) {
high = (char) (('a' + tmpChar) - 10);
} else {
high = (char) ('0' + tmpChar);
}
md5Str += high;
tmpChar = (char) (tb & 0x000f);
if (tmpChar >= 10) {
low = (char) (('a' + tmpChar) - 10);
} else {
low = (char) ('0' + tmpChar);
}
md5Str += low;
}
return md5Str;
}
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString;
}
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
public static void main(String[] args) {
System.out.println(md5("111111"));
}
}
/**
* 微信支付异步回调
* @Param
*/
@RequestMapping(value = "/notify",method = RequestMethod.POST)
@ApiOperation(value = "移动端 - 支付回调",tags = {"订单支付"})
public void notify(HttpServletRequest request, HttpServletResponse response){
logger.info("移动端 -- 支付异步回调 pay/notify,当前时间:{}", dateFormat.format(new Date()));
payService.notifyUrl(request,response);
}
//回调方法
public void notifyUrl(HttpServletRequest request, HttpServletResponse response) {
String inputLine;
String notityXml = "";
String resXml = "";
String errMessage = "";
//解析微信给返回的数据
try {
while ((inputLine = request.getReader().readLine()) != null) {
notityXml += inputLine;
}
request.getReader().close();
} catch (Exception e) {
resXml = returnXML("FAIL","xml解析失败");
}
try{
errMessage = notityXml;
Map map = WeiXinUtil.xmlToMap(notityXml);
String out_trade_no = (String) map.get("out_trade_no");// 获取商户订单号
String sign = (String) map.get("sign");// 获取签名
String time_end = (String) map.get("time_end");//支付完成时间
String transaction_id = (String) map.get("transaction_id");//微信支付订单号
String result_code = (String) map.get("result_code");// 业务结果
String return_code = (String) map.get("return_code");// SUCCESS/FAIL
//验证签名
String validSign = WeiXinUtil.getSign(map,mch_key);
if(!sign.equals(validSign)){
errMessage = "微信回调签名验证失败OrderCode:"+out_trade_no+" "+"xml:"+notityXml;
resXml = returnXML("FAIL","签名验证失败");
}else {
if ("SUCCESS".equals(result_code) && "SUCCESS".equals(return_code)){
//处理订单
OrderVO order = orderService.selectOrderByOrderCode(out_trade_no);
if(null == order){
resXml = returnXML("FAIL","订单不存在");
}else{
//支付成功 修改订单支付状态
if(updateStatus){
resXml = returnXML("SUCCESS","OK");
}else{
resXml = returnXML("FAIL","修改订单状态失败");
}
}
}else{
resXml = returnXML("FAIL","支付回调失败");
}
}
}catch (Exception e){
resXml = returnXML("FAIL","回调失败");
}
try {
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
} catch (Exception e) {
}
}
private String returnXML(String return_code,String return_msg) {
return "" +
"" +
" " +
"" +
"" +
" " +
" ";
}