java配置沙箱支付的實(shí)現(xiàn)示例
在 Java 項(xiàng)目中集成沙箱支付(以支付寶、微信支付這兩大主流平臺(tái)為例)是支付功能開發(fā)的必經(jīng)環(huán)節(jié),用于在測(cè)試環(huán)境驗(yàn)證支付流程的正確性,避免真實(shí)資金交易風(fēng)險(xiǎn)。以下是詳細(xì)的集成步驟,包含前期準(zhǔn)備、環(huán)境搭建、核心功能實(shí)現(xiàn)及測(cè)試驗(yàn)證。
一、沙箱支付概述
沙箱支付是支付平臺(tái)(如支付寶、微信支付)提供的模擬支付環(huán)境,包含測(cè)試用的 APPID、密鑰、網(wǎng)關(guān)、測(cè)試賬號(hào)等資源,其接口邏輯與正式環(huán)境一致,但交易僅為模擬,不涉及真實(shí)資金。
核心作用:驗(yàn)證支付下單、支付跳轉(zhuǎn)、異步通知、訂單查詢等全流程的正確性。
二、支付寶沙箱支付集成步驟(詳細(xì))
2.1 前期準(zhǔn)備:獲取沙箱環(huán)境參數(shù)
注冊(cè)支付寶開放平臺(tái)賬號(hào)
訪問 支付寶開放平臺(tái),注冊(cè)開發(fā)者賬號(hào)并完成實(shí)名認(rèn)證(個(gè)人開發(fā)者即可)。創(chuàng)建應(yīng)用并開通沙箱
- 進(jìn)入「控制臺(tái)」→「開發(fā)者中心」→「網(wǎng)頁 & 移動(dòng)應(yīng)用」,點(diǎn)擊「創(chuàng)建應(yīng)用」(選擇 “自研” 類型)。
- 應(yīng)用創(chuàng)建后,在應(yīng)用詳情頁的「開發(fā)設(shè)置」中,開啟 “沙箱” 功能(沙箱環(huán)境默認(rèn)自動(dòng)生成,無需審核)。
獲取核心參數(shù)
在「沙箱環(huán)境」頁面獲取以下參數(shù)(后續(xù)代碼中需使用):APPID:沙箱應(yīng)用唯一標(biāo)識(shí)(如2021000000000000)。- 支付寶網(wǎng)關(guān):沙箱環(huán)境專用網(wǎng)關(guān)
https://openapi.alipaydev.com/gateway.do(正式環(huán)境為https://openapi.alipay.com/gateway.do)。 - 密鑰:需生成 RSA2 密鑰對(duì)(公鑰 + 私鑰):
- 下載 支付寶開放平臺(tái)開發(fā)助手,生成 RSA2(2048 位)密鑰對(duì)。
- 將生成的應(yīng)用公鑰上傳到沙箱環(huán)境的「密鑰管理」中,支付寶會(huì)自動(dòng)生成對(duì)應(yīng)的支付寶公鑰(需保存,用于驗(yàn)簽)。
- 本地保留應(yīng)用私鑰(用于接口簽名)。
獲取沙箱測(cè)試賬號(hào)
在沙箱環(huán)境頁面可獲取測(cè)試用的買家賬號(hào)、買家支付密碼、測(cè)試銀行卡信息(用于模擬支付)。
2.2 環(huán)境搭建:引入依賴與配置
引入支付寶 Java SDK
在項(xiàng)目的pom.xml(Maven)中添加依賴(最新版本可參考支付寶 SDK 文檔):<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.38.0.ALL</version> </dependency>配置沙箱參數(shù)
在application.properties(或application.yml)中配置參數(shù):# 支付寶沙箱配置 alipay.app-id=你的沙箱APPID alipay.private-key=你的應(yīng)用私鑰(生成的私鑰,不含換行) alipay.public-key=支付寶公鑰(上傳應(yīng)用公鑰后生成的) alipay.gateway-url=https://openapi.alipaydev.com/gateway.do alipay.format=json alipay.charset=UTF-8 alipay.sign-type=RSA2
創(chuàng)建配置類
編寫 Java 配置類,初始化支付寶客戶端(AlipayClient):@Configuration public class AlipayConfig { @Value("${alipay.app-id}") private String appId; @Value("${alipay.private-key}") private String privateKey; @Value("${alipay.public-key}") private String publicKey; @Value("${alipay.gateway-url}") private String gatewayUrl; @Value("${alipay.format}") private String format; @Value("${alipay.charset}") private String charset; @Value("${alipay.sign-type}") private String signType; @Bean public AlipayClient alipayClient() { // 初始化支付寶客戶端(沙箱環(huán)境) return new DefaultAlipayClient( gatewayUrl, appId, privateKey, format, charset, publicKey, signType ); } }
2.3 核心功能實(shí)現(xiàn)
(1)創(chuàng)建支付訂單(電腦網(wǎng)站支付為例)
用戶下單后,調(diào)用支付寶接口生成支付表單,前端跳轉(zhuǎn)至沙箱支付頁面。
@Service
public class AlipayService {
@Autowired
private AlipayClient alipayClient;
/**
* 創(chuàng)建支付寶支付訂單
* @param outTradeNo 商戶訂單號(hào)(唯一)
* @param totalAmount 訂單金額(單位:元)
* @param subject 訂單標(biāo)題
* @param returnUrl 支付成功后同步跳轉(zhuǎn)地址(商戶系統(tǒng)頁面)
* @param notifyUrl 支付成功后異步通知地址(商戶系統(tǒng)接口)
* @return 支付表單HTML(前端自動(dòng)提交跳轉(zhuǎn))
* @throws AlipayApiException 接口調(diào)用異常
*/
public String createPayOrder(String outTradeNo, String totalAmount, String subject,
String returnUrl, String notifyUrl) throws AlipayApiException {
// 1. 創(chuàng)建支付請(qǐng)求對(duì)象(電腦網(wǎng)站支付用AlipayTradePagePayRequest)
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
// 設(shè)置同步回調(diào)地址(用戶支付成功后跳轉(zhuǎn))
request.setReturnUrl(returnUrl);
// 設(shè)置異步通知地址(支付寶主動(dòng)推送支付結(jié)果)
request.setNotifyUrl(notifyUrl);
// 2. 組裝請(qǐng)求參數(shù)(JSON格式)
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", outTradeNo); // 商戶訂單號(hào)(自定義,需唯一)
bizContent.put("total_amount", totalAmount); // 訂單金額(精確到分)
bizContent.put("subject", subject); // 訂單標(biāo)題
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY"); // 產(chǎn)品碼(固定值)
request.setBizContent(bizContent.toString());
// 3. 調(diào)用支付寶接口,獲取支付表單
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
if (response.isSuccess()) {
return response.getBody(); // 返回HTML表單,前端渲染后自動(dòng)提交
} else {
throw new RuntimeException("創(chuàng)建支付訂單失敗:" + response.getMsg());
}
}
}
(2)編寫控制器接口
提供給前端調(diào)用,觸發(fā)支付流程:
@RestController
@RequestMapping("/pay/alipay")
public class AlipayController {
@Autowired
private AlipayService alipayService;
/**
* 發(fā)起支付寶支付
*/
@PostMapping("/create")
public Result<String> createPay(@RequestBody PayDTO payDTO) {
try {
// 生成唯一商戶訂單號(hào)(可基于UUID或雪花算法)
String outTradeNo = "ORDER_" + System.currentTimeMillis();
// 調(diào)用服務(wù)生成支付表單
String payForm = alipayService.createPayOrder(
outTradeNo,
payDTO.getTotalAmount(), // 金額(如"0.01"元)
payDTO.getSubject(), // 訂單標(biāo)題(如"測(cè)試訂單")
"http://localhost:8080/pay/success", // 同步回調(diào)地址(前端頁面)
"http://你的服務(wù)器公網(wǎng)地址/pay/alipay/notify" // 異步通知地址(需公網(wǎng)可訪問)
);
return Result.success(payForm);
} catch (Exception e) {
return Result.error("支付創(chuàng)建失敗:" + e.getMessage());
}
}
}
(3)處理異步支付通知(核心?。?/h6>
支付寶在用戶支付成功后,會(huì)主動(dòng)向notifyUrl發(fā)送 POST 請(qǐng)求(攜帶支付結(jié)果),商戶需驗(yàn)證通知合法性并更新訂單狀態(tài)。
@PostMapping("/notify")
public String handleNotify(HttpServletRequest request) {
try {
// 1. 獲取請(qǐng)求參數(shù)(支付寶通知的所有參數(shù))
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
for (String key : requestParams.keySet()) {
String[] values = requestParams.get(key);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
params.put(key, valueStr);
}
// 2. 驗(yàn)證簽名(關(guān)鍵!防止偽造通知)
boolean signVerified = AlipaySignature.rsaCheckV1(
params,
alipayPublicKey, // 支付寶公鑰(配置文件中獲取)
charset, // 編碼(UTF-8)
signType // 簽名類型(RSA2)
);
if (!signVerified) {
return "fail"; // 簽名驗(yàn)證失敗,返回fail(支付寶會(huì)重試通知)
}
// 3. 驗(yàn)證通知參數(shù)(確保支付狀態(tài)為成功)
String tradeStatus = params.get("trade_status");
if (!"TRADE_SUCCESS".equals(tradeStatus)) {
return "fail"; // 支付未成功,不處理
}
// 4. 處理業(yè)務(wù)邏輯(更新訂單狀態(tài)、記錄支付信息等)
String outTradeNo = params.get("out_trade_no"); // 商戶訂單號(hào)
String tradeNo = params.get("trade_no"); // 支付寶交易號(hào)
String totalAmount = params.get("total_amount"); // 支付金額
// 示例:調(diào)用訂單服務(wù)更新狀態(tài)為“已支付”
orderService.updateOrderStatus(outTradeNo, tradeNo, totalAmount);
// 5. 處理完成,返回"success"(支付寶收到后停止重試)
return "success";
} catch (Exception e) {
// 異常時(shí)返回fail,支付寶會(huì)重試
return "fail";
}
}
(4)查詢支付狀態(tài)(可選)
用于主動(dòng)查詢訂單支付結(jié)果(如用戶未同步跳轉(zhuǎn)時(shí)):
public String queryPayStatus(String outTradeNo) throws AlipayApiException {
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", outTradeNo); // 商戶訂單號(hào)
request.setBizContent(bizContent.toString());
AlipayTradeQueryResponse response = alipayClient.execute(request);
if (response.isSuccess()) {
return response.getTradeStatus(); // 返回狀態(tài)(如TRADE_SUCCESS、WAIT_BUYER_PAY)
} else {
throw new RuntimeException("查詢支付狀態(tài)失敗:" + response.getMsg());
}
}
2.4 測(cè)試流程
- 前端處理:將后端返回的
payForm(HTML 表單)渲染到頁面,表單會(huì)自動(dòng)提交至支付寶沙箱支付頁面。 - 模擬支付:
- 使用沙箱環(huán)境提供的買家賬號(hào)登錄(非開發(fā)者賬號(hào))。
- 輸入沙箱買家支付密碼(如
111111),完成支付。
- 驗(yàn)證結(jié)果:
- 同步跳轉(zhuǎn):支付成功后跳轉(zhuǎn)到
returnUrl(前端成功頁)。 - 異步通知:支付寶會(huì)向
notifyUrl發(fā)送通知,驗(yàn)證訂單狀態(tài)是否更新為 “已支付”。 - 主動(dòng)查詢:調(diào)用
queryPayStatus接口,確認(rèn)狀態(tài)為TRADE_SUCCESS。
- 同步跳轉(zhuǎn):支付成功后跳轉(zhuǎn)到
三、微信支付沙箱支付集成步驟(簡(jiǎn)要)
微信支付沙箱流程與支付寶類似,但細(xì)節(jié)略有不同(如需要證書、沙箱密鑰需動(dòng)態(tài)獲?。?。
3.1 前期準(zhǔn)備
- 注冊(cè) 微信支付商戶平臺(tái),獲取
商戶號(hào)(mch_id)。 - 申請(qǐng) API 證書(在商戶平臺(tái)的「賬戶中心→API 安全」中下載,包含
apiclient_cert.p12等文件)。 - 生成 API 密鑰(32 位,用于簽名),并在商戶平臺(tái)配置。
3.2 核心差異點(diǎn)
沙箱密鑰獲取:
微信沙箱需先調(diào)用沙箱簽名接口獲取沙箱專用密鑰(sandbox_signkey),后續(xù)接口用該密鑰簽名。// 示例:獲取沙箱密鑰 WXPayConfig config = new MyWXPayConfig(); // 自定義配置類(含商戶號(hào)、API密鑰等) WXPay wxPay = new WXPay(config); Map<String, String> params = new HashMap<>(); params.put("mch_id", config.getMchID()); params.put("nonce_str", WXPayUtil.generateNonceStr()); params.put("sign", WXPayUtil.generateSignature(params, config.getKey())); Map<String, String> result = wxPay.sandboxnewKey(params); String sandboxSignKey = result.get("sandbox_signkey"); // 沙箱密鑰接口調(diào)用:
微信支付接口(如 JSAPI 支付)需使用沙箱網(wǎng)關(guān)(https://api.mch.weixin.qq.com/sandboxnew/),并通過WXPay類(微信 SDK)調(diào)用。// 示例:創(chuàng)建JSAPI支付訂單 Map<String, String> data = new HashMap<>(); data.put("body", "測(cè)試訂單"); data.put("out_trade_no", outTradeNo); data.put("total_fee", "1"); // 金額(單位:分) data.put("spbill_create_ip", "127.0.0.1"); data.put("notify_url", "http://你的公網(wǎng)地址/pay/wx/notify"); data.put("trade_type", "JSAPI"); data.put("openid", "用戶的openid"); // 微信用戶唯一標(biāo)識(shí) WXPay wxPay = new WXPay(new SandboxWXPayConfig(sandboxSignKey)); // 使用沙箱配置 Map<String, String> result = wxPay.unifiedOrder(data);異步通知處理:
與支付寶類似,需驗(yàn)證簽名(用沙箱密鑰),并返回"<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>"確認(rèn)接收。
四、注意事項(xiàng)
- 簽名驗(yàn)證:必須嚴(yán)格驗(yàn)證支付平臺(tái)的通知簽名,防止惡意請(qǐng)求偽造支付結(jié)果。
- 回調(diào)地址:異步通知地址(
notifyUrl)需公網(wǎng)可訪問(本地測(cè)試可使用 ngrok 等工具映射端口)。 - 冪等性處理:同一訂單可能收到多次異步通知,需確保訂單狀態(tài)更新邏輯冪等(如通過訂單號(hào)判斷是否已處理)。
- 沙箱與正式環(huán)境隔離:通過配置文件區(qū)分沙箱和正式環(huán)境的參數(shù)(網(wǎng)關(guān)、密鑰等),避免混淆。
- 異常處理:接口調(diào)用超時(shí)、網(wǎng)絡(luò)錯(cuò)誤等情況需重試,避免訂單狀態(tài)不一致。
通過以上步驟,可在 Java 項(xiàng)目中完整集成沙箱支付,驗(yàn)證支付全流程的正確性后,再切換至正式環(huán)境即可上線。
到此這篇關(guān)于java配置沙箱支付的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)java 沙箱支付內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入理解Java8新特性之Lambda表達(dá)式的基本語法和自定義函數(shù)式接口
Lambda 表達(dá)式,也可稱為閉包,它是推動(dòng) Java 8 發(fā)布的最重要新特性。Lambda 允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞進(jìn)方法中)。使用 Lambda 表達(dá)式可以使代碼變的更加簡(jiǎn)潔緊湊2021-11-11
java 基礎(chǔ)教程之多線程詳解及簡(jiǎn)單實(shí)例
這篇文章主要介紹了java 基礎(chǔ)教程之多線程詳解及簡(jiǎn)單實(shí)例的相關(guān)資料,線程的基本屬性、如何創(chuàng)建線程、線程的狀態(tài)切換以及線程通信,需要的朋友可以參考下2017-03-03
用java等語言仿360首頁拼音輸入全模糊搜索和自動(dòng)換膚
這篇文章主要為大家詳細(xì)介紹了仿360首頁支持拼音輸入全模糊搜索和自動(dòng)換膚的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
使用MybatisPlus實(shí)現(xiàn)sql日志打印優(yōu)化
本文主要介紹了使用MybatisPlus實(shí)現(xiàn)sql日志打印優(yōu)化,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-08-08
菜鳥學(xué)習(xí)java設(shè)計(jì)模式之單例模式
這篇文章主要為大家詳細(xì)介紹了java設(shè)計(jì)模式之單例模式的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
Spring Boot Feign服務(wù)調(diào)用之間帶token問題
這篇文章主要介紹了Spring Boot Feign服務(wù)調(diào)用之間帶token的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09

