1.搭建pay-consumer环境

1.MAVEN依赖

其他的依赖与前面的consumer项目相类似,主要增加了支付宝支付接口的依赖:alipay-sdk-java
项目结构:
image.png
依赖:

  1. <dependencies>
  2. <dependency>
  3. <groupId>com.zh.crowd</groupId>
  4. <artifactId>crowdfunding17-member-api</artifactId>
  5. <version>0.0.1-SNAPSHOT</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.springframework.cloud</groupId>
  9. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-web</artifactId>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.springframework.boot</groupId>
  17. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-configuration-processor</artifactId>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-data-redis</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework.session</groupId>
  29. <artifactId>spring-session-data-redis</artifactId>
  30. </dependency>
  31. <!-- 阿里支付接口依赖 -->
  32. <dependency>
  33. <groupId>com.alipay.sdk</groupId>
  34. <artifactId>alipay-sdk-java</artifactId>
  35. <version>4.10.124.ALL</version>
  36. </dependency>
  37. <!-- 测试 -->
  38. <dependency>
  39. <groupId>org.springframework.boot</groupId>
  40. <artifactId>spring-boot-starter-test</artifactId>
  41. <scope>test</scope>
  42. <exclusions>
  43. <exclusion>
  44. <groupId>org.junit.vintage</groupId>
  45. <artifactId>junit-vintage-engine</artifactId>
  46. </exclusion>
  47. </exclusions>
  48. </dependency>
  49. </dependencies>

2.支付宝接口需要的配置类

项目结构:
image.png
代码:

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. @Component
  5. @ConfigurationProperties(prefix = "ali.pay")
  6. public class PayProperties {
  7. private String appId;
  8. private String merchantPrivateKey;
  9. private String aliPayPublicKey;
  10. private String notifyUrl;
  11. private String returnUrl;
  12. private String signType;
  13. private String charset;
  14. private String gatewayUrl;
  15. }

3.配置application.yml

项目结构:
image.png
代码:

server:
  port: 7000
spring:
  application:
    name: crowd-pay
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
  redis:
    host: 127.0.0.1
  session:
    store-type: redis
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1000/eureka/
# 在配置文件中设置支付宝公钥等信息
ali:
  pay:
    ali-pay-public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxWd5LWnUoSSR8EEDFQURvlRnPmgu0ib/1DA+bZ6FNXK7ycgWY+QkAJ3Tq8MElZWo3rtGVOzX69sI5otYw2xME1zenB4q5vixJTE6RNvu2b72AcSBCXqtfIRGPbHZasUVBJAOphzb2yfWWj0eGkFageGOmQEbpD3hfO+axhrTnf9LleFS7uBsq6sMCoLFVKbZjPsVeGJasAGVhM82Q9zkmNcO77a0gltBDc4TGFFJ6nBwIk3lVM0gllf1X1QyEwEpsVjssvzlOsliH3Ol4ZYunW3p4YZS4V2iFwEHwPByVZUmt/N/W3dOmFjOeV1CiMl8UQ+7qVr4OfuOCSIdOXCbzwIDAQAB
    app-id: 2021000119603139
    charset: utf-8
    gateway-url: https://openapi.alipaydev.com/gateway.do
    merchant-private-key: MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCTQK5X2K+3NDW0wPevI26hc6mNA8tVKKTwYFnRuhqY1yz7nxt4+2rt9d0IMIyZwO6MN4ikBHeypzYmmhZWPTYbpvquigO4WaHlsF5ERSQ5ZE29pWgf7a1/iGFXYO96d9n3rO23/xxOXvztcEGKRa2JVIpoeE7m+nTjUNtZUU9Vyg7IWdMl5nKIpnV4GvmqkLerxbVsAlf89bF9qih+ELsG9rrzLfyZ1Qg45o//OqWBMMciyuRDQvwKCf8ZstwsHoKyKrXAgPXkPJE+bcbbsI4HV7z7nwAZqLoxfztU6QBWKK+ImDges1rEtRgBOgbZCQtnX3i/2z2Lz3E3bnPf8DmDAgMBAAECggEAF8dPIAJuxZ/ZuFmc5ZxMJig+TfY55IOOtv5oIrTxzJKawfs8fXnkF48nZUYpvj5ohx4Eo6/RAHl+BhfF2aFC1o5NVtgUp7VZrnSflFix886duy01tko0RS7pwSl3IoiuGw/Cx36bvJAcOd9xxflgf5e2Tg53d1ckfvy0nyTBMfITSHZz+GeQvR2yBvW38+/ZwBtoGKVaRxi+6oYROVizPrvEtDXuujyxUpGiVO1X/8zSgAuHuOr8l/v5HNF1QoNNJysTrRsBjz99K5ATJcVeH/IUYCt5G6ZEt+nhpoCcwOooTNHD0gEF3Gmxe5ClQA9QAHoaL+bq4IXqFvgy2aJiAQKBgQDNel4LLF30FFrAuQCo8uT+lorItUxYWvS8DIyd/CGs7pWJ5KY+QsKQTbWX+uXC+YQlXzQIQ0ia/rTLwK5UaYxQVUevjAdCdxFpjhtvO7/xLoaO0ktLAx6RzHnoEmINh9ntjY1eX1MddanC9I8bkI+Z9Lw0WCffT0ANRVKao2x3AQKBgQC3dV7splscTM69Bxj8qpzzW2abPcy9H9aT+ZHyuysm+doU0+1j6i8Yda23uzCNvdC3OTknaaVW+2b6SgfLRjjlc8n3qZwbVuN+sq+yjsbjTdeVNCS5ZA8F63ofs8byxNoDSyIOp/bMuBxv2HBcNTrc4Sh8/Bon2O15Qnv7o2NUgwKBgCx6/961VQVgXqD3q3/nTNEb76ExNfyue+o1YT9V9EhGQZLfL2ms9Ade+x+STaiecQ/SAyaCwjXjS2oMJPDbBGfjfigvTOcDdX5/J8s1iQCBzNkgBvKPE1AGjqFBUX6SWQfGq8KxKgHnlb9BR7V03tE6HH/MUZ1Sv4/f4Nje5b0BAoGAA4qHb4fygERXRTzK1a2xY90iyOJTfl8L9EU/9PB578Go3PgOP/x4Nj+nP8GkOUgtrCu1l7YpU9l8bl5ZcioD1SH8BwTGF50AvSRT0d2Bp0eMXrvn7ZAp3hJihBQjRfJYxvF/UcsaL29qVRpXaOA6J6NM9cSj+JruUoXfAMdxlmMCgYBNooQBfhfoddZn3RlRgKCPSJoBhdniMkVJ7+ZQYegwJGLmHCr+niPtOHUVJtvWNCrHpPh57tY9rRLXckMnwP3h9mollpRqlpRHWcmQKw1E9XPRaKivITluBxFET3LfO372NeYDUsgosdiwAP5+tJSL1QfqQHncpURjiEKq49UPow==
    notify-url: http://k64izg.natappfree.cc/pay/notify
    return-url: http://localhost/pay/return
    sign-type: RSA2

4.主启动类

项目结构:
image.png
代码:

@EnableFeignClients
@SpringBootApplication
public class CrowdMainPayApp {
    public static void main(String[] args) {
        SpringApplication.run(CrowdMainPayApp.class,args);
    }
}

2.“立即付款”跳转

1.前端页面

order-confirm-order.html
项目结构:
image.png
代码:

<!--按钮标签-->
<li style="margin-top:10px;">
    <button id="payBtn" disabled="disabled" type="button" class="btn btn-warning btn-lg">
        <i class="glyphicon glyphicon-credit-card"></i> 
        立即付款
    </button>
</li>

<!--添加一个初始无内容的表单,用于提交付款请求时携带数据-->
<form id="summaryForm" action="/pay/generate/order" method="post"></form>

<!--id=payBtn对应的按钮的单击响应函数-->
<script>
    // 支付按钮的单击响应事件
    $("#payBtn").click(function () {
        // 收集要提交给表单的数据
        var addressId = $("[name=addressId]:checked").val();
        var invoice = $("[name=invoiceRadio]:checked").val();
        var invoiceTitle = $.trim($("[name=invoiceTitle]").val());
        var remark = $.trim($("[name=remark]").val());
        // 提交表单
        $("#summaryForm")
            .append("<input type='hidden' name='addressId' value='"+ addressId +"' />")
            .append("<input type='hidden' name='invoice' value='"+ invoice +"' />")
            .append("<input type='hidden' name='invoiceTitle' value='"+ invoiceTitle +"' />")
            .append("<input type='hidden' name='orderRemark' value='"+ remark +"' />")
            .submit();
    });
</script>

2.后端handler方法

项目结构:

代码:

@Controller
public class PayHandler {

    @Autowired
    private PayProperties payProperties;

    @Autowired
    private MySQLRemoteService mySQLRemoteService;

    private Logger logger = LoggerFactory.getLogger(PayHandler.class);

    // 通过ResponseBody注解,让当前方法的返回值成为响应体,在浏览器上显示支付宝的支付界面
    @ResponseBody
    @RequestMapping("generate/order")
    public String generateOrder(OrderVO orderVO, HttpSession session) throws UnsupportedEncodingException, AlipayApiException {

        // 得到session域中的orderProjectVO
        OrderProjectVO orderProjectVO = (OrderProjectVO)session.getAttribute("orderProjectVO");

        // 将orderProjectVO赋给前端传来的orderVO
        orderVO.setOrderProjectVO(orderProjectVO);

        // 生成支付宝订单号
        // 使用uuid生成用户id部分
        String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();

        // 根据日期生成字符串
        String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());

        // 组装
        String orderNum = time + uuid;

        // 存入orderVO
        orderVO.setOrderNum(orderNum);

        // 计算订单金额
        Double orderAmount = (double)(orderProjectVO.getReturnCount() * orderProjectVO.getSupportPrice() + orderProjectVO.getFreight());

        // 存入orderVO
        orderVO.setOrderAmount(orderAmount);

        // 把orderVO存入session域
        session.setAttribute("orderVO", orderVO);

        return sendRequestToAliPay(orderNum,orderAmount,orderProjectVO.getProjectName(),orderProjectVO.getReturnContent());
    }

    /**
     *
     * @param orderNum 订单号
     * @param orderAmount 总金额
     * @param subject 订单名称,这里用项目名称
     * @param body 商品描述,这里用回报的描述
     * @return 返回页面
     * @throws AlipayApiException
     * @throws UnsupportedEncodingException
     */
    private String sendRequestToAliPay(String orderNum, Double orderAmount, String subject, String body) throws AlipayApiException, UnsupportedEncodingException {

        //获得初始化的AlipayClient
        AlipayClient alipayClient = new DefaultAlipayClient(
                payProperties.getGatewayUrl(),
                payProperties.getAppId(),
                payProperties.getMerchantPrivateKey(),
                "json",
                payProperties.getCharset(),
                payProperties.getAliPayPublicKey(),
                payProperties.getSignType());

        //设置请求参数
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
        alipayRequest.setReturnUrl(payProperties.getReturnUrl());
        alipayRequest.setNotifyUrl(payProperties.getNotifyUrl());


        alipayRequest.setBizContent("{\"out_trade_no\":\""+ orderNum +"\","
                + "\"total_amount\":\""+ orderAmount +"\","
                + "\"subject\":\""+ subject +"\","
                + "\"body\":\""+ body +"\","
                + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");

        // 返回
        return alipayClient.pageExecute(alipayRequest).getBody();
    }

    // return请求对应方法
    @ResponseBody
    @RequestMapping("/return")
    public String returnUrlMethod(HttpServletRequest request,HttpSession session) throws UnsupportedEncodingException, AlipayApiException {
        //获取支付宝GET过来反馈信息
        Map<String,String> params = 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] + ",";
            }
            //乱码解决,这段代码在出现乱码时使用
            valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name, valueStr);
        }

        boolean signVerified = AlipaySignature.rsaCheckV1(
                params,
                payProperties.getAliPayPublicKey(),
                payProperties.getCharset(),
                payProperties.getSignType()); //调用SDK验证签名

        //——请在这里编写您的程序(以下代码仅作参考)——
        if(signVerified) {
            //商户订单号
            String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");

            //支付宝交易号
            String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");

            //付款金额
            String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"),"UTF-8");

            // 得到session域中的orderVO
            OrderVO orderVO = (OrderVO)session.getAttribute("orderVO");

            // 给orderVO设置支付宝交易号
            orderVO.setPayOrderNum(trade_no);

            // 存入mysql数据库
            ResultEntity<String> resultEntity = mySQLRemoteService.saveOrderRemote(orderVO);

            logger.info("save order result: " + resultEntity.getResult());

            return "trade_no:"+trade_no+"<br/>out_trade_no:"+out_trade_no+"<br/>total_amount:"+total_amount;
        }else {
            return "验签失败";
        }
    }



    // notify请求对应方法
    @RequestMapping("/notify")
    public void notifyUrlMethod(HttpServletRequest request) throws AlipayApiException, UnsupportedEncodingException {
        //获取支付宝POST过来反馈信息
        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] + ",";
            }
            //乱码解决,这段代码在出现乱码时使用
            valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name, valueStr);
        }

        boolean signVerified = AlipaySignature.rsaCheckV1(
                params,
                payProperties.getAliPayPublicKey(),
                payProperties.getCharset(),
                payProperties.getSignType()); //调用SDK验证签名

        if(signVerified) {//验证成功
            //商户订单号
            String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"),"UTF-8");

            //支付宝交易号
            String trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"),"UTF-8");

            //交易状态
            String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"),"UTF-8");

            if(trade_status.equals("TRADE_FINISHED")){
                //判断该笔订单是否在商户网站中已经做过处理
                //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
                //如果有做过处理,不执行商户的业务程序

                //注意:
                //退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
            }else if (trade_status.equals("TRADE_SUCCESS")){
                //判断该笔订单是否在商户网站中已经做过处理
                //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
                //如果有做过处理,不执行商户的业务程序

                //注意:
                //付款完成后,支付宝系统发送该交易状态通知
            }

            logger.info("success");
            logger.info("out_trade_no" + out_trade_no);
            logger.info("trade_no" + trade_no);
            logger.info("trade_status" + trade_status);

        }else {//验证失败
            logger.info("fail");

            //调试用,写文本函数记录程序运行情况是否正常
            //String sWord = AlipaySignature.getSignCheckContentV1(params);
            //AlipayConfig.logResult(sWord);
        }
    }
}

支付宝对订单号的要求:
https://opendocs.alipay.com/support/01ray5
image.png

 // 生成支付宝订单号
        // 使用uuid生成用户id部分
        String uuid = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();

        // 根据日期生成字符串
        String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());

        // 组装
        String orderNum = time + uuid;


3.将订单存入数据库

1.修改OrderVO类的代码

将原本为Integer类型的addressId改为String类型,使其与OrderPO对象对应,否则会出现无法存入addressId的情况。
(记得让OrderVO实现序列化接口,因为需要存入session,存入redis)

2. 远程api接口

项目结构:
image.png
代码:

@RequestMapping("save/order/remote")
ResultEntity<String> saveOrderRemote(@RequestBody OrderVO orderVO);

3. mysql工程模块

1.handler方法

项目结构:
image.png
代码:

@RequestMapping("save/order/remote")
ResultEntity<String> saveOrderRemote(@RequestBody OrderVO orderVO) {
    try {
        orderService.saveOrder(orderVO);
        return ResultEntity.successWithoutData();
    } catch (Exception e) {
        e.printStackTrace();
        return ResultEntity.failed(e.getMessage());
    }
}

2.service接口

项目结构:/
image.png
代码:

void saveOrder(OrderVO orderVO);

3.service实现

项目结构:
image.png
代码:

@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
@Override
public void saveOrder(OrderVO orderVO) {
    // 创建OrderPO对象
    OrderPO orderPO = new OrderPO();
    // 从传入的OrderVO给OrderPO赋值
    BeanUtils.copyProperties(orderVO,orderPO);
    // 将OrderPO存入数据库
    orderPOMapper.insert(orderPO);
    // 得到存入后自增产生的order id
    Integer orderId = orderPO.getId();
    // 得到orderProjectVO
    OrderProjectVO orderProjectVO = orderVO.getOrderProjectVO();
    // 创建OrderProjectPO对象
    OrderProjectPO orderProjectPO = new OrderProjectPO();
    // 赋值
    BeanUtils.copyProperties(orderProjectVO,orderProjectPO);
    // 给orderProjectPO设置orderId
    orderProjectPO.setOrderId(orderId);
    // 存入数据库
    orderProjectPOMapper.insert(orderProjectPO);
}

4.注意

这里因为需要在存入OrderPO后得到自增的orderId,需要修改MyBatis的代码:
即增加 useGeneratedKeys=”true” keyProperty=”id”

 <insert id="insert" parameterType="com.zh.crowd.entity.po.OrderPO"  useGeneratedKeys="true" keyProperty="id">
    insert into t_order (id, order_num, pay_order_num, 
      order_amount, invoice, invoice_title, 
      order_remark, address_id)
    values (#{id,jdbcType=INTEGER}, #{orderNum,jdbcType=CHAR}, #{payOrderNum,jdbcType=CHAR}, 
      #{orderAmount,jdbcType=DOUBLE}, #{invoice,jdbcType=INTEGER}, #{invoiceTitle,jdbcType=CHAR}, 
      #{orderRemark,jdbcType=CHAR}, #{addressId,jdbcType=CHAR})
  </insert>