1
zj
9 hours ago d615fc515fc52d6ed970c11d59a017e48de4be32
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
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");
    }
}