SpringBoot集成支付寶支付的實現(xiàn)示例
最近在做一個網(wǎng)站,后端采用了SpringBoot,需要集成支付寶進行線上支付,在這個過程中研究了大量支付寶的集成資料,也走了一些彎路,現(xiàn)在總結(jié)出來,相信你讀完也能輕松集成支付寶支付。
在開始集成支付寶支付之前,我們需要準備一個支付寶商家賬戶,如果是個人開發(fā)者,可以通過注冊公司或者讓有公司資質(zhì)的單位進行授權(quán),后續(xù)在集成相關(guān)API的時候需要提供這些信息。
下面我以電腦網(wǎng)頁端在線支付為例,介紹整個從集成、測試到上線的具體流程。
1. 預期效果展示
在開始之前我們先看下我們要達到的最后效果,具體如下:
- 前端點擊支付跳轉(zhuǎn)到支付寶界面
- 支付寶界面展示付款二維碼
- 用戶手機端支付
- 完成支付,支付寶回調(diào)開發(fā)者指定的url。

2. 開發(fā)流程
2.1 沙盒調(diào)試
支付寶為我們準備了完善的沙盒開發(fā)環(huán)境,我們可以先在沙盒環(huán)境調(diào)試好程序,后續(xù)新建好應用并成功上線后,把程序中對應的參數(shù)替換為線上參數(shù)即可。
1. 創(chuàng)建沙盒應用
直接進入 open.alipay.com/develop/san… 創(chuàng)建沙盒應用即可,

這里因為是測試環(huán)境,我們就選擇系統(tǒng)默認密鑰就行了,下面選擇公鑰模式,另外應用網(wǎng)關(guān)地址就是用戶完成支付之后,支付寶會回調(diào)的url。在開發(fā)環(huán)境中,我們可以采用內(nèi)網(wǎng)穿透的方式,將我們本機的端口暴露在某個公網(wǎng)地址上,這里推薦 natapp.cn/ ,可以免費注冊使用。
2. SpringBoot代碼實現(xiàn)
在創(chuàng)建好沙盒應用,獲取到密鑰,APPID,商家賬戶PID等信息之后,就可以在測試環(huán)境開發(fā)集成對應的API了。這里我以電腦端支付API為例,介紹如何進行集成。
關(guān)于電腦網(wǎng)站支付的詳細產(chǎn)品介紹和API接入文檔可以參考:opendocs.alipay.com/open/repo-0038oa?ref=api 和 opendocs.alipay.com/open/270/01didh?ref=api
步驟1, 添加alipay sdk對應的Maven依賴。
<!-- alipay --> <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>4.35.132.ALL</version> </dependency>
步驟2,添加支付寶下單、支付成功后同步調(diào)用和異步調(diào)用的接口。
這里需要注意,同步接口是用戶完成支付后會自動跳轉(zhuǎn)的地址,因此需要是Get請求。異步接口,是用戶完成支付之后,支付寶會回調(diào)來通知支付結(jié)果的地址,所以是POST請求。
@RestController
@RequestMapping("/alipay")
public class AliPayController {
@Autowired
AliPayService aliPayService;
@PostMapping("/order")
public GenericResponse<Object> placeOrderForPCWeb(@RequestBody AliPayRequest aliPayRequest) {
try {
return aliPayService.placeOrderForPCWeb(aliPayRequest);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@PostMapping("/callback/async")
public String asyncCallback(HttpServletRequest request) {
return aliPayService.orderCallbackInAsync(request);
}
@GetMapping("/callback/sync")
public void syncCallback(HttpServletRequest request, HttpServletResponse response) {
aliPayService.orderCallbackInSync(request, response);
}
}步驟3,實現(xiàn)Service層代碼
這里針對上面controller中的三個接口,分別完成service層對應的方法。下面是整個支付的核心流程,其中有些地方需要根據(jù)你自己的實際情況進行保存訂單到DB或者檢查訂單狀態(tài)的操作,這個可以根據(jù)實際業(yè)務需求進行設計。
public class AliPayService {
@Autowired
AliPayHelper aliPayHelper;
@Resource
AlipayConfig alipayConfig;
@Transactional(rollbackFor = Exception.class)
public GenericResponse<Object> placeOrderForPCWeb(AliPayRequest aliPayRequest) throws IOException {
log.info("【請求開始-在線購買-交易創(chuàng)建】*********統(tǒng)一下單開始*********");
String tradeNo = aliPayHelper.generateTradeNumber();
String subject = "購買套餐1";
Map<String, Object> map = aliPayHelper.placeOrderAndPayForPCWeb(tradeNo, 100, subject);
if (Boolean.parseBoolean(String.valueOf(map.get("isSuccess")))) {
log.info("【請求開始-在線購買-交易創(chuàng)建】統(tǒng)一下單成功,開始保存訂單數(shù)據(jù)");
//保存訂單信息
// 添加你自己的業(yè)務邏輯,主要是保存訂單數(shù)據(jù)
log.info("【請求成功-在線購買-交易創(chuàng)建】*********統(tǒng)一下單結(jié)束*********");
return new GenericResponse<>(ResponseCode.SUCCESS, map.get("body"));
}else{
log.info("【失?。赫埱笫?在線購買-交易創(chuàng)建】*********統(tǒng)一下單結(jié)束*********");
return new GenericResponse<>(ResponseCode.INTERNAL_ERROR, String.valueOf(map.get("subMsg")));
}
}
// sync return page
public void orderCallbackInSync(HttpServletRequest request, HttpServletResponse response) {
try {
OutputStream outputStream = response.getOutputStream();
//通過設置響應頭控制瀏覽器以UTF-8的編碼顯示數(shù)據(jù),如果不加這句話,那么瀏覽器顯示的將是亂碼
response.setHeader("content-type", "text/html;charset=UTF-8");
String outputData = "支付成功,請返回網(wǎng)站并刷新頁面。";
/**
* data.getBytes()是一個將字符轉(zhuǎn)換成字節(jié)數(shù)組的過程,這個過程中一定會去查碼表,
* 如果是中文的操作系統(tǒng)環(huán)境,默認就是查找查GB2312的碼表,
*/
byte[] dataByteArr = outputData.getBytes("UTF-8");//將字符轉(zhuǎn)換成字節(jié)數(shù)組,指定以UTF-8編碼進行轉(zhuǎn)換
outputStream.write(dataByteArr);//使用OutputStream流向客戶端輸出字節(jié)數(shù)組
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String orderCallbackInAsync(HttpServletRequest request) {
try {
Map<String, String> map = aliPayHelper.paramstoMap(request);
String tradeNo = map.get("out_trade_no");
String sign = map.get("sign");
String content = AlipaySignature.getSignCheckContentV1(map);
boolean signVerified = aliPayHelper.CheckSignIn(sign, content);
// check order status
// 這里在DB中檢查order的狀態(tài),如果已經(jīng)支付成功,無需再次驗證。
if(從DB中拿到order,并且判斷order是否支付成功過){
log.info("訂單:" + tradeNo + " 已經(jīng)支付成功,無需再次驗證。");
return "success";
}
//驗證業(yè)務數(shù)據(jù)是否一致
if(!checkData(map, order)){
log.error("返回業(yè)務數(shù)據(jù)驗證失敗,訂單:" + tradeNo );
return "返回業(yè)務數(shù)據(jù)驗證失敗";
}
//簽名驗證成功
if(signVerified){
log.info("支付寶簽名驗證成功,訂單:" + tradeNo);
// 驗證支付狀態(tài)
String tradeStatus = request.getParameter("trade_status");
if(tradeStatus.equals("TRADE_SUCCESS")){
log.info("支付成功,訂單:"+tradeNo);
// 更新訂單狀態(tài),執(zhí)行一些業(yè)務邏輯
return "success";
}else{
System.out.println("支付失敗,訂單:" + tradeNo );
return "支付失敗";
}
}else{
log.error("簽名驗證失敗,訂單:" + tradeNo );
return "簽名驗證失敗.";
}
} catch (IOException e) {
log.error("IO exception happened ", e);
throw new RuntimeException(ResponseCode.INTERNAL_ERROR, e.getMessage());
}
}
public boolean checkData(Map<String, String> map, OrderInfo order) {
log.info("【請求開始-交易回調(diào)-訂單確認】*********校驗訂單確認開始*********");
//驗證訂單號是否準確,并且訂單狀態(tài)為待支付
if(驗證訂單號是否準確,并且訂單狀態(tài)為待支付){
float amount1 = Float.parseFloat(map.get("total_amount"));
float amount2 = (float) order.getOrderAmount();
//判斷金額是否相等
if(amount1 == amount2){
//驗證收款商戶id是否一致
if(map.get("seller_id").equals(alipayConfig.getPid())){
//判斷appid是否一致
if(map.get("app_id").equals(alipayConfig.getAppid())){
log.info("【成功:請求開始-交易回調(diào)-訂單確認】*********校驗訂單確認成功*********");
return true; }
}
}
}
log.info("【失敗:請求開始-交易回調(diào)-訂單確認】*********校驗訂單確認失敗*********");
return false; }
}步驟4,實現(xiàn)alipayHelper類。這個類里面對支付寶的接口進行封裝。
public class AliPayHelper {
@Resource
private AlipayConfig alipayConfig;
//返回數(shù)據(jù)格式
private static final String FORMAT = "json";
//編碼類型
private static final String CHART_TYPE = "utf-8";
//簽名類型
private static final String SIGN_TYPE = "RSA2";
/*支付銷售產(chǎn)品碼,目前支付寶只支持FAST_INSTANT_TRADE_PAY*/
public static final String PRODUCT_CODE = "FAST_INSTANT_TRADE_PAY";
private static AlipayClient alipayClient = null;
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
private static final Random random = new Random();
@PostConstruct
public void init(){
alipayClient = new DefaultAlipayClient(
alipayConfig.getGateway(),
alipayConfig.getAppid(),
alipayConfig.getPrivateKey(),
FORMAT,
CHART_TYPE,
alipayConfig.getPublicKey(),
SIGN_TYPE);
};
/*================PC網(wǎng)頁支付====================*/
/**
* 統(tǒng)一下單并調(diào)用支付頁面接口
* @param outTradeNo
* @param totalAmount
* @param subject
* @return
*/
public Map<String, Object> placeOrderAndPayForPCWeb(String outTradeNo, float totalAmount, String subject){
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setNotifyUrl(alipayConfig.getNotifyUrl());
request.setReturnUrl(alipayConfig.getReturnUrl());
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", outTradeNo);
bizContent.put("total_amount", totalAmount);
bizContent.put("subject", subject);
bizContent.put("product_code", PRODUCT_CODE);
request.setBizContent(bizContent.toString());
AlipayTradePagePayResponse response = null;
try {
response = alipayClient.pageExecute(request);
} catch (AlipayApiException e) {
e.printStackTrace();
}
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("isSuccess", response.isSuccess());
if(response.isSuccess()){
log.info("調(diào)用成功");
log.info(JSON.toJSONString(response));
resultMap.put("body", response.getBody());
} else {
log.error("調(diào)用失敗");
log.error(response.getSubMsg());
resultMap.put("subMsg", response.getSubMsg());
}
return resultMap;
}
/**
* 交易訂單查詢
* @param out_trade_no
* @return
*/
public Map<String, Object> tradeQueryForPCWeb(String out_trade_no){
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("trade_no", out_trade_no);
request.setBizContent(bizContent.toString());
AlipayTradeQueryResponse response = null;
try {
response = alipayClient.execute(request);
} catch (AlipayApiException e) {
e.printStackTrace();
}
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("isSuccess", response.isSuccess());
if(response.isSuccess()){
System.out.println("調(diào)用成功");
System.out.println(JSON.toJSONString(response));
resultMap.put("status", response.getTradeStatus());
} else {
System.out.println("調(diào)用失敗");
System.out.println(response.getSubMsg());
resultMap.put("subMsg", response.getSubMsg());
}
return resultMap;
}
/**
* 驗證簽名是否正確
* @param sign
* @param content
* @return
*/
public boolean CheckSignIn(String sign, String content){
try {
return AlipaySignature.rsaCheck(content, sign, alipayConfig.getPublicKey(), CHART_TYPE, SIGN_TYPE);
} catch (AlipayApiException e) {
e.printStackTrace();
}
return false;
}
/**
* 將異步通知的參數(shù)轉(zhuǎn)化為Map
* @return
*/
public Map<String, String> paramstoMap(HttpServletRequest request) throws UnsupportedEncodingException {
Map<String, String> params = new HashMap<String, String>();
Map<String, String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 亂碼解決,這段代碼在出現(xiàn)亂碼時使用。
// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
return params;
}
}步驟5,封裝config類,用于存放所有的配置屬性。
@Data
@Component
@ConfigurationProperties(prefix = "alipay")
public class AlipayConfig {
private String gateway;
private String appid;
private String pid;
private String privateKey;
private String publicKey;
private String returnUrl;
private String notifyUrl;
}另外需要在application.properties中,準備好上述對應的屬性。
# alipay config alipay.gateway=https://openapi.alipaydev.com/gateway.do alipay.appid=your_appid alipay.pid=your_pid alipay.privatekey=your_private_key alipay.publickey=your_public_key alipay.returnurl=完成支付后的同步跳轉(zhuǎn)地址 alipay.notifyurl=完成支付后,支付寶會異步回調(diào)的地址
3. 前端代碼實現(xiàn)
前端代碼只需要完成兩個功能,
- 根據(jù)用戶的請求向后端發(fā)起支付請求。
- 直接提交返回數(shù)據(jù)完成跳轉(zhuǎn)。
下面的例子中,我用typescript實現(xiàn)了用戶點擊支付之后的功能,
async function onPositiveClick() {
paymentLoading.value = true
const { data } = await placeAlipayOrder<string>({
//你的一些請求參數(shù),例如金額等等
})
const div = document.createElement('divform')
div.innerHTML = data
document.body.appendChild(div)
document.forms[0].setAttribute('target', '_blank')
document.forms[0].submit()
showModal.value = false
paymentLoading.value = false
}2.2 創(chuàng)建并上線APP
完成沙盒調(diào)試沒問題之后,我們需要創(chuàng)建對應的支付寶網(wǎng)頁應用并上線。
登錄 open.alipay.com/develop/man… 并選擇創(chuàng)建網(wǎng)頁應用,

填寫應用相關(guān)信息:

創(chuàng)建好應用之后,首先在開發(fā)設置中,設置好接口加簽方式以及應用網(wǎng)關(guān)。

注意密鑰選擇RSA2,其他按照上面的操作指南一步步走即可,注意保管好自己的私鑰和公鑰。
之后在產(chǎn)品綁定頁,綁定對應的API,比如我們這里是PC網(wǎng)頁端支付,找到對應的API綁定就可以了。如果第一次綁定,可能需要填寫相關(guān)的信息進行審核,按需填寫即可,一般審核一天就通過了。

最后如果一切就緒,我們就可以把APP提交上線了,上線成功之后,我們需要把下面SpringBoot中的properties替換為線上APP的信息,然后就可以在生產(chǎn)環(huán)境調(diào)用支付寶的接口進行支付了。
# alipay config alipay.gateway=https://openapi.alipaydev.com/gateway.do alipay.appid=your_appid alipay.pid=your_pid alipay.privatekey=your_private_key alipay.publickey=your_public_key alipay.returnurl=完成支付后的同步跳轉(zhuǎn)地址 alipay.notifyurl=完成支付后,支付寶會異步回調(diào)的地址
參考:
https://blog.csdn.net/xqnode/article/details/124457790
https://blog.51cto.com/u_15754099/5585676
https://zhuanlan.zhihu.com/p/596771147
https://segmentfault.com/a/1190000041974184
到此這篇關(guān)于SpringBoot集成支付寶支付的實現(xiàn)示例的文章就介紹到這了,更多相關(guān)SpringBoot支付寶支付內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot?配置文件類型properties?格式與yml?格式
這篇文章主要介紹了Spring?Boot?配置文件類型properties?格式與yml?格式,文章圍繞主題展開詳細內(nèi)容,具有一定的參考價值,需要的小伙伴可以參考一下2022-05-05
form-data與x-www-form-urlencoded的區(qū)別以及知識延伸
這篇文章主要給大家介紹了關(guān)于form-data與x-www-form-urlencoded的區(qū)別以及知識延伸,form-data和x-www-form-urlencoded都是HTTP請求中用于傳輸表單數(shù)據(jù)的編碼格式,需要的朋友可以參考下2023-11-11
sprintboot使用spring-security包,緩存內(nèi)存與redis共存方式
這篇文章主要介紹了sprintboot使用spring-security包,緩存內(nèi)存與redis共存方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
Java實現(xiàn)將Markdown轉(zhuǎn)換為純文本
這篇文章主要為大家詳細介紹了兩種在 Java 中實現(xiàn) Markdown 轉(zhuǎn)純文本的主流方法,文中的示例代碼講解詳細,大家可以根據(jù)需求選擇適合的方案2025-03-03

