前期配置準備可參考該鏈接文檔:https://blog.csdn.net/weixin_42739916/article/details/104320830
1、設置支付寶環境參數,並導入支付SDK依賴
//支付寶環境參數設置
ali.pay.appId=AppID
ali.pay.appPrivateKey=應用私鑰
ali.pay.publicKey=支付寶公鑰
ali.pay.returnUrl=回調地址
ali.pay.notifyUrl=異步地址
ali.pay.gateway=網關(測試和正式環境的網關不同,需要注意)
ali.pay.signType=RSA2
ali.pay.charset=UTF-8
ali.pay.format=json
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.8.10.ALL</version>
</dependency>
2、支付寶支付狀態常量類
package com.ganguomob.dev.dsprwpt.domain.service.alipay;
import org.springframework.util.StringUtils;
/**
* Created by luotj on 2019-08-15.
*/
public enum AliTradeStatus {
WAIT_BUYER_PAY("WAIT_BUYER_PAY", "交易創建,等待買家付款"),
TRADE_CLOSED("TRADE_CLOSED", "未付款交易超時關閉,或支付完成後全額退款"),
TRADE_SUCCESS("TRADE_SUCCESS", "交易支付成功"),
TRADE_FINISHED("TRADE_FINISHED", "交易結束,不可退款");
private String code;
private String msg;
AliTradeStatus(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
public static AliTradeStatus fromCode(String code) {
if (StringUtils.pathEquals(code, WAIT_BUYER_PAY.code)) {
return WAIT_BUYER_PAY;
}
if (StringUtils.pathEquals(code, TRADE_CLOSED.code)) {
return TRADE_CLOSED;
}
if (StringUtils.pathEquals(code, TRADE_SUCCESS.code)) {
return TRADE_SUCCESS;
}
if (StringUtils.pathEquals(code, TRADE_FINISHED.code)) {
return TRADE_FINISHED;
}
throw new RuntimeException("code error");
}
}
3、請求實體類
package com.ganguomob.dev.dsprwpt.ui.dto.request.api.alipay;
import lombok.Data;
import java.math.BigDecimal;
/**
* @author: create by luotj
* date: 2019-12-25 18:39
**/
@Data
public class AliPayRequest {
//支付金額
private BigDecimal totalAmount;
//客戶支付編碼
private String outTradeNo;
//訂單名稱
private String subject;
//商品描述
private String body;
//充值記錄id
private Long rechargeId;
//支付方式:1.支付寶
private Integer type;
}
4、支付寶回調響應類
package com.ganguomob.dev.dsprwpt.ui.dto.response.api.alipay;
import com.ganguomob.dev.dsprwpt.domain.service.alipay.AliTradeStatus;
import io.swagger.annotations.ApiModel;
import lombok.Data;
/**
* @author: create by luotj
* date: 2019-12-28 16:02
**/
@ApiModel
@Data
public class AlipayCallbackResponse {
// notify_time 通知時間 Date 是 通知的發送時間。格式爲yyyy-MM-dd HH:mm:ss 2015-14-27 15:45:58
private String notifyTime;
// notify_type 通知類型 String(64) 是 通知的類型 trade_status_sync
private String notifyType;
// notify_id 通知校驗ID String(128) 是 通知校驗ID ac05099524730693a8b330c5ecf72da9786
private String notifyId;
// app_id 支付寶分配給開發者的應用Id String(32) 是 支付寶分配給開發者的應用Id 2014072300007148
private String appId;
// charset 編碼格式 String(10) 是 編碼格式,如utf-8、gbk、gb2312等 utf-8
private String charset;
// version 接口版本 String(3) 是 調用的接口版本,固定爲:1.0 1.0
private String version;
// sign_type 簽名類型 String(10) 是 商戶生成簽名字符串所使用的簽名算法類型,目前支持RSA2和RSA,推薦使用RSA2 RSA2
private String signType;
// sign 簽名 String(256) 是 請參考文末 異步返回結果的驗籤 601510b7970e52cc63db0f44997cf70e
private String sign;
// trade_no 支付寶交易號 String(64) 是 支付寶交易憑證號 2013112011001004330000121536
private String tradeNo;
// out_trade_no 商戶訂單號 String(64) 是 原支付請求的商戶訂單號 6823789339978248
private String outTradeNo;
// out_biz_no 商戶業務號 String(64) 否 商戶業務ID,主要是退款通知中返回退款申請的流水號 HZRF001
private String outBizNo;
// buyer_id 買家支付寶用戶號 String(16) 否 買家支付寶賬號對應的支付寶唯一用戶號。以2088開頭的純16位數字 2088102122524333
private String buyerId;
// buyer_logon_id 買家支付寶賬號 String(100) 否 買家支付寶賬號 15901825620
private String buyerLogonId;
// seller_id 賣家支付寶用戶號 String(30) 否 賣家支付寶用戶號 2088101106499364
private String sellerId;
// seller_email 賣家支付寶賬號 String(100) 否 賣家支付寶賬號 [email protected]
private String sellerEmail;
// trade_status 交易狀態 String(32) 否 交易目前所處的狀態,見下張表 交易狀態說明 TRADE_CLOSED
private AliTradeStatus tradeStatus;
// total_amount 訂單金額 Number(9,2) 否 本次交易支付的訂單金額,單位爲人民幣(元) 20
private String totalAmount;
// receipt_amount 實收金額 Number(9,2) 否 商家在交易中實際收到的款項,單位爲元 15
private String receiptAmount;
// invoice_amount 開票金額 Number(9,2) 否 用戶在交易中支付的可開發票的金額 10.00
private String invoiceAmount;
// buyer_pay_amount 付款金額 Number(9,2) 否 用戶在交易中支付的金額 13.88
private String buyerPayAmount;
// point_amount 集分寶金額 Number(9,2) 否 使用集分寶支付的金額 12.00
private String pointAmount;
// refund_fee 總退款金額 Number(9,2) 否 退款通知中,返回總退款金額,單位爲元,支持兩位小數 2.58
private String refundFee;
// subject 訂單標題 String(256) 否 商品的標題/交易標題/訂單標題/訂單關鍵字等,是請求時對應的參數,原樣通知回來 當面付交易
private String subject;
// body 商品描述 String(400) 否 該訂單的備註、描述、明細等。對應請求時的body參數,原樣通知回來 當面付交易內容
private String body;
// gmt_create 交易創建時間 Date 否 該筆交易創建的時間。格式爲yyyy-MM-dd HH:mm:ss 2015-04-27 15:45:57
private String gmtCreate;
// gmt_payment 交易付款時間 Date 否 該筆交易的買家付款時間。格式爲yyyy-MM-dd HH:mm:ss 2015-04-27 15:45:57
private String gmtPayment;
// gmt_refund 交易退款時間 Date 否 該筆交易的退款時間。格式爲yyyy-MM-dd HH:mm:ss.S 2015-04-28 15:45:57.320
private String gmtRefund;
// gmt_close 交易結束時間 Date 否 該筆交易結束時間。格式爲yyyy-MM-dd HH:mm:ss 2015-04-29 15:45:57
private String gmtClose;
// fund_bill_list 支付金額信息 String(512) 否 支付成功的各個渠道金額信息,詳見下表 資金明細信息說明 [{“amount”:“15.00”,“fundChannel”:“ALIPAYACCOUNT”}]
private String fundBillList;
// passback_params 回傳參數 String(512) 否 公共回傳參數,如果請求時傳遞了該參數,則返回給商戶時會在異步通知時將該參數原樣返回。本參數必須進行UrlEncode之後纔可以發送給支付寶 merchantBizType%3d3C%26merchantBizNo%3d2016010101111
private String passbackParams;
// voucher_detail_list 優惠券信息 String 否 本交易支付時所使用的所有優惠券信息,詳見下表 優惠券信息說明 [{“amount”:“0.20”,“merchantContribute”:“0.00”,“name”:“一鍵創建券模板的券名稱”,“otherContribute”:“0.20”,“type”:“ALIPAY_DISCOUNT_VOUCHER”,“memo”:“學生卡8折優惠”]
private String voucherDetailList;
}
5、支付查詢結果響應類
package com.ganguomob.dev.dsprwpt.ui.dto.response.api.alipay;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 阿里支付查詢響應
*
* @author: create by luotj
* date: 2020-01-14 14:32
**/
@ApiModel
@Data
public class AliQueryResponse {
@ApiModelProperty("交易支付成功:1,其他類型返回0")
public Integer status;
@ApiModelProperty("支付返回data")
public String data;
}
6、Controller層發起請求
package com.ganguomob.dev.dsprwpt.ui.controller.api.user;
import com.ganguomob.dev.dsprwpt.service.api.user.UserService;
import com.ganguomob.dev.dsprwpt.ui.dto.request.api.alipay.AliPayRequest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "01. 賬戶管理")
@RestController
@RequestMapping("/api/account")
public class AccountController {
@Autowired
private UserServiceImpl mUserService;
@ApiOperation(value = "支付充值")
@PutMapping("/pay")
public String payRecharge() {
//支付寶支付信息
AliPayRequest aliPayRequest = new AliPayRequest()
.setBody("測試Body")
.setOutTradeNo("45313153")
.setSubject("測試subject")
.setRechargeId(1L)
.setType(1)
.setTotalAmount(new BigDecimal(100));
return mUserService.alipayPreCreate(aliPayRequest);
}
}
7、異步回調地址Controller
package com.ganguomob.dev.dsprwpt.ui.controller.api.alipay;
import com.ganguomob.dev.dsprwpt.service.api.alipay.AliPayService;
import com.ganguomob.dev.dsprwpt.ui.dto.response.api.alipay.AliQueryResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @author: create by luotj
* date: 2019-12-27 10:47
**/
@RestController
@Slf4j
@Api(tags = "12. 支付寶接口")
@RequestMapping("/api/alipay")
public class AdminAlipayNotificationController {
@Autowired
private UserServiceImpl mUserServiceImpl;
@ApiOperation(value = "異步消息回調")
@PostMapping("/notify/callback")
public String alipayCallback(HttpServletRequest request) {
return mUserServiceImpl.alipayCallback(request);
}
@ApiOperation(value = "查詢訂單支付狀態")
@GetMapping("/check-status/{orderNo}")
public AliQueryResponse checkOrderPayingStatus(
@PathVariable("orderNo") @ApiParam(value = "訂單號", required = true) String orderNo) {
return mUserServiceImpl.alipayQuery(orderNo);
}
}
8、Service層生成預支付,並返回信息給前端生成支付二維碼(實現邏輯核心)
package com.ganguomob.dev.dsprwpt.service.api.user;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ganguomob.dev.base.infrastructure.exception.CommonBusinessException;
import com.ganguomob.dev.dsprwpt.constant.PayType;
import com.ganguomob.dev.dsprwpt.constant.RechargeStatus;
import com.ganguomob.dev.dsprwpt.domain.repository.fee.IFeeRecordRepository;
import com.ganguomob.dev.dsprwpt.domain.repository.user.IUserRepository;
import com.ganguomob.dev.dsprwpt.domain.service.alipay.AliTradeStatus;
import com.ganguomob.dev.dsprwpt.infrastructure.util.xinbao.XinBaoUtils;
import com.ganguomob.dev.dsprwpt.jooq.tables.pojos.FeeRecordPOJO;
import com.ganguomob.dev.dsprwpt.ui.dto.request.api.alipay.AliPayRequest;
import com.ganguomob.dev.dsprwpt.ui.dto.response.api.alipay.AliQueryResponse;
import com.ganguomob.dev.dsprwpt.ui.dto.response.api.alipay.AlipayCallbackResponse;
import com.google.common.base.CaseFormat;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@Slf4j
@Service
public class UserServiceImpl{
@Value("${ali.pay.appId}")
private String appId;
@Value("${ali.pay.appPrivateKey}")
private String privateKey;
@Value("${ali.pay.publicKey}")
private String aliKey;
@Value("${ali.pay.returnUrl}")
private String returnUrl;
@Value("${ali.pay.notifyUrl}")
private String notifyUrl;
@Value("${ali.pay.gateway}")
private String gateWay;
@Value("${ali.pay.signType}")
private String signType;
@Value("${ali.pay.charset}")
private String charset;
@Value("${ali.pay.format}")
private String format;
/**
* 支付寶支付回調邏輯
* @param request
* @return
*/
@Override
public String alipayCallback(HttpServletRequest request) {
//將異步通知中收到的所有參數都存放到 map 中
Map<String, String> paramsMap = callbackRequestToMap(request);
alipayCheckRSAV1By(paramsMap);
AlipayCallbackResponse response = mapToAlipayResponse(paramsMap);
log.info("支付寶回調信息:訂單序列號{}", response.getOutTradeNo());
if (response.getTradeStatus() == null) {
log.error("ali pay callback response status is null " + "\n"
+ "out trade no:" + response.getOutTradeNo());
return "fail";
}
String orderNumber = response.getOutTradeNo();
//支付查詢是否支付成功
AliQueryResponse queryResponse = alipayQuery(orderNumber);
if (queryResponse.getStatus() == 1) {
//支付成功回調接口,並做狀態的判斷更改,邏輯
if (recordPOJO.getRechargeStatus().equals(RechargeStatus.PAY_FAIL)) {
//支付失敗,邏輯
}
return "success";
} else {
log.info("訂單序列號:{},支付失敗", orderNumber);
return "fail";
}
}
//生成預支付邏輯方法
public String alipayPreCreate(AliPayRequest aliPayRequest) {
log.info("預支付生成二維碼");
//校驗數據
checkPayParamsValid(aliPayRequest);
//創建客戶端
AlipayClient alipayClient = new DefaultAlipayClient(gateWay, appId, privateKey, format,charset, aliKey, signType);
//SDK中的請求實體類
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
//判斷訂單號是否爲空
Map<String, Object> paramMap = new HashMap<>();
if (aliPayRequest.getOutTradeNo().isEmpty()) {
paramMap.put("type", "2");
paramMap.put("data", "訂單號不能爲空");
return null;
}
//設置異步、回調地址
request.setNotifyUrl(notifyUrl);
request.setReturnUrl(returnUrl);
//設置主要參數,參數說明參考文檔
paramMap.put("timeout_express", "5m");
paramMap.put("out_trade_no", aliPayRequest.getOutTradeNo());
paramMap.put("total_amount", aliPayRequest.getTotalAmount().doubleValue());
paramMap.put("subject", aliPayRequest.getSubject());
//將數據轉換爲json數據,並設置到request中
ObjectMapper mapper = new ObjectMapper();
String qrCode = null;
try {
//發起預支付請求
String bizContent = mapper.writeValueAsString(paramMap);
request.setBizContent(bizContent);
AlipayTradePrecreateResponse precreateResponse = alipayClient.execute(request);
//獲取支付二維碼內容
qrCode = precreateResponse.getQrCode();
} catch (AlipayApiException e) {
log.error("AlipayApiException,錯誤信息:{},ErrMsg:{},msg:{}",
e.getErrCode(), e.getErrMsg(), e.getMessage());
} catch (JsonProcessingException e) {
log.error("轉換數據失敗,錯誤信息:{}", e.getMessage());
}
//返回支付二維碼內容(前段根據該內容生成支付二維碼)
return qrCode;
}
/**
* 阿里支付查詢
*
* @return
*/
@Override
public AliQueryResponse alipayQuery(String outTradeNo) {
AlipayClient alipayClient = new DefaultAlipayClient(gateWay, appId, privateKey, format,
charset, aliKey, signType);
//創建查詢對象
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("out_trade_no", outTradeNo);
//轉換爲json數據
ObjectMapper mapper = new ObjectMapper();
AliQueryResponse queryResponse = new AliQueryResponse();
try {
String queryMap = mapper.writeValueAsString(paramMap);
request.setBizContent(queryMap);
//執行查詢
AlipayTradeQueryResponse response = alipayClient.execute(request);
JSONObject jsonObject = JSON.parseObject(response.getBody())
.getJSONObject("alipay_trade_query_response");
String code = jsonObject.getString("code");
String msg = jsonObject.getString("msg");
if ("10000".equals(code) && "Success".equals(msg)) {
//支付成功,邏輯
queryResponse.setStatus(1)
.setData(tradeStatus);
} else {
//其它狀態,邏輯
queryResponse.setStatus(0)
.setData(tradeStatus);
}
} catch (Exception e) {
log.error("數據轉換異常:msg{}", e.getMessage());
}
return queryResponse;
}
private Map<String, String> callbackRequestToMap(HttpServletRequest request) {
// 獲取支付寶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] + ",";
}
// 亂碼解決,這段代碼在出現亂碼時使用。
// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name), valueStr);
}
return params;
}
//檢查簽名
public void alipayCheckRSAV1By(Map<String, String> paramsMap) {
boolean signVerified = false;
try {
//調用SDK驗證簽名
signVerified = AlipaySignature.rsaCheckV1(paramsMap, aliKey, charset, signType);
log.info("pay callback rsa check flag:" + signVerified);
} catch (AlipayApiException e) {
e.printStackTrace();
log.error("pay callback rsa check error" + "\n" +
"error code:" + e.getErrCode() + "\n" +
"error msg:" + e.getErrMsg());
throw new RuntimeException();
}
}
//校驗數據
private void checkPayParamsValid(AliPayRequest params) {
//訂單編碼不能爲空
if (Strings.isBlank(params.getOutTradeNo())) {
throw new RuntimeException("訂單編碼不能爲空");
}
//商品信息不能爲空
if (Strings.isBlank(params.getBody())) {
throw new RuntimeException("商品信息不能爲空");
}
//訂單名稱不能爲空
if (Strings.isBlank(params.getSubject())) {
throw new RuntimeException("訂單名稱不能爲空");
}
//支付價格必須大於0
if (params.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new RuntimeException("支付價格必須大於0");
}
}
private AlipayCallbackResponse mapToAlipayResponse(Map<String, String> params) {
AlipayCallbackResponse response = new AlipayCallbackResponse();
response.setNotifyTime(params.get("notify_time"));
response.setNotifyType(params.get("notify_type"));
response.setNotifyId(params.get("notify_id"));
response.setAppId(params.get("app_id"));
response.setCharset(params.get("charset"));
response.setVersion(params.get("version"));
response.setSignType(params.get("sign_type"));
response.setSign(params.get("sign"));
response.setTradeNo(params.get("trade_no"));
response.setOutTradeNo(params.get("out_trade_no"));
response.setOutBizNo(params.getOrDefault("out_biz_no", ""));
response.setBuyerId(params.getOrDefault("buyerId", ""));
response.setBuyerLogonId(params.getOrDefault("buyer_logon_id", ""));
response.setSellerId(params.getOrDefault("seller_id", ""));
response.setSellerEmail(params.getOrDefault("seller_email", ""));
response.setTradeStatus(params.containsKey("trade_status") ?
AliTradeStatus.fromCode(params.get("trade_status")) : null);
response.setTotalAmount(params.getOrDefault("total_amount", ""));
response.setReceiptAmount(params.getOrDefault("receipt_amount", ""));
response.setInvoiceAmount(params.getOrDefault("invoice_amount", ""));
response.setBuyerPayAmount(params.getOrDefault("buyer_pay_amount", ""));
response.setPointAmount(params.getOrDefault("point_amount", ""));
response.setRefundFee(params.getOrDefault("refund_fee", ""));
response.setSubject(params.getOrDefault("subject", ""));
response.setBody(params.getOrDefault("body", ""));
response.setGmtCreate(params.getOrDefault("gmt_create", ""));
response.setGmtPayment(params.getOrDefault("gmt_payment", ""));
response.setGmtRefund(params.getOrDefault("gmt_refund", ""));
response.setGmtClose(params.getOrDefault("gmt_close", ""));
response.setFundBillList(params.getOrDefault("fund_bill_list", ""));
response.setPassbackParams(params.getOrDefault("passback_params", ""));
response.setVoucherDetailList(params.getOrDefault("voucher_detail_list", ""));
return response;
}
}