支付系列-对接支付宝支付
前言:在商業(yè)開發(fā)中,支付業(yè)務(wù)是必不可少的一個環(huán)節(jié)。如今支付平臺常用微信支付、支付寶支付。相較而言,支付寶支付對接起來比較簡單,所以本文以支付寶支付為例。
支付寶提供的沙箱環(huán)境,入門門檻非常低,不需要商家認證那一套,這一點對開發(fā)者是非常友好的,可以直接在本地運行測試,跟最終的產(chǎn)品上線效果是一樣的,好了,廢話不說,進入正題。
一、開發(fā)流程
二、前景準備
登錄支付寶開放平臺:https://open.alipay.com/
企業(yè)支付寶注冊(非必備):https://memberprod.alipay.com/account/reg/index.htm
支付寶支付API:https://opendocs.alipay.com/apis
密鑰生成地址:https://miniu.alipay.com/keytool/create
請求參數(shù)API:https://opendocs.alipay.com/open/02ekfg?scene=19
01、進入沙箱環(huán)境
02、獲取參數(shù)
- APPID
- 商家私鑰
- 商家公鑰
- 支付寶公鑰
- 支付寶回調(diào)地址
- 網(wǎng)關(guān)地址
- 加密算法簽名RSA2
密鑰生成地址:https://miniu.alipay.com/keytool/create
注意: 線上開發(fā)獲取支付寶公鑰的方式為:使用應(yīng)用公鑰去換取支付寶公鑰!!!
03、體驗測試賬戶
因為環(huán)境為沙箱測試環(huán)境,它提供商戶號跟買家號,但是該賬戶只能在支付寶提供的測試APK上使用。
三、PC網(wǎng)頁支付實現(xiàn)
01、創(chuàng)建springboot工程
自行創(chuàng)建
02、導入依賴
<!--freemarker模板引擎--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <!--web依賴--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency> <!--lombok--> <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional> </dependency> <!--test測試類--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope> </dependency> <!--阿里云支付SDK--> <dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-sdk-java</artifactId><version>4.10.192.ALL</version> </dependency> <!--二維碼生成--> <dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.4.1</version> </dependency> <!--解析二維碼--> <dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.4.1</version> </dependency> <!--加密依賴--> <dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.15</version> </dependency> <!--http請求--> <dependency><groupId>commons-httpclient</groupId><artifactId>commons-httpclient</artifactId><version>3.1</version><exclusions><exclusion><artifactId>commons-logging</artifactId><groupId>commons-logging</groupId></exclusion></exclusions> </dependency> <!--配置文件加密處理--> <dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>2.1.0</version> </dependency>03、配置yml
server:port: 8081spring:freemarker:suffix: .htmlcache: false## 支付寶支付參數(shù)配置 alipay:app_id: # APPIDmerchant_private_key: #支付寶商戶私鑰alipay_public_key: #支付寶公鑰notify_url: #服務(wù)器異步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數(shù),必須外網(wǎng)可以正常訪問return_url: #頁面跳轉(zhuǎn)同步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數(shù),必須外網(wǎng)可以正常訪問(如果是二維碼掃碼可以不配置)sing_type: RSA2charset: utf-8gatewayurl: https://openapi.alipaydev.com/gateway.do # https://openapi.alipay.com/gateway.do 線上地址# 保存支付日志的地址log_path: D:/tmp/04、定義支付配置類 AlipayConfig
package com.qd.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.FileWriter; import java.io.IOException; /*** 支付寶支付配置類** @Blog: https://www.cnblogs.com/qd666* @Author: qiandu* @Date: 2021/11/27 11:22*/ @Component public class AlipayConfig {// 應(yīng)用IDpublic static String app_id;// 商戶私鑰public static String merchant_private_key;// 支付寶公鑰public static String alipay_public_key;// 服務(wù)器異步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數(shù),必須外網(wǎng)可以正常訪問public static String notify_url;// 頁面跳轉(zhuǎn)同步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數(shù),必須外網(wǎng)可以正常訪問public static String return_url;// 簽名方式public static String sign_type;// 字符編碼格式public static String charset;// 支付寶網(wǎng)關(guān)public static String gatewayUrl;// 日志地址public static String log_path;/*** 寫日志,方便測試(看網(wǎng)站需求,也可以改成把記錄存入數(shù)據(jù)庫)*/public static void logResult(String sWord) {FileWriter writer = null;try {writer = new FileWriter(log_path + "alipay_log_" + System.currentTimeMillis() + ".txt");writer.write(sWord);} catch (Exception e) {e.printStackTrace();} finally {if (writer != null) {try {writer.close();} catch (IOException e) {e.printStackTrace();}}}}/*** 配置文件靜態(tài)屬性注入*/@Value("${alipay.app_id}")public void setApp_id(String app_id) {this.app_id = app_id;}@Value("${alipay.merchant_private_key}")public void setMerchant_private_key(String merchant_private_key) {this.merchant_private_key = merchant_private_key;}@Value("${alipay.alipay_public_key}")public void setAlipay_public_key(String alipay_public_key) {this.alipay_public_key = alipay_public_key;}@Value("${alipay.notify_url}")public void setNotify_url(String notify_url) {this.notify_url = notify_url;}@Value("${alipay.return_url}")public void setReturn_url(String return_url) {this.return_url = return_url;}@Value("${alipay.sing_type}")public void setSign_type(String sign_type) {this.sign_type = sign_type;}@Value("${alipay.charset}")public void setCharset(String charset) { this.charset = charset;}@Value("${alipay.gatewayurl}")public void setGatewayUrl(String gatewayUrl) {this.gatewayUrl = gatewayUrl;}@Value("${alipay.log_path}")public void setLog_path(String log_path) {this.log_path = log_path;} }notify_url:異步通知,當你支付成功時,支付寶回調(diào)你本地啟動項目的接口,必須是外網(wǎng)可以訪問的,否則支付寶請求不到,但是又得是你本地的方法「不討論線上測試」可以使用工具實現(xiàn)內(nèi)網(wǎng)穿透
05、支付寶支付工具類
package com.qd.controller;import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.internal.util.AlipaySignature; import com.qd.config.AlipayConfig; import lombok.extern.slf4j.Slf4j; import javax.servlet.http.HttpServletRequest; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Iterator; import java.util.Map;/*** 支付寶支付工具類** @Blog: https://www.cnblogs.com/qd666* @Author: qiandu* @Date: 2021/11/27 11:25*/ @Slf4j public class AliPayUtil {/*** 初始化AlipayClient*/public static AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key, AlipayConfig.sign_type);/*** 轉(zhuǎn)碼*/public static String getByte(String param) {try {return new String(param.getBytes("ISO-8859-1"), "UTF-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();return null;}}/*** 校驗簽名*/public static boolean rsaCheckV1(HttpServletRequest request) {// https://docs.open.alipay.com/54/106370// 獲取支付寶POST過來反饋信息Map<String, String> params = new HashMap<>();Map requestParams = request.getParameterMap();for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {String name = (String) iter.next();String[] values = (String[]) requestParams.get(name);String valueStr = "";for (int i = 0; i < values.length; i++) {valueStr = (i == values.length - 1) ? valueStr + values[i]: valueStr + values[i] + ",";}params.put(name, valueStr);}try {boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type);return signVerified;} catch (AlipayApiException e) {log.debug("verify sigin error, exception is:{}", e);return false;}} }06、參數(shù)封裝成實體類
@Data @AllArgsConstructor @NoArgsConstructor public class Order implements java.io.Serializable{// 商品訂單號 必填private String out_trade_no;;// 訂單名稱 必填private String subject;// 付款金額 必填private String total_amount;// 商品描述private String description;// 超時時間private String timeout_express = "10m";// 產(chǎn)品編號 必填private String product_code = "FAST_INSTANT_TRADE_PAY"; }07、AliController(自行拆分service)
@Slf4j @Controller public class AliController {@ResponseBody@RequestMapping("/paySuccess")public String paySuccess() {return "支付成功!";}@ResponseBody@RequestMapping("/payFail")public String payFail() {return "支付失敗!";}/*** 調(diào)用支付*/@RequestMapping(value = "/goAlipay", produces = "text/html; charset=UTF-8")@ResponseBodypublic String goAlipay(String subject, String totalAmount, String description) throws IOException, AlipayApiException {// 獲得初始化的AlipayClientAlipayClient alipayClient = AliPayUtil.alipayClient;// 訂單模型測試//AlipayTradePagePayModel model = new AlipayTradePagePayModel();//model.setOutTradeNo(String.valueOf(System.currentTimeMillis()));//model.setSubject("subject:120元");//model.setTotalAmount("120");//model.setBody("這是一個description");//model.setProductCode("FAST_INSTANT_TRADE_PAY");//AlipayTradePagePayRequest pagePayRequest = new AlipayTradePagePayRequest();//pagePayRequest.setReturnUrl(AlipayConfig.return_url);//pagePayRequest.setNotifyUrl(AlipayConfig.notify_url);//pagePayRequest.setBizModel(model);// 1:設(shè)置參數(shù)Order order = new Order();order.setOut_trade_no(String.valueOf(System.currentTimeMillis()));order.setSubject("subject");order.setTotal_amount("120.00");order.setDescription("這是一個description");// 2:設(shè)置請求參數(shù)AlipayTradePagePayRequest pagePayRequest = new AlipayTradePagePayRequest();// 3:設(shè)置頁面跳轉(zhuǎn)同步通知頁面路徑pagePayRequest.setReturnUrl(AlipayConfig.return_url);// 4:設(shè)置服務(wù)器異步通知路徑pagePayRequest.setNotifyUrl(AlipayConfig.notify_url);// 5:以json格式封裝數(shù)據(jù)pagePayRequest.setBizContent(new ObjectMapper().writeValueAsString(order));// 請求String result = alipayClient.pageExecute(pagePayRequest).getBody();return result;}/*** 同步回調(diào)*/@RequestMapping("/return_url")public ModelAndView return_url(HttpServletResponse response, HttpServletRequest request) throws IOException, AlipayApiException {log.info(">>>>>>>>支付成功, 進入同步通知接口...");boolean verifyResult = AliPayUtil.rsaCheckV1(request);ModelAndView mv = null;if (verifyResult) {//商戶訂單號String out_trade_no = AliPayUtil.getByte(request.getParameter("out_trade_no"));//支付寶交易號String trade_no = AliPayUtil.getByte(request.getParameter("trade_no"));log.info("商戶訂單號:{},支付寶交易號,{}", out_trade_no, trade_no);mv = new ModelAndView("paySuccess");} else {mv = new ModelAndView("payFail");}return mv;}/*** 異步回調(diào)*/@ResponseBody@RequestMapping(value = "/notify_url", method = RequestMethod.POST)public String notify_url(HttpServletResponse response, HttpServletRequest request) throws IOException, AlipayApiException {log.info(">>>>>>>>支付成功, 進入異步通知接口...");// 一定要驗簽,防止黑客篡改參數(shù)Map<String, String[]> parameterMap = request.getParameterMap();StringBuilder notifyBuild = new StringBuilder(">>>>>>>>>> alipay notify >>>>>>>>>>>>>>\n");parameterMap.forEach((key, value) -> notifyBuild.append(key + "=" + value[0] + "\n"));notifyBuild.append(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");log.info(notifyBuild.toString());boolean flag = AliPayUtil.rsaCheckV1(request);if (flag) {/*** TODO 需要嚴格按照如下描述校驗通知數(shù)據(jù)的正確性** 商戶需要驗證該通知數(shù)據(jù)中的out_trade_no是否為商戶系統(tǒng)中創(chuàng)建的訂單號,* 并判斷total_amount是否確實為該訂單的實際金額(即商戶訂單創(chuàng)建時的金額),* 同時需要校驗通知中的seller_id(或者seller_email) 是否為out_trade_no這筆單據(jù)的對應(yīng)的操作方(有的時候,一個商戶可能有多個seller_id/seller_email),** 上述有任何一個驗證不通過,則表明本次通知是異常通知,務(wù)必忽略。* 在上述驗證通過后商戶必須根據(jù)支付寶不同類型的業(yè)務(wù)通知,正確的進行不同的業(yè)務(wù)處理,并且過濾重復的通知結(jié)果數(shù)據(jù)。* 在支付寶的業(yè)務(wù)通知中,只有交易通知狀態(tài)為TRADE_SUCCESS或TRADE_FINISHED時,支付寶才會認定為買家付款成功。*///交易狀態(tài)String tradeStatus = AliPayUtil.getByte(request.getParameter("trade_status"));// 商戶訂單號String out_trade_no = AliPayUtil.getByte(request.getParameter("out_trade_no"));//支付寶交易號String trade_no = AliPayUtil.getByte(request.getParameter("trade_no"));//付款金額String total_amount = AliPayUtil.getByte(request.getParameter("total_amount"));log.info("交易狀態(tài):{},商戶訂單號:{},支付寶交易號:{},付款金額:{}", tradeStatus, out_trade_no, trade_no, total_amount);// TRADE_FINISHED(表示交易已經(jīng)成功結(jié)束,并不能再對該交易做后續(xù)操作);// TRADE_SUCCESS(表示交易已經(jīng)成功結(jié)束,可以對該交易做后續(xù)操作,如:分潤、退款等);if (tradeStatus.equals("TRADE_FINISHED")) {//判斷該筆訂單是否在商戶網(wǎng)站中已經(jīng)做過處理//如果沒有做過處理,根據(jù)訂單號(out_trade_no)在商戶網(wǎng)站的訂單系統(tǒng)中查到該筆訂單的詳細,// 并判斷total_amount是否確實為該訂單的實際金額(即商戶訂單創(chuàng)建時的金額),并執(zhí)行商戶的業(yè)務(wù)程序//請務(wù)必判斷請求時的total_fee、seller_id與通知時獲取的total_fee、seller_id為一致的//如果有做過處理,不執(zhí)行商戶的業(yè)務(wù)程序//注意://如果簽約的是可退款協(xié)議,退款日期超過可退款期限后(如三個月可退款),支付寶系統(tǒng)發(fā)送該交易狀態(tài)通知//如果沒有簽約可退款協(xié)議,那么付款完成后,支付寶系統(tǒng)發(fā)送該交易狀態(tài)通知。} else if (tradeStatus.equals("TRADE_SUCCESS")) {//判斷該筆訂單是否在商戶網(wǎng)站中已經(jīng)做過處理//如果沒有做過處理,根據(jù)訂單號(out_trade_no)在商戶網(wǎng)站的訂單系統(tǒng)中查到該筆訂單的詳細,// 并判斷total_amount是否確實為該訂單的實際金額(即商戶訂單創(chuàng)建時的金額),并執(zhí)行商戶的業(yè)務(wù)程序//請務(wù)必判斷請求時的total_fee、seller_id與通知時獲取的total_fee、seller_id為一致的//如果有做過處理,不執(zhí)行商戶的業(yè)務(wù)程序//注意://如果簽約的是可退款協(xié)議,那么付款完成后,支付寶系統(tǒng)發(fā)送該交易狀態(tài)通知。}return "success";}return "fail";}/***查看支付流水*/@RequestMapping(value = "/queryPay")@ResponseBodypublic String queryPay(String orderId) throws IOException, AlipayApiException {AlipayClient alipayClient = AliPayUtil.alipayClient;AlipayTradePagePayModel model = new AlipayTradePagePayModel();model.setOutTradeNo(orderId);//設(shè)置請求參數(shù)AlipayTradeQueryRequest alipayRequest = new AlipayTradeQueryRequest();alipayRequest.setBizModel(model);//請求String result = alipayClient.execute(alipayRequest).getBody();return result;}/*** 退款** @param orderNo 商戶訂單號* @return*/@PostMapping("/refund")@ResponseBodypublic String refund(String orderNo) throws AlipayApiException {AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();AlipayTradeRefundModel model = new AlipayTradeRefundModel();// 商戶訂單號model.setOutTradeNo(orderNo);// 退款金額model.setRefundAmount("0.1");// 退款原因model.setRefundReason("無理由退貨");// 退款訂單號(同一個訂單可以分多次部分退款,當分多次時必傳)String outOrderId = UUID.randomUUID().toString();model.setOutRequestNo(outOrderId);log.info("退款請求號:{}", outOrderId);alipayRequest.setBizModel(model);AlipayTradeRefundResponse alipayResponse = AliPayUtil.alipayClient.execute(alipayRequest);return alipayResponse.getBody();}/*** 退款查詢** @param orderNo 商戶訂單號* @param refundOrderNo 請求退款接口時,傳入的退款請求號,如果在退款請求時未傳入,則該值為創(chuàng)建交易時的外部訂單號* @return* @throws AlipayApiException*/@GetMapping("/refundQuery")@ResponseBodypublic String refundQuery(String orderNo, String refundOrderNo) throws AlipayApiException {AlipayTradeFastpayRefundQueryRequest alipayRequest = new AlipayTradeFastpayRefundQueryRequest();AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel();model.setOutTradeNo(orderNo);model.setOutRequestNo(refundOrderNo);alipayRequest.setBizModel(model);AlipayTradeFastpayRefundQueryResponse alipayResponse = AliPayUtil.alipayClient.execute(alipayRequest);System.out.println(alipayResponse.getBody());return alipayResponse.getBody();}/*** 關(guān)閉交易** @param orderNo* @return* @throws AlipayApiException*/@PostMapping("/close")@ResponseBodypublic String close(String orderNo) throws AlipayApiException {AlipayTradeCloseRequest alipayRequest = new AlipayTradeCloseRequest();AlipayTradeCloseModel model = new AlipayTradeCloseModel();model.setOutTradeNo(orderNo);alipayRequest.setBizModel(model);AlipayTradeCloseResponse alipayResponse = AliPayUtil.alipayClient.execute(alipayRequest);System.out.println(alipayResponse.getBody());return alipayResponse.getBody();} }08、運行結(jié)果
四、二維碼當面付實現(xiàn)
01、創(chuàng)建springboot項目
02、導入依賴
03、定義支付配置類 AlipayConfig
04、支付寶支付工具類
這些步驟與前面一樣,請看上
05、配置yml文件
server:port: 8081spring:freemarker:suffix: .htmlcache: false## 支付寶支付參數(shù)配置 alipay:app_id: # APPIDmerchant_private_key: #支付寶商戶私鑰alipay_public_key: #支付寶公鑰notify_url: #服務(wù)器異步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數(shù),必須外網(wǎng)可以正常訪問return_url: #頁面跳轉(zhuǎn)同步通知頁面路徑 需http://格式的完整路徑,不能加?id=123這類自定義參數(shù),必須外網(wǎng)可以正常訪問(如果是二維碼掃碼可以不配置)sing_type: RSA2charset: utf-8gatewayurl: https://openapi.alipaydev.com/gateway.do # https://openapi.alipay.com/gateway.do 線上地址# 保存支付日志的地址log_path: D:/tmp/## 支付二維碼配置 qrcode:# 二維碼logo地址logo_location: D:\tmp\yjtp.png# 生成的二維碼地址file_path: D:\tmp\pay\06、生成二維碼工具類
package com.qd.utils;import com.google.zxing.*; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.BitMatrix; import com.google.zxing.common.HybridBinarizer; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import org.bouncycastle.util.encoders.Base64Encoder; import sun.misc.BASE64Encoder;import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.io.*; import java.util.Hashtable; import java.util.Random;/*** 二維碼生成工具類** @Blog: https://www.cnblogs.com/qd666* @Author: qiandu* @Date: 2021/11/27 19:05*/ public class QRCodeUtil {private static final String CHARSET = "utf-8";private static final String FORMAT = "JPG";// 二維碼尺寸private static final int QRCODE_SIZE = 300;// LOGO寬度private static final int LOGO_WIDTH = 60;// LOGO高度private static final int LOGO_HEIGHT = 60;private static BufferedImage createImage(String content, String logoPath, boolean needCompress) throws Exception {Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);hints.put(EncodeHintType.CHARACTER_SET, CHARSET);hints.put(EncodeHintType.MARGIN, 1);BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE,hints);int width = bitMatrix.getWidth();int height = bitMatrix.getHeight();BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);for (int x = 0; x < width; x++) {for (int y = 0; y < height; y++) {image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);}}if (logoPath == null || "".equals(logoPath)) {return image;}// 插入圖片QRCodeUtil.insertImage(image, logoPath, needCompress);return image;}/*** 插入LOGO** @param source 二維碼圖片* @param logoPath LOGO圖片地址* @param needCompress 是否壓縮* @throws Exception*/private static void insertImage(BufferedImage source, String logoPath, boolean needCompress) throws Exception {File file = new File(logoPath);if (!file.exists()) {throw new Exception("logo file not found.");}Image src = ImageIO.read(new File(logoPath));int width = src.getWidth(null);int height = src.getHeight(null);if (needCompress) { // 壓縮LOGOif (width > LOGO_WIDTH) {width = LOGO_WIDTH;}if (height > LOGO_HEIGHT) {height = LOGO_HEIGHT;}Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);Graphics g = tag.getGraphics();g.drawImage(image, 0, 0, null); // 繪制縮小后的圖g.dispose();src = image;}// 插入LOGOGraphics2D graph = source.createGraphics();int x = (QRCODE_SIZE - width) / 2;int y = (QRCODE_SIZE - height) / 2;graph.drawImage(src, x, y, width, height, null);Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);graph.setStroke(new BasicStroke(3f));graph.draw(shape);graph.dispose();}/*** 生成二維碼(內(nèi)嵌LOGO)* 二維碼文件名隨機,文件名可能會有重復** @param content 內(nèi)容* @param logoPath LOGO地址* @param destPath 存放目錄* @param needCompress 是否壓縮LOGO* @throws Exception*/public static String encode(String content, String logoPath, String destPath, boolean needCompress) throws Exception {BufferedImage image = QRCodeUtil.createImage(content, logoPath, needCompress);mkdirs(destPath);String fileName = System.currentTimeMillis() + new Random().nextInt(99999999) + "." + FORMAT.toLowerCase();ImageIO.write(image, FORMAT, new File(destPath + "/" + fileName));return fileName;}/*** 生成二維碼(內(nèi)嵌LOGO)* 調(diào)用者指定二維碼文件名** @param content 內(nèi)容* @param logoPath LOGO地址* @param destPath 存放目錄* @param fileName 二維碼文件名* @param needCompress 是否壓縮LOGO* @throws Exception*/public static String encode(String content, String logoPath, String destPath, String fileName, boolean needCompress) throws Exception {BufferedImage image = QRCodeUtil.createImage(content, logoPath, needCompress);mkdirs(destPath);fileName = fileName.substring(0, fileName.indexOf(".") > 0 ? fileName.indexOf(".") : fileName.length())+ "." + FORMAT.toLowerCase();ImageIO.write(image, FORMAT, new File(destPath + "/" + fileName));return fileName;}/*** 當文件夾不存在時,mkdirs會自動創(chuàng)建多層目錄,區(qū)別于mkdir.* (mkdir如果父目錄不存在則會拋出異常)** @param destPath 存放目錄*/public static void mkdirs(String destPath) {File file = new File(destPath);if (!file.exists() && !file.isDirectory()) {file.mkdirs();}}/*** 生成二維碼(內(nèi)嵌LOGO)** @param content 內(nèi)容* @param logoPath LOGO地址* @param destPath 存儲地址* @throws Exception*/public static String encode(String content, String logoPath, String destPath) throws Exception {return QRCodeUtil.encode(content, logoPath, destPath, false);}/*** 生成二維碼** @param content 內(nèi)容* @param destPath 存儲地址* @param needCompress 是否壓縮LOGO* @throws Exception*/public static String encode(String content, String destPath, boolean needCompress) throws Exception {return QRCodeUtil.encode(content, null, destPath, needCompress);}/*** 生成二維碼** @param content 內(nèi)容* @param destPath 存儲地址* @throws Exception*/public static String encode(String content, String destPath) throws Exception {return QRCodeUtil.encode(content, null, destPath, false);}/*** 生成二維碼(內(nèi)嵌LOGO)** @param content 內(nèi)容* @param logoPath LOGO地址* @param output 輸出流* @param needCompress 是否壓縮LOGO* @throws Exception*/public static void encode(String content, String logoPath, OutputStream output, boolean needCompress)throws Exception {BufferedImage image = QRCodeUtil.createImage(content, logoPath, needCompress);ImageIO.write(image, FORMAT, output);}/*** 生成二維碼** @param content 內(nèi)容* @param output 輸出流* @throws Exception*/public static void encode(String content, OutputStream output) throws Exception {QRCodeUtil.encode(content, null, output, false);}/*** 解析二維碼** @param file 二維碼圖片* @return* @throws Exception*/public static String decode(File file) throws Exception {BufferedImage image;image = ImageIO.read(file);if (image == null) {return null;}BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));Result result;Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>();hints.put(DecodeHintType.CHARACTER_SET, CHARSET);result = new MultiFormatReader().decode(bitmap, hints);String resultStr = result.getText();return resultStr;}/*** 解析二維碼** @param path 二維碼圖片地址* @return* @throws Exception*/public static String decode(String path) throws Exception {return QRCodeUtil.decode(new File(path));}/*** 二維碼圖片轉(zhuǎn)Base64** @param imagePath 圖片路徑* @return*/public static String imageToBase64(String imagePath) throws IOException {// 獲取文件流FileInputStream inputStream = new FileInputStream(imagePath);// 轉(zhuǎn)成字節(jié)byte[] bytes = new byte[inputStream.available()];inputStream.read(bytes);inputStream.close();// 對字符數(shù)組進行base64編碼BASE64Encoder base64Encoder = new BASE64Encoder();return base64Encoder.encode(bytes);}public static void main(String[] args) throws Exception {String text = "http://www.baidu.com";//不含Logo//QRCodeUtil.encode(text, null, "e:\\", true);//含Logo,不指定二維碼圖片名//QRCodeUtil.encode(text, "e:\\csdn.jpg", "e:\\", true);//含Logo,指定二維碼圖片名String qrcode = QRCodeUtil.encode(text, "D:\\qdSystem\\Resources\\Pictures\\Saved Pictures\\yjtp.png", "D:\\tmp", true);System.out.println(qrcode);String decode = QRCodeUtil.decode("D:\\tmp\\qrcode.jpg");System.out.println(decode);} }07、參數(shù)封裝成實體類
@Data @AllArgsConstructor @NoArgsConstructor public class Order implements java.io.Serializable {// 商品訂單號 必填private String out_trade_no;// 訂單名稱 必填private String subject;// 付款金額 必填private String total_amount;// 商品描述private String description;// 超時時間private String timeout_express = "10m";// 產(chǎn)品編號 必填private String product_code = "FACE_TO_FACE_PAYMENT"; // 二維碼當面支付填這個 FACE_TO_FACE_PAYMENT PC:FAST_INSTANT_TRADE_PAY }08、AliQrController(自行拆分service)
/*** 支付寶二維碼支付demo*/ @Slf4j @Controller @RequestMapping("/qr") public class AliQrController {@Value("${qrcode.logo_location}")private String logoLocation;@Value("${qrcode.file_path}")private String filePath;@ResponseBody@RequestMapping("/paySuccess")public String paySuccess() {return "支付成功!";}@ResponseBody@RequestMapping("/payFail")public String payFail() {return "支付失敗!";}/*** 調(diào)用支付*/@RequestMapping(value = "/goAlipay", produces = "text/html; charset=UTF-8")@ResponseBodypublic String goAlipay(String subject, String totalAmount, String description) throws IOException, AlipayApiException {// 獲得初始化的AlipayClientAlipayClient alipayClient = AliPayUtil.alipayClient;// 訂單模型測試//AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();//model.setOutTradeNo(String.valueOf(System.currentTimeMillis()));//model.setSubject("subject:120元");//model.setTotalAmount("120");//model.setBody("這是一個description");//model.setProductCode("FACE_TO_FACE_PAYMENT");//model.setTimeoutExpress("10m");//AlipayTradePrecreateRequest pagePayRequest = new AlipayTradePrecreateRequest();//pagePayRequest.setReturnUrl(AlipayConfig.return_url);//pagePayRequest.setNotifyUrl(AlipayConfig.notify_url);//pagePayRequest.setBizModel(model); 1:設(shè)置參數(shù)Order order = new Order();order.setOut_trade_no(String.valueOf(System.currentTimeMillis()));order.setSubject("subject");order.setTotal_amount("120.00");order.setDescription("這是一個description");//2:設(shè)置請求參數(shù)AlipayTradePrecreateRequest pagePayRequest = new AlipayTradePrecreateRequest();// 3:設(shè)置頁面跳轉(zhuǎn)同步通知頁面路徑pagePayRequest.setReturnUrl(AlipayConfig.return_url);// 4:設(shè)置服務(wù)器異步通知路徑pagePayRequest.setNotifyUrl(AlipayConfig.notify_url);// 5:以json格式封裝數(shù)據(jù)pagePayRequest.setBizContent(JSON.toJSONString(order));//請求AlipayTradePrecreateResponse response = alipayClient.execute(pagePayRequest);// 二維碼base64編碼內(nèi)容String base64Image = "";// 判斷是否調(diào)用成功if (response.isSuccess()) {JSONObject jsonObject = JSON.parseObject(response.getBody());JSONObject rsj = (JSONObject) jsonObject.get("alipay_trade_precreate_response");// 生成的二維碼地址內(nèi)容String qr_code = (String) rsj.get("qr_code");// 生成二維碼try {// 支付寶返回內(nèi)容 logo地址 二維碼生成地址 是否壓縮String encode = QRCodeUtil.encode(qr_code, logoLocation, filePath, true);log.info("工具生成的二維碼地址內(nèi)容------->{}", encode);base64Image = QRCodeUtil.imageToBase64(filePath + encode);} catch (Exception e) {e.printStackTrace();}}log.info("支付寶返回內(nèi)容------->{}", response.getBody());// 返回base64結(jié)果 前端自行處理return base64Image;}/*** 同步回調(diào)*/@RequestMapping("/return_url")public ModelAndView return_url(HttpServletResponse response, HttpServletRequest request) throws IOException, AlipayApiException {log.info(">>>>>>>>支付成功, 進入同步通知接口...");boolean verifyResult = AliPayUtil.rsaCheckV1(request);ModelAndView mv = null;if (verifyResult) {//商戶訂單號String out_trade_no = AliPayUtil.getByte(request.getParameter("out_trade_no"));//支付寶交易號String trade_no = AliPayUtil.getByte(request.getParameter("trade_no"));log.info("商戶訂單號:{},支付寶交易號,{}", out_trade_no, trade_no);mv = new ModelAndView("/qr/paySuccess");} else {mv = new ModelAndView("/qr/payFail");}return mv;}/*** 異步回調(diào)*/@ResponseBody@RequestMapping(value = "/notify_url", method = RequestMethod.POST)public String notify_url(HttpServletResponse response, HttpServletRequest request) throws IOException, AlipayApiException {log.info(">>>>>>>>支付成功, 進入異步通知接口...");// 一定要驗簽,防止黑客篡改參數(shù)Map<String, String[]> parameterMap = request.getParameterMap();StringBuilder notifyBuild = new StringBuilder(">>>>>>>>>> alipay notify >>>>>>>>>>>>>>\n");parameterMap.forEach((key, value) -> notifyBuild.append(key + "=" + value[0] + "\n"));notifyBuild.append(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");log.info(notifyBuild.toString());boolean flag = AliPayUtil.rsaCheckV1(request);if (flag) {/*** TODO 需要嚴格按照如下描述校驗通知數(shù)據(jù)的正確性** 商戶需要驗證該通知數(shù)據(jù)中的out_trade_no是否為商戶系統(tǒng)中創(chuàng)建的訂單號,* 并判斷total_amount是否確實為該訂單的實際金額(即商戶訂單創(chuàng)建時的金額),* 同時需要校驗通知中的seller_id(或者seller_email) 是否為out_trade_no這筆單據(jù)的對應(yīng)的操作方(有的時候,一個商戶可能有多個seller_id/seller_email),** 上述有任何一個驗證不通過,則表明本次通知是異常通知,務(wù)必忽略。* 在上述驗證通過后商戶必須根據(jù)支付寶不同類型的業(yè)務(wù)通知,正確的進行不同的業(yè)務(wù)處理,并且過濾重復的通知結(jié)果數(shù)據(jù)。* 在支付寶的業(yè)務(wù)通知中,只有交易通知狀態(tài)為TRADE_SUCCESS或TRADE_FINISHED時,支付寶才會認定為買家付款成功。*///交易狀態(tài)String tradeStatus = AliPayUtil.getByte(request.getParameter("trade_status"));// 商戶訂單號String out_trade_no = AliPayUtil.getByte(request.getParameter("out_trade_no"));//支付寶交易號String trade_no = AliPayUtil.getByte(request.getParameter("trade_no"));//付款金額String total_amount = AliPayUtil.getByte(request.getParameter("total_amount"));log.info("交易狀態(tài):{},商戶訂單號:{},支付寶交易號:{},付款金額:{}", tradeStatus, out_trade_no, trade_no, total_amount);// TRADE_FINISHED(表示交易已經(jīng)成功結(jié)束,并不能再對該交易做后續(xù)操作);// TRADE_SUCCESS(表示交易已經(jīng)成功結(jié)束,可以對該交易做后續(xù)操作,如:分潤、退款等);if (tradeStatus.equals("TRADE_FINISHED")) {//判斷該筆訂單是否在商戶網(wǎng)站中已經(jīng)做過處理//如果沒有做過處理,根據(jù)訂單號(out_trade_no)在商戶網(wǎng)站的訂單系統(tǒng)中查到該筆訂單的詳細,// 并判斷total_amount是否確實為該訂單的實際金額(即商戶訂單創(chuàng)建時的金額),并執(zhí)行商戶的業(yè)務(wù)程序//請務(wù)必判斷請求時的total_fee、seller_id與通知時獲取的total_fee、seller_id為一致的//如果有做過處理,不執(zhí)行商戶的業(yè)務(wù)程序//注意://如果簽約的是可退款協(xié)議,退款日期超過可退款期限后(如三個月可退款),支付寶系統(tǒng)發(fā)送該交易狀態(tài)通知//如果沒有簽約可退款協(xié)議,那么付款完成后,支付寶系統(tǒng)發(fā)送該交易狀態(tài)通知。} else if (tradeStatus.equals("TRADE_SUCCESS")) {//判斷該筆訂單是否在商戶網(wǎng)站中已經(jīng)做過處理//如果沒有做過處理,根據(jù)訂單號(out_trade_no)在商戶網(wǎng)站的訂單系統(tǒng)中查到該筆訂單的詳細,// 并判斷total_amount是否確實為該訂單的實際金額(即商戶訂單創(chuàng)建時的金額),并執(zhí)行商戶的業(yè)務(wù)程序//請務(wù)必判斷請求時的total_fee、seller_id與通知時獲取的total_fee、seller_id為一致的//如果有做過處理,不執(zhí)行商戶的業(yè)務(wù)程序//注意://如果簽約的是可退款協(xié)議,那么付款完成后,支付寶系統(tǒng)發(fā)送該交易狀態(tài)通知。}return "success";}return "fail";}/***查看支付流水*/@RequestMapping(value = "/queryPay")@ResponseBodypublic String queryPay(String orderId) throws IOException, AlipayApiException {AlipayClient alipayClient = AliPayUtil.alipayClient;AlipayTradePagePayModel model = new AlipayTradePagePayModel();model.setOutTradeNo(orderId);//設(shè)置請求參數(shù)AlipayTradeQueryRequest alipayRequest = new AlipayTradeQueryRequest();alipayRequest.setBizModel(model);//請求String result = alipayClient.execute(alipayRequest).getBody();return result;}/*** 退款** @param orderNo 商戶訂單號* @return*/@PostMapping("/refund")@ResponseBodypublic String refund(String orderNo) throws AlipayApiException {AlipayTradeRefundRequest alipayRequest = new AlipayTradeRefundRequest();AlipayTradeRefundModel model = new AlipayTradeRefundModel();// 商戶訂單號model.setOutTradeNo(orderNo);// 退款金額model.setRefundAmount("0.1");// 退款原因model.setRefundReason("無理由退貨");// 退款訂單號(同一個訂單可以分多次部分退款,當分多次時必傳)String outOrderId = UUID.randomUUID().toString();model.setOutRequestNo(outOrderId);log.info("退款請求號:{}", outOrderId);alipayRequest.setBizModel(model);AlipayTradeRefundResponse alipayResponse = AliPayUtil.alipayClient.execute(alipayRequest);return alipayResponse.getBody();}/*** 退款查詢** @param orderNo 商戶訂單號* @param refundOrderNo 請求退款接口時,傳入的退款請求號,如果在退款請求時未傳入,則該值為創(chuàng)建交易時的外部訂單號* @return* @throws AlipayApiException*/@GetMapping("/refundQuery")@ResponseBodypublic String refundQuery(String orderNo, String refundOrderNo) throws AlipayApiException {AlipayTradeFastpayRefundQueryRequest alipayRequest = new AlipayTradeFastpayRefundQueryRequest();AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel();model.setOutTradeNo(orderNo);model.setOutRequestNo(refundOrderNo);alipayRequest.setBizModel(model);AlipayTradeFastpayRefundQueryResponse alipayResponse = AliPayUtil.alipayClient.execute(alipayRequest);System.out.println(alipayResponse.getBody());return alipayResponse.getBody();}/*** 關(guān)閉交易** @param orderNo* @return* @throws AlipayApiException*/@PostMapping("/close")@ResponseBodypublic String close(String orderNo) throws AlipayApiException {AlipayTradeCloseRequest alipayRequest = new AlipayTradeCloseRequest();AlipayTradeCloseModel model = new AlipayTradeCloseModel();model.setOutTradeNo(orderNo);alipayRequest.setBizModel(model);AlipayTradeCloseResponse alipayResponse = AliPayUtil.alipayClient.execute(alipayRequest);System.out.println(alipayResponse.getBody());return alipayResponse.getBody();} }09、訪問測試
tip:為了數(shù)據(jù)更加的安全,可以加密部分數(shù)據(jù),加密部分本文暫未涉及
五、yml文件加密
前言:在實際的項目開發(fā)中,在我們的配置文件中會有隱私數(shù)不想以明文方式展現(xiàn),所以要加密yml文件
01、導入依賴
<!--配置文件加密處理--> <dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>2.1.0</version> </dependency>02、編寫測試類
@SpringBootTest public class JasypTest {// 加密測試@Testpublic void encryptTest() {StandardPBEStringEncryptor standardPBEBigDecimalEncryptor = new StandardPBEStringEncryptor();EnvironmentPBEConfig config = new EnvironmentPBEConfig();config.setPassword("fsdfs8979!~#!@#rewr"); //密鑰 不要告訴別人!!!standardPBEBigDecimalEncryptor.setConfig(config);String plainText = "hell,qiandu"; // 要加密的內(nèi)容String encryptText = standardPBEBigDecimalEncryptor.encrypt(plainText);System.out.println("加密結(jié)果---->" + encryptText);}// 解密測試@Testpublic void decTest() {StandardPBEStringEncryptor standardPBEBigDecimalEncryptor = new StandardPBEStringEncryptor();EnvironmentPBEConfig config = new EnvironmentPBEConfig();config.setPassword("fsdfs8979!~#!@#rewr"); //密鑰 不要告訴別人!!!standardPBEBigDecimalEncryptor.setConfig(config);String encryptText = "6fJIuViOKsNpxPk8ce6CAUiIftXuKb9x"; // 要解密的內(nèi)容String plainText = standardPBEBigDecimalEncryptor.decrypt(encryptText);System.out.println("解密結(jié)果---->" + plainText);} }03、啟動類上加入注解
@SpringBootApplication @EnableEncryptableProperties //開啟加密注解 public class AlipayApplication {public static void main(String[] args) {SpringApplication.run(AlipayApplication.class, args);}}04、在yml中配置(記得作隔離!)
## jasypt配置 jasypt:encryptor:# 密鑰password: #你的密鑰結(jié)束~
示例代碼:https://chenyu6666.lanzoui.com/iK7qewz1o3a
參考文章:https://blog.csdn.net/qq_40673786/article/details/90260736
總結(jié)
以上是生活随笔為你收集整理的支付系列-对接支付宝支付的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 两个乞丐
- 下一篇: 2022前端面试题上岸手册-浏览器部分