應(yīng)用配置
- 登錄支付寶平臺货徙,簽約需要的服務(wù)
-
切換到開放平臺褂删,創(chuàng)建應(yīng)用并審核上線
image.png
支付配置
獲取方式已在字段注釋中
package org.weapon.core.pay.alipay.config;
import lombok.Data;
import java.io.Serializable;
/**
* 支付配置信息
*
* @author lieber
*/
@Data
public class AliPayConfig implements Serializable {
/**
* 設(shè)置應(yīng)用Id类腮,創(chuàng)建應(yīng)用時支付寶提供鉴裹,后續(xù)可查看
*/
private String appId;
/**
* 設(shè)置應(yīng)用私鑰,設(shè)置“接口加簽方式”時設(shè)置的立膛,后續(xù)不能查看晨汹,設(shè)置時牢記终议,文件名為:域名_私鑰.txt虫埂, 非->應(yīng)用私鑰2048.txt
*/
private String privateKey;
/**
* 應(yīng)用公鑰證書內(nèi)容 appCertPublicKey_xxx.cert
*/
private String appCertContent;
/**
* 支付寶公鑰證書內(nèi)容 -- alipayCertPublicKey_RSA2.cert
*/
private String aliPayCertContent;
/**
* 支付寶根證書內(nèi)容 alipayRootCert.cert
*/
private String aliPayRootCertContent;
/**
* 可直接從證書中獲取
*/
private String publicKey;
}
各個方式的支付及查詢處理
引入支付寶SDK
<!-- 支付寶sdk -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.10.97.ALL</version>
</dependency>
<!-- 二維碼sdk -->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.1.0</version>
</dependency>
通用實體
- 下單結(jié)果祥山,喚起支付參數(shù)
package org.weapon.core.pay.center.ali;
import lombok.Builder;
import lombok.Data;
/**
* @author lieber
*/
@Data
@Builder
public class OrderResult {
private String body;
}
- 下單參數(shù)
package org.weapon.core.pay.center.ali;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
/**
* @author lieber
*/
@Data
public class PayParam {
/** 商品標(biāo)題/交易標(biāo)題/訂單標(biāo)題/訂單關(guān)鍵字 */
@NotBlank(message = "subject不能為空")
private String subject;
/** 外部訂單號 */
@NotBlank(message = "系統(tǒng)訂單號不能為空")
private String outTradeNo;
/** 支付總金額,單位分 */
@Min(0)
@Max(Integer.MAX_VALUE)
private Integer totalAmount;
/** 用戶付款中途退出返回商戶網(wǎng)站的地址 */
private String quitUrl;
/** 原樣返回字符 */
private String addition;
/** 商品描述,不必填 */
private String body;
/** 通知地址 */
@NotBlank(message = "通知地址不能為空")
private String notifyUrl;
@NotBlank(message = "回跳地址不能為空")
private String redirectUrl;
/** 支付者 */
private String payerIp;
private Integer timeoutExpress;
}
- 退款參數(shù)
package org.weapon.core.pay.center.ali;
import lombok.Data;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
/**
* @author lieber
*/
@Data
public class RefundParam {
/**
* 商戶訂單號
*/
@NotBlank(message = "原訂單號不能為空")
private String outTradeNo;
/**
* 商戶退款單號
*/
@NotBlank(message = "退款訂單號不能為空")
private String outRefundNo;
/**
* 退款金額
*/
@Min(0)
@Max(Integer.MAX_VALUE)
private int refund;
/**
* 原訂單總額
*/
@Min(0)
@Max(Integer.MAX_VALUE)
private int total;
/**
* 退款原因掉伏;非必傳
*/
private String reason;
@NotBlank(message = "通知地址不能為空")
private String notifyUrl;
}
- 退款查詢參數(shù)
package org.weapon.core.pay.center.ali;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
/**
* @author lieber
*/
@Data
public class RefundQueryParam implements Serializable {
/**
* 原訂單外部交易訂單號
*/
@NotBlank(message = "原訂單號不能為空")
private String outTradeNo;
/**
* 退款外部交易訂單號
*/
@NotBlank(message = "退款訂單號不能為空")
private String outRefundNo;
}
工具類
- 生成二維碼
package org.weapon.core.pay.center.ali;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.extern.slf4j.Slf4j;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.Hashtable;
import java.util.Map;
@Slf4j
public class QrCodeUtil {
private static final int BLACK = 0xFF000000;
private static final int WHITE = 0xFFFFFFFF;
private static String toBufferedImage(BitMatrix matrix) throws IOException {
int width = matrix.getWidth();
int height = matrix.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, matrix.get(x, y) ? BLACK : WHITE);
}
}
ByteArrayOutputStream stream = new ByteArrayOutputStream();
ImageIO.write(image, "png", stream);
return Base64.getEncoder().encodeToString(stream.toByteArray());
}
/**
* 將內(nèi)容contents生成長寬均為width的圖片
*/
public static String getQrCodeImage(String contents, int width) {
return getQrCodeImage(contents, width, width);
}
/**
* 將內(nèi)容contents生成長為width缝呕,寬為width的圖片
*/
public static String getQrCodeImage(String contents, int width, int height) {
try {
Map<EncodeHintType, Object> hints = new Hashtable<>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
hints.put(EncodeHintType.CHARACTER_SET, "UTF8");
BitMatrix bitMatrix = new MultiFormatWriter().encode(contents, BarcodeFormat.QR_CODE, width, height, hints);
return toBufferedImage(bitMatrix);
} catch (Exception e) {
log.error("create QR code error!", e);
return null;
}
}
}
- 獲取支付配置,可使用配置文件斧散、數(shù)據(jù)庫等
package org.weapon.core.pay.center.ali;
/**
* @author lieber
*/
public interface IAliPayConfigService {
/**
* 獲取支付寶支付配置
*
* @return 配置
*/
AliPayConfig getConfig();
}
支付寶支付及相關(guān)處理對接
package org.weapon.core.pay.center.ali;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.CertAlipayRequest;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.*;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @author lieber
*/
@Slf4j
public class AliPay {
private final static String SERVER_URL = "https://openapi.alipay.com/gateway.do";
private final static String SIGN_TYPE = "RSA2";
private final static String FORMAT = "json";
private final BigDecimal ONE_HUNDRED = new BigDecimal(100);
private IAliPayConfigService configService;
public AliPay(IAliPayConfigService configService) {
this.configService = configService;
}
private AlipayClient client = null;
/**
* 掃碼支付
*
* @param payParam 支付參數(shù)
* @return 結(jié)果
* @throws AlipayApiException 支付寶調(diào)用異常
*/
public OrderResult callNative(PayParam payParam) throws AlipayApiException {
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
model.setBody(payParam.getBody());
model.setSubject(payParam.getSubject());
model.setOutTradeNo(payParam.getOutTradeNo());
String timeoutExpress = this.timeoutExpress(payParam.getTimeoutExpress());
model.setTimeoutExpress(timeoutExpress);
model.setQrCodeTimeoutExpress(timeoutExpress);
model.setTotalAmount(new BigDecimal(payParam.getTotalAmount()).divide(ONE_HUNDRED, 2, BigDecimal.ROUND_DOWN).toPlainString());
model.setProductCode("FACE_TO_FACE_PAYMENT");
request.setBizModel(model);
request.setNotifyUrl(payParam.getNotifyUrl());
//這里和普通的接口調(diào)用不同供常,使用的是sdkExecute
AlipayTradePrecreateResponse response = this.getClient().sdkExecute(request);
return OrderResult.builder().body(QrCodeUtil.getQrCodeImage(response.getQrCode(), 400)).build();
}
/**
* APP支付
*
* @param payParam 支付參數(shù)
* @return 結(jié)果
* @throws AlipayApiException 支付寶調(diào)用異常
*/
public OrderResult callApp(PayParam payParam) throws AlipayApiException {
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
if (payParam.getAddition() != null) {
try {
model.setPassbackParams(URLEncoder.encode(payParam.getAddition(), StandardCharsets.UTF_8.name()));
} catch (UnsupportedEncodingException e) {
log.error("加密參數(shù)出現(xiàn)異常:{} exception: ", payParam.getAddition(), e);
}
}
model.setBody(payParam.getBody());
model.setSubject(payParam.getSubject());
model.setOutTradeNo(payParam.getOutTradeNo());
model.setTimeoutExpress(this.timeoutExpress(payParam.getTimeoutExpress()));
model.setTotalAmount(new BigDecimal(payParam.getTotalAmount()).divide(ONE_HUNDRED, 2, BigDecimal.ROUND_DOWN).toPlainString());
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
request.setNotifyUrl(payParam.getNotifyUrl());
request.setReturnUrl(payParam.getRedirectUrl());
//這里和普通的接口調(diào)用不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = this.getClient().sdkExecute(request);
return OrderResult.builder().body(response.getBody()).build();
}
/**
* H5支付
*
* @param payParam 支付參數(shù)
* @return 結(jié)果
* @throws AlipayApiException 支付寶調(diào)用異常
*/
public OrderResult callH5(PayParam payParam) throws AlipayApiException {
AlipayTradeWapPayModel wapPayModel = new AlipayTradeWapPayModel();
if (payParam.getAddition() != null) {
try {
wapPayModel.setPassbackParams(URLEncoder.encode(payParam.getAddition(), StandardCharsets.UTF_8.name()));
} catch (UnsupportedEncodingException e) {
log.error("加密參數(shù)出現(xiàn)異常:{} exception: ", payParam.getAddition(), e);
}
}
wapPayModel.setOutTradeNo(payParam.getOutTradeNo());
wapPayModel.setSubject(payParam.getSubject());
wapPayModel.setTotalAmount(new BigDecimal(payParam.getTotalAmount()).divide(ONE_HUNDRED, 2, BigDecimal.ROUND_DOWN).toPlainString());
wapPayModel.setBody(payParam.getBody());
wapPayModel.setTimeoutExpress(this.timeoutExpress(payParam.getTimeoutExpress()));
wapPayModel.setProductCode("QUICK_WAP_WAY");
if (null != payParam.getQuitUrl()) {
wapPayModel.setQuitUrl(payParam.getQuitUrl());
}
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setBizModel(wapPayModel);
request.setNotifyUrl(payParam.getNotifyUrl());
request.setReturnUrl(payParam.getRedirectUrl());
AlipayTradeWapPayResponse response = this.getClient().pageExecute(request, "GET");
return OrderResult.builder().body(response.getBody()).build();
}
/**
* PC支付
*
* @param payParam 支付參數(shù)
* @return 結(jié)果
* @throws AlipayApiException 支付寶調(diào)用異常
*/
public OrderResult callPc(PayParam payParam) throws AlipayApiException {
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
if (payParam.getAddition() != null) {
try {
model.setPassbackParams(URLEncoder.encode(payParam.getAddition(), StandardCharsets.UTF_8.name()));
} catch (UnsupportedEncodingException e) {
log.error("加密參數(shù)出現(xiàn)異常:{} exception: ", payParam.getAddition(), e);
}
}
model.setBody(payParam.getBody());
model.setSubject(payParam.getSubject());
model.setOutTradeNo(payParam.getOutTradeNo());
model.setTimeoutExpress(this.timeoutExpress(payParam.getTimeoutExpress()));
model.setTotalAmount(new BigDecimal(payParam.getTotalAmount()).divide(ONE_HUNDRED, 2, BigDecimal.ROUND_DOWN).toPlainString());
model.setProductCode("FAST_INSTANT_TRADE_PAY");
request.setBizModel(model);
request.setNotifyUrl(payParam.getNotifyUrl());
request.setReturnUrl(payParam.getRedirectUrl());
AlipayTradePagePayResponse response = this.getClient().pageExecute(request);
return OrderResult.builder().body(response.getBody()).build();
}
/**
* 訂單查詢
*
* @param outTradeNo 訂單號
* @return 訂單信息
* @throws AlipayApiException 支付寶調(diào)用異常
*/
public AlipayTradeQueryResponse query(String outTradeNo) throws AlipayApiException {
AlipayTradeQueryRequest queryRequest = new AlipayTradeQueryRequest();
AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo(outTradeNo);
queryRequest.setBizModel(model);
AlipayTradeQueryResponse response = this.getClient().certificateExecute(queryRequest);
return response;
}
/**
* 退款
*
* @param refundParam 退款參數(shù)
* @return 退款響應(yīng)
* @throws AlipayApiException 支付寶調(diào)用異常
*/
public AlipayTradeRefundResponse refund(RefundParam refundParam) throws AlipayApiException {
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
model.setOutTradeNo(refundParam.getOutTradeNo());
model.setOutRequestNo(refundParam.getOutRefundNo());
model.setRefundReason(refundParam.getReason());
model.setRefundAmount(new BigDecimal(refundParam.getRefund()).divide(ONE_HUNDRED, 2, BigDecimal.ROUND_DOWN).toPlainString());
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
request.setBizModel(model);
request.setNotifyUrl(refundParam.getNotifyUrl());
AlipayTradeRefundResponse response = this.getClient().certificateExecute(request);
return response;
}
/**
* 退款查詢
*
* @param refundQueryParam 退款查詢參數(shù)
* @return 退款單信息
* @throws AlipayApiException 支付寶調(diào)用異常
*/
public AlipayTradeFastpayRefundQueryResponse queryRefund(RefundQueryParam refundQueryParam) throws AlipayApiException {
AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel();
model.setOutTradeNo(refundQueryParam.getOutTradeNo());
model.setOutRequestNo(refundQueryParam.getOutRefundNo());
request.setBizModel(model);
AlipayTradeFastpayRefundQueryResponse response = this.getClient().certificateExecute(request);
return response;
}
/**
* 支付結(jié)果通知處理
*
* @param request 請求
* @return 處理成功時返回數(shù)據(jù)信息鸡捐,否則返回空
*/
public Map<String, String> payNotify(HttpServletRequest request) {
Map<String, String> body = this.getRequestBody(request);
boolean flag = this.verifySign(body);
log.info("驗簽結(jié)果為:{}", flag);
if (!flag) {
return null;
}
return body;
}
/**
* 支付寶通知響應(yīng)
*
* @param success 是否處理成功
* @return 響應(yīng)字符串
*/
public String response(boolean success) {
return success ? "success" : "fail";
}
private boolean verifySign(Map<String, String> notifyParam) {
AliPayConfig config = this.configService.getConfig();
String signType = notifyParam.get("sign_type");
boolean flag = false;
try {
flag = AlipaySignature.rsaCheckV1(notifyParam, config.getPublicKey(), StandardCharsets.UTF_8.name(), signType);
} catch (AlipayApiException e) {
log.error("驗證簽名出現(xiàn)異常:{} {}", e, notifyParam);
}
return flag;
}
private Map<String, String> getRequestBody(HttpServletRequest request) {
Map<String, String> params = new HashMap<>(10);
Map<String, String[]> requestParams = request.getParameterMap();
for (String name : requestParams.keySet()) {
String[] values = requestParams.get(name);
StringBuilder stringBuilder = new StringBuilder();
for (String val : values) {
stringBuilder.append(val).append(",");
}
params.put(name, stringBuilder.substring(0, stringBuilder.length() - 1));
}
return params;
}
private AlipayClient getClient() {
if (client == null) {
client = buildClient();
if (client == null) {
throw new IllegalArgumentException("參數(shù)錯誤");
}
}
return client;
}
private AlipayClient buildClient() {
AliPayConfig config = configService.getConfig();
if (config == null) {
return null;
}
// 構(gòu)造client
CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
// 設(shè)置網(wǎng)關(guān)地址
certAlipayRequest.setServerUrl(SERVER_URL);
// 設(shè)置應(yīng)用Id
certAlipayRequest.setAppId(config.getAppId());
// 設(shè)置應(yīng)用私鑰
certAlipayRequest.setPrivateKey(config.getPrivateKey());
// 設(shè)置請求格式栈暇,固定值json
certAlipayRequest.setFormat(FORMAT);
// 設(shè)置字符集
certAlipayRequest.setCharset(StandardCharsets.UTF_8.name());
// 設(shè)置簽名類型
certAlipayRequest.setSignType(SIGN_TYPE);
// 設(shè)置應(yīng)用公鑰證書路徑
certAlipayRequest.setCertContent(config.getAppCertContent());
// 設(shè)置支付寶公鑰證書路徑
certAlipayRequest.setAlipayPublicCertContent(config.getAliPayCertContent());
// 設(shè)置支付寶根證書路徑
certAlipayRequest.setRootCertContent(config.getAliPayRootCertContent());
// 構(gòu)造client
try {
return new DefaultAlipayClient(certAlipayRequest);
} catch (AlipayApiException e) {
log.error("創(chuàng)建AliPayClient異常:{}", e);
return null;
}
}
private String timeoutExpress(Integer timeoutExpress) {
if (timeoutExpress == null) {
return "15m";
}
return String.format("%dm", timeoutExpress);
}
}
到此即可使用,上述中二維碼和H5支付均已投入使用箍镜,APP和PC(未簽約)支付尚未驗證是否能喚起源祈。希望對大家有幫助。