微信退款接口开发
步驟:
發送退款請求到微信->同步告訴你請求成功還是失敗->異步回調告訴你退款成功還是失敗
說明:微信退款是需要證書的,先要下載證書.如何下載百度一下就可以,很簡單
//所需要的參數
appid:公眾賬號id,微信分配的公眾賬號id
mch_id:微信分配的商戶id
nonce_str:隨機字符創,長度不超過32位,隨機生成便可以
out_refund_no:商戶退款單號,請求方生成的流水號
out_trade_no:商戶訂單號,微信支付時,商戶自己生成的訂單號
refund_fee:退款金額
total_fee:訂單金額(支付的金額)
notify_url:回調地址,一定要能夠請求到的地址
wxKey:微信商戶平臺秘鑰(這個在商戶平臺上能找到)
?Map<String, String> map = new HashMap<String, String>(); //參數對象
??????? map.put("appid", appid);
??????? map.put("mch_id", mchId);
??????? map.put("nonce_str", StringUtils.getUUId());
??????? map.put("out_refund_no",outRefundId);
??????? map.put("out_trade_no", orderId);
??????? map.put("refund_fee",String.valueOf(refundFee));
??????? map.put("total_fee",String.valueOf(totalFee));
??????? map.put("notify_url", notify);
String sing=refundToSign(map,wxKey);
map.put("sign",sing);
//將map轉換為xml格式
?String param=mapToXml(map);
//發送請求
//發送請求的url地址:https://api.mch.weixin.qq.com/secapi/pay/refund
//下載下來的證書的路徑:classpath:cert/apiclient_cert.p12? //這里我是在resource下面建了一個cert文件夾
String result = refundHttpRequest(mchId,url, param, classpath);
//將返回來的xml格式數據轉換為map格式的數據
Map<String, String> map=xmlToMap(result);
//獲取返回的狀態碼
String returnCode = (String) map.get("return_code");
//判斷退款請求是否發送成功
if("SUCCESS".equals(returnCode)){
System.err.pringln("發送成功!"+returnCode);
//獲取業務結果狀態碼
?String resultCode = (String) map.get("result_code");
if("SUCCESS".equals(resultCode){
System.err.pringln(“微信退款請求發送成功==”+resultCode);
}
}
?
//接收異步返回信息,微信的異步返回信息是加密的,所以要解密,解密有很多的坑,要注意
??? @Path("http:reciver.com") //這里的路徑是你在發送請求的時候所寫的notify_url地址,一定要保證能夠請求
?? ?public boolean refundSuccess(Map<String, String> map) throws Exception {
?? ??? ?// 獲取加密信息
?? ??? ?String information = map.get("req_info").toString();
?? ???? System.err.println("加密信息==" + information);
//獲取返回的微信appId
?? ??? ?String appId = map.get("appid");
?? ???? System.err.println("返回來的appId==" + appId);
?? ??? ?//微信解密是要根據秘鑰解密的
?? ???? String wxKey = “”; //這里的秘鑰和appid一樣都是微信給的
?? ????
?? ??? ?Aes aes=new Aes();
?? ??? ?// 解密返回來的密文
?? ??? ?String aesInformation = aes.getRefundDecrypt(information, wxKey);
?? ??? ?// 將xml格式轉換為map
?? ??? ?Map<String, String> maps = xmlToMap(aesInformation);
?? ??? ?logger.info("解密出來的內容[map}===" + maps);
?? ??? ?try {
//判斷是否退款成功
?? ??? ??? ?if ("SUCCESS".equals((String) maps.get("refund_status"))) {
?? ??? ??? ??? ?logger.info("微信退款成功!!");
?? ??? ??? ?}
?? ??? ?} catch (Exception e) {
?? ??? ??? ?logger.info("出現異常[e==]" + e);
?? ??? ??? ?return false;
?? ??? ?}
?? ?}
?
?
?
?
?
?
//生成簽名
??? public String refundToSign(Map<String, String> map, String wxKey) {
??????? String prestr =getParamsOrderByKey(map); // 把數組所有元素,按照“參數=參數值”的模式用“&”字符拼接成字符串
??????? String key = "&key=" + wxKey; // 商戶支付密鑰
??????? // MD5運算生成簽名
??????? String mysign = PayUtil.sign(prestr, key, "utf-8").toUpperCase();
??????? return mysign;
??? }
?/**
???? * 按照key排序得到參數列表字符串
???? *
???? * @param paramValues 參數map對象
???? * @return 參數列表字符串
???? *
???? *???????? /** 除去map中的空值和簽名參數
???? * @param sArray 簽名參數組
???? * @return 去掉空值與簽名參數后的新簽名參數組
???? */
??? public static String getParamsOrderByKey(Map<String, String> paramValues) {
??????? String params = "";
??????? Set<String> key = paramValues.keySet();
??????? String beginLetter = "";
??????? List<String> paramNames = new ArrayList<String>(paramValues.size());
??????? paramNames.addAll(paramValues.keySet());
??????? Collections.sort(paramNames);
??????? for (String paramName : paramNames) {
??????????? Object b = paramValues.get(paramName);
??????????? if (b == null || b.equals("") || b.toString().equalsIgnoreCase("sign") || b.toString().equalsIgnoreCase("sign_type")) {
??????????????? continue;
??????????? }
??????????? if (params.equals("")) {
??????????????? params += beginLetter + paramName + "=" + paramValues.get(paramName);
??????????? } else {
??????????????? params += "&" + paramName + "=" + paramValues.get(paramName);
??????????? }
??????? }
??????? return params;
??? }
?
?
//生成out_refund_no
public String getRandomUUID() {
?? ??? ?java.util.Date dateNow = new java.util.Date();
?? ??? ?SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
?? ??? ?String dateNowStr = dateFormat.format(dateNow);
?? ??? ?StringBuffer sb = new StringBuffer(dateNowStr);
?? ??? ?Random rd = new Random();
?? ??? ?String n = "";
?? ??? ?int rdGet;
?? ??? ?do {
?? ??? ??? ?rdGet = Math.abs(rd.nextInt()) % 10 + 48;
?? ??? ??? ?char num1 = (char) rdGet;
?? ??? ??? ?String dd = Character.toString(num1);
?? ??? ??? ?n += dd;
?? ??? ?} while (n.length() < 6);
?? ??? ?sb.append(n);
?? ??? ?return sb.toString();
?? ?}
//生成nonce_str
??? public static String getUUId(){
?? ??? ?return UUID.randomUUID().toString().replaceAll("-", "");
?? ?}
? /**
???? * 將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;
??? }
?
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();
??? }
}
? /**
???? * 向微信退款發送請求
???? * @param mchid 商戶號
???? * @param url 微信退款請求路徑
???? * @param parm 請求的參數
???? * @return
???? */
?? ?
??? public static String refundHttpRequest(String mchid,String url,String parm,String filepath) {
?? ??? ?/**
???????? * 注意PKCS12證書 是從微信商戶平臺-》賬戶設置-》 API安全 中下載的
???????? */
??????? KeyStore keyStore;
?? ??? ?try {
?? ??? ??? ?keyStore = KeyStore.getInstance("PKCS12");
?? ??? ??? ?File file = (ResourceUtils.getFile(filepath));//證書路徑
?? ??? ??? ?FileInputStream instream = new FileInputStream(file);
?? ??? ??? ?keyStore.load(instream,mchid.toCharArray());//這里寫密碼..默認是你的MCHID
?? ??? ??????? SSLContext sslcontext = SSLContexts.custom()
?? ??? ???????????????? .loadKeyMaterial(keyStore, mchid.toCharArray())//這里密碼
?? ??? ???????????????? .build();
?? ??? ??????? SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,new String[] { "TLSv1" },null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
?? ??? ???????? CloseableHttpClient httpclient = HttpClients.custom()
?? ??? ???????????????? .setSSLSocketFactory(sslsf)
?? ??? ???????????????? .build();
?? ??? ???????? try {
?? ??? ???????????? HttpPost httpost = new HttpPost(url);
?? ??? ???????????? httpost.setEntity(new StringEntity(parm, "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();
?? ??? ???????? }
?? ??? ??????? ?
?? ??? ?} catch (KeyStoreException e) {
?? ??? ??? ?e.printStackTrace();
?? ??? ?} catch (NoSuchAlgorithmException e) {
?? ??? ??? ?e.printStackTrace();
?? ??? ?} catch (CertificateException e) {
?? ??? ??? ?e.printStackTrace();
?? ??? ?} catch (IOException e) {
?? ??? ??? ?e.printStackTrace();
?? ??? ?} catch (KeyManagementException e) {
?? ??? ??? ?e.printStackTrace();
?? ??? ?} catch (UnrecoverableKeyException e) {
?? ??? ??? ?e.printStackTrace();
?? ??? ?}
???? return null;
??? }
?? ?
?/**
???? * XML格式字符串轉換為Map
???? *
???? * @param strXML XML字符串
???? * @return XML數據轉換后的Map
???? * @throws Exception
???? */
??? public static Map<String, String> xmlToMap(String strXML) throws Exception {
??????? try {
??????????? Map<String, String> data = new HashMap<String, String>();
??????????? DocumentBuilder documentBuilder = WXPayXmlUtil.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) {
??????????????? // do nothing
??????????? }
??????????? return data;
??????? } catch (Exception ex) {
??????????? throw ex;
??????? }
??? }
?
//微信返回的加密信息進行解密
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Security;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;
public class Aes {
?? ?static Logger logger = LoggerFactory.getLogger(Aes.class);
?? ?private static MessageDigest sMd5MessageDigest;
?? ?private static StringBuilder sStringBuilder;
?? ?private static void removeCryptographyRestrictions() {
?? ???? if (!isRestrictedCryptography()) {
?? ???????? logger.info("Cryptography restrictions removal not needed");
?? ???????? return;
?? ???? }
?? ???? try {
?? ???????? final Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
?? ???????? final Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
?? ???????? final Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");
?? ???????? final Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
?? ???????? isRestrictedField.setAccessible(true);
?? ???????? final Field modifiersField = Field.class.getDeclaredField("modifiers");
?? ???????? modifiersField.setAccessible(true);
?? ???????? modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL);
?? ???????? isRestrictedField.set(null, false);
?? ???????? final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
?? ???????? defaultPolicyField.setAccessible(true);
?? ???????? final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);
?? ???????? final Field perms = cryptoPermissions.getDeclaredField("perms");
?? ???????? perms.setAccessible(true);
?? ???????? ((Map<?, ?>) perms.get(defaultPolicy)).clear();
?? ???????? final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
?? ???????? instance.setAccessible(true);
?? ???????? defaultPolicy.add((Permission) instance.get(null));
?? ???????? logger.info("Successfully removed cryptography restrictions");
?? ???? } catch (final Exception e) {
?? ???????? logger.error("Failed to remove cryptography restrictions", e);
?? ???? }
?? ?}
?? ?private static boolean isRestrictedCryptography() {
?? ???? // This matches Oracle Java 7 and 8, but not Java 9 or OpenJDK.
?? ???? final String name = System.getProperty("java.runtime.name");
?? ???? final String ver = System.getProperty("java.version");
?? ???? return name != null && name.equals("Java(TM) SE Runtime Environment")
?? ???????????? && ver != null && (ver.startsWith("1.7") || ver.startsWith("1.8"));
?? ?}
?? ?
?? ?//破解jdk密文長度限制,因為在使用ase解密的時候,會有長度限制
?? ?public? void Crack(){
?? ??? ?try {
?? ??? ??? ?Class<?> clazz = Class.forName("javax.crypto.JceSecurity");
?? ??? ??? ?Field nameField = clazz.getDeclaredField("isRestricted");
?? ??? ??? ?Field modifiersField = Field.class.getDeclaredField("modifiers");
?? ??? ??? ?modifiersField.setAccessible(true);
?? ??? ??? ?modifiersField.setInt(nameField, nameField.getModifiers() & ~Modifier.FINAL);
?? ??? ??? ?nameField.setAccessible(true);
?? ??? ??? ?nameField.set(null, java.lang.Boolean.FALSE);
?? ??? ?} catch (Exception ex) {
?? ??? ??? ?logger.info("破解密文長度出現異常=="+ex);
?? ??? ?}
?? ?}
?? ?public? String getRefundDecrypt(String reqInfoSecret, String key) {
?? ??? ?removeCryptographyRestrictions();
?? ??? ?//解密之前先調用破解方法
?? ??? ?Crack();
?? ??? ?String result = "";
?? ??? ?try {
?? ??? ??? ?Security.addProvider(new BouncyCastleProvider());
?? ??? ??? ?BASE64Decoder decoder = new sun.misc.BASE64Decoder();
?? ??? ??? ?byte[] bt = decoder.decodeBuffer(reqInfoSecret);
?? ??? ??? ?String md5key = md5(key).toLowerCase();
?? ??? ??? ?SecretKey secretKey = new SecretKeySpec(md5key.getBytes(), "AES");
?? ??? ??? ?Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC");
?? ??? ??? ?cipher.init(Cipher.DECRYPT_MODE, secretKey);
?? ??? ??? ?byte[] resultbt = cipher.doFinal(bt);
?? ??? ??? ?result = new String(resultbt);
?? ??? ?} catch (Exception e) {
?? ??? ??? ?logger.info("轉換過程中出現的異常==" + e);
?? ??? ?}
?? ??? ?return result;
?? ?}
?? ?public static String md5(String s) {
?? ??? ?md5();
?? ??? ?sMd5MessageDigest.reset();
?? ??? ?sMd5MessageDigest.update(s.getBytes());
?? ??? ?byte[] digest = sMd5MessageDigest.digest();
?? ??? ?sStringBuilder.setLength(0);
?? ??? ?for (int i = 0; i < digest.length; ++i) {
?? ??? ??? ?int b = digest[i] & 255;
?? ??? ??? ?if (b < 16) {
?? ??? ??? ??? ?sStringBuilder.append('0');
?? ??? ??? ?}
?? ??? ??? ?sStringBuilder.append(Integer.toHexString(b));
?? ??? ?}
?? ??? ?return sStringBuilder.toString().toUpperCase();
?? ?}
?? ?public static void md5(){
?? ??? ?try {
?? ??? ??? ?sMd5MessageDigest = MessageDigest.getInstance("MD5");
?? ??? ?} catch (NoSuchAlgorithmException ex) {
?? ??? ??? ?logger.info("md5出現異常==="+ex);?? ?
?? ??? ?}
?? ??? ?sStringBuilder = new StringBuilder();
?? ?}
/*
?? ?public static void main(String[] args) {
?? ??? ?String a ="";
?? ??? ?String key = "";
?? ??? ?Aes as=new Aes();
?? ??? ?System.out.println(a);
?? ??? ?System.out.println(key);
?? ??? ?String B =as. getRefundDecrypt(a, key);
?? ??? ?System.out.println(B);
?? ??? ?try {
?? ??? ??? ?Map<String, String> map = WXPayUtil.xmlToMap(B);
?? ??? ??? ?System.err.println(map);
?? ??? ?} catch (Exception e) {
?? ??? ??? ?e.printStackTrace();
?? ??? ?}
?? ?}*/
}
說明:
微信的加密信息要用到ase解密,而jdk對密文的長度是有限制的,JAVA運行環境默認不允許256位密鑰的AES加解密,會報Illegal key size or default parameters.。在jdk1.8.5后的版本它會自帶兩個文件夾,一個是有限制的,一個是沒有限制的,只要切換為沒有限制的就可以,但是之前的版本,是沒有的,所以只能將有限制的文件給替換掉
在官方網站下載JCE無限制權限策略文件:
JDK7版本JCE下載地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
JDK8版本JCE下載地址:http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
注意:你的jdk版本是幾就下載對應的版本
下載解壓后就是替換${java_home}/jre/lib/security/ 下面的local_policy.jar和US_export_policy.jar。
除此之外還有一種辦法就是將其規則給破解掉,但是這種方法是不推薦使用的,我因為特殊情況就是用了這種辦法,在上面的代碼中已經有說明
https://blog.csdn.net/z199172177/article/details/78954503 ;這是破解方法的出處
https://blog.csdn.net/tangtao_xp/article/details/84944049;這里也有說明,可以查看
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_4&index=6 //這個是微信開發地址
微信退款證書說明:
證書放在項目中,在服務器上是堵不到的,你在本地測試的時候是可以的,所以要在pom.xml中配置一下,在<plugins></plugins>中添加
??? ???? <plugins>
?? ??? ??? ?<plugin>
?? ??? ??? ??? ?<groupId>org.apache.maven.plugins</groupId>??
?? ??? ??? ??? ?<artifactId>maven-resources-plugin</artifactId>? //這里要注意不要和maven-compiler-plugin搞成一樣
?? ??? ??? ??? ?<configuration>
?? ??? ??? ??? ??? ?<encoding>UTF-8</encoding>
?? ??? ??? ??? ??? ?<!-- 過濾后綴為pem、pfx的證書文件 -->
?? ??? ??? ??? ??? ?<nonFilteredFileExtensions>
?? ??? ??? ??? ??? ??? ?<nonFilteredFileExtension>pem</nonFilteredFileExtension>
?? ??? ??? ??? ??? ??? ?<nonFilteredFileExtension>pfx</nonFilteredFileExtension>
?? ??? ??? ??? ??? ??? ?<nonFilteredFileExtension>p12</nonFilteredFileExtension>
?? ??? ??? ??? ??? ?</nonFilteredFileExtensions>
?? ??? ??? ??? ?</configuration>
?? ??? ??? ?</plugin>
<plugin>
?? ??? ??? ??? ?<groupId>org.apache.maven.plugins</groupId>
?? ??? ??? ??? ?<artifactId>maven-compiler-plugin</artifactId>
?? ??? ??? ??? ?<version>3.3</version>
?? ??? ??? ??? ?<configuration>
?? ??? ??? ??? ??? ?<source>1.7</source>
?? ??? ??? ??? ??? ?<target>1.7</target>
?? ??? ??? ??? ??? ?<encoding>utf-8</encoding>
?? ??? ??? ??? ?</configuration>
?? ??? ??? ?</plugin>
?? ??? ?</plugins>
?
?
?
?
?
?
?
?
?
?
?
???
轉載于:https://www.cnblogs.com/lxsxsy/p/10783672.html
總結
- 上一篇: 掌握这些PPT技巧,让你的工作效率提高1
- 下一篇: 为梦想而战,高考励志主题教育班会PPT