练手项目1笔记 day18

目标

  • 掌握二维码生成插件qrious的使用
  • 能够说出微信支付开发的整体思路
  • 能够调用微信支付接口(统一下单)生成支付二维码
  • 能够调用微信支付接口(查询订单)查询支付状态
  • 实现支付日志的生成与订单状态的修改

1. 二维码

1. 什么是二维码

二维码,又称QR Code,QR全称为Quick Response,是一个近几年来移动设备上超流行的一种编码方式,比传统的Bar Code条形码能存更多信息,也能表示更多的数据类型

二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的。它具有条码技术的一些共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化点。

2. 二维码的优势

  • 信息容量大,可以容纳多达1850个大写字母或2710个数字或500多个汉字
  • 应用范围广,支持文字、声音、图片、指纹等
  • 容错能力强,即使图片出现部分破损也能使用
  • 成本低,容易制作

3. 二维码容错级别

L级(低) 7%的码字可以被恢复

M级(中) 15%可以被恢复

Q级(四分)25%可以被恢复

H级(高) 30%可以被恢复

4. 二维码生成插件qrious

qrious是一款基于HTML5 Canvas的纯js二维码生成插件。

参数如下

在这里插入图片描述

配置:

<html>
  <head>
    <title>demo</title>
    <meta charset="utf-8">
    <script src="qrious.min.js"></script>
  </head>
  <body>
    <img id="qrious">

    <script>
      var qr = new QRious(
        {
          element:document.getElementById('qrious'),
          size:250,
          value:'http://www.baidu.com',
          level:'H'
        }
      );
    </script>
  </body>
</html>

2. 微信扫码支付简介

1. 微信扫码支付申请

微信扫码支付是商户系统按微信支付协议生成支付二维码,用户再用微信“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。

申请步骤:(了解)

第一步:注册公众号(类型须为:服务号)

请根据营业执照类型选择以下主体注册:个体工商户| 企业/公司| 政府| 媒体| 其他类型

第二步:认证公众号

公众号认证后才可申请微信支付,认证费:300元/次。

第三步:提交资料申请微信支付

登录公众平台,点击左侧菜单【微信支付】,开始填写资料等待审核,审核时间为1-5个工作日内。

第四步:开户成功,登录商户平台进行验证

资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。

第五步:在线签署协议

本协议为线上电子协议,签署后方可进行交易及资金结算,签署完立即生效。

2. 开发文档

微信支付接口调用的整体思路:

按API要求组装参数,以XML方式发送(POST)给微信支付接口(URL),微信支付接口也是以XML方式给予响应。程序根据返回的结果(其中包括支付URL)生成二维码或判断订单状态。

在线微信支付开发文档:

https://pay.weixin.qq.com/wiki/doc/api/index.html

在本章课程中会用到”统一下单”和”查询订单”两组API

  1.   appid:微信公众账号或开放平台APP的唯一标识
    
  2.   mch_id:商户号  (配置文件中的partner)
    
  3.   partnerkey:商户密钥
    
  4.   sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性
    

3. 微信支付SDK

下载源码后install到本地仓库

mvn install:install-file -DgroupId=com.github.wxpay -DartifactId=wxpay-sdk -Dvers ion=0.0.3 -Dpackaging=jar -Dfile=e:\wxpay-sdk-0.0.3.jar

使用微信支付SDK,在maven工程引入依赖

<dependency>
  <groupId>com.github.wxpay</groupId>
  <artifactId>wxpay-sdk</artifactId>
  <version>0.0.3</version>
</dependency>

主要会用到微信支付SDK的以下功能

  1. 获取随机字符串
WXPayUtil.generateNonceStr()
  1. MAP转换xml字符串(自动添加签名)
WXPayUtil.gennerateSignedXml(param,partnerkey)
  1. XML字符串转换map
WXPayUtil.xmlToMap(result)

4. HttpClient工具类

HttpClient是Apache Jakarta Common下的子项目,提供支持http协议的客户端编程工具包,支持http协议最新的版本和建议。应用于很多项目,如Cactus和HTMLUtil都使用HttpClient

通俗说就是模拟浏览器的行为

为了简化HttpClient的使用,提供工具类HttpClient(对原生HttpClient进行封装)

该工具类的使用步骤

HttpClient client=new HttpClient(请求的url地址);
client.setHttps(true);//是否是HTTPS协议
client.setXmlParam(xmlParam);//发送的xml数据
client.post();//执行post请求
String result=client.getContent();//获取结果

5. 工程搭建和准备工作

  1. 建立支付服务接口模块pay-interface
  2. 建立支付服务实现模块pay-service(war),依赖pay-interface和其他,参见content-service,不用依赖dao。微信SDK也要引入,同之前。添加tomcat插件,端口为9000,添加spring配置文件
  3. 在common工程添加工具类HttpClient.java,并添加依赖
<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
</dependency>

添加配置文件weixinpay.properties

appid=wx8397f8696b538317
partner=1473426802
partnerkey=xxxxxx
notifyurl=http://a31ef7db.ngrok.io/WeChatPay/WeChatPayNotify

appid:微信公众账号或开放平台APP的唯一标识

partner:财付通平台的商户账号

partnerkey:财付通平台的商户密钥

notifyurl:回调地址

  1. cart-web依赖工程pay-service
  2. 将二维码插件QRious,拷贝到cart-web的plugins目录

3. 品优购-微信支付二维码生成

1. 需求分析和实现思路

1. 需求分析

在支付页面生成支付二维码,并显示订单号和金额

用户拿出手机,打开微信扫描页面上的二维码,然后在微信中完成支付

在这里插入图片描述

2. 实现思路

通过HttpClient工具类实现对远程支付接口的调用

接口链接:https://api.mch.weixin.qq.com/pay/unifiedorder

具体参数见“统一下单”API,构建参数发给统一下单的url,返回的信息中有支付url,根据url生成二维码,显示的订单号和金额也在返回的信息中

2. 后端代码实现

1. 服务接口层
  1. 在pay-interface创建包com.pinyougou.pay.service,包下建立接口
public interface WeixinPayService {

  /**
     *  生成二维码
     * @param out_trade_no 商户订单号
     * @param total_fee 标价金额
     * @return
     */
  public Map createNative(String out_trade_no,String total_fee);
2. 服务实现层

pay-service创建com.pinyougou.pay.service.impl包,新建类

@Service
public class WeixinPayServiceImpl implements WeixinPayService {

  @Value("${appid}")
  private String appid;

  @Value("${partner}")
  private String partner;

  @Value("${partnerkey}")
  private String partnerkey;

  @Value("${notifyurl}")
  private String notifyurl;

  @Override
  public Map createNative(String out_trade_no, String total_fee) {
    // 1. 参数封装
    Map param = new HashMap();
    param.put("appid",appid);//公众账号ID
    param.put("mch_id",partner);// 商户号
    param.put("nonce_str", WXPayUtil.generateNonceStr());//随机字符串
    //        param.put("sign","");// 签名 ,之后生成
    param.put("body","品优购");// 商品描述
    param.put("out_trade_no",out_trade_no);//商户订单号
    param.put("total_fee",total_fee);//交易金额,分
    param.put("spbill_create_ip","127.0.0.1");//终端IP
    param.put("notify_url",notifyurl);// 通知地址
    param.put("trade_type","NATIVE ");// 交易类型

    try {
      String paramXml = WXPayUtil.generateSignedXml(param, partnerkey);
      System.out.println("请求的参数:"+paramXml);

      // 2. 发送请求
      HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
      client.setHttps(true);
      client.setXmlParam(paramXml);
      client.post();
      // 3. 获取结果
      String xmlResult = client.getContent();
      Map<String, String> mapResult = WXPayUtil.xmlToMap(xmlResult);
      Map map = new HashMap();
      map.put("code_url",mapResult.get("code_url"));
      map.put("out_trade_no",out_trade_no);
      map.put("total_fee",total_fee);

      return map;
    } catch (Exception e) {
      e.printStackTrace();
      return new HashMap();
    }
  }
}
3. 控制层

cart-web创建PayController.java

@RestController
@RequestMapping("/pay")
public class PayController {

  @Reference
  private WeixinPayService weixinPayService;

  @RequestMapping("/createNative")
  public Map createNative(){
    IdWorker idWorker = new IdWorker();
    return weixinPayService.createNative(idWorker.nextId()+"","1");
  }
}

这里订单号通过分布式id生成器生成,金额暂时写死,后续开发再对接业务系统得到订单号和金额。

4. 测试

启动user-service、cart-service、order-service、pay-service

为了测试方便,将cart-web的security放行pay部分的后端代码,启动cart-web

发现签名错误

3. 前端代码实现

1. 服务层

在cart-web创建payService.js

app.service('payService',function ($http) {

  // 本地支付
  this.createNative=function () {
    return $http.get('pay/createNative.do');
  }
});
2. 控制层

在cart-web创建payController.js

app.controller('payController',function ($scope, payService) {
  payService.createNative().success(
    function (response) {
      // 显示订单号和金额
      $scope.money = (response.total_fee/100).toFixed(2);
      $scope.out_trade_no = response.out_trade_no;

      // 生成二维码
      var qr = new QRious({
        element:document.getElementById('qrious'),
        size:250,
        value:response.code_url,
        level:'H'
      });
    }
  );
});
3. 页面

修改pay.html,引入js

<script type="text/javascript" src="plugins/angularjs/angular.min.js">  </script>
<script type="text/javascript" src="js/base.js">  </script>
<script type="text/javascript" src="js/service/payService.js">  </script>
<script type="text/javascript" src="js/controller/payController.js">  </script>
<script type="text/javascript" src="plugins/qrious.min.js"></script>

指令

<body ng-app="pinyougou" ng-controller="payController" ng-init="createNative()">

设置二维码图片的id

<img id="qrious">

显示订单号和金额

<h4 class="fl tit-txt"><span class="success-icon"></span><span  class="success-info">订单提交成功,请您及时付款!订单号:{{out_trade_no}}</span></h4>
<span class="fr"><em class="sui-lead">应付金额:</em><em  class="orange money">¥{{money}}</em></span>

4. 品优购-检测支付状态

1. 需求分析及实现思路

1. 需求分析

当用户支付成功后跳转到成功页面

在这里插入图片描述

当返回异常时跳转到错误页面

在这里插入图片描述

2. 实现思路

通过HttpClient工具类实现对远程支付接口的调用

接口链接:https://api.mch.weixin.qq.com/pay/orderquery

具体参数参见“查询订单”api,在controller方法中轮询调用查询订单(间隔3秒),当返回状态为success,在controller方法返回结果。前端代码收到结果跳转到成功页面

怎么能让支付完成马上就知道呢?

设置循环调用

一种是前端循环调用后端

一种是后端循环调用微信支付查询结果,前端调用后端

2. 检测支付状态-后端代码

1. 服务接口层

WeixinPayService.java

// 查询订单支付状态
public Map queryPayStatus(String out_trade_no);
2. 服务实现层
@Override
public Map queryPayStatus(String out_trade_no) {
  // 1. 封装参数
  Map param = new HashMap();
  param.put("appid",appid);//公众账号ID
  param.put("mch_id",partner);//商户号
  param.put("out_trade_no",out_trade_no);//商户订单号
  param.put("nonce_str",WXPayUtil.generateNonceStr());//随机字符串
  try {
    String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);
    // 2. 发送请求

    HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
    client.setHttps(true);
    client.setXmlParam(xmlParam);
    client.post();

    // 3. 获取结果
    String xmlResult = client.getContent();
    Map<String, String> mapResult = WXPayUtil.xmlToMap(xmlResult);
    System.out.println("调用查询API返回结果:"+mapResult);
    return mapResult;
  } catch (Exception e) {
    e.printStackTrace();
    return null;
  }
}
3. 控制层

cart-web的PayController.java

@RequestMapping("/queryPayStatus")
public Result queryPayStatus(String out_trade_no){
  Result result = null;
  while(true){
    Map map = weixinPayService.queryPayStatus(out_trade_no);
    if(map==null){
      result = new Result(false,"支付发生错误");
      break;
    }
    if("SUCCESS".equals(map.get("trade_state"))){
      result = new Result(true,"支付成功");
      break;
    }

    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
  return result;
}

3. 检测支付状态-前端代码

生成二维码之后就开始调用方法查询状态

1. 服务层
// 查询支付状态
this.queryPayStatus = function (out_trade_no) {
  return $http.get('pay/queryPayStatus.do?out_trade_no='+out_trade_no);
}
2. 控制层

payController.js

// 调用查询
queryPayStatus = function () {
  payService.queryPayStatus($scope.out_trade_no).success(
    function (response) {
      if(response.success){
        location.href = "paysuccess.html";
      }else{
        location.href = "payfail.html";
      }
    }
  );
}
payService.createNative().success(
  function (response) {
    // 显示订单号和金额
    $scope.money = (response.total_fee/100).toFixed(2);
    $scope.out_trade_no = response.out_trade_no;

    // 生成二维码
    var qr = new QRious({
      element:document.getElementById('qrious'),
      size:250,
      value:response.code_url,
      level:'H'
    });

    queryPayStatus();//调用查询
  }
);

4. 查询时间限制

1. 问题分析

如果用户到了二维码页面一直未支付,或是关掉了支付页面,代码会一直循环调用微信接口,这样会对程序造成很大的压力,所以需要加一个时间限制或者循环次数限制,当超过该阈值,跳出循环。

2. 代码完善

  1. 修改cart-web工程PayController.java的queryPayStatus方法
@RequestMapping("/queryPayStatus")
public Result queryPayStatus(String out_trade_no){
  Result result = null;
  int x = 0;
  while(true){
    Map map = weixinPayService.queryPayStatus(out_trade_no);
    if(map==null){
      result = new Result(false,"支付发生错误");
      break;
    }
    if("SUCCESS".equals(map.get("trade_state"))){
      result = new Result(true,"支付成功");
      break;
    }

    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    // 为了不让循环无休止的运行,定义一个循环变量,如果这个变量超过这个值退出循环,设置时间为5min
    x++;
    if(x>=100){
      result = new Result(false,"二维码超时");
      break;
    }
  }
  return result;
}
  1. 修改payController.js
// 查询支付状态
queryPayStatus = function () {
  payService.queryPayStatus($scope.out_trade_no).success(
    function (response) {
      if(response.success){
        location.href = "paysuccess.html";
      }else{
        if(response.message==="二维码超时"){
          $scope.createNative();//重新生成二维码
        }else{
          location.href = "payfail.html";
        }
      }
    }
  );
}
  1. 测试

5. 支付成功页面显示金额

1. 问题分析

支付成功页面需要显示真正支付金额

2. 代码完善
  1. 修改payController.js,跳转页面传参
// 查询支付状态
queryPayStatus = function () {
  payService.queryPayStatus($scope.out_trade_no).success(
    function (response) {
      if(response.success){
        location.href = "paysuccess.html#?money="+$scope.money;
      }else{
        if(response.message==="二维码超时"){
          $scope.createNative();//重新生成二维码
        }else{
          location.href = "payfail.html";
        }
      }
    }
  );
}
  1. 在payController.js中引入$location服务,新增方法
// 获取金额
$scope.getMoney = function () {
  return $location.search()['money'];
}
  1. 修改页面paysuccess.html,引入js和body指令,用表达式显示金额
<p>支付金额:¥{{getMoney()}}元</p>

5. 品优购-支付日志

1. 需求分析

现在系统还有两个问题

  1. 系统中无法查询到支付记录
  2. 支付后订单状态没有改变

现在来解决这两个问题

实现思路
  1. 在用户下订单时,判断如果为微信支付,就向支付日志表添加一条记录,信息包括支付总金额、订单id(多个)、用户id、下单时间等信息,支付状态为0(未支付)
  2. 生成的支付日志对象放入redis中,以用户id作为key,在生成支付二维码时就可以从redis中提取支付日志对象的金额和订单号
  3. 当用户支付成功,修改支付日志的支付状态为1(已支付),并记录微信传递给我们的交易流水号,根据订单id(多个)修订订单的状态为2(已付款)

2. 表结构分析

tb_paylog 支付日志表

在这里插入图片描述

3. 插入日志记录

修改order-service工程OrderServiceImpl.java的add方法

内容:判断如果支付方式为微信支付,向数据库插入支付日志记录,并放入redis存储。

@Override
public void add(TbOrder order) {
  // 1. 从redis中提取购物车列表
  List<Cart> cartList = (List<Cart>) redisTemplate.boundHashOps("cartList").get(order.getUserId());
  // 2. 循环购物车列表添加订单

  List<String> orderIdList = new ArrayList<>();//订单id列表
  double total_money = 0;//总金额(元)
  for (Cart cart : cartList) {
    TbOrder tbOrder = new TbOrder();
    long orderId = idWorker.nextId();
    tbOrder.setOrderId(orderId);
    tbOrder.setPaymentType(order.getPaymentType());// 付款方式
    tbOrder.setStatus("1");//未付款
    tbOrder.setCreateTime(new Date());
    tbOrder.setUpdateTime(new Date());
    tbOrder.setUserId(order.getUserId());
    tbOrder.setReceiverAreaName(order.getReceiverAreaName());//收货人地址
    tbOrder.setReceiverMobile(order.getReceiverMobile());//收货人电话
    tbOrder.setReceiver(order.getReceiver());// 收货人
    tbOrder.setSourceType(order.getSourceType());//订单来源
    tbOrder.setSellerId(cart.getSellerId());//商家id

    double money = 0;//合计数
    // 循环购物车中每条明细记录
    for (TbOrderItem orderItem : cart.getOrderItemList()) {
      orderItem.setId(idWorker.nextId());
      orderItem.setOrderId(orderId);//订单编号
      orderItem.setSellerId(order.getSellerId());//商家id
      orderItemMapper.insert(orderItem);
      money += orderItem.getTotalFee().doubleValue();
    }
    tbOrder.setPayment(new BigDecimal(money));//合计
    orderMapper.insert(tbOrder);

    orderIdList.add(orderId+"");//添加到订单列表
    total_money +=money;//累加到总金额
  }

  // 添加支付日志
  if("1".equals(order.getPaymentType())){
    TbPayLog payLog = new TbPayLog();

    payLog.setOutTradeNo(idWorker.nextId()+"");//支付单号
    payLog.setCreateTime(new Date());
    payLog.setUserId(order.getUserId());
    payLog.setOrderList(orderIdList.toString().replace("[","").replace("]",""));
    payLog.setTotalFee((long) (total_money*100));//金额(分)
    payLog.setTradeState("0");//交易状态
    payLog.setPayType("1");//微信

    payLogMapper.insert(payLog);

    redisTemplate.boundHashOps("payLog").put(order.getUserId(),payLog);//放入缓存

  }
  // 3. 清除redis中的购物车
  redisTemplate.boundHashOps("cartList").delete(order.getUserId());
}

4. 读取支付日志

1. 服务接口层

order-interface工程的OrderService.java新增方法

// 根据用户id获取支付日志
public TbPayLog searchPayLogFromRedis(String userId);
2. 服务实现层
@Override
public TbPayLog searchPayLogFromRedis(String userId) {
  return (TbPayLog) redisTemplate.boundHashOps("payLog").get(userId);
}
3. 控制层

修改cart-web工程的PayController.java的createNative方法

实现思路:调用获取支付日志对象的方法,得到订单号和金额

@RequestMapping("/createNative")
public Map createNative(){
  // 1. 获取当前登录用户名
  String username = SecurityContextHolder.getContext().getAuthentication().getName();
  // 2. 提取支付日志(缓存)
  TbPayLog payLog = orderService.searchPayLogFromRedis(username);
  // 3. 调用微信支付接口
  if(payLog!=null){
    return weixinPayService.createNative(payLog.getOutTradeNo(),payLog.getTotalFee()+"");
  }else{
    return new HashMap();
  }

}

5. 修改订单状态

1. 服务接口层

在order-interface的OrderService.java新增方法

// 支付成功修改状态
public void updateOrderStatus(String out_trade_no,String transaction_id);
2. 服务实现层

在order-service工程OrderServiceImpl.java实现方法

做三件事:

  1. 修改支付日志的状态
  2. 修改关联的订单状态
  3. 清除缓存的支付日志对象
@Override
public void updateOrderStatus(String out_trade_no, String transaction_id) {
  // 1. 修改支付日志的状态和相关字段
  TbPayLog payLog = payLogMapper.selectByPrimaryKey(out_trade_no);
  payLog.setPayTime(new Date());//支付时间
  payLog.setTradeState("1");// 交易成功
  payLog.setTransactionId(transaction_id);//微信交易流水号

  payLogMapper.updateByPrimaryKey(payLog);//修改

  // 2. 修改订单表的状态
  String orderList = payLog.getOrderList();// 订单id 串
  String[] orderIds = orderList.split(",");

  for (String orderId : orderIds) {
    TbOrder order = orderMapper.selectByPrimaryKey(Long.valueOf(orderId));
    order.setStatus("2");//已付款状态
    order.setPaymentTime(new Date());//支付时间
    orderMapper.updateByPrimaryKey(order);
  }

  // 3. 清除缓存的payLog
  redisTemplate.boundHashOps("payLog").delete(payLog.getUserId());
}
3. 控制层

修改cart-web的PayController.java,在微信支付接口有成功返回状态时,调用修改状态的方法

if("SUCCESS".equals(map.get("trade_state"))){
  result = new Result(true,"支付成功");
  // 修改订单状态
  orderService.updateOrderStatus(out_trade_no, (String) map.get("transaction_id"));
  break;
}

6. 支付日志显示

需求:在运营商后台,显示支付日志列表,实现按日期、状态、用户进行查询

TODO

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值