Java后端对接微信支付(微信公众号、PC端扫码)
生活随笔
收集整理的這篇文章主要介紹了
Java后端对接微信支付(微信公众号、PC端扫码)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
微信流程圖
項目結構
前期準備
復制證書
將證書中名為apiclient_key.pem的文件復制到你的項目中
依賴
<!-- 微信支付SDK--> <dependency><groupId>com.github.wechatpay-apiv3</groupId><artifactId>wechatpay-apache-httpclient</artifactId><version>0.3.0</version> </dependency>properties文件(yaml也可以自己換一下)
# 微信支付相關參數 # 下面兩個用來標識用戶 # 商戶號 wxpay.mch-id= ****************** # APPID wxpay.appid= ****************** # 接下來兩個用來確保SSL(內容未作任何加密,只做了簽名.) # 商戶API證書序列號 wxpay.mch-serial-no= ******************# 商戶私鑰文件(第一步) wxpay.private-key-path= apiclient_key.pem # APIv3密鑰(在微信支付回調通知和商戶獲取平臺證書使用APIv3密鑰) wxpay.api-v3-key= ******************# 接下來兩個是相關地址 # 微信服務器地址 wxpay.domain= https://api.mch.weixin.qq.com # 接收結果通知地址 # 注意:每次重新啟動ngrok,都需要根據實際情況修改這個配置 wxpay.notify-domain= 填自己的回調地址寫配置文件WxPayConfig(建議新建一個config文件夾存放配置文件)
package com.leng.paymentdemo.config;import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder; import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier; import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials; import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator; import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.http.impl.client.CloseableHttpClient; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource;import java.io.FileInputStream; import java.io.FileNotFoundException; import java.nio.charset.StandardCharsets; import java.security.PrivateKey;/*** 微信支付信息配置* @author Admin*/ @Configuration @PropertySource("classpath:wxpay.properties") //讀取配置文件 @ConfigurationProperties(prefix="wxpay") //讀取wxpay節點 @Data //使用set方法將wxpay節點中的值填充到當前類的屬性中 @Slf4j public class WxPayConfig {// 商戶號private String mchId;// 商戶API證書序列號private String mchSerialNo;// 商戶私鑰文件private String privateKeyPath;// APIv3密鑰private String apiV3Key;// APPIDprivate String appid;// 微信服務器地址private String domain;// 接收結果通知地址private String notifyDomain;//商戶APIv2 keyprivate String partnerKey;/*** 獲取商戶的私鑰文件* @param filename* @return*/public PrivateKey getPrivateKey(String filename){try {return PemUtil.loadPrivateKey(new FileInputStream(filename));} catch (FileNotFoundException e) {throw new RuntimeException("私鑰文件不存在",e);}}/*** 獲取簽名驗證器* @return*/@Beanpublic ScheduledUpdateCertificatesVerifier getVerifier(){log.info("獲取簽名驗證器");//獲取商戶私鑰PrivateKey privateKey = getPrivateKey(privateKeyPath);//私鑰簽名對象PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);//身份認證對象WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);// 使用定時更新的簽名驗證器,不需要傳入證書ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(wechatPay2Credentials,apiV3Key.getBytes(StandardCharsets.UTF_8));return verifier;}/*** 獲取http請求對象* @param verifier* @return*/@Bean(name = "wxPayClient")public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){log.info("獲取httpClient");//獲取商戶私鑰PrivateKey privateKey = getPrivateKey(privateKeyPath);WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, privateKey).withValidator(new WechatPay2Validator(verifier));// ... 接下來,你仍然可以通過builder設置各種參數,來配置你的HttpClient// 通過WechatPayHttpClientBuilder構造的HttpClient,會自動的處理簽名和驗簽,并進行證書自動更新CloseableHttpClient httpClient = builder.build();return httpClient;}}自定義統一返回結果(建議創建一個response存放)
CustomizeResultCode
package com.leng.paymentdemo.response;public interface CustomizeResultCode {/*** 獲取錯誤代碼* @return 錯誤狀態碼*/Integer getCode();/*** 獲取錯誤信息* @return 錯誤信息*/String getMessage(); }Result
package com.leng.paymentdemo.response;import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.experimental.Accessors;import java.util.HashMap; import java.util.Map;/*** 自定義的統一返回結果類*/ @Data @Accessors(chain = true) //允許當前類進行鏈式操作 public class Result {@ApiModelProperty(value = "是否成功")private Boolean success;@ApiModelProperty(value = "返回碼")private Integer code;@ApiModelProperty(value = "返回消息")private String message;@ApiModelProperty(value = "返回數據")private Map<String, Object> data = new HashMap<>();/*** 構造方法私有化,里面的方法都是靜態方法* 達到保護屬性的作用*/private Result(){}/*** 這里是使用鏈式編程* @return 返回訪問成功后的結果集*/public static Result ok(){Result result=new Result();result.setSuccess(true);result.setCode(ResultCode.SUCCESS.getCode());result.setMessage(ResultCode.SUCCESS.getMessage());return result;}/*** 失敗* @return*/public static Result error(){Result result=new Result();result.setSuccess(false);result.setCode(ResultCode.COMMON_FAIL.getCode());result.setMessage(ResultCode.COMMON_FAIL.getMessage());return result;}/*** 根基錯誤類型返回* @param resultCoe* @return*/public static Result error(ResultCode resultCoe){Result result=new Result();result.setSuccess(false);result.setCode(resultCoe.getCode());result.setMessage(resultCoe.getMessage());return result;}public Result success(Boolean success){this.setSuccess(success);return this;}public Result message(String message){this.setMessage(message);return this;}public Result code(Integer code){this.setCode(code);return this;}public Result data(String key,Object value){this.data.put(key,value);return this;} }ResultCode
package com.leng.paymentdemo.response; /*** @Author:* @Description: 返回碼定義* 規定:* #200表示成功* #1001~1999 區間表示參數錯誤* #2001~2999 區間表示用戶錯誤* #3001~3999 區間表示接口異常* #后面對什么的操作自己在這里注明就行了*/ public enum ResultCode implements CustomizeResultCode {/* 成功 */SUCCESS(200,"成功"),/* 默認失敗 */COMMON_FAIL(999,"失敗"),/* 參數錯誤:1000~1999 */PARAM_NOT_VALID(1001,"參數無效"),PARAM_IS_BLANK(1002,"參數無效"),PARAM_TYPE_ERROR(1003,"參數類型錯誤"),PARAM_NOT_COMPLETE(1004,"參數缺失"),/* 用戶錯誤 */USER_NOT_LOGIN(2001,"用戶未登錄"),USER_ACCOUNT_EXPIRED(2002,"賬戶已過期"),USER_CREDENTIALS_ERROR(2003,"密碼錯誤"),USER_CREDENTIALS_EXPIRED(2004,"密碼過期"),USER_ACCOUNT_DISABLE(2005,"賬戶不可用"),USER_ACCOUNT_LOCKED(2006,"賬戶被鎖定"),USER_ACCOUNT_NOT_EXIST(2007,"賬戶不存在"),USER_ACCOUNT_ALREADY_EXIST(2008,"賬戶已存在"),USER_ACCOUNT_USE_BY_OTHERS(2009,"賬戶下線"),/* 部門錯誤 */DEPARTMENT_NOT_EXIST(3007,"部門不存在"),DEPARTMENT_ALREADY_EXIST(3008,"部門已存在"),/* 業務錯誤 */NO_PERMISSION(3001,"沒有權限"),BUCKET_IS_EXISTS(3002,"bucket已經存在了"),/* 算數異常 */ARITHMETIC_EXCEPTION(9001,"算數異常"),NULL_POINTER_EXCEPTION(9002,"空指針異常");private Integer code;private String message;ResultCode(Integer code,String message){this.code=code;this.message=message;}@Overridepublic Integer getCode() {return code;}@Overridepublic String getMessage() {return message;} }定義一個全局異常處理器(exception)
BusinessException
package com.leng.paymentdemo.exception;import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;@Data @AllArgsConstructor @NoArgsConstructor public class BusinessException extends RuntimeException{@ApiModelProperty(value = "狀態碼")private Integer code;@ApiModelProperty(value = "錯誤信息")private String errMsg;}GlobalExceptionHandler
package com.leng.paymentdemo.exception;import com.leng.paymentdemo.response.Result; import com.leng.paymentdemo.response.ResultCode; import lombok.extern.slf4j.Slf4j; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import java.util.stream.Collectors;/***@作者: 冷俊杰*@類名: GlobalExceptionHandler類*@創建時間: 2022/8/22 14:21*@描述:*全局異常處理器,它只會處理controller層的異常*@修改原圖*@修改作者*@修改時間*/ @Slf4j @ControllerAdvice public class GlobalExceptionHandler {@ResponseBody@ExceptionHandler(value = ArithmeticException.class)public Result exception(ArithmeticException e) {System.out.println("出現了異常");return Result.error(ResultCode.ARITHMETIC_EXCEPTION);}@ResponseBody@ExceptionHandler(value = NullPointerException.class)public Result exception(NullPointerException e) {System.out.println("出現了異常");return Result.error(ResultCode.NULL_POINTER_EXCEPTION);}/* @ResponseBody@ExceptionHandler(value = BusinessException.class)public Result exception(BusinessException e) {System.out.println("出現了異常");return Result.error().code(e.getCode()).message(e.getErrMsg());}*/@ResponseBody@ExceptionHandler(value = Exception.class)public Result exception(Exception e) {e.printStackTrace();log.error("出現了異常"+e.getMessage()+"-----------");return Result.error();}//處理Get請求中 使用@Valid 驗證路徑中請求實體校驗失敗后拋出的異常@ExceptionHandler(BindException.class)@ResponseBodypublic Result BindExceptionHandler(BindException e) {String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());return Result.error().message(message);}//處理請求參數格式錯誤 @RequestParam上validate失敗后拋出的異常是javax.validation.ConstraintViolationException /* @ExceptionHandler(ConstraintViolationException.class)@ResponseBodypublic ResponseBean ConstraintViolationExceptionHandler(ConstraintViolationException e) {String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());return ResponseBean.error(message);}*///處理請求參數格式錯誤 @RequestBody上validate失敗后拋出的異常是MethodArgumentNotValidException異常。@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseBodypublic Result MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {String message = e.getBindingResult().getAllErrors().stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());return Result.error().message(message);}/*** 處理servlet異常* @param req* @param e* @return*/@ExceptionHandler(value = ServletException.class)@ResponseBodypublic Result servletExceptionHandler(HttpServletRequest req, ServletException e){log.error("web服務器異常 {}",e.getMessage());return Result.error().message(e.getMessage());}/*** 處理自定義的業務異常* @param req* @param e* @return*/@ExceptionHandler(value = BusinessException.class)@ResponseBodypublic Result bizExceptionHandler(HttpServletRequest req, BusinessException e){log.error("業務異常=>{}",e.getMessage());return Result.error().code(e.getCode()).message(e.getErrMsg());}/*** shiro的異常* @param e* @return*/ /* @ExceptionHandler(ShiroException.class)public ResponseBean handle401(ShiroException e) {log.error("shiro異常=>{}",e.getMessage());return new ResponseBean(401, e.getMessage(), null);}*//*** 獲取狀態碼* @param request* @return*/private HttpStatus getStatus(HttpServletRequest request) {Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");if (statusCode == null) {return HttpStatus.INTERNAL_SERVER_ERROR;}return HttpStatus.valueOf(statusCode);} }返回狀態碼(自定義)
OrderStatus類
package com.leng.paymentdemo.enums;import lombok.AllArgsConstructor; import lombok.Getter;@AllArgsConstructor @Getter public enum OrderStatus {/*** 未支付*/NOTPAY("未支付"),/*** 支付成功*/SUCCESS("支付成功"),/*** 已關閉*/CLOSED("超時已關閉"),/*** 已取消*/CANCEL("用戶已取消"),/*** 退款中*/REFUND_PROCESSING("退款中"),/*** 已退款*/REFUND_SUCCESS("已退款"),/*** 退款異常*/REFUND_ABNORMAL("退款異常");/*** 類型*/private final String type; }PayType
package com.leng.paymentdemo.enums;import lombok.AllArgsConstructor; import lombok.Getter;@AllArgsConstructor @Getter public enum PayType {/*** 微信*/WXPAY("微信"),/*** 支付寶*/ALIPAY("支付寶");/*** 類型*/private final String type; }WxApiType
package com.leng.paymentdemo.enums.wxpay;import lombok.AllArgsConstructor; import lombok.Getter;@AllArgsConstructor @Getter public enum WxApiType {/*** Native下單*/NATIVE_PAY("/v3/pay/transactions/native"),/*** Native下單*/NATIVE_PAY_V2("/pay/unifiedorder"),/*** 查詢訂單*/ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),/*** 關閉訂單*/CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),/*** 申請退款*/DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),/*** 查詢單筆退款*/DOMESTIC_REFUNDS_QUERY("/v3/refund/domestic/refunds/%s"),/*** 申請交易賬單*/TRADE_BILLS("/v3/bill/tradebill"),/*** 申請資金賬單*/FUND_FLOW_BILLS("/v3/bill/fundflowbill");/*** 類型*/private final String type; }WxNotifyType
package com.leng.paymentdemo.enums.wxpay;import lombok.AllArgsConstructor; import lombok.Getter;@AllArgsConstructor @Getter public enum WxNotifyType {/*** 支付通知*/NATIVE_NOTIFY("/api/wx-pay/native/notify"),/*** 支付通知*/NATIVE_NOTIFY_V2("/api/wx-pay-v2/native/notify"),/*** 退款結果通知*/REFUND_NOTIFY("/api/wx-pay/refunds/notify");/*** 類型*/private final String type; }WxRefundStatus
package com.leng.paymentdemo.enums.wxpay;import lombok.AllArgsConstructor; import lombok.Getter;@AllArgsConstructor @Getter public enum WxRefundStatus {/*** 退款成功*/SUCCESS("SUCCESS"),/*** 退款關閉*/CLOSED("CLOSED"),/*** 退款處理中*/PROCESSING("PROCESSING"),/*** 退款異常*/ABNORMAL("ABNORMAL");/*** 類型*/private final String type; }WxTradeState
package com.leng.paymentdemo.enums.wxpay;import lombok.AllArgsConstructor; import lombok.Getter;@AllArgsConstructor @Getter public enum WxTradeState {/*** 支付成功*/SUCCESS("SUCCESS"),/*** 未支付*/NOTPAY("NOTPAY"),/*** 已關閉*/CLOSED("CLOSED"),/*** 轉入退款*/REFUND("REFUND");/*** 類型*/private final String type; }utils工具類
HttpClientUtils
package com.leng.paymentdemo.util;import org.apache.http.Consts; import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.*; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContextBuilder; import org.apache.http.conn.ssl.TrustStrategy; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils;import javax.net.ssl.SSLContext; import java.io.IOException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.ParseException; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map;/*** http請求客戶端*/ public class HttpClientUtils {private String url;private Map<String, String> param;private int statusCode;private String content;private String xmlParam;private boolean isHttps;public boolean isHttps() {return isHttps;}public void setHttps(boolean isHttps) {this.isHttps = isHttps;}public String getXmlParam() {return xmlParam;}public void setXmlParam(String xmlParam) {this.xmlParam = xmlParam;}public HttpClientUtils(String url, Map<String, String> param) {this.url = url;this.param = param;}public HttpClientUtils(String url) {this.url = url;}public void setParameter(Map<String, String> map) {param = map;}public void addParameter(String key, String value) {if (param == null)param = new HashMap<String, String>();param.put(key, value);}public void post() throws ClientProtocolException, IOException {HttpPost http = new HttpPost(url);setEntity(http);execute(http);}public void put() throws ClientProtocolException, IOException {HttpPut http = new HttpPut(url);setEntity(http);execute(http);}public void get() throws ClientProtocolException, IOException {if (param != null) {StringBuilder url = new StringBuilder(this.url);boolean isFirst = true;for (String key : param.keySet()) {if (isFirst) {url.append("?");isFirst = false;}else {url.append("&");}url.append(key).append("=").append(param.get(key));}this.url = url.toString();}HttpGet http = new HttpGet(url);execute(http);}/*** set http post,put param*/private void setEntity(HttpEntityEnclosingRequestBase http) {if (param != null) {List<NameValuePair> nvps = new LinkedList<NameValuePair>();for (String key : param.keySet())nvps.add(new BasicNameValuePair(key, param.get(key))); // 參數http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 設置參數}if (xmlParam != null) {http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));}}private void execute(HttpUriRequest http) throws ClientProtocolException,IOException {CloseableHttpClient httpClient = null;try {if (isHttps) {SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {// 信任所有public boolean isTrusted(X509Certificate[] chain,String authType)throws CertificateException {return true;}}).build();SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();} else {httpClient = HttpClients.createDefault();}CloseableHttpResponse response = httpClient.execute(http);try {if (response != null) {if (response.getStatusLine() != null)statusCode = response.getStatusLine().getStatusCode();HttpEntity entity = response.getEntity();// 響應內容content = EntityUtils.toString(entity, Consts.UTF_8);}} finally {response.close();}} catch (Exception e) {e.printStackTrace();} finally {httpClient.close();}}public int getStatusCode() {return statusCode;}public String getContent() throws ParseException, IOException {return content;}}HttpUtils
package com.leng.paymentdemo.util;import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException;public class HttpUtils {/*** 將通知參數轉化為字符串* @param request* @return*/public static String readData(HttpServletRequest request) {BufferedReader br = null;try {StringBuilder result = new StringBuilder();br = request.getReader();for (String line; (line = br.readLine()) != null; ) {if (result.length() > 0) {result.append("\n");}result.append(line);}return result.toString();} catch (IOException e) {throw new RuntimeException(e);} finally {if (br != null) {try {br.close();} catch (IOException e) {e.printStackTrace();}}}} }OrderNoUtils
package com.leng.paymentdemo.util;import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random;/*** 訂單號工具類** @author qy* @since 1.0*/ public class OrderNoUtils {/*** 獲取訂單編號* @return*/public static String getOrderNo() {return "ORDER_" + getNo();}/*** 獲取退款單編號* @return*/public static String getRefundNo() {return "REFUND_" + getNo();}/*** 獲取編號* @return*/public static String getNo() {SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");String newDate = sdf.format(new Date());String result = "";Random random = new Random();for (int i = 0; i < 3; i++) {result += random.nextInt(10);}return newDate + result;}}使用
創建支付
官方接口文檔可以參考微信支付-開發者文檔 (qq.com)
新建一個WxPayController類,用來實現對下單,退款等操作
Native下單方法
/*** Native下單* @param productId* @return* @throws Exception*/ @ApiOperation("調用統一下單API,生成支付二維碼") @PostMapping("native/{productId}") public Result nativePay(@PathVariable Long productId) throws Exception {log.info("發起支付請求 v3.....................");//通過map接受返回的支付二維碼鏈接和訂單號Map<String, Object> map = wxPayService.nativePay(productId);return Result.ok().setData(map); }service層
/*** 創建訂單,調用Native支付接口*/ Map<String, Object> nativePay(Long productId) throws Exception;service實現層
/*** 創建訂單,調用Native支付接口*/ @Transactional(rollbackFor = Exception.class) @Override public Map<String, Object> nativePay(Long productId) throws Exception {log.info("生成訂單");//生成訂單并存入數據庫OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);String code_url = orderInfo.getCodeUrl();if (orderInfo != null && !StringUtils.isEmpty(code_url)) {log.info("訂單已存在,二維碼以保存");//創建返回值HashMap<String, Object> map = new HashMap<>();map.put("codeUrl", code_url);map.put("orderNo", orderInfo.getOrderNo());return map;}log.info("調用統一下單API");//調用統一下單APIHttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));// 請求body參數Gson gson = new Gson();Map paramsMap = new HashMap<>();paramsMap.put("appid", wxPayConfig.getAppid());//應用IDparamsMap.put("mchid", wxPayConfig.getMchId());//直連商戶號paramsMap.put("description", orderInfo.getTitle());//商品描述paramsMap.put("out_trade_no", orderInfo.getOrderNo());//商戶訂單號paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));//通知地址HashMap<String, Object> amountMap = new HashMap<>();amountMap.put("total", orderInfo.getTotalFee());amountMap.put("currency", "CNY");paramsMap.put("amount", amountMap);//將參數轉換成字符串形式String jsonParams = gson.toJson(paramsMap);log.info("請求參數" + jsonParams);StringEntity entity = new StringEntity(jsonParams,"utf-8");entity.setContentType("application/json");httpPost.setEntity(entity);httpPost.setHeader("Accept", "application/json");//完成簽名并執行請求CloseableHttpResponse response = wxPayClient.execute(httpPost);try {//響應體String bodyAsString = EntityUtils.toString(response.getEntity());//響應狀態碼int statusCode = response.getStatusLine().getStatusCode();//處理成功if (statusCode == 200) {log.info("成功,返回結果y = " + bodyAsString);//處理成功,無返回Body} else if (statusCode == 204) {log.info("成功");} else {log.error("Native下單失敗,響應碼 = " + statusCode + ",返回結果 = " + bodyAsString);throw new IOException("request failed");}//響應結果Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);//二維碼code_url = resultMap.get("code_url");//保存二維碼String orderNo = orderInfo.getOrderNo();orderInfoService.saveCodeUrl(orderNo, code_url);//創建返回值:二維碼Map<String, Object> map = new HashMap<>();map.put("codeUrl", code_url);map.put("orderNo", orderInfo.getOrderNo());return map;} finally {//釋放資源response.close();} }生成訂單并存入數據庫
servcie層
/*** 生成訂單并存入數據庫*/ OrderInfo createOrderByProductId(Long productId);service實現層
/*** 生成訂單并存入數據庫*/ @Override public OrderInfo createOrderByProductId(Long productId) {//查詢已存在但是未支付的訂單OrderInfo orderInfo = this.getNoPayOrderByProductId(productId);if (orderInfo != null) {return orderInfo;}//獲取商品信息Product product = productMapper.selectById(productId);//生成訂單orderInfo = new OrderInfo();orderInfo.setTitle(product.getTitle());//訂單標題orderInfo.setOrderNo(OrderNoUtils.getOrderNo());//訂單號orderInfo.setProductId(productId);//支付產品idorderInfo.setTotalFee(product.getPrice());//商品金額,單位是分orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());//訂單狀態baseMapper.insert(orderInfo);return orderInfo; }存儲訂單二維碼
servcie層
/*** 存儲訂單二維碼*/ void saveCodeUrl(String orderNo, String codeUrl);servcie實現層
/*** 存儲訂單二維碼*/ @Override public void saveCodeUrl(String orderNo, String codeUrl) {QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<OrderInfo>();queryWrapper.eq("order_no", orderNo);OrderInfo orderInfo = new OrderInfo();orderInfo.setCodeUrl(codeUrl);baseMapper.update(orderInfo, queryWrapper); }支付通知
官方接口文檔:微信支付-開發者文檔 (qq.com)
支付通知方法
/*** 支付通知* 微信支付通過支付通知接口將用戶支付成功消息通知給商戶*/ @PostMapping("/native/notify") public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {Gson gson = new Gson();//應答對象HashMap<String, String> map = new HashMap<>();try {//處理通知參數String body = HttpUtils.readData(request);log.info("支付通知的參數 ===> {}", body);Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);String requestId = (String) bodyMap.get("id");log.info("支付通知的id ===> {}", requestId);//簽名的驗證WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier, requestId, body);if (!wechatPay2ValidatorForRequest.validate(request)) {log.error("驗簽失敗");//失敗應答response.setStatus(500);map.put("code", "ERROR");map.put("message", "驗簽失敗");return gson.toJson(map);}log.info("驗簽成功");//處理訂單wxPayService.processOrder(bodyMap);//應答超時//模擬接收微信端的重復通知//TimeUnit.SECONDS.sleep(5);//成功應答response.setStatus(200);map.put("code", "SUCCESS");map.put("message", "成功");return gson.toJson(map);} catch (Exception e) {e.printStackTrace();//失敗應答response.setStatus(500);map.put("code", "ERROR");map.put("message", "失敗");return gson.toJson(map);} }處理訂單
service層
/*** 處理訂單*/ void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException;service實現層
/*** 處理訂單*/ @Transactional(rollbackFor = Exception.class) @Override public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {log.info("處理訂單");//解密報文String plainText = decryptFromResource(bodyMap);//將明文轉換成mapGson gson = new Gson();HashMap hashMap = gson.fromJson(plainText, HashMap.class);String orderNo = (String) hashMap.get("out_trade_no");/*在對業務數據進行狀態檢查和處理之前,要采用數據鎖進行并發控制,以避免函數重入造成的數據混亂*///嘗試獲取鎖:// 成功獲取則立即返回true,獲取失敗則立即返回false。不必一直等待鎖的釋放if (lock.tryLock()) {try {//處理重復通知//保證接口調用的冪等性:無論接口被調用多少次,產生的結果是一致的String orderStatus = orderInfoService.getOrderStatus(orderNo);if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {return;}//更新訂單狀態orderInfoService.updateStatusByorderNo(orderNo, OrderStatus.SUCCESS);//記錄支付日志paymentInfoService.createPaymentInfo(plainText);} finally {//要主動釋放鎖lock.unlock();}} }查詢訂單
官方接口文檔:微信支付-開發者文檔 (qq.com)
/*** 查詢訂單** @param orderNo* @return* @throws URISyntaxException* @throws IOException*/ @ApiOperation("查詢訂單:測試訂單狀態用") @GetMapping("query/{orderNo}") public Result queryOrder(@PathVariable String orderNo) throws Exception {log.info("查詢訂單");String result = wxPayService.queryOrder(orderNo);return Result.ok().setMessage("查詢成功").data("result", result); }service層
/*** 查單接口調用*/ String queryOrder(String orderNo) throws Exception;service實現層
/*** 根據訂單號查詢微信支付查單接口,核實訂單狀態* 如果訂單已支付,則更新商戶端訂單狀態,并記錄支付日志* 如果訂單未支付,則調用關單接口關閉訂單,并更新商戶端訂單狀態** @param orderNo*/ @Transactional(rollbackFor = Exception.class) @Override public void checkOrderStatus(String orderNo) throws Exception {log.warn("根據訂單號核實訂單狀態 ===> {}", orderNo);//調用微信支付查單接口String result = this.queryOrder(orderNo);Gson gson = new Gson();Map resultMap = gson.fromJson(result, HashMap.class);//獲取微信支付端的訂單狀態Object tradeState = resultMap.get("trade_state");//判斷訂單狀態if(WxTradeState.SUCCESS.getType().equals(tradeState)){log.warn("核實訂單已支付 ===> {}", orderNo);//如果確認訂單已支付則更新本地訂單狀態orderInfoService.updateStatusByorderNo(orderNo, OrderStatus.SUCCESS);//記錄支付日志paymentInfoService.createPaymentInfo(result);}if(WxTradeState.NOTPAY.getType().equals(tradeState)){log.warn("核實訂單未支付 ===> {}", orderNo);//如果訂單未支付,則調用關單接口this.closeOrder(orderNo);//更新本地訂單狀態orderInfoService.updateStatusByorderNo(orderNo, OrderStatus.CLOSED);} }關單接口
/*** 關單接口的調用** @param orderNo*/ private void closeOrder(String orderNo) throws Exception {log.info("關單接口的調用,訂單號 ===> {}", orderNo);//創建遠程請求對象String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);url = wxPayConfig.getDomain().concat(url);HttpPost httpPost = new HttpPost(url);//組裝json請求體Gson gson = new Gson();Map<String, String> paramsMap = new HashMap<>();paramsMap.put("mchid", wxPayConfig.getMchId());String jsonParams = gson.toJson(paramsMap);log.info("請求參數 ===> {}", jsonParams);//將請求參數設置到請求對象中StringEntity entity = new StringEntity(jsonParams, "utf-8");entity.setContentType("application/json");httpPost.setEntity(entity);httpPost.setHeader("Accept", "application/json");//完成簽名并執行請求CloseableHttpResponse response = wxPayClient.execute(httpPost);try {int statusCode = response.getStatusLine().getStatusCode();//響應狀態碼if (statusCode == 200) { //處理成功log.info("成功200");} else if (statusCode == 204) { //處理成功,無返回Bodylog.info("成功204");} else {log.info("Native下單失敗,響應碼 = " + statusCode);throw new IOException("request failed");}} finally {response.close();} }申請退款
/*** 申請退款*/@ApiOperation("申請退款")@PostMapping("/refunds/{orderNo}/{reason}")public Result refunds(@PathVariable String orderNo, @PathVariable String reason)throws Exception {log.info("申請退款");wxPayService.refund(orderNo, reason);return Result.ok();}service層
/*** 退款*/ void refund(String orderNo, String reason) throws Exception;service實現層
* 退款** @param orderNo* @param reason*/ @Transactional(rollbackFor = Exception.class) @Override public void refund(String orderNo, String reason) throws Exception {log.info("創建退款單記錄");//根據訂單編號創建退款單RefundInfo refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo,reason);log.info("調用退款API");//調用統一下單APIString url =wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());HttpPost httpPost = new HttpPost(url);// 請求body參數Gson gson = new Gson();Map paramsMap = new HashMap();paramsMap.put("out_trade_no", orderNo);//訂單編號paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款單編號paramsMap.put("reason", reason);//退款原因paramsMap.put("notify_url",wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址Map amountMap = new HashMap();amountMap.put("refund", refundsInfo.getRefund());//退款金額amountMap.put("total", refundsInfo.getTotalFee());//原訂單金額amountMap.put("currency", "CNY");//退款幣種paramsMap.put("amount", amountMap);//將參數轉換成json字符串String jsonParams = gson.toJson(paramsMap);log.info("請求參數 ===> {}" + jsonParams);StringEntity entity = new StringEntity(jsonParams, "utf-8");entity.setContentType("application/json");//設置請求報文格式httpPost.setEntity(entity);//將請求報文放入請求對象httpPost.setHeader("Accept", "application/json");//設置響應報文格式//完成簽名并執行請求,并完成驗簽CloseableHttpResponse response = wxPayClient.execute(httpPost);try {//解析響應結果String bodyAsString = EntityUtils.toString(response.getEntity());int statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) {log.info("成功, 退款返回結果 = " + bodyAsString);} else if (statusCode == 204) {log.info("成功");} else {throw new RuntimeException("退款異常, 響應碼 = " + statusCode + ", 退款返回結果 = " + bodyAsString);}//更新訂單狀態orderInfoService.updateStatusByorderNo(orderNo,OrderStatus.REFUND_PROCESSING);//更新退款單refundsInfoService.updateRefund(bodyAsString);} finally {response.close();} }創建退款訂單
service層
/*** 根據訂單號創建退款訂單*/ RefundInfo createRefundByOrderNo(String orderNo, String reason);service實現層
/*** 根據訂單號創建退款訂單** @param orderNo* @param reason*/ @Override public RefundInfo createRefundByOrderNo(String orderNo, String reason) {//根據訂單號獲取訂單信息OrderInfo orderInfo = orderInfoService.getOrderByOrderNo(orderNo);//根據訂單號生成退款訂單RefundInfo refundInfo = new RefundInfo();refundInfo.setOrderNo(orderNo);//訂單編號refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款單編號refundInfo.setTotalFee(orderInfo.getTotalFee());//原訂單金額(分)refundInfo.setRefund(orderInfo.getTotalFee());//退款金額(分)refundInfo.setReason(reason);//退款原因//保存退款訂單baseMapper.insert(refundInfo);return refundInfo; }獲取訂單狀態
/*** 根據訂單號獲取訂單*/ OrderInfo getOrderByOrderNo(String orderNo); /*** 根據訂單號獲取訂單** @param orderNo*/ @Override public OrderInfo getOrderByOrderNo(String orderNo) {QueryWrapper<OrderInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("order_no", orderNo);OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);return orderInfo; }根據退款單號核實退款單狀態
service層
/*** 根據退款單號核實退款單狀態*/ void checkRefundStatus(String refundNo) throws Exception;service實現層
/*** 根據退款單號核實退款單狀態** @param refundNo*/ @Transactional(rollbackFor = Exception.class) @Override public void checkRefundStatus(String refundNo) throws Exception {log.warn("根據退款單號核實退款單狀態 ===> {}", refundNo);//調用查詢退款單接口String result = this.queryRefund(refundNo);//組裝json請求體字符串Gson gson = new Gson();Map<String, String> resultMap = gson.fromJson(result, HashMap.class);//獲取微信支付端退款狀態String status = resultMap.get("status");String orderNo = resultMap.get("out_trade_no");if (WxRefundStatus.SUCCESS.getType().equals(status)) {log.warn("核實訂單已退款成功 ===> {}", refundNo);//如果確認退款成功,則更新訂單狀態orderInfoService.updateStatusByorderNo(orderNo,OrderStatus.REFUND_SUCCESS);//更新退款單refundsInfoService.updateRefund(result);}if (WxRefundStatus.ABNORMAL.getType().equals(status)) {log.warn("核實訂單退款異常 ===> {}", refundNo);//如果確認退款成功,則更新訂單狀態orderInfoService.updateStatusByorderNo(orderNo,OrderStatus.REFUND_ABNORMAL);//更新退款單refundsInfoService.updateRefund(result);} }更新退款狀態
service層
/*** 記錄退款記錄*/ void updateRefund(String content);service實現層
/*** 記錄退款記錄** @param content*/ @Override public void updateRefund(String content) {//將json字符串轉換成MapGson gson = new Gson();Map<String, String> resultMap = gson.fromJson(content, HashMap.class);//根據退款單編號修改退款單QueryWrapper<RefundInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("refund_no", resultMap.get("out_refund_no"));//設置要修改的字段RefundInfo refundInfo = new RefundInfo();refundInfo.setRefundId(resultMap.get("refund_id"));//微信支付退款單號//查詢退款和申請退款中的返回參數if(resultMap.get("status") != null){refundInfo.setRefundStatus(resultMap.get("status"));//退款狀態refundInfo.setContentReturn(content);//將全部響應結果存入數據庫的content字段}//退款回調中的回調參數if(resultMap.get("refund_status") != null){refundInfo.setRefundStatus(resultMap.get("refund_status"));//退款狀態refundInfo.setContentNotify(content);//將全部響應結果存入數據庫的content字段}//更新退款單baseMapper.update(refundInfo, queryWrapper); }處理退款
service層
/*** 處理退款單*/ void processRefund(Map<String, Object> bodyMap) throws Exception;service實現層
/*** 處理退款單** @param bodyMap*/ @Transactional(rollbackFor = Exception.class) @Override public void processRefund(Map<String, Object> bodyMap) throws Exception {log.info("退款單");//解密報文String plainText = decryptFromResource(bodyMap);//將明文轉換成mapGson gson = new Gson();HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);String orderNo = (String) plainTextMap.get("out_trade_no");if (lock.tryLock()) {try {String orderStatus = orderInfoService.getOrderStatus(orderNo);if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {return;}//更新訂單狀態orderInfoService.updateStatusByorderNo(orderNo,OrderStatus.REFUND_SUCCESS);//更新退款單refundsInfoService.updateRefund(plainText);} finally {//要主動釋放鎖lock.unlock();}} }源碼
GitHub
Gitee
總結
以上是生活随笔為你收集整理的Java后端对接微信支付(微信公众号、PC端扫码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android应用开发用Kotlin还是
- 下一篇: QQ旋风离线下载的建议