支付寶支付java版實戰(含視頻講解)


1.背景

實際開發中用到支付寶支付的概念非常大......

這里重點分析一下支付寶支付實際生產必須要實現的功能

1.獲取支付鏈接(統一下單)

2.支付回調(異步通知)

3.統一下單交易查詢

4.退款

5.退款查詢

6.對賬單下載

官方接口文檔如下

https://opendocs.alipay.com/apis

2.需要的賬號數據

appid

公鑰

私鑰

網外可以訪問的支付寶異步回調地址,如果沒有學習可以使用免費的:https://www.cnblogs.com/newAndHui/p/14241177.html

當然實際生產這些是需要公司資質申請才可以的

但是支付做的非常人性化,提供了沙箱環境給我們測試、開發用

沙箱環境配置文檔(很簡單的幾部就搞定)

https://opendocs.alipay.com/open/200/105311

沙箱環境配置好后可以得到如下數據

appid

公鑰

私鑰

買家支付寶賬號(用於測試的時候登錄付款)

商家賬號(這里用不到)

配置好后截圖如下:

 

3.獲取支付鏈接(統一下單)

這里以手機網站支付為例,因為這個應用場景最常見

文檔:https://opendocs.alipay.com/open/203/105287

 統一下單代碼:

package com.ldp.user.service.impl;

import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import com.ldp.user.common.constant.AliPayData;
import com.ldp.user.common.constant.PayConstant;
import com.ldp.user.common.exception.AliPayException;
import com.ldp.user.entity.bo.PayBO;
import com.ldp.user.entity.vo.PayVO;
import com.ldp.user.service.IPayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.HashMap;

/**
 * @author 姿勢帝-博客園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/08 9:04
 * @description
 */
@Service
@Slf4j
public class AliPayService implements IPayService {
    // 緩存阿里客戶端
    private static HashMap<String, AlipayClient> payClient = new HashMap<>();

    @Override
    public String getPayType() {
        return PayConstant.ALIPAY;
    }

    @Override
    public PayVO getPayInfo(PayBO payBO) throws Exception {
        // 實際生中這里應該根據訂單號查詢產品 與 查詢 收款賬號數據
        // 這里模擬訂單數據  賬號使用支付寶的沙箱環境
        // 沙箱環境文檔 https://opendocs.alipay.com/open/200/105311
        payBO.setAppId("2016092900623927");
        payBO.setPublicKey(AliPayData.PUBLIC_KEY);
        payBO.setPrivateKey(AliPayData.PRIVATE_KEY);
        payBO.setPayUrl("https://openapi.alipaydev.com/gateway.do");
        payBO.setAsynchronousNotifyUrl("http://lidongping.free.idcfengye.com/api/async/alipay/payment");
        payBO.setSynchronizationNotifyUrl("http://lidongping.free.idcfengye.com/api/sync/alipay/payment");
        payBO.setPayTitle("購買課程測試");
        payBO.setPayMoney(100.01);
        // 准備支付客戶端
        AlipayClient client = initAlipay(payBO);
        AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
        // 設置異步通知地址
        request.setNotifyUrl(payBO.getAsynchronousNotifyUrl());
        // 設置同步通知地址
        request.setReturnUrl(payBO.getSynchronizationNotifyUrl());
        JSONObject bizContent = new JSONObject();
        String payTitle = URLUtil.encode(payBO.getPayTitle(), "utf-8");
        bizContent.put("subject", payTitle);
        bizContent.put("out_trade_no", payBO.getOrderNo());
        bizContent.put("total_amount", String.format("%.2f", payBO.getPayMoney()));
        //用戶付款中途退出返回商戶網站的地址(同步返回地址)
        bizContent.put("quit_url", payBO.getSynchronizationNotifyUrl());
        bizContent.put("product_code", "QUICK_WAP_WAY");
        //90分鍾超時
        bizContent.put("timeout_express", "90m");
        request.setBizContent(bizContent.toJSONString());
        log.info("支付寶下單參數:" + JSON.toJSONString(request));
        AlipayTradeWapPayResponse response = client.pageExecute(request);
        log.info("支付寶下單響應:" + JSON.toJSONString(response));
        if (!response.isSuccess()) {
            throw new AliPayException(response.getMsg());
        }
        PayVO payVO = new PayVO().setOrderNo(payBO.getOrderNo()).setPayInfo(response.getBody());
        return payVO;
    }

    /**
     * @param payBO
     * @return
     */
    private AlipayClient initAlipay(PayBO payBO) {
        log.info("收款支付寶賬號客戶端初始化,url:{},AppId:{}", payBO.getPayUrl(), payBO.getAppId());
        AlipayClient alipayClient = payClient.get(payBO.getAppId());
        if (alipayClient != null) {
            return alipayClient;
        }
        alipayClient = new DefaultAlipayClient(payBO.getPayUrl(), payBO.getAppId(),
                payBO.getPrivateKey(), "json", "utf-8", payBO.getPublicKey(), "RSA2");
        payClient.put(payBO.getAppId(), alipayClient);
        return alipayClient;
    }
}
View Code

4.支付回調

文檔:https://opendocs.alipay.com/open/203/105286/

代碼:

package com.ldp.user.controller;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alipay.api.internal.util.AlipaySignature;
import com.ldp.user.common.base.BaseResponse;
import com.ldp.user.common.base.ResponseBuilder;
import com.ldp.user.common.constant.AliPayConstant;
import com.ldp.user.common.constant.AliPayData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @author 姿勢帝-博客園
 * @address https://www.cnblogs.com/newAndHui/
 * @WeChat 851298348
 * @create 01/09 6:24
 * @description
 */
@RestController
@Slf4j
public class AliPayController {
    /**
     * 支付寶支付 異步回調
     * 官方文檔
     * https://opendocs.alipay.com/open/203/105286/
     *
     * @param request
     * @return
     * @throws Exception
     */
    @PostMapping("/async/alipay/payment")
    public String notifyPaymentResult(HttpServletRequest request) throws Exception {
        log.info("異步回調....");
        Map<String, String> paramsMap = ServletUtil.getParamMap(request);
        // 解析參數
        String tradeStatus = paramsMap.get("trade_status");
        String totalAmount = paramsMap.get("total_amount");
        String orderNo = paramsMap.get("out_trade_no");
        String tradeNo = paramsMap.get("trade_no");
        String buyerId = paramsMap.get("buyer_id");
        if (StrUtil.isEmpty(tradeStatus) || StrUtil.isEmpty(orderNo) ||
                (!tradeStatus.equals(AliPayConstant.WAIT_BUYER_PAY) &&
                        !tradeStatus.equals(AliPayConstant.TRADE_CLOSED) &&
                        !tradeStatus.equals(AliPayConstant.TRADE_SUCCESS) &&
                        !tradeStatus.equals(AliPayConstant.TRADE_FINISHED))) {
            log.error("支付寶回調參數異常");
            return AliPayConstant.RESP_FAILURE;
        }
        // 不處理狀態
        if (tradeStatus.equals(AliPayConstant.WAIT_BUYER_PAY) ||
                tradeStatus.equals(AliPayConstant.TRADE_CLOSED)) {
            return AliPayConstant.RESP_SUCCESS;
        }
        // TODO 根據訂單號獲取收款賬號數據... 實際生產應該這樣寫 這里略
        // 這里使用模擬數據
        String publicKey = AliPayData.PUBLIC_KEY;
        // 參數驗簽
        boolean signPassed = AlipaySignature.rsaCheckV1(paramsMap, publicKey, "utf-8", "RSA2");
        if (!signPassed) {
            log.error("支付寶驗簽失敗");
            return AliPayConstant.RESP_FAILURE;
        }
        // TODO 更新數據庫訂單數據 ... 這里略
        return AliPayConstant.RESP_SUCCESS;
    }

    /**
     * 支付寶支付 同步返回
     *
     * @return
     * @throws Exception
     */
    @GetMapping("/async/alipay/payment")
    public BaseResponse async() {
        log.info("同步返回.....");
        // TODO 同步返回一般是指用戶支付完成后返回的頁面,通常是一個頁面,這里模擬一下同步返回...
        return ResponseBuilder.success("同步返回額....");
    }
}
View Code

5.測試

1.調用獲取支付信息接口

/**
     * 支付寶沙箱環境 支付賬號
     * 買家賬號djpbvo6259@sandbox.com
     * 登錄密碼111111
     * 支付密碼111111
     */
    @Test
    void getPayInfo() {
        String url = urlLocal + "/userOrder/payInfo";
        System.out.println("請求地址:" + url);
        HttpRequest request = HttpUtil.createRequest(Method.GET, url);
        Map<String, Object> map = new HashMap<>();
        map.put("orderNo", "NO" + System.currentTimeMillis());
        // 微信支付 wechat  支付寶 alipay
        map.put("payCategory", "alipay");
        // 201 jsapi支付 , 202 微信H5  101支付寶H5
        map.put("payType", "101");
        request.form(map);
        System.out.println("請求參數:" + map);
        request.header("X-Real_IP", "192.168.5.195");
        request.setConnectionTimeout(60 * 1000);
        String respone = request.execute().body();
        System.out.println("響應結果:" + respone);
    }

測試請求如下:

請求地址:http://localhost:8080/api/userOrder/payInfo
請求參數:{payCategory=alipay, orderNo=NO1610170051893, payType=101}
響應結果:{"message":"success","code":100,"data":{"orderNo":"NO1610170051893","payInfo":"<form name=\"punchout_form\" method=\"post\" action=\"https://openapi.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.wap.pay&sign=GDby0sDLbIeH8Q7lbE7cls9Ar5uF0hPsy7f8BVxZff6%2B38rRXioUtAhYhf9vEd464vCV0spWLIpZmoL0Kx9HtIkHdZXDt90yL37JHKgedvn292kE3sqYHnF0mpXBLqFcbwxllxkq%2BpS8IQA7KKZWADTuHDPdGOYy6%2BZD3gP607aJ7i34hNKUReCeFcAc3M0ctUubn6u%2F%2FiC%2FXQQARI%2FJ7pR2MMD86m5GKuev%2FNv1hUvYDvW0SMH9IADGxoCo4Fn4faCX2Yz3HTjUWAgpzQun6eohEnyRD%2Fg1JLGMZ2PJc4NYnYpDDmFYHf430nQvIiKpQ%2BR3wO%2BUph54zKBsMIS7pA%3D%3D&return_url=http%3A%2F%2Flidongping.free.idcfengye.com%2Fapi%2Fsync%2Falipay%2Fpayment&notify_url=http%3A%2F%2Flidongping.free.idcfengye.com%2Fapi%2Fasync%2Falipay%2Fpayment&version=1.0&app_id=2016092900623927&sign_type=RSA2&timestamp=2021-01-09+13%3A27%3A32&alipay_sdk=alipay-sdk-java-3.7.26.ALL&format=json\">\n<input type=\"hidden\" name=\"biz_content\" value=\"{&quot;out_trade_no&quot;:&quot;NO1610170051893&quot;,&quot;total_amount&quot;:&quot;100.01&quot;,&quot;quit_url&quot;:&quot;http://lidongping.free.idcfengye.com/api/sync/alipay/payment&quot;,&quot;subject&quot;:&quot;%E8%B4%AD%E4%B9%B0%E8%AF%BE%E7%A8%8B%E6%B5%8B%E8%AF%95&quot;,&quot;timeout_express&quot;:&quot;90m&quot;,&quot;product_code&quot;:&quot;QUICK_WAP_WAY&quot;}\">\n<input type=\"submit\" value=\"立即支付\" style=\"display:none\" >\n</form>\n<script>document.forms[0].submit();</script>"}}

payInfo是支付寶返回的支付信息,測試是建議在斷點的情況下復制出來,因為輸出后form表單中有轉移符

2.獲得支付信息后,模擬訪問html頁面中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>支付測試</title>
</head>
<body>
<h2>test ali pay</h2>
<div>
    <!-- TODO 這里的代碼時獲取支付信息時支付寶返回的,實際生產中應該在前端填充然后會自動跳轉,
    這里只是測試,手動復制接口獲得的支付信息,然后在使用瀏覽器訪問該html頁面,即可就如支付寶支付頁面-->
    <form name="punchout_form" method="post"
          action="https://openapi.alipaydev.com/gateway.do?charset=utf-8&method=alipay.trade.wap.pay&sign=fMqWzKUHlcaj0UkqXlR%2Fl4Gx4bmPQ7xRIxZ49n%2BXCjVT2wPtEmUyCfJG7d1x0UfAXUhgaCnEgu8vS1IjBPH5aAfCHni5UMDOJBDArAqcJvGvZkp43zCp9f9fFsPf4kUwXOLCvpO%2BjqEbM2puF6k5JpTT%2BsBWIo9CaILT4GC5fvyTXxAC6DmMwlN%2F4NrsbFXbQjxdnMrF4LTLWJmPFZn%2BftdLIstsgh79RvS0r%2BnVy6dDpaEbxCYIlal%2FGGhdnp8bttnI3WHxv4fZqu8qiyGHFdQ6N9ZHx09HgthCnd7PJqkO6IOy1aeYo4ZhSmSRzQxbMUTqPXIS7orNPHMtJBdcNg%3D%3D&return_url=http%3A%2F%2Flidongping.free.idcfengye.com%2Fapi%2Fwc%2Fnotify%2Fcode&notify_url=http%3A%2F%2Flidongping.free.idcfengye.com%2Fapi%2Fwc%2Fnotify%2Fcode&version=1.0&app_id=2016092900623927&sign_type=RSA2&timestamp=2021-01-08+22%3A01%3A19&alipay_sdk=alipay-sdk-java-3.7.26.ALL&format=json">
        <input type="hidden" name="biz_content"
               value="{&quot;out_trade_no&quot;:&quot;NO1610114479772&quot;,&quot;total_amount&quot;:&quot;100.01&quot;,&quot;quit_url&quot;:&quot;http://lidongping.free.idcfengye.com/api/wc/notify/code&quot;,&quot;subject&quot;:&quot;%E8%B4%AD%E4%B9%B0%E8%AF%BE%E7%A8%8B%E6%B5%8B%E8%AF%95&quot;,&quot;timeout_express&quot;:&quot;90m&quot;,&quot;product_code&quot;:&quot;QUICK_WAP_WAY&quot;}">
        <input type="submit" value="立即支付" style="display:none">
    </form>
    <script>document.forms[0].submit();</script>
</div>
</body>
</html>

3.訪問html頁面

 因為我們是在瀏覽器上演示,選擇繼續瀏覽器付款

選擇支付寶登錄

4.登錄沙箱環境買家賬號

 

5.輸入密碼確認支付

6.支付寶異步回調

異步處理代碼

  /**
     * 支付寶支付 異步回調
     * 官方文檔
     * https://opendocs.alipay.com/open/203/105286/
     *
     * @param
     * @return
     * @throws Exception
     */
    @PostMapping("/async/alipay/payment")
    public String notifyPaymentResult(@RequestParam Map<String, String> paramsMap) throws Exception {
        log.info("異步回調....");
        // 解析參數
        String tradeStatus = paramsMap.get("trade_status");
        String totalAmount = paramsMap.get("total_amount");
        String orderNo = paramsMap.get("out_trade_no");
        String tradeNo = paramsMap.get("trade_no");
        String buyerId = paramsMap.get("buyer_id");
        if (StrUtil.isEmpty(tradeStatus) || StrUtil.isEmpty(orderNo) ||
                (!tradeStatus.equals(AliPayConstant.WAIT_BUYER_PAY) &&
                        !tradeStatus.equals(AliPayConstant.TRADE_CLOSED) &&
                        !tradeStatus.equals(AliPayConstant.TRADE_SUCCESS) &&
                        !tradeStatus.equals(AliPayConstant.TRADE_FINISHED))) {
            log.error("支付寶回調參數異常");
            return AliPayConstant.RESP_FAILURE;
        }
        // 不處理狀態
        if (tradeStatus.equals(AliPayConstant.WAIT_BUYER_PAY) ||
                tradeStatus.equals(AliPayConstant.TRADE_CLOSED)) {
            return AliPayConstant.RESP_SUCCESS;
        }
        // TODO 根據訂單號獲取收款賬號數據... 實際生產應該這樣寫 這里略
        // 這里使用模擬數據
        String publicKey = AliPayData.PUBLIC_KEY;
        // 參數驗簽
        boolean signPassed = AlipaySignature.rsaCheckV1(paramsMap, publicKey, "utf-8", "RSA2");
        if (!signPassed) {
            log.error("支付寶驗簽失敗");
            return AliPayConstant.RESP_FAILURE;
        }
        // TODO 更新數據庫訂單數據和緩存 ... 這里略
        // 這里只是模擬方放入緩存,便於前端查詢訂單  60 * 60 * 2L 表示緩存2小時
        RedisUtil.set(orderNo, JSON.toJSON(paramsMap), 60 * 60 * 2L);
        log.info("異步處理成功.............");
        return AliPayConstant.RESP_SUCCESS;
    }
View Code

 異步處理結果

01-09 22:42:17.323 [http-nio-8080-exec-4] INFO c.l.u.c.i.HttpServletRequestWrapperFilter - ContentType: application/x-www-form-urlencoded; charset=utf-8
01-09 22:42:17.323 [http-nio-8080-exec-4] INFO c.l.u.c.i.HttpServletRequestWrapperFilter - 請求地址: /api/async/alipay/payment
01-09 22:42:17.323 [http-nio-8080-exec-4] INFO c.l.u.c.i.HttpServletRequestWrapperFilter - 請求方法: POST
01-09 22:42:17.323 [http-nio-8080-exec-4] INFO c.l.u.c.i.HttpServletRequestWrapperFilter - 請求參數: gmt_create=2021-01-09 22:42:11&charset=utf-8&seller_email=ybtvrf9123@sandbox.com&subject=購買課程測試&sign=md0r4kl8kNllBUv+kTZXH/QnaVJuqyfEVwO5swkTDc08eHw0yM6JY/KASVAuYQV/1eJf4X1DRNmxN0Tr786k7PclEedO4wm4Qd4xC3Har5h6NlaGKCvWGRGfeIiaypxm4ILgct9MHKtCPztJDiU2Cg03vzqmidkMrdKcTr2RZN0ZgHGCzNodbTyp5ijt7purGFAdLEyAWfDjsLwMXUSgiSqhYhqe6qMm1meSVSjQkLH9e0GlJGgUU7pxjEQesrLcp6fzFhOVF0wwsccZeXJ+fFWLewTlJTb8L+ER8r1tGLBPMKATfGwuNkUWTUuEjsJXgp07qmNBgbAFQdnvkqZ4bg==&buyer_id=2088102178030268&invoice_amount=100.01&notify_id=2021010900222224212030260509089969&fund_bill_list=[{"amount":"100.01","fundChannel":"ALIPAYACCOUNT"}]&notify_type=trade_status_sync&trade_status=TRADE_SUCCESS&receipt_amount=100.01&buyer_pay_amount=100.01&app_id=2016092900623927&sign_type=RSA2&seller_id=2088102177806680&gmt_payment=2021-01-09 22:42:12&notify_time=2021-01-09 22:42:13&version=1.0&out_trade_no=NO1610203242503&total_amount=100.01&trade_no=2021010922001430260501106994&auth_app_id=2016092900623927&buyer_logon_id=djp***@sandbox.com&point_amount=0.00
01-09 22:42:17.323 [http-nio-8080-exec-4] INFO com.ldp.user.controller.AliPayController - 異步回調....
01-09 22:42:17.339 [http-nio-8080-exec-4] INFO com.ldp.user.controller.AliPayController - 異步處理成功.............
01-09 22:42:17.339 [http-nio-8080-exec-4] INFO c.l.u.c.i.HttpServletRequestWrapperFilter - 響應結果: success
01-09 22:42:17.339 [http-nio-8080-exec-4] INFO c.l.u.c.i.HttpServletRequestWrapperFilter - HTTP狀態: 200
01-09 22:42:17.339 [http-nio-8080-exec-4] INFO c.l.u.c.i.HttpServletRequestWrapperFilter - 處理時長: 16毫秒
View Code

7.同步返回

  /**
     * 支付寶支付 同步返回
     *
     * @return
     * @throws Exception
     */
    @GetMapping("/sync/alipay/payment")
    public BaseResponse async() {
        log.info("同步返回.....");
        // TODO 同步返回一般是指用戶支付完成后返回的頁面,通常是一個頁面,這里模擬一下同步返回...
        return ResponseBuilder.success("同步返回額....");
    }

7.查詢訂單支付狀態

 /**
     * 獲取支付信息
     * <p>
     *
     * @param orderNo
     * @return
     */
    @GetMapping("/order")
    public BaseResponse getOrder(String orderNo) throws Exception {
        //  TODO 實際生產中應該根據訂單號 先查詢緩存,緩存沒有在查詢數據庫
        //  這里只是測試,只查詢緩存
        Object order = RedisUtil.get(orderNo);
        if (null == order) {
            // TODO 查詢數據庫 略
        }
        return ResponseBuilder.success(order);
    }

6.小結

1.這里代碼是實際開發中的代碼,只是將業務邏輯去掉,架構模式保留,如果要完整的代碼可以找我拿源碼;

2.這里暫時只是演示下單和回調接口,后面在組織項目的時候再詳細講解;

3.如果看了博客后還是是很理解,該博客有視頻詳細的講解與演示,可以看視頻講解或單獨問我;

完美!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM