Java - 微信支付

朴灿烈づ我的快乐病毒、 2023-02-18 14:07 61阅读 0赞

首先贴出官方文档,关于介绍,场景,参数说明,可以直接看文档:https://pay.weixin.qq.com/wiki/doc/api/index.html

一. APP支付

官方文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1

推荐maven依赖:

  1. <!--微信支付sdk -->
  2. <dependency>
  3. <groupId>com.github.wxpay</groupId>
  4. <artifactId>wxpay-sdk</artifactId>
  5. <version>3.0.9</version>
  6. </dependency>

1. 【统一下单】

商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识后再在APP里面调起支付。

微信统一工具类

  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.io.InputStream;
  4. import java.lang.reflect.Field;
  5. import java.security.KeyStore;
  6. import java.security.SecureRandom;
  7. import java.util.*;
  8. import com.alibaba.fastjson.JSON;
  9. import com.alibaba.fastjson.JSONObject;
  10. import com.github.wxpay.sdk.WXPayConstants;
  11. import com.github.wxpay.sdk.WXPayUtil;
  12. import com.google.common.collect.Maps;
  13. import org.apache.commons.lang.StringUtils;
  14. import org.apache.http.HttpResponse;
  15. import org.apache.http.client.HttpClient;
  16. import org.apache.http.client.config.RequestConfig;
  17. import org.apache.http.client.methods.CloseableHttpResponse;
  18. import org.apache.http.client.methods.HttpPost;
  19. import org.apache.http.conn.ssl.DefaultHostnameVerifier;
  20. import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
  21. import org.apache.http.entity.StringEntity;
  22. import org.apache.http.impl.client.CloseableHttpClient;
  23. import org.apache.http.impl.client.HttpClientBuilder;
  24. import org.apache.http.impl.client.HttpClients;
  25. import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
  26. import org.apache.http.util.EntityUtils;
  27. import org.bouncycastle.jce.provider.BouncyCastleProvider;
  28. import org.springframework.http.HttpEntity;
  29. import org.springframework.http.HttpHeaders;
  30. import org.springframework.http.MediaType;
  31. import org.springframework.web.client.RestTemplate;
  32. import javax.net.ssl.KeyManagerFactory;
  33. import javax.net.ssl.SSLContext;
  34. import javax.crypto.Cipher;
  35. import javax.crypto.NoSuchPaddingException;
  36. import javax.crypto.spec.SecretKeySpec;
  37. import java.security.InvalidKeyException;
  38. import java.security.MessageDigest;
  39. import java.security.NoSuchAlgorithmException;
  40. import java.security.Security;
  41. public class WXUtil {
  42. public static final String APP_ID = "";
  43. public static final String APPLET_APP_ID = "";
  44. public static final String MCH_ID = ""; // 微信商户号
  45. public static final String KEY = ""; // 微信支付密钥
  46. public static final String NOTIFY_URL = ""; // 微信支付成功回调url
  47. public static final String REFUND_NOTIFY_URL = ""; // 微信退款成功回调
  48. public static final String SIGN_PACKAGE = "Sign=WXPay";
  49. private static Cipher cipher = null; //解码器
  50. public static String createSign(Map parameters) throws Exception {
  51. List<String> keys = new ArrayList<String>(parameters.keySet());
  52. Collections.sort(keys);
  53. String prestr = "";
  54. for (int i = 0; i < keys.size(); i++) {
  55. String key = keys.get(i);
  56. Object value = parameters.get(key);
  57. // value = URLEncoder.encode(value, "UTF-8");
  58. if(StringUtils.isNotBlank(parameters.get(key)+"") && value != null) {
  59. if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符
  60. prestr = prestr + key + "=" + value;
  61. } else {
  62. prestr = prestr + key + "=" + value + "&";
  63. }
  64. }
  65. }
  66. prestr += "&key="+KEY;
  67. return prestr;
  68. }
  69. public static Map<String, Object> objectToMap(Object obj) throws Exception {
  70. if(obj == null){
  71. return null;
  72. }
  73. Map<String, Object> map = new HashMap<String, Object>();
  74. Field[] declaredFields = obj.getClass().getDeclaredFields();
  75. for (Field field : declaredFields) {
  76. field.setAccessible(true);
  77. map.put(field.getName(), field.get(obj));
  78. }
  79. return map;
  80. }
  81. public static Map applayRefund(String userId, String payId, String orderNo, Double amount, Double refundMoney) throws Exception {
  82. Map map = new HashMap();
  83. map.put("appid", APP_ID);
  84. map.put("mch_id", MCH_ID);
  85. map.put("nonce_str", UUID_MD5.getUUID());
  86. map.put("transaction_id", payId);
  87. map.put("out_refund_no", orderNo);
  88. int total_fee = (int) (amount * 100);
  89. map.put("total_fee", total_fee+"");
  90. int refund_fee = (int) (refundMoney * 100);
  91. map.put("refund_fee", refund_fee+"");
  92. map.put("notify_url", REFUND_NOTIFY_URL);
  93. String stringSign = WXUtil.createSign(map);
  94. String sign = WXPayUtil.MD5(stringSign).toUpperCase();
  95. map.put("sign", sign);
  96. String data = WXPayUtil.mapToXml(map);
  97. File file = new File("apiclient_cert.p12");
  98. InputStream in = new FileInputStream(file);
  99. char password[] = WXUtil.MCH_ID.toCharArray();
  100. java.io.InputStream certStream = in;
  101. KeyStore ks = KeyStore.getInstance("PKCS12");
  102. ks.load(certStream, password);
  103. KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
  104. kmf.init(ks, password);
  105. SSLContext sslContext = SSLContext.getInstance("TLS");
  106. sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
  107. SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
  108. new String[] { "TLSv1" }, null, new DefaultHostnameVerifier());
  109. BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager();
  110. CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
  111. String url = (new StringBuilder()).append("https://").append("api.mch.weixin.qq.com")
  112. .append("/secapi/pay/refund").toString();
  113. HttpPost httpPost = new HttpPost(url);
  114. RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();
  115. httpPost.setConfig(requestConfig);
  116. StringEntity postEntity = new StringEntity(data, "UTF-8");
  117. httpPost.addHeader("Content-Type", "text/xml");
  118. httpPost.addHeader("User-Agent", (new StringBuilder()).append(WXPayConstants.USER_AGENT).append(" ")
  119. .append(map.get("mch_id")).toString());
  120. httpPost.setEntity(postEntity);
  121. CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
  122. org.apache.http.HttpEntity httpEntity = httpResponse.getEntity();
  123. String reponseString = EntityUtils.toString(httpEntity, "UTF-8");
  124. return WXPayUtil.xmlToMap(reponseString);
  125. }
  126. /**
  127. * 提现
  128. * @param openid 微信openid
  129. * @param realName 真实姓名
  130. * @param money 提现金额
  131. * @return
  132. * @throws Exception
  133. */
  134. public static Map wxWithdraw(String openid,String realName, Double money) throws Exception{
  135. Map map = new HashMap();
  136. map.put("mch_appid", APP_ID);
  137. map.put("mchid", MCH_ID);
  138. map.put("nonce_str", UUID_MD5.getUUID());
  139. map.put("partner_trade_no", UUID_MD5.getUUID());
  140. map.put("openid", openid);
  141. map.put("check_name", "FORCE_CHECK"); //强制实名,如需不校真实姓名,则改为NO_CHECK
  142. map.put("re_user_name", realName);
  143. int total_fee = (int) (money * 100);
  144. map.put("amount", total_fee+"");
  145. map.put("desc", "账户提现");
  146. map.put("spbill_create_ip", "xxx.xxx.xxx.xxx");
  147. String stringSign = WXUtil.createSign(map);
  148. String sign = WXPayUtil.MD5(stringSign).toUpperCase();
  149. map.put("sign", sign);
  150. String data = WXPayUtil.mapToXml(map);
  151. File file = new File("apiclient_cert.p12");
  152. InputStream in = new FileInputStream(file);
  153. char password[] = WXUtil.MCH_ID.toCharArray();
  154. java.io.InputStream certStream = in;
  155. KeyStore ks = KeyStore.getInstance("PKCS12");
  156. ks.load(certStream, password);
  157. KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
  158. kmf.init(ks, password);
  159. SSLContext sslContext = SSLContext.getInstance("TLS");
  160. sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
  161. SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,
  162. new String[] { "TLSv1" }, null, new DefaultHostnameVerifier());
  163. BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager();
  164. CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
  165. String url = (new StringBuilder()).append("https://").append("api.mch.weixin.qq.com")
  166. .append("/mmpaymkttransfers/promotion/transfers").toString();
  167. HttpPost httpPost = new HttpPost(url);
  168. RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();
  169. httpPost.setConfig(requestConfig);
  170. StringEntity postEntity = new StringEntity(data, "UTF-8");
  171. httpPost.addHeader("Content-Type", "text/xml");
  172. httpPost.addHeader("User-Agent", (new StringBuilder()).append(WXPayConstants.USER_AGENT).append(" ")
  173. .append(map.get("mch_id")).toString());
  174. httpPost.setEntity(postEntity);
  175. CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
  176. org.apache.http.HttpEntity httpEntity = httpResponse.getEntity();
  177. String reponseString = EntityUtils.toString(httpEntity, "UTF-8");
  178. return WXPayUtil.xmlToMap(reponseString);
  179. }
  180. /**
  181. * 微信返回参数解密
  182. * @param reqInfo
  183. * @return
  184. * @throws Exception
  185. */
  186. public static Map parseReqInfo(String reqInfo) throws Exception {
  187. init();
  188. Base64.Decoder decoder = Base64.getDecoder();
  189. byte[] base64ByteArr = decoder.decode(reqInfo);
  190. String result = new String(cipher.doFinal(base64ByteArr));
  191. return WXPayUtil.xmlToMap(result);
  192. }
  193. public static void init() {
  194. String key = getMD5(KEY);
  195. SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
  196. Security.addProvider(new BouncyCastleProvider());
  197. try {
  198. cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
  199. cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
  200. } catch (NoSuchAlgorithmException e) {
  201. e.printStackTrace();
  202. } catch (NoSuchPaddingException e) {
  203. e.printStackTrace();
  204. } catch (InvalidKeyException e) {
  205. e.printStackTrace();
  206. }
  207. }
  208. public static String getMD5(String str) {
  209. try {
  210. MessageDigest md = MessageDigest.getInstance("MD5");
  211. String result = MD5(str, md);
  212. return result;
  213. } catch (NoSuchAlgorithmException e) {
  214. e.printStackTrace();
  215. return "";
  216. }
  217. }
  218. public static String MD5(String strSrc, MessageDigest md) {
  219. byte[] bt = strSrc.getBytes();
  220. md.update(bt);
  221. String strDes = bytes2Hex(md.digest());
  222. return strDes;
  223. }
  224. public static String bytes2Hex(byte[] bts) {
  225. StringBuffer des = new StringBuffer();
  226. String tmp = null;
  227. for (int i = 0; i < bts.length; i++) {
  228. tmp = (Integer.toHexString(bts[i] & 0xFF));
  229. if (tmp.length() == 1) {
  230. des.append("0");
  231. }
  232. des.append(tmp);
  233. }
  234. return des.toString();
  235. }
  236. public static String httpClient(String data) throws Exception{
  237. BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager();
  238. HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connManager).build();
  239. String url = (new StringBuilder()).append("https://").append("api.mch.weixin.qq.com").append("/pay/unifiedorder").toString();
  240. HttpPost httpPost = new HttpPost(url);
  241. RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000).build();
  242. httpPost.setConfig(requestConfig);
  243. StringEntity postEntity = new StringEntity(data, "UTF-8");
  244. httpPost.addHeader("Content-Type", "text/xml");
  245. httpPost.addHeader("User-Agent", (new StringBuilder()).append(WXPayConstants.USER_AGENT).append(" ").append(WXUtil.MCH_ID).toString());
  246. httpPost.setEntity(postEntity);
  247. HttpResponse httpResponse = httpClient.execute(httpPost);
  248. org.apache.http.HttpEntity httpEntity = httpResponse.getEntity();
  249. String reponseString = EntityUtils.toString(httpEntity, "UTF-8");
  250. return reponseString;
  251. }
  252. }

统一下单 - 微信预支付

  1. @NoArgsConstructor
  2. @Data
  3. @Accessors(chain = true)
  4. public class WeixinPayReqDto {
  5. @ApiModelProperty(value="请求参数")
  6. private WeixinPayReqData data;
  7. @NoArgsConstructor
  8. @Data
  9. @Accessors(chain=true)
  10. public class WeixinPayReqData {
  11. @ApiModelProperty(value="总金额")
  12. private double totalMoney;
  13. @ApiModelProperty(value="订单号")
  14. private String out_trade_no;
  15. @ApiModelProperty(value="openid")
  16. private String openid;
  17. }
  18. }
  19. @PostMapping("weixinPay")
  20. public BaseDataResDto<Map<String, Object>> weixinPay(@RequestBody WeixinPayReqDto param) throws Exception {
  21. return capitalMainLogService.weixinPay(param, maijiToken);
  22. }
  23. @Override
  24. public BaseDataResDto<Map<String, Object>> weixinPay(WeixinPayReqDto param) throws Exception {
  25. // 根据订单号查看订单是否存在
  26. ShopingOrder shopingOrder = new ShopingOrder();
  27. shopingOrder.setOrderNo(param.getData().getOut_trade_no());
  28. ShopingOrder newShopingOrder = shopingOrderMapper.selectOne(shopingOrder);
  29. if (newShopingOrder == null) return new BaseDataResDto<>(Status.PARAMETERERROR, "订单编号不存在");
  30. if (newShopingOrder.getStatus() != 0) {
  31. return new BaseDataResDto<>(Status.PARAMETERERROR, "该订单已支付");
  32. }
  33. // 校验支付金额和应付金额是否一致
  34. if (param.getData().getTotalMoney() != newShopingOrder.getAmount()) {
  35. return new BaseDataResDto<>(Status.PARAMETERERROR, "支付金额错误");
  36. }
  37. String out_trade_no = shopingOrder.getOrderNo() + "_" + (int) (Math.random() * 1000000);
  38. Map map = new HashMap();
  39. map.put("appid", WXUtil.APP_ID);
  40. map.put("mch_id", WXUtil.MCH_ID);
  41. map.put("nonce_str", UUID_MD5.getUUID());
  42. map.put("sign_type", "MD5");
  43. map.put("body", "商品支付");
  44. map.put("out_trade_no", out_trade_no);
  45. map.put("fee_type", "CNY");
  46. map.put("total_fee", (int) (param.getData().getTotalMoney() * 100) + "");
  47. map.put("spbill_create_ip", "192.168.100.8");
  48. map.put("notify_url", WXUtil.NOTIFY_URL);
  49. map.put("trade_type", "APP");
  50. map.put("sign", WXPayUtil.MD5(WXUtil.createSign(map)).toUpperCase());
  51. // 请求报文 (map转为xml)
  52. String data = WXPayUtil.mapToXml(map);
  53. Map mapResponse = WXPayUtil.xmlToMap(WXUtil.httpClient(data));
  54. BaseDataResDto paramMap = new BaseDataResDto(Status.SUCCESS);
  55. Map<String, String> mapWeixinPay = new HashMap<String, String>();
  56. if (mapResponse.get("return_code") != null && mapResponse.get("result_code") != null) {
  57. mapWeixinPay.put("appid", WXUtil.APP_ID);
  58. mapWeixinPay.put("partnerid", WXUtil.MCH_ID);
  59. if (mapResponse.get("prepay_id") == null) return new BaseDataResDto<>(Status.PARAMETERERROR);
  60. long time = System.currentTimeMillis() / 1000;
  61. mapWeixinPay.put("noncestr", Long.toString(time));
  62. mapWeixinPay.put("timestamp", Long.toString(time));
  63. mapWeixinPay.put("prepayid", (String) mapResponse.get("prepay_id"));
  64. mapWeixinPay.put("package", "Sign=WXPay");
  65. String signString = WXUtil.createSign(mapWeixinPay);
  66. String signs = WXPayUtil.MD5(signString).toUpperCase();
  67. mapWeixinPay.put("sign", signs);
  68. paramMap.setData(mapWeixinPay);
  69. newShopingOrder.setPrepayId((String) mapResponse.get("prepay_id"));
  70. newShopingOrder.setNonceStr(map.get("nonce_str") + "");
  71. } else {
  72. paramMap.setStatus(Status.PARAMETERERROR);
  73. }
  74. // 保存预支付订单号
  75. shopingOrderMapper.updateById(newShopingOrder.setOutTradeNo(out_trade_no));
  76. return paramMap;
  77. }

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k_size_16_color_FFFFFF_t_70 watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k_size_16_color_FFFFFF_t_70 1

成功返回给安卓/IOS,由他们调起微信收银台发起付款即可。

2. 【微信支付成功回调】 (支付结果通知)

支付完成后,微信会把相关支付结果及用户信息通过数据流的形式发送给商户,商户需要接收处理,并按文档规范返回应答。

该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。

通知url必须为直接可访问的url,不能携带参数。

注意:

① 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。

② 后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信会判定本次通知失败,重新发送通知,直到成功为止(在通知一直不成功的情况下,微信总共会发起多次通知,通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m),但微信不保证通知最终一定能成功。

③ 在订单状态不明或者没有收到微信支付结果通知的情况下,建议商户主动调用微信支付【查询订单API】确认订单状态。

  1. @XmlRootElement(name = "xml")
  2. @XmlAccessorType(XmlAccessType.FIELD)
  3. @NoArgsConstructor
  4. @Data
  5. @Accessors(chain=true)
  6. public class PayResDto {
  7. private String return_msg;
  8. private String return_code;
  9. private String appid;
  10. private String mch_id;
  11. private String device_info;
  12. private String nonce_str;
  13. private String sign;
  14. private String result_code;
  15. private String err_code;
  16. private String err_code_des;
  17. private String openid;
  18. private String is_subscribe;
  19. private String trade_type;
  20. private String bank_type;
  21. private Integer total_fee;
  22. private String fee_type;
  23. private Integer cash_fee;
  24. private String cash_fee_type;
  25. private Integer coupon_fee;
  26. private Integer coupon_count;
  27. private String coupon_id_$n;
  28. private Integer coupon_fee_$n;
  29. private String transaction_id;
  30. private String out_trade_no;
  31. private String attach;
  32. private String time_end;
  33. }
  34. import com.github.wxpay.sdk.WXPayUtil;
  35. @PostMapping("weixinPayCallBack")
  36. public String weixinPayCallBack(@RequestBody PayResDto param) throws Exception {
  37. Map map = capitalMainLogService.weixinPayCallBack(param);
  38. return WXPayUtil.mapToXml(map);
  39. }
  40. @Override
  41. public Map weixinPayCallBack(PayResDto param) {
  42. Map<String, Object> map = new HashMap<String, Object>();
  43. map.put("return_code", "SUCCESS");
  44. if (param == null) {
  45. map.put("return_msg", "参数为空");
  46. return map;
  47. }
  48. // 根据订单号查看订单信息
  49. ShopingOrder shopingOrder = shopingOrderService.selectOne(new EntityWrapper<ShopingOrder>().eq("order_no", param.getOut_trade_no().substring(0, 20)));
  50. // 已经付款
  51. if (Arrays.asList(1, 2, 3, 5, 6).contains(shopingOrder.getStatus())) {
  52. map.put("return_msg", "OK");
  53. return map;
  54. }
  55. shopingOrder.setStatus(1).setPayId(param.getTransaction_id()).setPayDate(newDate()).setOutTradeNo(param.getOut_trade_no()).setPayType(2);
  56. // 修改支付状态为成功
  57. shopingOrderMapper.updateById(shopingOrder);
  58. // 分销
  59. distributionService.asyncDistributionFund(shopingOrder);
  60. map.put("return_msg", "OK");
  61. return map;
  62. }

3. 【查询订单】

接口提供所有微信支付订单的查询,商户可以通过该接口主动查询订单状态,完成下一步的业务逻辑。

需要调用查询接口的情况:

◆ 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;

◆ 调用支付接口后,返回系统错误或未知交易状态情况;

◆ 调用被扫支付API,返回USERPAYING的状态;

◆ 调用关单或撤销接口API之前,需确认支付状态;

  1. @Bean
  2. public RestTemplate restTemplate() {
  3. RestTemplate restTemplate = new RestTemplate();
  4. Charset thirdRequest = Charset.forName("UTF-8");
  5. // 处理请求中文乱码问题
  6. List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
  7. for (HttpMessageConverter<?> messageConverter : messageConverters) {
  8. if (messageConverter instanceof StringHttpMessageConverter) {
  9. ((StringHttpMessageConverter) messageConverter).setDefaultCharset(thirdRequest);
  10. }
  11. if (messageConverter instanceof MappingJackson2HttpMessageConverter) {
  12. ((MappingJackson2HttpMessageConverter) messageConverter).setDefaultCharset(thirdRequest);
  13. }
  14. if (messageConverter instanceof AllEncompassingFormHttpMessageConverter) {
  15. ((AllEncompassingFormHttpMessageConverter) messageConverter).setCharset(thirdRequest);
  16. }
  17. }
  18. return restTemplate;
  19. }
  20. @Autowired
  21. private RestTemplate restTemplate;
  22. /** 微信查询订单 */
  23. @PostMapping("weixinOrderQuery")
  24. public Map<String, String> weixinOrderQuery(@RequestParam String out_trade_no) throws Exception {
  25. Map map = new HashMap();
  26. map.put("appid", WXUtil.APP_ID);
  27. map.put("mch_id", WXUtil.MCH_ID);
  28. map.put("nonce_str", UUID_MD5.getUUID());
  29. map.put("out_trade_no", out_trade_no);
  30. String sign = WXPayUtil.MD5(WXUtil.createSign(map)).toUpperCase();
  31. map.put("sign", sign);
  32. String data = WXPayUtil.mapToXml(map);
  33. String response = restTemplate.postForObject("https://api.mch.weixin.qq.com/pay/orderquery", data, String.class);
  34. return WXPayUtil.xmlToMap(response);
  35. }

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k_size_16_color_FFFFFF_t_70 2 watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k_size_16_color_FFFFFF_t_70 3

4. 【退款】

当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。

注意:

◆ 交易时间超过一年的订单无法提交退款;

◆ 微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号。

◆ 请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次

错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次

◆ 每个支付订单的部分退款次数不能超过50次

◆ 如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败

  1. @Override
  2. public BaseResDto executeRefund(String orderRefundId) throws Exception{
  3. ShoppingOrderRefundEntity shoppingOrderRefund = shopingOrderRefundService.selectById(orderRefundId).setRefundMiddleTime(new Date()).setStatus(3);//退款中
  4. String orderId = shoppingOrderRefund.getOrderId();
  5. ShopingOrder shopingOrder = shopingOrderMapper.selectById(orderId) .setRefundStatus(3);//退款中
  6. Double refundMoney = shoppingOrderRefund.getRefundMoney();
  7. Double amount = shopingOrder.getAmount();
  8. if (refundManey > amount)
  9. return BaseResDto.baseResDto(Status.ERROR, "退款金额错误!");
  10. Map wxMap = WXUtil.applayRefund(shopingOrder.getUserId(), shopingOrder.getPayId(), shopingOrder.getOutTradeNo(), amount, refundMoney);
  11. if (!wxMap.get("result_code").equals("SUCCESS"))
  12. return BaseResDto.baseResDto(Status.ERROR, "申请微信退款失败!");
  13. // 下面接入自己的业务逻辑...
  14. return new BaseResDto(Status.SUCCESS);
  15. }

5. 【退款回调通知】

当商户申请的退款有结果后(退款状态为:退款成功、退款关闭、退款异常),微信会把相关结果发送给商户,商户需要接收处理,并返回应答。
对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功(通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)。
注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
特别说明:退款结果对重要的数据进行了加密,商户需要用商户秘钥进行解密后才能获得结果通知的内容。

  1. @XmlRootElement(name = "xml")
  2. @XmlAccessorType(XmlAccessType.FIELD)
  3. @NoArgsConstructor
  4. @Data
  5. @Accessors(chain = true)
  6. public class WXRefundReqDto { //严格按照微信官方文档封装的实体类
  7. private String return_msg;
  8. private String return_code;
  9. private String appid;
  10. private String mch_id;
  11. private String nonce_str;
  12. private String req_info;
  13. private String transaction_id; //微信订单号
  14. private String out_trade_no; //商户订单号
  15. private String refund_id; //微信退款单号
  16. private String out_refund_no; //商户退款单号
  17. private String total_fee; //订单金额
  18. private String settlement_total_fee; //应结订单金额
  19. private String refund_fee; //申请退款金额
  20. private String settlement_refund_fee; //退款金额
  21. private String refund_status; //退款状态
  22. private String success_time; //退款成功时间
  23. private String refund_recv_accout; //退款入账账户
  24. private String refund_account; //退款资金来源
  25. private String refund_request_source; //退款发起来源
  26. }
  27. @PostMapping("refundCallBack")
  28. public String refundCallBack(@RequestBody WXRefundReqDto param) throws Exception {
  29. Map map = capitalMainLogService.refundCallBack(param);
  30. return WXPayUtil.mapToXml(map);
  31. }
  32. @Override
  33. public Map refundCallBack(WXRefundReqDto param) throws Exception {
  34. Map map = new HashMap();
  35. if (param == null) {
  36. map.put("return_code", "FAIL");
  37. map.put("return_msg", "参数为空");
  38. return map;
  39. }
  40. if (!"SUCCESS".equals(param.getReturn_code())) {
  41. map.put("return_code", "FAIL");
  42. map.put("return_msg", "退款失败");
  43. return map;
  44. }
  45. Map signMap = objectToMap(param);
  46. signMap.remove("return_code");
  47. signMap.remove("return_msg");
  48. signMap.remove("appid");
  49. signMap.remove("mch_id");
  50. signMap.remove("nonce_str");
  51. signMap.remove("req_info");
  52. String refundSignString = WXUtil.createSign(signMap);
  53. String sign = WXPayUtil.MD5(refundSignString).toUpperCase();
  54. // 验证签名
  55. if (!sign.equals(param.getReq_info())) {
  56. map.put("return_code", "FAIL");
  57. map.put("return_msg", "签名错误");
  58. }
  59. // 根据订单号查看订单信息
  60. String order_no = (String) WXUtil.parseReqInfo(param.getReq_info()).get("out_trade_no");
  61. ShopingOrder shopingOrder = shopingOrderMapper.selectOne(new ShopingOrder().setOutTradeNo(order_no));
  62. // 已经退款
  63. if (shopingOrder.getStatus() == 4) {
  64. map.put("return_msg", "OK");
  65. map.put("return_code", "SUCCESS");
  66. return map;
  67. }
  68. // 修改订单表退款状态
  69. shopingOrder.setRefundStatus(4); //退款成功
  70. if (!shopingOrderService.updateById(shopingOrder)) {
  71. map.put("return_code", "FAIL");
  72. return map;
  73. }
  74. // 其他业务逻辑自行编写...
  75. map.put("return_msg", "OK");
  76. map.put("return_code", "SUCCESS");
  77. return map;
  78. }

6. 【查询退款】

提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。

  1. @Autowired
  2. private RestTemplate restTemplate;
  3. /** 查询微信退款 */
  4. @PostMapping("wxRefundQuery")
  5. public Map<String, String> wxRefundQuery(@RequestParam String out_trade_no) throws Exception{
  6. Map map = new HashMap();
  7. map.put("appid", WXUtil.APP_ID);
  8. map.put("mch_id", WXUtil.MCH_ID);
  9. map.put("nonce_str", UUID_MD5.getUUID());
  10. map.put("out_trade_no", out_trade_no);
  11. String sign = WXPayUtil.MD5(WXUtil.createSign(map)).toUpperCase();
  12. map.put("sign", sign);
  13. String data = WXPayUtil.mapToXml(map);
  14. String response = restTemplate.postForObject("https://api.mch.weixin.qq.com/pay/refundquery", data, String.class);
  15. return WXPayUtil.xmlToMap(response);
  16. }

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k_size_16_color_FFFFFF_t_70 4

7. 【企业付款到零钱】(提现)

使用条件和开通步骤参考官方文档:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1

企业付款为企业提供付款至用户零钱的能力,支持通过API接口付款,或通过微信支付商户平台(pay.weixin.qq.com)网页操作付款。

【使用条件】(切记,博主就被坑过)

◆ 商户号(或同主体其他非服务商商户号)已入驻90日

◆ 截止今日回推30天,商户号(或同主体其他非服务商商户号)连续不间断保持有交易

◆ 登录微信支付商户平台-产品中心,开通企业付款。

◆ spbill_create_ip 必须在商户平台配置好IP白名单

  1. @PostMapping("withdraw")
  2. public BaseResDto withdraw(@RequestParam Bigdecimal money,@RequestHeader("token") String token) {
  3. // 验证token,并根据token获取用户实体UserEntity,此处省略...
  4. Map map = WXUtil.wxWithdraw(userEntity.getWxOpenId(), userEntity.getRealName(), money); //如果check_name为NO_CHECK,则传昵称也可以
  5. if (!("SUCCESS".equals(map.get("return_code")) && "SUCCESS".equals(map.get("result_code"))))
  6. return new BaseResDto(Status.PARAMETERERROR, "提现失败");
  7. // 此处编写业务逻辑,并记录提现日志
  8. return new BaseResDto(Status.SUCCESS);
  9. }

二. 扫码支付

Native支付可分为两种模式,商户根据支付场景选择相应模式。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k_size_16_color_FFFFFF_t_70 5

一般我们电商网站都会选用模式二:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5

  1. @RestController
  2. public class WxNativePay {
  3. @PostMapping("/wxNativePay")
  4. public BaseDataResDto<JSONObject> wxNativePay() {
  5. try{
  6. Map map = new HashMap();
  7. map.put("appid", WXUtil.APP_ID);
  8. map.put("mch_id", WXUtil.MCH_ID);
  9. map.put("nonce_str", UUID_MD5.getUUID());
  10. map.put("sign_type", "MD5");
  11. map.put("body", "商品支付");
  12. map.put("out_trade_no", "1234560001");
  13. map.put("fee_type", "CNY");
  14. map.put("total_fee", 10 + "");
  15. map.put("spbill_create_ip", "127.0.0.1");
  16. map.put("notify_url", WXUtil.NOTIFY_URL);
  17. map.put("trade_type", "NATIVE");
  18. map.put("sign", WXPayUtil.MD5(WXUtil.createSign(map)).toUpperCase());
  19. // 请求报文 (map转为xml)
  20. String data = WXPayUtil.mapToXml(map);
  21. Map<String, String> mapResponse = WXPayUtil.xmlToMap(WXUtil.httpClient(data));
  22. if (Objects.equals(mapResponse.get("result_code"),"SUCCESS")) {
  23. String content = mapResponse.get("code_url");
  24. JSONObject json = new JSONObject();
  25. json.put("codeUrl", content);
  26. return new BaseDataResDto(Status.SUCCESS).setData(json);
  27. }
  28. } catch (Exception e) {
  29. e.printStackTrace();
  30. }
  31. return new BaseDataResDto(Status.ERROR);
  32. }
  33. }

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0FraXJhTmlja3k_size_16_color_FFFFFF_t_70 6 20200623111553742.png

直接将code_url返给前端,前端调用第三方库转化为二维码,供用户扫码付款;注意:code_url有效期为2小时,过期后扫码不能再发起支付。

【小结】:

原理很简单,签名验签是关键,无非就是API;微信小程序支付,H5,Web扫码支付,APP支付,区别不大,且支付结果通知(异步回调),查询订单,退款,查询退款等的API是一致的;下单API仅仅也只是 trade_type 不同而已 ~

发表评论

表情:
评论列表 (有 0 条评论,61人围观)

还没有评论,来说两句吧...

相关阅读