javascript
SpringBoot集成支付宝沙箱手机网站支付详细流程和踩坑分享
描述
本文主要講解SpringBoot集成支付寶沙箱手機(jī)網(wǎng)站支付,即網(wǎng)頁(yè)點(diǎn)擊按鈕發(fā)起支付,跳轉(zhuǎn)到沙箱app付款
由于其他博客的流程大多籠統(tǒng),有時(shí)候并不能找到正確的集成方式,本文盡可能詳細(xì)的闡述付款,異步通知,驗(yàn)簽,退款的全部流程以及踩坑的分享,希望可以幫助你們少走彎路
必要的準(zhǔn)備工作如公私鑰,沙箱申請(qǐng)?jiān)斠?jiàn)??支付寶沙箱簡(jiǎn)單集成
編譯環(huán)境:IDEA
支付寶手機(jī)支付Demo
地址:https://docs.open.alipay.com/203/105910/
其中僅有一個(gè)信息配置類(lèi),其他的邏輯都集成在了,jsp頁(yè)面中,個(gè)人感覺(jué)不是太友好
所以本文把邏輯集成到了service和controller中
流程
1. 添加Maven依賴(lài)
//支付寶SDK <dependency><groupId>com.alipay.sdk</groupId><artifactId>alipay-sdk-java</artifactId><version>4.5.0.ALL</version> </dependency> //不用寫(xiě)get和set方法的輔助依賴(lài),可以根據(jù)需要選擇不添加,那就要手動(dòng)添加get()和set()方法 //若僅添加依賴(lài)還是報(bào)錯(cuò),需要在File-settings-Plugins中搜索添加,也可百度詳細(xì)安裝方式 <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId> </dependency>2. 添加配置文件
為了實(shí)現(xiàn)模塊化,看上去更專(zhuān)業(yè)(嘿嘿),在根目錄(與Application同級(jí))建立config文件夾,該文件夾下建立AlipayConfig.java文件
import lombok.Data; import org.springframework.stereotype.Component;@Data @Component public class AlipayConfig {// 沙箱appidpublic static String APPID = "2016101400683082";// 私鑰 pkcs8格式的public static String RSA_PRIVATE_KEY = "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";// 請(qǐng)求網(wǎng)關(guān) 固定public static String URL = "https://openapi.alipaydev.com/gateway.do";//異步通知地址public static String notify_url = "http://xxx/alipay/notify";//同步地址 // public static String return_url = "http://xxx/alipay/return";// 編碼public static String CHARSET = "UTF-8";// 返回格式public static String FORMAT = "json";// 支付寶公鑰public static String ALIPAY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjrEVFMOSiNJXaRNKicQuQdsREraftDA9Tua3WNZwcpeXeh8Wrt+V9JilLqSa7N7sVqwpvv8zWChgXhX/A96hEg97Oxe6GKUmzaZRNh0cZZ88vpkn5tlgL4mH/dhSr3Ip00kvM4rHq9PwuT4k7z1DpZAf1eghK8Q5BgxL88d0X07m9X96Ijd0yMkXArzD7jg+noqfbztEKoH3kPMRJC2w4ByVdweWUT2PwrlATpZZtYLmtDvUKG/sOkNAIKEMg3Rut1oKWpjyYanzDgS7Cg3awr1KPTl9rHCazk15aNYowmYtVabKwbGVToCAGK+qQ1gT3ELhkGnf3+h53fukNqRH+wIDAQAB";// 沙箱支付寶公鑰public static String ZHIFUBAO_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArS0SLdzGCafgBwLv3IXIrr7cXKk2pzC5fgQxVz1M9F05ReSXQOfWfjDstNrToiI3kwY3XRGI/ULzywbpKwTm/IzOULxSnexCCJRxQonmQcV1C3ixsQi9rqkL0XaV7YBQl0DfmZoHKbbpmfj/7Uv9hQ2viJ/3n844bhaIwYhR7+Smu8xk+hbT0DEpp75cJV9pt+ngCHj6x3vkGHPj7w70JGKY73wkT6wBD0A7vz/cHHkMH6EeIkus1R5umd2rGXE/8zaPFRpysNKiys4ujAW7tOwCkqiuaon3AxGdQrHom3Twp+cpm7gkzc5v/p4qHgVUyZVKjrj6VJTRIgEQOegDtQIDAQAB";// RSA2public static String SIGNTYPE = "RSA2"; }這里說(shuō)明一下
1.同步地址是支付成功后跳轉(zhuǎn)的地址,由于我的業(yè)務(wù)邏輯是前端傳給后臺(tái)要跳轉(zhuǎn)的地址,所以在這并未設(shè)置,根據(jù)需要配置
2.為什么有支付寶公鑰和沙箱支付寶公鑰那,demo中自身寫(xiě)的是支付寶公鑰,然而和我的沙箱中寫(xiě)的支付寶公鑰并不一樣。但是使用demo的公鑰下單并沒(méi)有問(wèn)題,但是在驗(yàn)簽和退款時(shí)都會(huì)出錯(cuò),要使用沙箱中的支付寶公鑰。并沒(méi)有嘗試下單時(shí)也使用沙箱支付寶公鑰,諸位可以試一試。這兩個(gè)字符串長(zhǎng)得很像,我一開(kāi)始以為是一個(gè)!!!
3. 創(chuàng)建service層
同樣根目錄創(chuàng)建文件夾,后創(chuàng)建 AlipayService接口,分別寫(xiě)創(chuàng)建訂單,異步通知和退款三個(gè)接口方法
public interface AlipayService {String create(String orderId, String returnUrl);//異步通知會(huì)返回一個(gè)requestvoid notify(HttpServletRequest request);void refund(String orderId); }后創(chuàng)建Impl實(shí)現(xiàn)類(lèi)? AlipayServiceImpl.java
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayClient; import com.alipay.api.DefaultAlipayClient; import com.alipay.api.domain.AlipayTradeRefundModel; import com.alipay.api.domain.AlipayTradeWapPayModel; import com.alipay.api.internal.util.AlipaySignature; import com.alipay.api.request.AlipayTradeRefundRequest; import com.alipay.api.request.AlipayTradeWapPayRequest; import com.imooc.config.AlipayConfig; import com.imooc.service.AlipayService; import com.imooc.utils.JsonUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Iterator; import java.util.Map;/*** @Author Sakura* @Date 9/9/2019**/ @Service @Slf4j public class AlipayServiceImpl implements AlipayService {@Autowiredprivate AlipayConfig alipayConfig;//訂單名稱(chēng)private static final String ORDER_NAME = "自定義訂單名稱(chēng)";//和支付寶簽約的產(chǎn)品碼 固定值private static final String PRODUCTCODE = "QUICK_WAP_WAY";//支付成功標(biāo)識(shí)(可退款的簽約是TRADE_SUCCESS,不可退款的簽約是TRADE_FINISHED)private static final String TRADE_SUCCESS = "TRADE_SUCCESS";@Overridepublic String create(String orderId, String returnUrl) {AlipayClient client = new DefaultAlipayClient(alipayConfig.URL, alipayConfig.APPID, alipayConfig.RSA_PRIVATE_KEY, alipayConfig.FORMAT, alipayConfig.CHARSET, alipayConfig.ALIPAY_PUBLIC_KEY,alipayConfig.SIGNTYPE);AlipayTradeWapPayRequest alipay_request=new AlipayTradeWapPayRequest();// 封裝請(qǐng)求支付信息AlipayTradeWapPayModel model= new AlipayTradeWapPayModel();//訂單編號(hào),不可重復(fù)model.setOutTradeNo(orderId);//訂單名稱(chēng)model.setSubject(ORDER_NAME);//訂單金額model.setTotalAmount("0.01");//產(chǎn)品嗎model.setProductCode(PRODUCTCODE);alipay_request.setBizModel(model);//支付成功后跳轉(zhuǎn)的地址alipay_request.setReturnUrl(returnUrl);//異步通知地址alipay_request.setNotifyUrl(alipayConfig.notify_url);// form表單生產(chǎn)String result = "";try {// 調(diào)用SDK生成表單result = client.pageExecute(alipay_request).getBody();} catch (AlipayApiException e) {log.info("【支付寶支付】支付失敗 error={}", e);}return result;}@Overridepublic void notify(HttpServletRequest request) {Map<String, String> map = new HashMap<>();Map<String, String[]> requestParams = request.getParameterMap();for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {String name = iter.next();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] + ",";}map.put(name, valueStr);}//驗(yàn)證簽名boolean signVerified = false;try {signVerified = AlipaySignature.rsaCheckV1(map, alipayConfig.ZHIFUBAO_PUBLIC_KEY, alipayConfig.CHARSET, alipayConfig.SIGNTYPE);} catch (com.alipay.api.AlipayApiException e) {log.info("[支付驗(yàn)證] 異常={}", JsonUtil.toJson(e));return;}if (signVerified) {//處理自己的業(yè)務(wù)邏輯}// log.info("[支付驗(yàn)證] 驗(yàn)證結(jié)果={}", signVerified);return;}@Overridepublic void refund(String orderId) {AlipayClient client = new DefaultAlipayClient(alipayConfig.URL, alipayConfig.APPID, alipayConfig.RSA_PRIVATE_KEY, alipayConfig.FORMAT, alipayConfig.CHARSET, alipayConfig.ZHIFUBAO_PUBLIC_KEY,alipayConfig.SIGNTYPE);AlipayTradeRefundRequest alipay_request = new AlipayTradeRefundRequest();AlipayTradeRefundModel model=new AlipayTradeRefundModel();//退款的訂單Id,也可以設(shè)置流水號(hào)model.setOutTradeNo(orderId);//退款金額model.setRefundAmount("0.01");alipay_request.setBizModel(model);String alipay_response = "";try {alipay_response = client.execute(alipay_request).getBody();} catch (AlipayApiException e) {log.info("【支付寶支付】退款失敗 error={}", e);} // log.info("[支付退款] response={}", alipay_response);} }日志我使用的是Slf4j,也可以直接System.out.println()
以上代碼下單,退款的部分是demo中的代碼,拷貝改了一下,具體的支付等的注意事項(xiàng)在編寫(xiě)controller再詳細(xì)闡述
這里重點(diǎn)說(shuō)一下異步通知? ?官方文檔:https://docs.open.alipay.com/203/105286/
異步通知的主要作用就是驗(yàn)證付款之后,修改數(shù)據(jù)庫(kù)的支付信息等
支付成功后跳轉(zhuǎn)return_url的同時(shí)回將支付的信息異步發(fā)送到notify_url,可以是一個(gè)網(wǎng)頁(yè),但最好是controller中的一個(gè)路徑,便于業(yè)務(wù)處理
跳轉(zhuǎn)url以 http://xxx/?...的方式返回,?之后是攜帶的信息,完整的是
https://api.xx.com/receive_notify.htm?total_amount=2.00&buyer_id=2088102116773037&body=大樂(lè)透2.1&trade_no=2016071921001003030200089909&refund_fee=0.00¬ify_time=2016-07-19 14:10:49&subject=大樂(lè)透2.1&sign_type=RSA2&charset=utf-8¬ify_type=trade_status_sync&out_trade_no=0719141034-6418&gmt_close=2016-07-19 14:10:46&gmt_payment=2016-07-19 14:10:47&trade_status=TRADE_SUCCESS&version=1.0&sign=kPbQIjX+xQc8F0/A6/AocELIjhhZnGbcBN6G4MM/HmfWL4ZiHM6fWl5NQhzXJusaklZ1LFuMo+lHQUELAYeugH8LYFvxnNajOvZhuxNFbN2LhF0l/KL8ANtj8oyPM4NN7Qft2kWJTDJUpQOzCzNnV9hDxh5AaT9FPqRS6ZKxnzM=&gmt_create=2016-07-19 14:10:44&app_id=2015102700040153&seller_id=2088102119685838¬ify_id=4a91b7a78a503640467525113fb7d8bg8e所以我們用一個(gè)Map把key-value存儲(chǔ)起來(lái),也可以使用
@RequestParam("total_amout") String total_amout的方式僅接收自己所需要的參數(shù),因?yàn)楹芏鄥?shù)都是多余的,看個(gè)人,閑麻煩就用Map
比較重要的信息有??
out_trade_no 訂單號(hào) trade_status支付狀態(tài) total_amount訂單金額一般驗(yàn)證付款的嚴(yán)格流程是
- 使用AlipaySignature.rsaCheckV1驗(yàn)證訂單是否正確
- out_trade_no訂單號(hào)是否是后臺(tái)支付時(shí)的訂單號(hào)
- 判斷total_amount付款金額是否等于后臺(tái)應(yīng)付金額
- 驗(yàn)證付款方是否是下單方
這里第四條看自己業(yè)務(wù)能不能支持他人代付
附我的map中的信息
4.創(chuàng)建controller
根目錄創(chuàng)建controller文件夾,內(nèi)創(chuàng)建 AlipayController.java
import com.imooc.exception.SellException; import com.imooc.service.AlipayService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest; import java.util.Map;/*** @Author Sakura* @Date 9/9/2019**/ @Controller @RequestMapping("/alipay") public class AlipayController {@Autowiredprivate AlipayService alipayService;@GetMapping("/create")@ResponseBodypublic String create(@RequestParam("orderId") String orderId,@RequestParam("returnUrl") String returnUrl) {//發(fā)起支付String payUrl = alipayService.create(orderId,returnUrl);return payUrl;}/*** 支付寶異步通知*/@PostMapping("/notify")public void notify(HttpServletRequest request) {alipayService.notify(request);} }1. 創(chuàng)建訂單時(shí),一定要使用? @ResponseBody ,并return 通過(guò)service返回的結(jié)果,因?yàn)榍岸它c(diǎn)擊按鈕發(fā)起支付時(shí),會(huì)先跳轉(zhuǎn)至付款頁(yè)面,后打開(kāi)沙箱app付款
2. 退款的業(yè)務(wù)邏輯, 一般是自己的service中先進(jìn)行數(shù)據(jù)庫(kù)的退款信息更改,然后
@Autowired private AlipayService alipayService;調(diào)用refund方法傳入訂單編號(hào)
踩坑分享
1. 前端點(diǎn)擊按鈕發(fā)起支付無(wú)法跳轉(zhuǎn)頁(yè)面支付
controller中創(chuàng)建訂單一定要使用? @ResponseBody ,并return 通過(guò)service返回的結(jié)果,使返回結(jié)果是一個(gè)頁(yè)面
2. 驗(yàn)簽,退款異常
new?DefaultAlipayClient() 時(shí),其中傳遞的時(shí)沙箱中支付寶公鑰而不是應(yīng)用公鑰(通過(guò)開(kāi)發(fā)助手生成的),也不是demo中自帶的支付寶公鑰
3. 支付寶網(wǎng)關(guān)地址是固定的,一定不要改
有不妥之處,歡迎交流~~
總結(jié)
以上是生活随笔為你收集整理的SpringBoot集成支付宝沙箱手机网站支付详细流程和踩坑分享的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 立统 视频防泄密系统 技术白皮书2021
- 下一篇: 重温士兵突击之后 职场风云