简介:
首先,对接微信支付要知道需要什么参数,然后等我们拿到这些需要的参数。就要开始怎么去做。微信有专门的支付dome,可惜我才疏学浅,没怎么看懂(哭泣~)。不过里面的有一些工具类是我们所需要的。
这里我们可以看到所需的参数:
公众账号ID appid:这个是从商户公众号后台里面获取的
商户号 mch_id:这个也是从商户后台获取的,申请退款的时候这商户号就是退款证书的AES加解密算法的key
随机字符串 nonce_str:这个可以在微信官网的dome里面使用工具类
终端IP spbill_create_ip:官网的dome中获得
通知地址 notify_url:比如下单成功之后的回调地址。通知url必须为外网可访问的url,不能携带参数
用户标识 openid:trade_type=JSAPI时(即JSAPI支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识
交易类型 trade_type:JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付
签名 sign:通过签名算法计算得出的签名值,详见签名生成算法
这里我们所作的是JSAPI支付方式,所以这里的openid必需要获得:如何获得,该怎么获得。
首先,要知道我们使用的是微信里面自带的浏览器,我们通过微信浏览器给当前用户进行用户授权。看JS 代码
var code = "";
$(function(){
/**
如果你之前从上一个页面已经获得code,你可以直接从url带过来
这个code是从微信浏览器获得的,随后通过这个code去后台请求微信端
获取openid
*/
code=getUrlParam("code");
if(code==undefined || code==""){
if(is_weixn()){
$(location).attr("href","https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxx&redirect_uri=xxx&response_type=code&scope=snsapi_base&state=1#wechat_redirect");
}else{
//跳转让他关注公众号
//Alert3('不是微信浏览器');
$(location).attr("href","#");
}
}
})
/**
*判断是否微信浏览器
*/
function is_weixn(){
var ua = navigator.userAgent.toLowerCase();
if(ua.match(/MicroMessenger/i)=="micromessenger") {
return true;
} else {
return false;
}
}
/**
*获取url中的参数
*/
function getUrlParam(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]); return null;
}
这里有几个参数需要注意以下:
获取code 的请求地址:https://open.weixin.qq.com/connect/oauth2/authorize?
后面带参数appid=商户后台获取的appid
redirect_uri=当前需要支付授权的页面的完整地址(跳转回调redirect_uri,应当使用https链接来确保授权code的安全性。),需要https,需要对地址进行urlEncode处理,需要注意参数顺序。
后面参数固定不变。
这个code 在前端获取之后就有这个值,需请求后台即可拿到openid
订单支付
/**
*微信支付
*/
$("#wxpay").click(function(){
var parameter={};
if(code !=null && code!=undefined && code!=""){
parameter["code"]=code;
}else{
Alert3('微信授权失败,无法支付');
return;
}
//支付方式0/1、wx/zfb
parameter["payMethod"]="0";
parameter["amount"]=价格;
var data=getData2(parameter);
var requestDate=ajax(url + "/xxx",data);
if(requestDate["code"]==10000){
res(requestDate["prepayId"]); //prepayId微信支付成功的返回参数,需要带到前台
}else{
alert(requestDate["msg"]);
}
})
function getData2(parameter){
var data={};
data["parameter"]=parameter;
return JSON.stringify(data);
}
/**
*支付标识验证
*/
function res(prepayId){
var appId="xxx";//商户appid
var timestamp=Math.round(new Date().getTime()/1000).toString();
var nonceStr=getMachine();
var package="prepay_id="+prepayId;
var signType="MD5";
var stringSignTemp="appId={0}&nonceStr={1}&package={2}&signType={3}&timeStamp={4}&key=qwertyuiopasdfghjklzxcvbqwertyui";
stringSignTemp=String.format(stringSignTemp,appId,nonceStr,package,signType,timestamp);
var paySign=$.md5(stringSignTemp).toUpperCase();
onBridgeReady(appId,timestamp,nonceStr,package,signType,paySign);
}
/**
*支付参数校验
*/
function onBridgeReady(appId,timestamp,nonceStr,package,signType,paySign){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId, //公众号名称,由商户传入
"timeStamp":timestamp, //时间戳,自1970年以来的秒数
"nonceStr":nonceStr, //随机串
"package":package,
"signType":signType, //微信签名方式:
"paySign":paySign //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ){
alert("支付成功");
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
} else{
alert("支付失败");
}
});
}
/**
*获得随机数
*/
function getMachine(){
var chars = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'
,'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
var machine="";
for(var i=0; i<16;i++){
var t=parseInt(61*Math.random())
machine=machine+chars[t];
}
return machine;
}
后台调用
//支付调用
String payMethod = object.optString("payMethod"); //支付方式
// String orderNo = "xxx";
if (payMethod.trim().equals("0")){
String code = object.optString("code");
String wx_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + SysConfig.getWx_appid() + "&secret=" + SysConfig.getWx_secret() + "&code=" + code + "&grant_type=authorization_code";
String sb2 = HttpUtil.httpsGet(wx_url);
if(sb2 == null || "".equals(sb2)){
map.put("code",30001);
map.put("msg","网络繁忙,请稍后重试");
return map;
}
JSONObject object2 = JSONObject.fromObject(sb2);
System.out.println("object2==="+object2);
openid = object2.optString("openid");
if (openid == ""){
map.put("code", 31002);
map.put("msg", "授权失败,请稍后重试");
return map;
}
Map<String, String> data = new HashMap<String, String>();
data.put("appid", SysConfig.getWx_appid());//商户appid
data.put("body", "xxx支付:");
data.put("detail", "xxx描述");
data.put("mch_id", SysConfig.getWx_mch_id());
data.put("nonce_str",WXPayUtil.generateNonceStr());//随机串
data.put("notify_url", SysConfig.getWx_notify_verify());//回调地址
data.put("openid",openid);
data.put("out_trade_no", orderNo);//商户订单
data.put("sign_type", "MD5");
data.put("spbill_create_ip", GetIpUtil.getIp(request));
data.put("total_fee", String.valueOf((int)(amount*100)));//金额:分
data.put("trade_type", "JSAPI");
String xml = WXPayUtil.generateSignedXml(data,SysConfig.getWx_key());//这里是微信商户的支付key,也就是api密钥
System.out.println("xml==="+xml);
String respn = HttpUtil.sendJsonPost(SysConfig.getWx_payMent(),xml).toString();
if (respn == null){
map.put("code", 31001);
map.put("msg", "系统繁忙,请稍后重试");
return map;
}
Map resp = JSONObject.fromObject(WXPayUtil.parseXml(respn).get("xml"));
if("SUCCESS".equals(resp.get("return_code"))){
prepayId = resp.get("prepay_id").toString();
}else{
map.put("code", 31003);
map.put("msg", "系统繁忙,请稍后重试");
return map;
}
}else{
map.put("code", 10004);
map.put("msg", "支付方式异常!");
}
下面是支付所需的工具类:
发起请求的POST和GET
public static String httpsGet(String urlStr) {
StringBuilder result = new StringBuilder();
BufferedReader reader = null;
try {
// 创建连接
URL url = new URL(urlStr);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.connect();
// 读取响应
reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
String line;
System.out.println("Https返回:");
while ((line = reader.readLine()) != null) {
result.append(line.trim());
}
} catch (Exception e) {
e.printStackTrace();
}
finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException ex) {
LogUtil.error("io流关闭错误!" + ex);
ex.printStackTrace();
}
}
return result.toString();
}
//发送json请求
public static StringBuilder sendJsonPost(String url, String param) {
if (url == null) {
return new StringBuilder("NETWORKURLERROR");
}
PrintWriter out = null;
BufferedReader in = null;
StringBuilder result = new StringBuilder("");
try {
LogUtil.info(url + ">>>>>>" + param);
URL realUrl = new URL(url);// 创建连接
HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setInstanceFollowRedirects(true);
connection.setRequestMethod("POST"); // 设置请求方式
connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式
connection.connect();
OutputStreamWriter out1 = new OutputStreamWriter(
connection.getOutputStream(), "UTF-8"); // utf-8编码
out1.append(param);
out1.flush();
out1.close();
// 读取响应
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(connection.getInputStream(), "UTF-8"));
String line;
while ((line = in.readLine()) != null) {
result.append(line);
}
} catch (Exception e) {
LogUtil.error("发送 POST 请求出现异常!" + e);
e.printStackTrace();
return new StringBuilder("NETWORKERROR");
}
//使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
LogUtil.error("io流关闭错误!" + ex);
ex.printStackTrace();
}
}
return result;
}
微信配置类SysConfig
/**
* Created by jy on 2019/8/21.
* 系统级公共参数配置
*/
public class SysConfig {
private static String ip = "xxxx";
private static String ip2 = "xxxx";
//微信
private static String wx_certPath = "D:\\cer\\apiclient_cert.p12";//退款需要
private static String wx_appid = "xxx";//商户获得
private static String wx_mch_id = "xxx";//商户获得
private static String wx_paymentKey = "xxx";//商户获得
private static String wx_secret = "xxx";//商户获得
private static String wx_notify_verify = "xxx";//微信回调地址
//微信接口地址
private static String wx_payMent = "https://api.mch.weixin.qq.com/pay/unifiedorder";
private static String wx_refund = "https://api.mch.weixin.qq.com/secapi/pay/refund";
private static String wx_query = "https://api.mch.weixin.qq.com/pay/orderquery";
public static String getWx_payMent() {
return wx_payMent;
}
public static String getWx_query() {
return wx_query;
}
public static String getWx_refund() {
return wx_refund;
}
public static String getIp() {
return ip;
}
public static String getIp2() {
return ip2;
}
public static String getWx_certPath() {
return wx_certPath;
}
public static String getWx_appid() {
return wx_appid;
}
public static String getWx_mch_id() {
return wx_mch_id;
}
public static String getWx_key() {
return wx_paymentKey;
}
public static String getWx_secret() {
return wx_secret;
}
public static String getWx_notify_verify() {
return wx_notify_verify;
}
}
微信支付的工具类WXPayUtil
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayConstants.SignType;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;
/**
* Created with IDEA
* author:Jy
* Date:2019/8/30
* Time:10:11
*/
public class WXPayUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
*
* @param data Map类型数据
* @param key API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
return isSignatureValid(data, key, SignType.MD5);
}
/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名方式
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignatureValid(Map<String, String> data, String key, SignType signType) throws Exception {
if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
return false;
}
String sign = data.get(WXPayConstants.FIELD_SIGN);
return generateSignature(data, key, signType).equals(sign);
}
/**
* 生成签名
*
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, SignType.MD5);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, SignType signType) throws Exception {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WXPayConstants.FIELD_SIGN)) {
continue;
}
if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
sb.append("key=").append(key);
if (SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
}
else if (SignType.HMACSHA256.equals(signType)) {
return HMACSHA256(sb.toString(), key);
}
else {
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) throws Exception {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 生成 HMACSHA256
* @param data 待处理数据
* @param key 密钥
* @return 加密结果
* @throws Exception
*/
public static String HMACSHA256(String data, String key) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 获取当前时间戳,单位秒
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis()/1000;
}
/**
* 获取当前时间戳,单位毫秒
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
return generateSignedXml(data, key, SignType.MD5);
}
/**
* 生成带有 sign 的 XML 格式字符串
*
* @param data Map类型数据
* @param key API密钥
* @param signType 签名类型
* @return 含有sign字段的XML
*/
public static String generateSignedXml(final Map<String, String> data, String key, SignType signType) throws Exception {
String sign = generateSignature(data, key, signType);
data.put(WXPayConstants.FIELD_SIGN, sign);
return mapToXml(data);
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key: data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
try {
writer.close();
}
catch (Exception ex) {
}
return output;
}
/**
* xml转为map,map中有list(节点相同时候),list中有map
* @param xml
* @return
* @throws DocumentException
*/
public static Map<String, Object> parseXml(String xml) throws DocumentException {
Map map = new HashMap();
try {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new ByteArrayInputStream(xml.getBytes("UTF-8")));
Element root = document.getRootElement();
elementTomap(root, map);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return map;
}
@SuppressWarnings("unchecked")
public static Map<String, Object> elementTomap(Element outele, Map<String, Object> outmap) {
List<Element> list = outele.elements();
int size = list.size();
if (size == 0) {
outmap.put(outele.getName(), outele.getTextTrim());
} else {
Map<String, Object> innermap = new HashMap<String, Object>();
int i = 1;
for (Element ele1 : list) {
String eleName = ele1.getName();
String value = ele1.getText();
Object obj = innermap.get(eleName);
if (obj == null) {
elementTomap(ele1, innermap);
} else {
if (obj instanceof java.util.Map) {
List<Map<String, Object>> list1 = new ArrayList<Map<String, Object>>();
list1.add((Map<String, Object>) innermap.remove(eleName));
elementTomap(ele1, innermap);
list1.add((Map<String, Object>) innermap.remove(eleName));
innermap.put(eleName, list1);
} else if (obj instanceof String) {
innermap.put(eleName + i, value);
i++;
} else {
elementTomap(ele1, innermap);
Map<String, Object> listValue = (Map<String, Object>) innermap.get(eleName);
((List<Map<String, Object>>) obj).add(listValue);
innermap.put(eleName, obj);
}
}
}
outmap.put(outele.getName(), innermap);
}
return outmap;
}
}
微信获取IP的工具类GetIpUtil
package com.cn.hjsj.util.http;
import javax.servlet.http.HttpServletRequest;
/**
* Created by jy on 2019/8/21.
*/
public class GetIpUtil {
public static String getIp(HttpServletRequest request) {
String ip = null;
try{
ip = request.getHeader("x-forwarded-for");
// System.out.println("ip0=" + ip);
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
// System.out.println("ip1=" + ip);
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
// System.out.println("ip2=" + ip);
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
// System.out.println("ip3=" + ip);
}
} catch(Exception e){
e.printStackTrace();
}
return ip;
}
}
微信xml转化的工具类WXPayXmlUtil
import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* 2018/7/3
*/
public final class WXPayXmlUtil {
public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
return documentBuilderFactory.newDocumentBuilder();
}
public static Document newDocument() throws ParserConfigurationException {
return newDocumentBuilder().newDocument();
}
}
可能需要用到的maven依赖
<dependency> <groupId>com.mashape.unirest</groupId> <artifactId>unirest-java</artifactId> <version>1.4.9</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.6</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpasyncclient</artifactId> <version>4.0.2</version> </dependency> <!--微信 --> <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency>
至此,支付就基本上可以使用了。
支付回调返回的参数是xml形式,直接贴xml返回串,具体根据自己的业务需求做响应的变化
<xml><appid><![CDATA[xxxx]]></appid>
<bank_type><![CDATA[CFT]]></bank_type>
<cash_fee><![CDATA[1]]></cash_fee>
<fee_type><![CDATA[CNY]]></fee_type>
<is_subscribe><![CDATA[Y]]></is_subscribe>
<mch_id><![CDATA[xxxxx]]></mch_id>
<nonce_str><![CDATA[aydmDbuhMAYW7b9jhyvsJqBcQs2X1XRwXk]]></nonce_str>
<openid><![CDATA[oGTD2jo4F9ElXBmdbz66FAFxRrcE]]></openid>
<out_trade_no><![CDATA[OL201909170944228907100003]]></out_trade_no>
<result_code><![CDATA[SUCCESS]]></result_code>
<return_code><![CDATA[SUCCESS]]></return_code>
<sign><![CDATA[3764A45AFE8F110EC4359B3C268A7DBE]]></sign>
<time_end><![CDATA[20190917095333]]></time_end>
<total_fee>1</total_fee>
<trade_type><![CDATA[JSAPI]]></trade_type>
<transaction_id><![CDATA[4200000408201909173245179213]]></transaction_id>
</xml>
订单退款
订单退款首先需要商户号证书,这个证书是从商户平台下载下来的。
我这里封装查询订单和退款的工具类WxInterfaceUtil
import com.cn.hjsj.base.cache.SysConfig;
import com.cn.hjsj.util.http.HttpUtil;
import net.sf.json.JSONObject;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
/**
* 微信接口调用
* Created with IDEA
* author:Jy
* Date:2019/8/30
* Time:9:58
*/
public class WxInterfaceUtil {
/**
* 微信申请退款
*
* @param orderNo
* @param amount
* @return
*/
public static Map<String, Object> reWxRefund(String orderNo, int amount) {
Map<String, Object> map = new HashMap<String, Object>();
try {
Map<String, String> data = new HashMap<String, String>();
data.put("appid", SysConfig.getWx_appid());
data.put("mch_id", SysConfig.getWx_mch_id());
data.put("notify_url", SysConfig.getWx_notify_verify());
data.put("nonce_str", WXPayUtil.generateNonceStr());
data.put("out_trade_no", orderNo);
data.put("out_refund_no","RE" + orderNo);
data.put("sign_type", "MD5");
data.put("total_fee", String.valueOf(amount));
data.put("refund_fee", String.valueOf(amount));
data.put("refund_desc", "预订单审核不通过");
String xml = WXPayUtil.generateSignedXml(data, SysConfig.getWx_key());
String respn = WxInterfaceUtil.doRefund(SysConfig.getWx_mch_id(), SysConfig.getWx_refund(), xml);
System.out.println(respn);
if (respn == null) {
map.put("code", 31001);
map.put("msg", "系统繁忙,请稍后重试");
return map;
}
Map resp = JSONObject.fromObject(WXPayUtil.parseXml(respn).get("xml"));
if ("SUCCESS".equals(resp.get("return_code"))) {
if ("SUCCESS".equals(resp.get("result_code"))) {
map.put("code", 10000);
map.put("msg", "申请退款成功");
} else {
map.put("code", 31003);
map.put("msg", resp.get("err_code_des"));
return map;
}
} else {
map.put("code", 31003);
map.put("msg", "系统繁忙,请稍后重试");
return map;
}
} catch (Exception e) {
e.printStackTrace();
map.put("code", 30007);
map.put("msg", e.getMessage());
}
return map;
}
/**
* 微信查询订单
*
* @param orderNo
* @return
*/
public static Map<String, Object> orWxQuery(String orderNo) {
HashMap<String, Object> map = new HashMap<String, Object>();
try {
Map<String, String> data = new HashMap<String, String>();
data.put("appid", SysConfig.getWx_appid());
data.put("mch_id", SysConfig.getWx_mch_id());
data.put("nonce_str", WXPayUtil.generateNonceStr());
data.put("out_trade_no", orderNo);
data.put("sign_type", "MD5");
String xml = WXPayUtil.generateSignedXml(data, SysConfig.getWx_key());
String respn = HttpUtil.sendJsonPost(SysConfig.getWx_query(), xml).toString();
if (respn == null) {
map.put("code", 31001);
map.put("msg", "系统繁忙,请稍后重试");
return map;
}
Map resp = JSONObject.fromObject(WXPayUtil.parseXml(respn).get("xml"));
if ("SUCCESS".equals(resp.get("return_code"))) {
map.put("amount", resp.get("total_fee"));
map.put("code", 10000);
map.put("msg", "查询金额成功");
} else {
map.put("code", 31003);
map.put("msg", "系统繁忙,请稍后重试");
return map;
}
} catch (Exception e) {
e.printStackTrace();
map.put("code", 30007);
map.put("msg", e.getMessage());
}
return map;
}
/**
* 退款工具
*
* @param mchId
* @param url
* @param data
* @return
* @throws Exception
*/
public static String doRefund(String mchId, String url, String data) throws Exception {
/**
* 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
*/
KeyStore keyStore = KeyStore.getInstance("PKCS12");
//P12文件目录 证书路径,这里需要你自己修改,linux下还是windows下的根路径
FileInputStream instream = new FileInputStream(SysConfig.getWx_certPath());
try {
keyStore.load(instream, mchId.toCharArray());//这里写密码..默认是你的MCHID
} finally {
instream.close();
}
// Trust own CA and all self-signed certs
SSLContext sslcontext = SSLContexts.custom()
.loadKeyMaterial(keyStore, mchId.toCharArray())//这里也是写密码的
.build();
// Allow TLSv1 protocol only
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
try {
HttpPost httpost = new HttpPost(url); // 设置响应头信息
httpost.addHeader("Connection", "keep-alive");
httpost.addHeader("Accept", "*/*");
httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
httpost.addHeader("Host", "api.mch.weixin.qq.com");
httpost.addHeader("X-Requested-With", "XMLHttpRequest");
httpost.addHeader("Cache-Control", "max-age=0");
httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
httpost.setEntity(new StringEntity(data, "UTF-8"));
CloseableHttpResponse response = httpclient.execute(httpost);
try {
HttpEntity entity = response.getEntity();
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
EntityUtils.consume(entity);
return jsonStr;
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
}
发起微信退款的时候需要校验证书,发起成功之后我们会收到退款回调:参数如下
<xml>
<return_code>SUCCESS</return_code>
<appid><![CDATA[xxxxx]]></appid>
<mch_id><![CDATA[xxxxxx]]></mch_id>
<nonce_str><![CDATA[e748ab147wef242f34f342a2a5b91998]]></nonce_str>
<req_info><![CDATA[AES加密内容]]></req_info>
</xml>
这里我们需要使用使用工具类进行解密,解密
过程由于使用的AES解密,jdk中的jre自带的AES解密算法和正式的解密有一点偏差,会出现
AES加解密报错:Illegal key size or default parameters
推荐查看:https://blog.csdn.net/tomatocc/article/details/85096911
微信退款工具类Base64
/**
* Created with IDEA
* author:Jy
* Date:2019/9/16
* Time:10:13
*/
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
public class Base64 {
private static final char S_BASE64CHAR[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
private static final byte S_DECODETABLE[];
static {
S_DECODETABLE = new byte[128];
for (int i = 0; i < S_DECODETABLE.length; i++)
S_DECODETABLE[i] = 127;
for (int i = 0; i < S_BASE64CHAR.length; i++)
S_DECODETABLE[S_BASE64CHAR[i]] = (byte) i;
}
/**
* @param ibuf
* @param obuf
* @param wp
* @return
*/
private static int decode0(char ibuf[], byte obuf[], int wp) {
int outlen = 3;
if (ibuf[3] == '=')
outlen = 2;
if (ibuf[2] == '=')
outlen = 1;
int b0 = S_DECODETABLE[ibuf[0]];
int b1 = S_DECODETABLE[ibuf[1]];
int b2 = S_DECODETABLE[ibuf[2]];
int b3 = S_DECODETABLE[ibuf[3]];
switch (outlen) {
case 1: // '\001'
obuf[wp] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3);
return 1;
case 2: // '\002'
obuf[wp++] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3);
obuf[wp] = (byte) (b1 << 4 & 240 | b2 >> 2 & 15);
return 2;
case 3: // '\003'
obuf[wp++] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3);
obuf[wp++] = (byte) (b1 << 4 & 240 | b2 >> 2 & 15);
obuf[wp] = (byte) (b2 << 6 & 192 | b3 & 63);
return 3;
}
throw new RuntimeException("Internal error");
}
/**
* @param data
* @param off
* @param len
* @return
*/
public static byte[] decode(char data[], int off, int len) {
char ibuf[] = new char[4];
int ibufcount = 0;
byte obuf[] = new byte[(len / 4) * 3 + 3];
int obufcount = 0;
for (int i = off; i < off + len; i++) {
char ch = data[i];
if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))
continue;
ibuf[ibufcount++] = ch;
if (ibufcount == ibuf.length) {
ibufcount = 0;
obufcount += decode0(ibuf, obuf, obufcount);
}
}
if (obufcount == obuf.length) {
return obuf;
} else {
byte ret[] = new byte[obufcount];
System.arraycopy(obuf, 0, ret, 0, obufcount);
return ret;
}
}
/**
* @param data
* @return
*/
public static byte[] decode(String data) {
char ibuf[] = new char[4];
int ibufcount = 0;
byte obuf[] = new byte[(data.length() / 4) * 3 + 3];
int obufcount = 0;
for (int i = 0; i < data.length(); i++) {
char ch = data.charAt(i);
if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))
continue;
ibuf[ibufcount++] = ch;
if (ibufcount == ibuf.length) {
ibufcount = 0;
obufcount += decode0(ibuf, obuf, obufcount);
}
}
if (obufcount == obuf.length) {
return obuf;
} else {
byte ret[] = new byte[obufcount];
System.arraycopy(obuf, 0, ret, 0, obufcount);
return ret;
}
}
/**
* @param data
* @param off
* @param len
* @param ostream
* @throws IOException
*/
public static void decode(char data[], int off, int len, OutputStream ostream) throws IOException {
char ibuf[] = new char[4];
int ibufcount = 0;
byte obuf[] = new byte[3];
for (int i = off; i < off + len; i++) {
char ch = data[i];
if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))
continue;
ibuf[ibufcount++] = ch;
if (ibufcount == ibuf.length) {
ibufcount = 0;
int obufcount = decode0(ibuf, obuf, 0);
ostream.write(obuf, 0, obufcount);
}
}
}
/**
* @param data
* @param ostream
* @throws IOException
*/
public static void decode(String data, OutputStream ostream) throws IOException {
char ibuf[] = new char[4];
int ibufcount = 0;
byte obuf[] = new byte[3];
for (int i = 0; i < data.length(); i++) {
char ch = data.charAt(i);
if (ch != '=' && (ch >= S_DECODETABLE.length || S_DECODETABLE[ch] == 127))
continue;
ibuf[ibufcount++] = ch;
if (ibufcount == ibuf.length) {
ibufcount = 0;
int obufcount = decode0(ibuf, obuf, 0);
ostream.write(obuf, 0, obufcount);
}
}
}
/**
* @param data
* @return
*/
public static String encode(byte data[]) {
return encode(data, 0, data.length);
}
/**
* @param data
* @param off
* @param len
* @return
*/
public static String encode(byte data[], int off, int len) {
if (len <= 0)
return "";
char out[] = new char[(len / 3) * 4 + 4];
int rindex = off;
int windex = 0;
int rest;
for (rest = len - off; rest >= 3; rest -= 3) {
int i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255);
out[windex++] = S_BASE64CHAR[i >> 18];
out[windex++] = S_BASE64CHAR[i >> 12 & 63];
out[windex++] = S_BASE64CHAR[i >> 6 & 63];
out[windex++] = S_BASE64CHAR[i & 63];
rindex += 3;
}
if (rest == 1) {
int i = data[rindex] & 255;
out[windex++] = S_BASE64CHAR[i >> 2];
out[windex++] = S_BASE64CHAR[i << 4 & 63];
out[windex++] = '=';
out[windex++] = '=';
} else if (rest == 2) {
int i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255);
out[windex++] = S_BASE64CHAR[i >> 10];
out[windex++] = S_BASE64CHAR[i >> 4 & 63];
out[windex++] = S_BASE64CHAR[i << 2 & 63];
out[windex++] = '=';
}
return new String(out, 0, windex);
}
/**
* @param data
* @param off
* @param len
* @param ostream
* @throws IOException
*/
public static void encode(byte data[], int off, int len, OutputStream ostream) throws IOException {
if (len <= 0)
return;
byte out[] = new byte[4];
int rindex = off;
int rest;
for (rest = len - off; rest >= 3; rest -= 3) {
int i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255);
out[0] = (byte) S_BASE64CHAR[i >> 18];
out[1] = (byte) S_BASE64CHAR[i >> 12 & 63];
out[2] = (byte) S_BASE64CHAR[i >> 6 & 63];
out[3] = (byte) S_BASE64CHAR[i & 63];
ostream.write(out, 0, 4);
rindex += 3;
}
if (rest == 1) {
int i = data[rindex] & 255;
out[0] = (byte) S_BASE64CHAR[i >> 2];
out[1] = (byte) S_BASE64CHAR[i << 4 & 63];
out[2] = 61;
out[3] = 61;
ostream.write(out, 0, 4);
} else if (rest == 2) {
int i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255);
out[0] = (byte) S_BASE64CHAR[i >> 10];
out[1] = (byte) S_BASE64CHAR[i >> 4 & 63];
out[2] = (byte) S_BASE64CHAR[i << 2 & 63];
out[3] = 61;
ostream.write(out, 0, 4);
}
}
/**
* @param data
* @param off
* @param len
* @param writer
* @throws IOException
*/
public static void encode(byte data[], int off, int len, Writer writer) throws IOException {
if (len <= 0)
return;
char out[] = new char[4];
int rindex = off;
int rest = len - off;
int output = 0;
do {
if (rest < 3)
break;
int i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255);
out[0] = S_BASE64CHAR[i >> 18];
out[1] = S_BASE64CHAR[i >> 12 & 63];
out[2] = S_BASE64CHAR[i >> 6 & 63];
out[3] = S_BASE64CHAR[i & 63];
writer.write(out, 0, 4);
rindex += 3;
rest -= 3;
if ((output += 4) % 76 == 0)
writer.write("\n");
}
while (true);
if (rest == 1) {
int i = data[rindex] & 255;
out[0] = S_BASE64CHAR[i >> 2];
out[1] = S_BASE64CHAR[i << 4 & 63];
out[2] = '=';
out[3] = '=';
writer.write(out, 0, 4);
} else if (rest == 2) {
int i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255);
out[0] = S_BASE64CHAR[i >> 10];
out[1] = S_BASE64CHAR[i >> 4 & 63];
out[2] = S_BASE64CHAR[i << 2 & 63];
out[3] = '=';
writer.write(out, 0, 4);
}
}
}
MD5工具类
/**
* Created with IDEA
* author:Jy
* Date:2019/9/16
* Time:10:41
*/
import java.security.MessageDigest;
public class MD5Util {
public final static String MD5(String s) {
char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
try {
byte[] btInput = s.getBytes();
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
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];
}
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 final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
}
AES工具类
import com.cn.hjsj.base.cache.SysConfig;
import com.github.wxpay.sdk.WXPayUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.security.Security;
import java.util.Map;
/**
* Created with IDEA
* author:Jy
* Date:2019/9/16
* Time:10:42
*/
public class AESUtil {
/**
* 密钥算法
*/
private static final String ALGORITHM = "AES";
/**
* 加解密算法/工作模式/填充方式
*/
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
/**
* 生成key
*/
//微信支付API密钥设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
private static String paySign = SysConfig.getWx_key();
//对商户key做md5,得到32位小写key*
private static SecretKeySpec key = new SecretKeySpec(MD5Util.MD5Encode(paySign, "UTF-8").toLowerCase().getBytes(), ALGORITHM);
/**
* AES加密
*
* @param data
* @return
* @throws Exception
*/
public static String encryptData(String data) throws Exception {
Security.addProvider(new BouncyCastleProvider());
// 创建密码器
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
// 初始化
cipher.init(Cipher.ENCRYPT_MODE, key);
return Base64.encode(cipher.doFinal(data.getBytes()));
}
/**
* AES解密
* <p>
* (1)对加密串A做base64解码,得到加密串B
* (2)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)
*
* @param base64Data
* @return
* @throws Exception
*/
public static String decryptData(String base64Data) throws Exception {
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(Base64.decode(base64Data)));
}
}
到这里微信退款就基本完成了,当然微信api里面也有更方便的发起方式,代码也会更加简洁。当初使用的是微信的api发起方式老是出现签名不通过,随后没用了,自己封装才可以用。错误也知道是什么原因了。是我创建WxPay对象的时候把支付key弄错了。
转载:https://blog.csdn.net/weixin_42102798/article/details/100925448