暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

项目对接支付宝支付

奔跑的小梁 2023-02-23
1001
  • 后端应用对接支付宝需要准备

    • 应用的公钥【需要上传到支付宝】

    • 应用的私钥【自己保留】

    • 支付宝的公钥【支付宝提供】


  • 支付宝官方提供了生成公私钥的工具

    • https://opendocs.alipay.com/open/291/introduce

  • 引入支付宝的jar包依赖【maven中央仓库中提供】

    <!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-sdk-java -->
    <dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.10.218.ALL</version>
    </dependency>
    • 编写alipay的配置文件信息

      #支付宝配置
      alipay:
      #支付成功的跳转页面,支付成功后用户端跳转页面
        success_return_url: https://home.net


      #支付宝通知回调接口,支付成功后的通知接口,要求公网可以访问
        callback_url: https://callback.net
      • 编写AlipayConfig 封装AlipayClient用于后续发起请求【沙箱环境】,不需要交由spring管理,因为本身是单例设计模式编写

        • 需要准备好【相当于指定应用和支付宝之间的交互协议格式】:

          • 支付宝网关地址(官方查询)

          • 商户的appid (申请开发的时候官方给定的

          • app的私钥(自己用工具生成的,上传到支付宝)

          • 支付宝的公钥(支付宝官方提供)

          • 签名类型

          • 字符的编码格式

          • 请求的返回值类型

        public class AlipayConfig {


        /**
        * 支付宝网关地址 TODO
        */
        public static final String PAY_GATEWAY="https://openapi.alipaydev.com/gateway.do";
        /**
        * 支付宝 APPID TODO
        */
        public static final String APPID="2016092000555936";
        /**
        * 应用私钥 TODO
        */
        public static final String APP_PRI_KEY = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDh2PCkJ956QB49e3YK/RBAbAcTolDwKNa9qvmKq1LwZ74It+UDkf5YDkcQXAjsLri5A8B1j910ERIyv0UjIZUUu1gNry57vqA8gLHtvgthyRNh3897kqEXL8AxrEW83R/XjZVcVcqd6f2yOBW6N4lPj60qM90WTo/n2+I1wlXY5moRguB8NFoLC8Q1kAzQvjqILUIOjbqodoxcKRVS9RuAduNfE5XzmYFCDnB9RdtWXs10jfLG1pF37FY9RKOvMRNzlo5sH23HmDizHz29dUk9RRHge21hYgECP0hs8FHJBzPt6tIvbDE2Wu1uwQ3Bhrl4pURZ7d0CWr/cPRGaciEBAgMBAAECggEAL1QtKdmJEAl7zNqgpDDgRP/eg8jSOWH1jo3T+bFpkiPLeTxAa/0eVgm37r+6xvQLlgopPPnHKNmi/KuEq5YQDeYsz8FUdm4+Wi+GGhJnhDiFLU+fxX+27or9NeuqOagFUkDDejQoX+t3VO/X7cxRpDCx01RHErOoCKjVwPpWzzslWmVNdIwZFowFJosiHIj1u4Jt72dJ0AXeYoXUvaoKGEc0pb0TUlLXiosksWjYa/gZI7m4hxuzbUT8k0V/+zMxQ3fIJ3BNUPg0eH3HZRI/rZZnOinwxmX5BMRZDzuvm3TNxRO0Tz0EcQE+b3Fzcgnod9L96gAuJSYRjOJmMN2JIQKBgQDyvy/XxytWzwcHxGDq53VLOWzZr81dVz53AMx5y1qhcTXBC4v8s8Bp8yq3xw9v7I3EHVWuKRwRJn8JuTZKJCQONM6p09kHq8hZX3neDJtJg/+CB6hWKtwWGxJpV5GSzmmEE02HC29UZlz0WVOluVU3ETlR78chsKKE5EZkG0HlBQKBgQDuLY3myRAQt7q0RHFw4D5EjsIFZUCooptQgzWzgo5diN+9/ShdzKaKz/ByujXJ+hRX0V6WPknB4AOmp0i03ZD7bajFexOSj0a7CmL7DpRYrR30pA1N29hHh4b609GDwefjVuo2i2oPlkn1pCLnpmuxkV8XHeAL7jdN0Hw7IuOMzQKBgH7jAw4mlPfmdcVQmFyRqlUs6kILzCxbW1J3P2r2qiQzeiazc3QfPZfkPNMdoWse2qfFsbC82mf4mHUrtD4jEBnA7roE+7Av0iUtbBVuv4k5D0kX2Z1Y//wqIICh/n0fmjYopODPUF1suHAddUTuUKXdQfobfRqWKw2OCWFwggxFAoGBAN7Ig4PMrBdHE/+MSaPwTK4p7jfHxtw2BRshL/jx9KJu5gG7K6fZRipvaBSYMrnzDlY6Q8Q7DRiopiGbNbnfyb40i3n0rpEXLWzEwWLd90qe23c6gCtEqR/3F/3EZaRdmr6eTMOqUhG0XQfEeNW/Z9qXBKAF1My0DMzfZ2SVNM09AoGBAIvnGe74eaN+uXFUfDU8NC6lnZJDARU11SzHc3yuOVyl4Sr/dGEytWo4Y5F6XYEsWcO6b+VDMI5qMzXpVIAO51esiapR6R7grVadiExRP1xcx+0wqM3zjqQxH8NrUuxgTFXROzQaOSYOmcZy6BNEqLpyRO4GlXK4TIbvaw47hF8I";
        /**
        * 支付宝公钥 TODO
        */
        public static final String ALIPAY_PUB_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk8OPeu/Bhbxr8chgRXnDSm0FDMZoK3r/qLLzqbv+L2/mYTLxHKw5U3c5XPHXar5SVWRp+IgLsZdTfYeZ+jYFmY+dTYk8mG8GXc1uYwHFADcWQAcR4KA3cbxiaubg7g//ECEhwA5CmaosjJ2p8UAz4j7cDB37TDON7oGMFbkXnSBlGhj5oy6rxdUXS+KeG9YmRPkQU3x3ljK37RRLFhwPPRR218IGDCmCtnI5ddKEq1hMAPnYmKNezerxSGGxxIaggt+sDwN1S3GNNSU7AelLPuvNEza49KaQijsBJpaLKUvqt5KO4IVfvPCmRsIFe7KzgdEmjygIY81qXacPzopJIQIDAQAB";
        /**
        * 签名类型
        */
        public static final String SIGN_TYPE="RSA2";
        /**
        * 字符编码
        */
        public static final String CHARSET="UTF-8";
        /**
        * 返回参数格式
        */
        public static final String FORMAT="json";
        /**
        * 构造函数私有化
        */
        private AlipayConfig(){


        }
        private volatile static AlipayClient instance = null;


        /**
        * 单例模式获取, 双重锁校验
        * @return
        */
        public static AlipayClient getInstance(){


        if(instance==null){
        synchronized (AlipayConfig.class){
        if(instance == null){
        instance = new DefaultAlipayClient(PAY_GATEWAY,APPID,APP_PRI_KEY,FORMAT,CHARSET,ALIPAY_PUB_KEY,SIGN_TYPE);
        }
        }
        }
        return instance;
        }


        • 读取配置文件中的回调url和页面跳转url,并交由spring管理

          • 在项目开发中,通常会用一个单独的配置类来承载配置文件中的常量信息,方便开发过程中的使用。

          @Configuration
          @Data
          public class PayUrlConfig {
          /**
          * 支付成功页面跳转
          */
          @Value("${alipay.success_return_url}")
          private String alipaySuccessReturnUrl;


          /**
          * 支付成功,回调通知
          */
          @Value("${alipay.callback_url}")
          private String alipayCallbackUrl;
          }
          • 测试接口

            • 测试接口的流程如下

              • 订单基本信息封装

              • 将订单基本信息和回调接口url,页面跳转url封装为AlipayTradeWapPayRequest(AlipayTradeWapPayRequest这个类是支付宝提供的封装入参的request请求)

              • 通过AlipayClient发起请求

              • 判断响应结果

            /**
            * 测试支付方法
            */
            @GetMapping("test_pay")
            public void testAlipay(HttpServletResponse response) throws AlipayApiException, IOException {

            //构造订单信息map集合
            HashMap<String,String> content = new HashMap<>();
            //商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复
            String no = UUID.randomUUID().toString();
            log.info("订单号:{}",no);
            content.put("out_trade_no", no);
            content.put("product_code", "FAST_INSTANT_TRADE_PAY");
            //订单总金额,单位为元,精确到小数点后两位
            content.put("total_amount", String.valueOf("111.99"));
            //商品标题/交易标题/订单标题/订单关键字等。 注意:不可使用特殊字符,如 ,=,&amp; 等。
            content.put("subject", "杯子");
            //商品描述,可空
            content.put("body", "好的杯子");
            // 该笔订单允许的最晚付款时间,逾期将关闭交易。取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。 该参数数值不接受小数点, 如 1.5h,可转换为 90m。
            content.put("timeout_express", "5m");

            //将订单信息和url封装为alipay的request
            AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
            request.setBizContent(JSON.toJSONString(content));
            request.setNotifyUrl(payUrlConfig.getAlipayCallbackUrl());
            request.setReturnUrl(payUrlConfig.getAlipaySuccessReturnUrl());

            //向支付宝网关发起请求并收到返回结果
            AlipayTradeWapPayResponse alipayResponse = AlipayConfig.getInstance().pageExecute(request);


            //读取返回值信息并响应给用户
            if(alipayResponse.isSuccess()){
            System.out.println("调用成功");
            //返回值form是html形式的表单格式,可以直接写出
            String form = alipayResponse.getBody();
            response.setContentType("text/html;charset=UTF-8");
            response.getWriter().write(form);
            response.getWriter().flush();
            response.getWriter().close();
            } else {
            System.out.println("调用失败");
            }
            }

            • 支付宝回调接口的开发

              • 应用场景:当用户付款成功以后,支付宝会回调我们商户后台的callback()接口,通知我们用户已经付款成功了。

            • 回调接口CallbackController

              • 回调接口一定是可以在公网访问的一个域名。

              @Api("订单回调通知模块")
              @Controller
              @RequestMapping("/api/callback/order/v1")
              @Slf4j
              public class CallbackController {


              @Autowired
              private ProductOrderService productOrderService;


              /**
              * 支付回调通知 post方式
              * @param request
              * @param response
              * @return
              */
              @PostMapping("alipay")
              public String alipayCallback(HttpServletRequest request, HttpServletResponse response){
              //将异步通知中收到的所有参数存储到map中
              Map<String,String> paramsMap = convertRequestParamsToMap(request);
              log.info("支付宝回调通知结果:{}",paramsMap);
              //调用SDK验证签名
              try {
              boolean signVerified = AlipaySignature.rsaCheckV1(paramsMap, AlipayConfig.ALIPAY_PUB_KEY, AlipayConfig.CHARSET, AlipayConfig.SIGN_TYPE);
              if(signVerified){
              //签名验证通过
              JsonData jsonData = productOrderService.handlerOrderCallbackMsg(ProductOrderPayTypeEnum.ALIPAY,paramsMap);
              if(jsonData.getCode() == 0){
              //通知结果确认成功,不然会一直通知,八次都没返回success就认为交易失败
              return "success";
              }
              }


              } catch (AlipayApiException e) {
              log.info("支付宝回调验证签名失败:异常:{},参数:{}",e,paramsMap);
              }
              return "failure";
              }


              /**
              * 将request中的参数转换成Map
              * @param request
              * @return
              */
              private static Map<String, String> convertRequestParamsToMap(HttpServletRequest request) {
              Map<String, String> paramsMap = new HashMap<>(16);
              Set<Map.Entry<String, String[]>> entrySet = request.getParameterMap().entrySet();


              for (Map.Entry<String, String[]> entry : entrySet) {
              String name = entry.getKey();
              String[] values = entry.getValue();
              int size = values.length;
              if (size == 1) {
              paramsMap.put(name, values[0]);
              } else {
              paramsMap.put(name, "");
              }
              }
              return paramsMap;
              }
              }
              • service层的修改订单状态的业务逻辑

                /***
                * 支付通知结果更新订单状态
                * @param alipay
                * @param paramsMap
                * @return
                */
                @Override
                public JsonData handlerOrderCallbackMsg(ProductOrderPayTypeEnum payType, Map<String, String> paramsMap) {


                if(payType.name().equalsIgnoreCase(ProductOrderPayTypeEnum.ALIPAY.name())){
                //支付宝支付
                //获取商户订单号
                String outTradeNo = paramsMap.get("out_trade_no");
                //交易的状态
                String tradeStatus = paramsMap.get("trade_status");
                if("TRADE_SUCCESS".equalsIgnoreCase(tradeStatus) || "TRADE_FINISHED".equalsIgnoreCase(tradeStatus)){
                //更新订单状态,采用的是cas操作来完成更新
                productOrderMapper.updateOrderPayState(outTradeNo,ProductOrderStateEnum.PAY.name(),ProductOrderStateEnum.NEW.name());
                return JsonData.buildSuccess();
                }


                } else if(payType.name().equalsIgnoreCase(ProductOrderPayTypeEnum.WECHAT.name())){
                //微信支付 TODO
                }


                return JsonData.buildResult(BizCodeEnum.PAY_ORDER_CALLBACK_NOT_SUCCESS);
                }

                算法训练营永久班授课,欢迎同学们参加呀~

                奔跑的小梁,公众号:梁霖编程工具库算法训练营春节价格通知,2023年2月12日



                文章转载自奔跑的小梁,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                评论