package com.nq.utils.pay;
|
|
import com.alipay.api.AlipayApiException;
|
import com.alipay.api.AlipayClient;
|
import com.alipay.api.AlipayConfig;
|
import com.alipay.api.DefaultAlipayClient;
|
import com.alipay.api.domain.AlipayTradeWapPayModel;
|
import com.alipay.api.internal.util.AlipaySignature;
|
import com.alipay.api.request.AlipayTradeWapPayRequest;
|
import com.alipay.api.response.AlipayTradeWapPayResponse;
|
import com.nq.utils.PropertiesUtil;
|
import org.apache.commons.lang3.StringUtils;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
|
import javax.servlet.http.HttpServletRequest;
|
import java.nio.charset.StandardCharsets;
|
import java.nio.file.Files;
|
import java.nio.file.Paths;
|
import java.security.KeyFactory;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.util.Base64;
|
import java.util.HashMap;
|
import java.util.Map;
|
|
/**
|
* 支付宝手机网站支付(alipay.trade.wap.pay),对接官方 SDK 示例写法。
|
*/
|
public final class AlipayPayUtil {
|
|
private static final Logger log = LoggerFactory.getLogger(AlipayPayUtil.class);
|
/** 手机网站支付产品码 */
|
private static final String PRODUCT_CODE = "QUICK_WAP_WAY";
|
private static final String SIGN_TYPE = "RSA2";
|
private static final String FORMAT = "json";
|
private static final String CHARSET = "UTF-8";
|
|
private AlipayPayUtil() {
|
}
|
|
public static boolean isConfigured() {
|
return getConfigError() == null;
|
}
|
|
/** @return 配置错误说明,null 表示配置有效 */
|
public static String getConfigError() {
|
if (StringUtils.isBlank(PropertiesUtil.getProperty("alipay.app-id"))) {
|
return "请填写 alipay.app-id";
|
}
|
if (StringUtils.isBlank(PropertiesUtil.getProperty("alipay.alipay-public-key"))) {
|
return "请填写 alipay.alipay-public-key(支付宝公钥)";
|
}
|
String path = PropertiesUtil.getProperty("alipay.private-key-path");
|
String inlineKey = PropertiesUtil.getProperty("alipay.private-key");
|
if (StringUtils.isAllBlank(path, inlineKey)) {
|
return "请填写 alipay.private-key(应用私钥 PKCS8)或 alipay.private-key-path(私钥文件路径)";
|
}
|
try {
|
toSdkPrivateKey(loadPrivateKeyMaterial());
|
return null;
|
} catch (IllegalArgumentException e) {
|
return e.getMessage();
|
} catch (Exception e) {
|
return "应用私钥读取失败:" + e.getMessage();
|
}
|
}
|
|
public static String buildWapPayForm(String orderSn, String amount, String subject,
|
String notifyUrl, String returnUrl, String quitUrl)
|
throws AlipayApiException {
|
AlipayClient client = createClient();
|
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
|
request.setNotifyUrl(notifyUrl);
|
request.setReturnUrl(returnUrl);
|
|
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
|
model.setOutTradeNo(orderSn);
|
model.setTotalAmount(amount);
|
model.setSubject(subject);
|
model.setProductCode(PRODUCT_CODE);
|
if (StringUtils.isNotBlank(quitUrl)) {
|
model.setQuitUrl(quitUrl);
|
}
|
request.setBizModel(model);
|
|
AlipayTradeWapPayResponse response = client.pageExecute(request, "POST");
|
if (response == null || StringUtils.isBlank(response.getBody())) {
|
throw new AlipayApiException("支付宝返回为空");
|
}
|
if (!response.isSuccess()) {
|
log.error("支付宝 WAP 下单失败 code={} msg={} subCode={} subMsg={}",
|
response.getCode(), response.getMsg(), response.getSubCode(), response.getSubMsg());
|
throw new AlipayApiException(StringUtils.defaultIfBlank(response.getSubMsg(), response.getMsg()));
|
}
|
return response.getBody();
|
}
|
|
public static Map<String, String> parseRequestParams(HttpServletRequest request) {
|
Map<String, String> params = new HashMap<>();
|
Map<String, String[]> requestParams = request.getParameterMap();
|
for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
|
String[] values = entry.getValue();
|
if (values == null || values.length == 0) {
|
continue;
|
}
|
StringBuilder valueStr = new StringBuilder();
|
for (int i = 0; i < values.length; i++) {
|
if (i > 0) {
|
valueStr.append(",");
|
}
|
valueStr.append(values[i]);
|
}
|
params.put(entry.getKey(), valueStr.toString());
|
}
|
return params;
|
}
|
|
public static boolean verifyNotify(Map<String, String> params) {
|
try {
|
String alipayPublicKey = normalizePublicKey(PropertiesUtil.getProperty("alipay.alipay-public-key"));
|
return AlipaySignature.rsaCheckV1(params, alipayPublicKey, CHARSET, SIGN_TYPE);
|
} catch (Exception e) {
|
log.error("支付宝回调验签失败", e);
|
return false;
|
}
|
}
|
|
private static AlipayClient createClient() throws AlipayApiException {
|
try {
|
return new DefaultAlipayClient(buildAlipayConfig());
|
} catch (IllegalArgumentException e) {
|
throw new AlipayApiException(e.getMessage(), e);
|
} catch (Exception e) {
|
throw new AlipayApiException("支付宝私钥配置错误,请检查 alipay.private-key", e);
|
}
|
}
|
|
private static AlipayConfig buildAlipayConfig() throws Exception {
|
AlipayConfig config = new AlipayConfig();
|
config.setServerUrl(StringUtils.defaultIfBlank(
|
PropertiesUtil.getProperty("alipay.gateway-url"),
|
"https://openapi.alipay.com/gateway.do"));
|
config.setAppId(PropertiesUtil.getProperty("alipay.app-id"));
|
config.setPrivateKey(toSdkPrivateKey(loadPrivateKeyMaterial()));
|
config.setFormat(FORMAT);
|
config.setAlipayPublicKey(normalizePublicKey(PropertiesUtil.getProperty("alipay.alipay-public-key")));
|
config.setCharset(CHARSET);
|
config.setSignType(SIGN_TYPE);
|
return config;
|
}
|
|
private static String loadPrivateKeyMaterial() {
|
String path = PropertiesUtil.getProperty("alipay.private-key-path");
|
if (StringUtils.isNotBlank(path)) {
|
try {
|
return new String(Files.readAllBytes(Paths.get(path.trim())), StandardCharsets.UTF_8);
|
} catch (Exception e) {
|
throw new IllegalArgumentException("读取 alipay.private-key-path 失败: " + path, e);
|
}
|
}
|
return PropertiesUtil.getProperty("alipay.private-key");
|
}
|
|
static String toSdkPrivateKey(String raw) throws Exception {
|
if (StringUtils.isBlank(raw)) {
|
throw new IllegalArgumentException("alipay.private-key 不能为空");
|
}
|
String compact = extractKeyBody(raw);
|
if (isLikelyPublicKey(compact)) {
|
throw new IllegalArgumentException(
|
"alipay.private-key 配置错误:当前填入的是公钥。请填写「应用私钥 PKCS8」,不要填应用公钥或支付宝公钥。");
|
}
|
if (compact.length() < 800) {
|
throw new IllegalArgumentException(
|
"alipay.private-key 长度异常(" + compact.length() + "),应用私钥通常远长于公钥,请确认已完整粘贴 PKCS8 私钥。");
|
}
|
try {
|
KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(compact)));
|
return compact;
|
} catch (Exception e) {
|
throw new IllegalArgumentException(
|
"应用私钥格式不正确,Java 需使用 PKCS8 格式。请用支付宝开放平台「密钥生成器」生成并选择 PKCS8(Java适用)。", e);
|
}
|
}
|
|
private static String extractKeyBody(String raw) {
|
String material = raw.trim().replace("\\n", "\n");
|
return material
|
.replace("-----BEGIN RSA PRIVATE KEY-----", "")
|
.replace("-----END RSA PRIVATE KEY-----", "")
|
.replace("-----BEGIN PRIVATE KEY-----", "")
|
.replace("-----END PRIVATE KEY-----", "")
|
.replace("-----BEGIN PUBLIC KEY-----", "")
|
.replace("-----END PUBLIC KEY-----", "")
|
.replaceAll("\\s+", "");
|
}
|
|
private static String normalizePublicKey(String key) {
|
if (StringUtils.isBlank(key)) {
|
return key;
|
}
|
return extractKeyBody(key);
|
}
|
|
private static boolean isLikelyPublicKey(String compactKey) {
|
return compactKey.startsWith("MIIBIjAN") || compactKey.startsWith("MIGfMA0G");
|
}
|
}
|