企业网站如何接入支付宝开放平台

企业网站接入支付宝开放平台实现支付功能,需要完成商户入驻、创建应用、开发配置、接口集成等步骤。以下是详细接入指南:

一、入驻支付宝开放平台

  1. 注册开发者账号
    访问 支付宝开放平台,用企业支付宝账户登录并完成实名认证。

  2. 签约商户

    • 进入「商户中心」>「产品签约」,选择所需支付产品(如当面付、手机网站支付、电脑网站支付等)。
    • 按指引提交企业资质(营业执照、法人身份证等),等待审核(1-3个工作日)。

二、创建应用并配置

  1. 创建应用

    • 进入「控制台」>「应用」>「创建应用」,填写应用名称、简介,选择应用类型(网站应用)。
    • 提交审核后,获得 AppID(应用唯一标识)。
  2. 配置应用信息

    • 接口加签方式:设置应用的签名方式(推荐 RSA2),生成并上传应用公钥。
    • 授权回调地址:配置用户授权或支付完成后跳转的URL。
    • IP白名单:设置允许调用支付宝接口的服务器IP(生产环境需配置)。
  3. 绑定PID
    在应用管理页面绑定签约的商户PID(合作伙伴ID),用于收款账户关联。

三、开发准备

  1. 获取开发资源

    • 开发文档:参考 支付宝开放平台文档,选择对应支付产品的接口文档。
    • SDK/工具:下载支付宝提供的SDK(如Java、PHP、Python等),简化开发。
    • 沙箱环境:使用沙箱账户进行测试,避免影响生产环境。
  2. 密钥管理

    • 生成应用私钥(本地保存,不泄露)和公钥(上传至开放平台)。
    • 保存支付宝公钥(用于验证支付宝返回的签名)。

四、接口集成步骤

电脑网站支付 为例,核心流程如下:

  1. 生成支付请求
    商户后端根据订单信息调用支付宝的 统一收单交易创建接口,获取支付URL。

  2. 跳转支付页面
    将用户重定向至支付宝收银台页面,用户完成支付。

  3. 接收支付结果

    • 同步通知:支付完成后,支付宝将用户重定向回商户配置的回调URL(需验证签名)。
    • 异步通知:支付宝服务器主动向商户服务器发送支付结果(更可靠,需验证签名和处理幂等性)。

五、测试与上线

  1. 沙箱测试

    • 使用沙箱环境进行功能测试,包括支付流程、异步通知等。
    • 沙箱账户可在开放平台控制台获取。
  2. 安全加固

    • 生产环境使用HTTPS域名。
    • 敏感信息加密存储(如密钥)。
    • 验证异步通知的IP来源是否为支付宝服务器。
  3. 提交审核
    应用开发完成后,在开放平台提交上线审核,审核通过后即可正式接入。

六、常见问题

  1. 签名错误

    • 检查密钥格式是否正确(PKCS8格式)。
    • 确保参数排序和编码方式一致。
  2. 支付后无法跳转回商户

    • 检查回调URL配置是否正确,是否支持HTTPS。
  3. 异步通知未收到

    • 检查服务器是否能访问外网,防火墙是否拦截支付宝IP。

七、代码实现示例

以下是使用Java实现支付宝电脑网站支付的示例代码,基于Spring Boot框架:

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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package com.example.alipay;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

@SpringBootApplication
@RestController
@RequestMapping("/alipay")
public class AlipayApplication {

// 支付宝配置参数
private static final String APP_ID = "你的AppID";
private static final String ALIPAY_PUBLIC_KEY = "支付宝公钥";
private static final String MERCHANT_PRIVATE_KEY = "应用私钥";
private static final String GATEWAY_URL = "https://openapi.alipay.com/gateway.do";
private static final String SIGN_TYPE = "RSA2";
private static final String CHARSET = "UTF-8";
private static final String FORMAT = "JSON";

public static void main(String[] args) {
SpringApplication.run(AlipayApplication.class, args);
}

/**
* 创建订单并生成支付宝支付URL
*/
@PostMapping("/createOrder")
public String createOrder(@RequestParam String amount) throws UnsupportedEncodingException {
String outTradeNo = "ORDER_" + System.currentTimeMillis(); // 生成订单号

// 组装请求参数
Map<String, String> params = new HashMap<>();
params.put("app_id", APP_ID);
params.put("method", "alipay.trade.page.pay");
params.put("format", FORMAT);
params.put("charset", CHARSET);
params.put("sign_type", SIGN_TYPE);
params.put("timestamp", getCurrentTime());
params.put("version", "1.0");
params.put("notify_url", "https://your-domain.com/alipay/notify"); // 异步通知URL
params.put("return_url", "https://your-domain.com/alipay/return"); // 同步通知URL

// 业务参数
Map<String, String> bizContent = new HashMap<>();
bizContent.put("out_trade_no", outTradeNo);
bizContent.put("total_amount", amount);
bizContent.put("subject", "商品名称");
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");

params.put("biz_content", toJsonString(bizContent));

// 生成签名
String sign = generateSign(params, MERCHANT_PRIVATE_KEY);
params.put("sign", sign);

// 构造请求URL
String payUrl = buildRequestUrl(params);
return "<script>window.location.href='" + payUrl + "';</script>";
}

/**
* 支付宝同步通知处理
*/
@GetMapping("/return")
public String alipayReturn(HttpServletRequest request) {
Map<String, String> params = getRequestParams(request);

if (verifySign(params, ALIPAY_PUBLIC_KEY)) {
String tradeStatus = params.get("trade_status");
if ("TRADE_SUCCESS".equals(tradeStatus)) {
return "支付成功";
}
}
return "支付失败或签名验证失败";
}

/**
* 支付宝异步通知处理
*/
@PostMapping("/notify")
public String alipayNotify(HttpServletRequest request) {
Map<String, String> params = getRequestParams(request);

if (verifySign(params, ALIPAY_PUBLIC_KEY)) {
String tradeStatus = params.get("trade_status");
String outTradeNo = params.get("out_trade_no");

if ("TRADE_SUCCESS".equals(tradeStatus)) {
// 更新订单状态为已支付
// 注意:需处理幂等性,避免重复通知导致问题
return "success"; // 必须返回success,否则支付宝会重复通知
}
}
return "fail";
}

/**
* 生成签名
*/
private String generateSign(Map<String, String> params, String privateKey) {
// 排序并拼接参数
String stringToSign = getSignContent(params);

try {
// RSA2签名
PrivateKey priKey = getPrivateKeyFromPKCS8("RSA", privateKey);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(priKey);
signature.update(stringToSign.getBytes(CHARSET));
byte[] signed = signature.sign();
return Base64.getEncoder().encodeToString(signed);
} catch (Exception e) {
throw new RuntimeException("生成签名失败", e);
}
}

/**
* 验证签名
*/
private boolean verifySign(Map<String, String> params, String publicKey) {
String sign = params.remove("sign");
String stringToVerify = getSignContent(params);

try {
PublicKey pubKey = getPublicKeyFromX509("RSA", publicKey);
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(pubKey);
signature.update(stringToVerify.getBytes(CHARSET));
return signature.verify(Base64.getDecoder().decode(sign));
} catch (Exception e) {
throw new RuntimeException("验证签名失败", e);
}
}

/**
* 获取当前时间(格式:yyyy-MM-dd HH:mm:ss)
*/
private String getCurrentTime() {
return new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date());
}

/**
* 将Map转换为JSON字符串
*/
private String toJsonString(Map<String, String> map) {
StringBuilder sb = new StringBuilder();
sb.append("{");
boolean first = true;
for (Map.Entry<String, String> entry : map.entrySet()) {
if (!first) {
sb.append(",");
}
sb.append("\"").append(entry.getKey()).append("\":\"").append(entry.getValue()).append("\"");
first = false;
}
sb.append("}");
return sb.toString();
}

/**
* 构建请求URL
*/
private String buildRequestUrl(Map<String, String> params) throws UnsupportedEncodingException {
StringBuilder sb = new StringBuilder(GATEWAY_URL);
sb.append("?");
boolean first = true;
for (Map.Entry<String, String> entry : params.entrySet()) {
if (!first) {
sb.append("&");
}
sb.append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue(), CHARSET));
first = false;
}
return sb.toString();
}

/**
* 获取请求参数
*/
private Map<String, String> getRequestParams(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (String name : requestParams.keySet()) {
String[] values = 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);
}
return params;
}

/**
* 拼接签名内容
*/
private String getSignContent(Map<String, String> params) {
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);

StringBuilder content = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
if (i != 0) {
content.append("&");
}
content.append(key).append("=").append(value);
}
return content.toString();
}

/**
* 获取私钥
*/
private PrivateKey getPrivateKeyFromPKCS8(String algorithm, String privateKey) throws Exception {
if (privateKey == null || "".equals(privateKey)) {
return null;
}
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
byte[] encodedKey = Base64.getDecoder().decode(privateKey);
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
}

/**
* 获取公钥
*/
private PublicKey getPublicKeyFromX509(String algorithm, String publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
byte[] encodedKey = Base64.getDecoder().decode(publicKey);
return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
}
}

依赖配置(Maven)

pom.xml中添加以下依赖:

1
2
3
4
5
6
7
8
9
10
11
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
</dependencies>

代码说明

  1. 配置参数:需替换为真实的AppID、公钥、私钥等信息。
  2. 签名生成:使用SHA256withRSA算法生成和验证签名。
  3. 支付流程
    • 创建订单时生成唯一订单号并调用支付宝接口。
    • 处理同步通知(页面跳转)和异步通知(服务器回调)。
  4. 安全验证:验证签名确保数据未被篡改。

注意事项

  1. 生产环境需配置HTTPS域名。
  2. 私钥需妥善保管,建议存储在配置文件或环境变量中。
  3. 处理异步通知时需考虑幂等性,避免重复处理同一订单。
  4. 支付宝公钥可从开放平台获取,注意区分沙箱环境和生产环境。