zoukankan      html  css  js  c++  java
  • 支付宝支付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">
    <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;}">
    <input type="submit" value="立即支付" style="display:none" >
    </form>
    <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.如果看了博客后还是是很理解,该博客有视频详细的讲解与演示,可以看视频讲解或单独问我;

    完美!

  • 相关阅读:
    秋意浓浓回成都2月杂记
    验证表单的js代码段
    C#算法小程序(1)
    C#数据结构之单向链表
    Excel VBA编程的常用代码
    我的漫漫系分路
    2007年下半年系统分析师上午试卷及参考答案
    「2014年間休日カレンダー」のご案内
    Eclipse的几点使用技巧
    沧海一粟小组(第一次作业)
  • 原文地址:https://www.cnblogs.com/newAndHui/p/14254029.html
Copyright © 2011-2022 走看看