乐尚代驾十订单支付

news2024/11/15 12:27:32

账单信息

  • 司机结束代驾之后,生成账单(包含账单信息和分账信息)
  • 司机发送账单给乘客
  • 乘客获取账单之后,进行支付
    在这里插入图片描述

获取账单信息

order_bill表记录的账单信息,我们直接获取即可

@Operation(summary = "根据订单id获取实际账单信息")
@GetMapping("/getOrderBillInfo/{orderId}")
public Result<OrderBillVo> getOrderBillInfo(@PathVariable Long orderId) {
    return Result.ok(orderInfoService.getOrderBillInfo(orderId));
}





@Override
public OrderBillVo getOrderBillInfo(Long orderId) {
    LambdaQueryWrapper<OrderBill> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(OrderBill::getOrderId,orderId);
    OrderBill orderBill = orderBillMapper.selectOne(wrapper);
    
    OrderBillVo orderBillVo = new OrderBillVo();
    BeanUtils.copyProperties(orderBill,orderBillVo);
    return orderBillVo;
}






/**
 * 根据订单id获取实际账单信息
 * @param orderId
 * @return
 */
@GetMapping("/order/info/getOrderBillInfo/{orderId}")
Result<OrderBillVo> getOrderBillInfo(@PathVariable("orderId") Long orderId);

获取分账信息

@Operation(summary = "根据订单id获取实际分账信息")
@GetMapping("/getOrderProfitsharing/{orderId}")
public Result<OrderProfitsharingVo> getOrderProfitsharing(@PathVariable Long orderId) {
    return Result.ok(orderInfoService.getOrderProfitsharing(orderId));
}





@Override
public OrderProfitsharingVo getOrderProfitsharing(Long orderId) {
    LambdaQueryWrapper<OrderProfitsharing> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(OrderProfitsharing::getOrderId,orderId);
    OrderProfitsharing orderProfitsharing = orderProfitsharingMapper.selectOne(wrapper);
    
    OrderProfitsharingVo orderProfitsharingVo = new OrderProfitsharingVo();
    BeanUtils.copyProperties(orderProfitsharing,orderProfitsharingVo);
    return orderProfitsharingVo;
}





/**
 * 根据订单id获取实际分账信息
 * @param orderId
 * @return
 */
@GetMapping("/order/info/getOrderProfitsharing/{orderId}")
Result<OrderProfitsharingVo> getOrderProfitsharing(@PathVariable("orderId") Long orderId);

司机端获取账单信息

@Operation(summary = "获取订单账单详细信息")
@GuiguLogin
@GetMapping("/getOrderInfo/{orderId}")
public Result<OrderInfoVo> getOrderInfo(@PathVariable Long orderId) {
    Long driverId = AuthContextHolder.getUserId();
    return Result.ok(orderService.getOrderInfo(orderId, driverId));
}







@Override
public OrderInfoVo getOrderInfo(Long orderId, Long driverId) {
    OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();
    if(orderInfo.getDriverId() != driverId) {
        throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
    }
    
    //获取账单和分账数据,封装到vo里面
    OrderBillVo orderBillVo = null;
    OrderProfitsharingVo orderProfitsharingVo = null;
    //判断,是否结束代驾
    if(orderInfo.getStatus() >= OrderStatus.END_SERVICE.getStatus()) {
        //账单信息
        orderBillVo = orderInfoFeignClient.getOrderBillInfo(orderId).getData();

        //分账信息
        orderProfitsharingVo = orderInfoFeignClient.getOrderProfitsharing(orderId).getData();
    }
    
    OrderInfoVo orderInfoVo = new OrderInfoVo();
    orderInfoVo.setOrderId(orderId);
    BeanUtils.copyProperties(orderInfo,orderInfoVo);
    orderInfoVo.setOrderBillVo(orderBillVo);
    orderInfoVo.setOrderProfitsharingVo(orderProfitsharingVo);
    return orderInfoVo;
}

司机发送账单

  • 发送账单就是更新订单状态,未支付
@Operation(summary = "发送账单信息")
@GetMapping("/sendOrderBillInfo/{orderId}/{driverId}")
Result<Boolean> sendOrderBillInfo(@PathVariable Long orderId, @PathVariable Long driverId) {
    return Result.ok(orderInfoService.sendOrderBillInfo(orderId, driverId));
}





@Override
public Boolean sendOrderBillInfo(Long orderId, Long driverId) {
    //更新订单信息
    LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(OrderInfo::getId, orderId);
    queryWrapper.eq(OrderInfo::getDriverId, driverId);
    //更新字段
    OrderInfo updateOrderInfo = new OrderInfo();
    updateOrderInfo.setStatus(OrderStatus.UNPAID.getStatus());
    //只能更新自己的订单
    int row = orderInfoMapper.update(updateOrderInfo, queryWrapper);
    if(row == 1) {
        return true;
    } else {
        throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);
    }
}







/**
 * 司机发送账单信息
 * @param orderId
 * @param driverId
 * @return
 */
@GetMapping("/order/info/sendOrderBillInfo/{orderId}/{driverId}")
Result<Boolean> sendOrderBillInfo(@PathVariable("orderId") Long orderId, @PathVariable("driverId") Long driverId);





@Operation(summary = "司机发送账单信息")
@GuiguLogin
@GetMapping("/sendOrderBillInfo/{orderId}")
public Result<Boolean> sendOrderBillInfo(@PathVariable Long orderId) {
    Long driverId = AuthContextHolder.getUserId();
    return Result.ok(orderService.sendOrderBillInfo(orderId, driverId));
}






@Override
public Boolean sendOrderBillInfo(Long orderId, Long driverId) {
    return orderInfoFeignClient.sendOrderBillInfo(orderId, driverId).getData();
}

乘客获取账单

@Override
public OrderInfoVo getOrderInfo(Long orderId, Long customerId) {
    OrderInfo orderInfo = orderInfoFeignClient.getOrderInfo(orderId).getData();
    //判断
    if(orderInfo.getCustomerId() != customerId) {
        throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
    }
    
    //获取司机信息
    DriverInfoVo driverInfoVo = null;
    Long driverId = orderInfo.getDriverId();
    if(driverId != null) {
        driverInfoVo = driverInfoFeignClient.getDriverInfo(driverId).getData();
    }
    
    //获取账单信息
    OrderBillVo orderBillVo = null;
    if(orderInfo.getStatus() >= OrderStatus.UNPAID.getStatus()) {
        orderBillVo = orderInfoFeignClient.getOrderBillInfo(orderId).getData();
    }

    OrderInfoVo orderInfoVo = new OrderInfoVo();
    orderInfoVo.setOrderId(orderId);
    BeanUtils.copyProperties(orderInfo,orderInfoVo);
    orderInfoVo.setOrderBillVo(orderBillVo);
    orderInfoVo.setDriverInfoVo(driverInfoVo);
    return orderInfoVo;
}

微信支付

获取乘客openid

@Operation(summary = "获取客户OpenId")
@GetMapping("/getCustomerOpenId/{customerId}")
public Result<String> getCustomerOpenId(@PathVariable Long customerId) {
   return Result.ok(customerInfoService.getCustomerOpenId(customerId));
}




@Override
public String getCustomerOpenId(Long customerId) {
    LambdaQueryWrapper<CustomerInfo> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(CustomerInfo::getId,customerId);
    CustomerInfo customerInfo = customerInfoMapper.selectOne(wrapper);
    return customerInfo.getWxOpenId();
}




/**
 * 获取客户OpenId
 * @param customerId
 * @return
 */
@GetMapping("/customer/info/getCustomerOpenId/{customerId}")
Result<String> getCustomerOpenId(@PathVariable("customerId") Long customerId);

获取司机openid

  • 与获取乘客openid基本一致

获取订单支付信息

@Operation(summary = "获取订单支付信息")
@GetMapping("/getOrderPayVo/{orderNo}/{customerId}")
public Result<OrderPayVo> getOrderPayVo(@PathVariable String orderNo, @PathVariable Long customerId) {
    return Result.ok(orderInfoService.getOrderPayVo(orderNo, customerId));
}






@Override
public OrderPayVo getOrderPayVo(String orderNo, Long customerId) {
    OrderPayVo orderPayVo = orderInfoMapper.selectOrderPayVo(orderNo,customerId);
    if(orderPayVo != null) {
        String content = orderPayVo.getStartLocation() + " 到 "+orderPayVo.getEndLocation();
        orderPayVo.setContent(content);
    }
    return orderPayVo;
}





<select id="selectOrderPayVo" resultType="com.atguigu.daijia.model.vo.order.OrderPayVo">
    select
        info.id as order_id,
        info.customer_id,
        info.driver_id,
        info.order_no,
        info.start_location,
        info.end_location,
        info.status,
        bill.pay_amount,
        bill.coupon_amount

    from order_info info inner join order_bill bill on info.id = bill.order_id
    where info.customer_id = #{customerId}
      and info.order_no = #{orderNo}
</select>









/**
 * 获取订单支付信息
 * @param orderNo
 * @param customerId
 * @return
 */
@GetMapping("/order/info/getOrderPayVo/{orderNo}/{customerId}")
Result<OrderPayVo> getOrderPayVo(@PathVariable("orderNo") String orderNo, @PathVariable("customerId") Long customerId);

申请并绑定微信支付

微信支付商户平台:https://pay.weixin.qq.com/index.php/core/home/login

对于商家来说,想要开通微信支付,必须要去“微信支付商户平台”注册,然后把需要的资料提交上去,经过审核通过,你就开通了微信支付功能。

企业申请资料:

1、营业执照:彩色扫描件或数码照片

2、组织机构代码证:彩色扫描件或数码照片,若已三证合一,则无需提供

3、对公银行账户:包含开户行省市信息,开户账号

4、法人身份证:彩色扫描件或数码照片

如果想要在网站或者小程序上面使用微信支付,还要在微信公众平台上面关联你自己的微信商户账号。前提是你的微信开发者账号必须是企业身份,个人身份的开发者账号是无法调用微信支付API的。

支付密钥和数字证书

因为调用微信支付平台的API接口,必须要用到支付密钥和数字证书,这些参数在微信支付商户平台都可以获取。

<dependencies>
    <dependency>
        <groupId>com.github.wechatpay-apiv3</groupId>
        <artifactId>wechatpay-java</artifactId>
    </dependency>
</dependencies>



wx:
  v3pay:
    #小程序微信公众平台appId
    appid: wxcc651fcbab275e33
    #商户号
    merchantId: 163*******
    #商户API私钥路径
    privateKeyPath: /root/daijia/apiclient_key.pem
    #商户证书序列号
    merchantSerialNumber: 4AE80**********
    #商户APIV3密钥
    apiV3key: 84***************
    #异步回调地址
    notifyUrl: http://139.198.127.41:8600/payment/wxPay/notify






package com.atguigu.daijia.payment.config;

import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix="wx.v3pay") //读取节点
@Data
public class WxPayV3Properties {

    private String appid;
    /** 商户号 */
    public String merchantId;
    /** 商户API私钥路径 */
    public String privateKeyPath;
    /** 商户证书序列号 */
    public String merchantSerialNumber;
    /** 商户APIV3密钥 */
    public String apiV3key;
    /** 回调地址 */
    private String notifyUrl;

    @Bean
    public RSAAutoCertificateConfig getConfig(){
        return new RSAAutoCertificateConfig.Builder()
                        .merchantId(this.getMerchantId())
                        .privateKeyFromPath(this.getPrivateKeyPath())
                        .merchantSerialNumber(this.getMerchantSerialNumber())
                        .apiV3Key(this.getApiV3key())
                        .build();

    }
}

微信支付接口

@Tag(name = "微信支付接口")
@RestController
@RequestMapping("payment/wxPay")
@Slf4j
public class WxPayController {

    @Operation(summary = "创建微信支付")
    @PostMapping("/createJsapi")
    public Result<WxPrepayVo> createWxPayment(@RequestBody PaymentInfoForm paymentInfoForm) {
        return Result.ok(wxPayService.createWxPayment(paymentInfoForm));
    }
}







@Override
public WxPrepayVo createWxPayment(PaymentInfoForm paymentInfoForm) {
    try {
        //1 添加支付记录到支付表里面
        //判断:如果表存在订单支付记录,不需要添加
        LambdaQueryWrapper<PaymentInfo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PaymentInfo::getOrderNo,paymentInfoForm.getOrderNo());
        PaymentInfo paymentInfo = paymentInfoMapper.selectOne(wrapper);
        if(paymentInfo == null) {
            paymentInfo = new PaymentInfo();
            BeanUtils.copyProperties(paymentInfoForm,paymentInfo);
            paymentInfo.setPaymentStatus(0);
            paymentInfoMapper.insert(paymentInfo);
        }

        //2 创建微信支付使用对象
        JsapiServiceExtension service =
                new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();

        //3 创建request对象,封装微信支付需要参数
        PrepayRequest request = new PrepayRequest();
        Amount amount = new Amount();
        amount.setTotal(paymentInfoForm.getAmount().multiply(new BigDecimal(100)).intValue());
        request.setAmount(amount);
        request.setAppid(wxPayV3Properties.getAppid());
        request.setMchid(wxPayV3Properties.getMerchantId());
        //string[1,127]
        String description = paymentInfo.getContent();
        if(description.length() > 127) {
            description = description.substring(0, 127);
        }
        request.setDescription(description);
        request.setNotifyUrl(wxPayV3Properties.getNotifyUrl());
        request.setOutTradeNo(paymentInfo.getOrderNo());

        //获取用户信息
        Payer payer = new Payer();
        payer.setOpenid(paymentInfoForm.getCustomerOpenId());
        request.setPayer(payer);

        //是否指定分账,不指定不能分账
        SettleInfo settleInfo = new SettleInfo();
        settleInfo.setProfitSharing(true);
        request.setSettleInfo(settleInfo);

        //4 调用微信支付使用对象里面方法实现微信支付调用
        PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);

        //5 根据返回结果,封装到WxPrepayVo里面
        WxPrepayVo wxPrepayVo = new WxPrepayVo();
        BeanUtils.copyProperties(response,wxPrepayVo);
        wxPrepayVo.setTimeStamp(response.getTimeStamp());
        return wxPrepayVo;
    }catch (Exception e) {
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }
}






@FeignClient(value = "service-payment")
public interface WxPayFeignClient {

    /**
     * 创建微信支付
     * @param paymentInfoForm
     * @return
     */
    @PostMapping("/payment/wxPay/createWxPayment")
    Result<WxPrepayVo> createWxPayment(@RequestBody PaymentInfoForm paymentInfoForm);
}









@Operation(summary = "创建微信支付")
@GuiguLogin
@PostMapping("/createWxPayment")
public Result<WxPrepayVo> createWxPayment(@RequestBody CreateWxPaymentForm createWxPaymentForm) {
    Long customerId = AuthContextHolder.getUserId();
    createWxPaymentForm.setCustomerId(customerId);
    return Result.ok(orderService.createWxPayment(createWxPaymentForm));
}






@Autowired
private WxPayFeignClient wxPayFeignClient;

@Override
public WxPrepayVo createWxPayment(CreateWxPaymentForm createWxPaymentForm) {
    //获取订单支付信息
    OrderPayVo orderPayVo = orderInfoFeignClient.getOrderPayVo(createWxPaymentForm.getOrderNo(),
            createWxPaymentForm.getCustomerId()).getData();
    //判断
    if(orderPayVo.getStatus() != OrderStatus.UNPAID.getStatus()) {
        throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
    }

    //获取乘客和司机openid
    String customerOpenId = customerInfoFeignClient.getCustomerOpenId(orderPayVo.getCustomerId()).getData();

    String driverOpenId = driverInfoFeignClient.getDriverOpenId(orderPayVo.getDriverId()).getData();

    //封装需要数据到实体类,远程调用发起微信支付
    PaymentInfoForm paymentInfoForm = new PaymentInfoForm();
    paymentInfoForm.setCustomerOpenId(customerOpenId);
    paymentInfoForm.setDriverOpenId(driverOpenId);
    paymentInfoForm.setOrderNo(orderPayVo.getOrderNo());
    paymentInfoForm.setAmount(orderPayVo.getPayAmount());
    paymentInfoForm.setContent(orderPayVo.getContent());
    paymentInfoForm.setPayWay(1);

    WxPrepayVo wxPrepayVo = wxPayFeignClient.createWxPayment(paymentInfoForm).getData();
    return wxPrepayVo;
}

查询支付状态

在这里插入图片描述

  • 判断微信支付是否成功,有两种方式,就是以上两种红字
  • 微信支付成功/失败就会回调我们的接口
    收到调用之后我们就可以继续接下来的操作
  • 用消息队列保证数据的最终一致性,在支付成功后我们后续还有很多的操作,如果把所有的操作全部成功再向用户进行返回,那就太慢了,如果支付成功,我们就先向用户返回结果,并向消息队列发送关键信息用于后续的操作

根据订单编号查询支付状态

@Operation(summary = "支付状态查询")
@GetMapping("/queryPayStatus/{orderNo}")
public Result queryPayStatus(@PathVariable String orderNo) {
    return Result.ok(wxPayService.queryPayStatus(orderNo));
}






//查询支付状态
@Override
public Boolean queryPayStatus(String orderNo) {
    //1 创建微信操作对象
    JsapiServiceExtension service =
            new JsapiServiceExtension.Builder().config(rsaAutoCertificateConfig).build();

    //2 封装查询支付状态需要参数
    QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
    queryRequest.setMchid(wxPayV3Properties.getMerchantId());
    queryRequest.setOutTradeNo(orderNo);

    //3 调用微信操作对象里面方法实现查询操作
    Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);

    //4 查询返回结果,根据结果判断
    if(transaction != null
            && transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {
        //5 如果支付成功,调用其他方法实现支付后处理逻辑
        this.handlePayment(transaction);

        return true;
    }
    return false;
}








/**
 * 支付状态查询
 * @param orderNo
 * @return
 */
@GetMapping("/payment/wxPay/queryPayStatus/{orderNo}")
Result<Boolean> queryPayStatus(@PathVariable("orderNo") String orderNo);








@Operation(summary = "支付状态查询")
@GuiguLogin
@GetMapping("/queryPayStatus/{orderNo}")
public Result<Boolean> queryPayStatus(@PathVariable String orderNo) {
    return Result.ok(orderService.queryPayStatus(orderNo));
}






@Override
public Boolean queryPayStatus(String orderNo) {
    return wxPayFeignClient.queryPayStatus(orderNo).getData();
}

微信支付成功回调接口

@Operation(summary = "微信支付异步通知接口")
@PostMapping("/notify")
public Map<String,Object> notify(HttpServletRequest request) {
    try {
        wxPayService.wxnotify(request);

        //返回成功
        Map<String,Object> result = new HashMap<>();
        result.put("code", "SUCCESS");
        result.put("message", "成功");
        return result;
    } catch (Exception e) {
        e.printStackTrace();
    }

    //返回失败
    Map<String,Object> result = new HashMap<>();
    result.put("code", "FAIL");
    result.put("message", "失败");
    return result;
}







    //微信支付成功后,进行的回调
    @Override
    public void wxnotify(HttpServletRequest request) {
//1.回调通知的验签与解密
        //从request头信息获取参数
        //HTTP 头 Wechatpay-Signature
        // HTTP 头 Wechatpay-Nonce
        //HTTP 头 Wechatpay-Timestamp
        //HTTP 头 Wechatpay-Serial
        //HTTP 头 Wechatpay-Signature-Type
        //HTTP 请求体 body。切记使用原始报文,不要用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。
        String wechatPaySerial = request.getHeader("Wechatpay-Serial");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String signature = request.getHeader("Wechatpay-Signature");
        String requestBody = RequestUtils.readData(request);

        //2.构造 RequestParam
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(wechatPaySerial)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .body(requestBody)
                .build();
        
        //3.初始化 NotificationParser
        NotificationParser parser = new NotificationParser(rsaAutoCertificateConfig);
        //4.以支付通知回调为例,验签、解密并转换成 Transaction
        Transaction transaction = parser.parse(requestParam, Transaction.class);

        if(null != transaction && transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {
            //5.处理支付业务
            this.handlePayment(transaction);
        }
    }

内网穿透配置

在这里插入图片描述

  • 还可以把项目部署到腾讯云、华为云等来解决这个问题
  • 测试的时候我们没有域名,就可以使用内网穿透的工具

这里使用的工具是ngrok.cc

支付成功后续处理

封装RabbitMQ

@Service
public class RabbitService {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    //发送消息
    public boolean sendMessage(String exchange,
                               String routingkey,
                               Object message) {
        rabbitTemplate.convertAndSend(exchange,routingkey,message);
        return true;
    }
}
  • 修改nacos配置

发送端

//如果支付成功,调用其他方法实现支付后处理逻辑
public void handlePayment(Transaction transaction) {

    //1 更新支付记录,状态修改为 已经支付
    //订单编号
    String orderNo = transaction.getOutTradeNo();
    //根据订单编号查询支付记录
    LambdaQueryWrapper<PaymentInfo> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(PaymentInfo::getOrderNo,orderNo);
    PaymentInfo paymentInfo = paymentInfoMapper.selectOne(wrapper);
    //如果已经支付,不需要更新
    if(paymentInfo.getPaymentStatus() == 1) {
        return;
    }
    paymentInfo.setPaymentStatus(1);
    paymentInfo.setOrderNo(transaction.getOutTradeNo());
    paymentInfo.setTransactionId(transaction.getTransactionId());
    paymentInfo.setCallbackTime(new Date());
    paymentInfo.setCallbackContent(JSON.toJSONString(transaction));
    paymentInfoMapper.updateById(paymentInfo);

    //2 发送端:发送mq消息,传递 订单编号
    //  接收端:获取订单编号,完成后续处理
    rabbitService.sendMessage(MqConst.EXCHANGE_ORDER,
            MqConst.ROUTING_PAY_SUCCESS,
            orderNo);
}

接收端

@Component
public class PaymentReceiver {

    @Autowired
    private WxPayService wxPayService;

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(value = MqConst.QUEUE_PAY_SUCCESS,durable = "true"),
            exchange = @Exchange(value = MqConst.EXCHANGE_ORDER),
            key = {MqConst.ROUTING_PAY_SUCCESS}
    ))
    public void paySuccess(String orderNo, Message message, Channel channel) {
        wxPayService.handleOrder(orderNo);
    }
}






//支付成功后续处理
@Override
public void handleOrder(String orderNo) {
    //1 远程调用:更新订单状态:已经支付
    orderInfoFeignClient.updateOrderPayStatus(orderNo);

    //2 远程调用:获取系统奖励,打入到司机账户
    OrderRewardVo orderRewardVo = orderInfoFeignClient.getOrderRewardFee(orderNo).getData();
    if(orderRewardVo != null && orderRewardVo.getRewardFee().doubleValue()>0) {
        TransferForm transferForm = new TransferForm();
        transferForm.setTradeNo(orderNo);
        transferForm.setTradeType(TradeType.REWARD.getType());
        transferForm.setContent(TradeType.REWARD.getContent());
        transferForm.setAmount(orderRewardVo.getRewardFee());
        transferForm.setDriverId(orderRewardVo.getDriverId());
        driverAccountFeignClient.transfer(transferForm);
    }

    //3 TODO 其他

}

订单状态更新接口

@Operation(summary = "更改订单支付状态")
@GetMapping("/updateOrderPayStatus/{orderNo}")
public Result<Boolean> updateOrderPayStatus(@PathVariable String orderNo) {
    return Result.ok(orderInfoService.updateOrderPayStatus(orderNo));
}






@Override
public Boolean updateOrderPayStatus(String orderNo) {
    //1 根据订单编号查询,判断订单状态
    LambdaQueryWrapper<OrderInfo> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(OrderInfo::getOrderNo,orderNo);
    OrderInfo orderInfo = orderInfoMapper.selectOne(wrapper);
    if(orderInfo == null || orderInfo.getStatus() == OrderStatus.PAID.getStatus()) {
        return true;
    }
    
    //2 更新状态
    LambdaQueryWrapper<OrderInfo> updateWrapper = new LambdaQueryWrapper<>();
    updateWrapper.eq(OrderInfo::getOrderNo,orderNo);
    
    OrderInfo updateOrderInfo = new OrderInfo();
    updateOrderInfo.setStatus(OrderStatus.PAID.getStatus());
    updateOrderInfo.setPayTime(new Date());

    int rows = orderInfoMapper.update(updateOrderInfo, updateWrapper);
    
    if(rows == 1) {
        return true;
    } else {
        throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);
    }
}






/**
 * 更改订单支付状态
 * @param orderNo
 * @return
 */
@GetMapping("/order/info//updateOrderPayStatus/{orderNo}")
Result<Boolean> updateOrderPayStatus(@PathVariable("orderNo") String orderNo);

获取订单系统奖励

@Operation(summary = "获取订单的系统奖励")
@GetMapping("/getOrderRewardFee/{orderNo}")
public Result<OrderRewardVo> getOrderRewardFee(@PathVariable String orderNo) {
    return Result.ok(orderInfoService.getOrderRewardFee(orderNo));
}





@Override
public OrderRewardVo getOrderRewardFee(String orderNo) {
    //根据订单编号查询订单表
    OrderInfo orderInfo = 
            orderInfoMapper.selectOne(
                    new LambdaQueryWrapper<OrderInfo>()
                            .eq(OrderInfo::getOrderNo, orderNo)
                            .select(OrderInfo::getId,OrderInfo::getDriverId));
    
    //根据订单id查询系统奖励表
    OrderBill orderBill = 
            orderBillMapper.selectOne(new LambdaQueryWrapper<OrderBill>()
                    .eq(OrderBill::getOrderId, orderInfo.getId())
                    .select(OrderBill::getRewardFee));
    
    //封装到vo里面
    OrderRewardVo orderRewardVo = new OrderRewardVo();
    orderRewardVo.setOrderId(orderInfo.getId());
    orderRewardVo.setDriverId(orderInfo.getDriverId());
    orderRewardVo.setRewardFee(orderBill.getRewardFee());
    return orderRewardVo;
}





/**
 * 获取订单的系统奖励
 * @param orderNo
 * @return
 */
@GetMapping("/order/info//getOrderRewardFee/{orderNo}")
Result<OrderRewardVo> getOrderRewardFee(@PathVariable("orderNo") String orderNo);

系统奖励打入司机账户

@Tag(name = "司机账户API接口管理")
@RestController
@RequestMapping(value="/driver/account")
@SuppressWarnings({"unchecked", "rawtypes"})
public class DriverAccountController {

    @Autowired
    private DriverAccountService driverAccountService;

    @Operation(summary = "转账")
    @PostMapping("/transfer")
    public Result<Boolean> transfer(@RequestBody TransferForm transferForm) {
        return Result.ok(driverAccountService.transfer(transferForm));
    }
}






@Override
public Boolean transfer(TransferForm transferForm) {
    //1 去重
    LambdaQueryWrapper<DriverAccountDetail> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(DriverAccountDetail::getTradeNo,transferForm.getTradeNo());
    Long count = driverAccountDetailMapper.selectCount(wrapper);
    if(count > 0) {
        return true;
    }

    //2 添加奖励到司机账户表
    driverAccountMapper.add(transferForm.getDriverId(),transferForm.getAmount());

    //3 添加交易记录 
    DriverAccountDetail driverAccountDetail = new DriverAccountDetail();
    BeanUtils.copyProperties(transferForm,driverAccountDetail);
    driverAccountDetailMapper.insert(driverAccountDetail);

    return true;
}








<update id="add">
   update driver_account
   set total_amount = total_amount + #{amount}, available_amount = available_amount + #{amount}, total_income_amount = total_income_amount + #{amount}
   where driver_id = #{driverId}
</update>







@FeignClient(value = "service-driver")
public interface DriverAccountFeignClient {

    /**
     * 转账
     * @param transferForm
     * @return
     */
    @PostMapping("/driver/account/transfer")
    Result<Boolean> transfer(@RequestBody TransferForm transferForm);
}

seata实现分布式事务

订单支付成功后,订单状态更新、获取订单系统奖励、系统奖励打入司机账户都是通过远程调用来实现的,我们就在这儿使用seata分布式事务。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.7.1</version>
</dependency>





seata.tx-service-group=tingshu-tx-group
seata.service.vgroup-mapping.tingshu-tx-group=default
seata.service.grouplist.default=127.0.0.1:8091




// 在事务的入口添加@GlobalTransactional
// 其他的小事务依然使用@Transactional

乘客下单功能

  • 乘客登录之后,选择代驾开始和结束地址,之后乘客呼叫代驾

  • 乘客呼叫代驾之后,等待司机接单

  • 但是,如果在乘客呼叫代驾之后15分钟之后,如果还是没有司机接单,订单自动取消,如果15分钟以内有司机接单,完成代驾过程

  • 总结:15分钟没有司机接单,自动取消订单

  • 总体思路:使用延迟队列消息实现订单到时间自动取消功能

  • 有很多种方式:

第一种 使用RabbitMQ里面TTL和死信队列实现

第二种 在RabbitMQ安装延迟队列实现

  • 就是装个插件,在这个队列中的消息不能被消费,只有延时时间到了才能被消费

第三种 使用Redisson实现

使用RabbitMQ里面TTL和死信队列实现

  • TTL:Time To Live,消息存活时间

发送端:发送消息,设置存活时间10s

接收端:发送完成之后,10s以内如果从队列获取发送过来消息,操作结束

​ 如果超过10s消息没有被消费,消息超时了,无法被消费,成为死信

  • 死信队列:无法被消费的消息

– 超过存活时间没有被消费的消息

– 消息端拒绝接收,不能放回队列里面

– 队列最大长度

在这里插入图片描述

使用Redisson实现

利用redissonClient 发送延迟消息。

redissonClient.getBlockingDeque(): 创建一个阻塞队列

redissonClient.getDelayedQueue(): 获取延迟队列

delayedQueue.offer(): 向队列中存储数据

blockingDeque.take(): 从队列中获取数据
//乘客下单
@Override
public Long saveOrderInfo(OrderInfoForm orderInfoForm) {
    //order_info添加订单数据
    OrderInfo orderInfo = new OrderInfo();
    BeanUtils.copyProperties(orderInfoForm,orderInfo);
    //订单号
    String orderNo = UUID.randomUUID().toString().replaceAll("-","");
    orderInfo.setOrderNo(orderNo);
    //订单状态
    orderInfo.setStatus(OrderStatus.WAITING_ACCEPT.getStatus());
    orderInfoMapper.insert(orderInfo);
    
    //生成订单之后,发送延迟消息
    this.sendDelayMessage(orderInfo.getId());

    //记录日志
    this.log(orderInfo.getId(),orderInfo.getStatus());

    //向redis添加标识
    //接单标识,标识不存在了说明不在等待接单状态了
    redisTemplate.opsForValue().set(RedisConstant.ORDER_ACCEPT_MARK,
            "0", RedisConstant.ORDER_ACCEPT_MARK_EXPIRES_TIME, TimeUnit.MINUTES);

    return orderInfo.getId();
}
  • 编写发送延迟消息的方法
//生成订单之后,发送延迟消息
private void sendDelayMessage(Long orderId) {
    try{
        //1 创建队列
        RBlockingQueue<Object> blockingDueue = redissonClient.getBlockingQueue("queue_cancel");

        //2 把创建队列放到延迟队列里面
        RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDueue);
        
        //3 发送消息到延迟队列里面
        //设置过期时间
        delayedQueue.offer(orderId.toString(),15,TimeUnit.MINUTES);
        
    }catch (Exception e) {
        e.printStackTrace();
        throw new GuiguException(ResultCodeEnum.DATA_ERROR);
    }
}
  • 监听延迟队列消息
//监听延迟队列
@Component
public class RedisDelayHandle {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private OrderInfoService orderInfoService;

    @PostConstruct
    public void listener() {
        new Thread(()->{
        // while true 实现一直监听
            while(true) {
                //获取延迟队列里面阻塞队列
                RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("queue_cancel");

                //从队列获取消息
                try {
                    String orderId = blockingQueue.take();

                    //取消订单
                    if(StringUtils.hasText(orderId)) {
                        //调用方法取消订单
                        orderInfoService.orderCancel(Long.parseLong(orderId));
                    }

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        }).start();
    }
}








//调用方法取消订单
@Override
public void orderCancel(long orderId) {
    //orderId查询订单信息
    OrderInfo orderInfo = orderInfoMapper.selectById(orderId);
    //判断
    if(orderInfo.getStatus()==OrderStatus.WAITING_ACCEPT.getStatus()) {
        //修改订单状态:取消状态
        orderInfo.setStatus(OrderStatus.CANCEL_ORDER.getStatus());
        int rows = orderInfoMapper.updateById(orderInfo);
        if(rows == 1) {
            //删除接单标识

            redisTemplate.delete(RedisConstant.ORDER_ACCEPT_MARK);
        }
    }
}

出电梯门,李国华心想是不是走太远了。他不碰有钱人家的小孩,因为麻烦。

软得像奶母的手心。鹌鹑蛋的手心。诗眼的手心。也许走对了不一定。

房思琪的初恋乐园
林奕含

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1972477.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

递归~~~

一.定义 计算机科学中&#xff0c;递归是一种解决计算问题的方法&#xff0c;其中解决方案取决于同一类问题的更小子集。 比如单链表递归遍历的例子&#xff1a; void f(Node node){if (node null){return;}f(node.next);} 说明&#xff1a; 1.自己调用自己&#xff0c;如…

基于SpringBoot+Vue的汽车服务管理系统(带1w+文档)

基于SpringBootVue的汽车服务管理系统(带1w文档) 基于SpringBootVue的汽车服务管理系统(带1w文档) 在开发系统过程中采用Java语言、MySQL数据库存储数据。系统以B/S为基础&#xff0c;实现管理一体化、规范化&#xff0c;为用户提供一个高效快捷的交流系统[5]。利用springboot架…

LearnOpenGL之3D显示

前序 AndroidLearnOpenGL是本博主自己实现的LearnOpenGL练习集合&#xff1a; Github地址&#xff1a;https://github.com/wangyongyao1989/AndroidLearnOpenGL 系列文章&#xff1a; 1、LearnOpenGL之入门基础 2、LearnOpenGL之3D显示 显示效果 根据上一篇文章的LearnO…

结构型设计模式:桥接/组合/装饰/外观/享元

结构型设计模式&#xff1a;适配器/代理 (qq.com)

浮动IP(Floating IP)计费;OpenStack算力共享;OpenStack实现资源虚拟化;算力调度策略

目录 浮动IP(Floating IP)计费 浮动IP的定义与作用 计费中的浮动IP数据 浮动IP在计费中的作用 OpenStack算力共享 一、OpenStack在算力共享中的角色 二、OpenStack与算力共享的结合方式 三、实际应用案例 算力调度策略 算力计费策略 OpenStack实现资源虚拟化 1.虚…

用于仅摄像头闭环驾驶的视觉语言模型

CarLLaVA: Vision language models for camera-only closed-loop driving 用于仅摄像头闭环驾驶的视觉语言模型 Abstract In this technical report, we present CarLLaVA, a Vision Language Model (VLM) for autonomous driving, developed for the CARLA Autonomous Driv…

【云原生】kubernetes最新版本1.30.2,集群搭建部署全方位攻略

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

SimGCL graph contrastive learning by finding homophily in heterophily

发表于: Knowledge and Information Systems, ccfb 推荐指数: #paper/ ⭐ 总结: 重新定义了相似度矩阵, 重新定义了特征, 重新设计了节点删除概率等, 但是, 换汤不换药, 引入了大量的超参 (快 10 个了吧). 创新点不够, 所以 ccf B 期刊理所应该. (甚至我觉得更低) 相关知识: 本…

详细教程 MySQL 数据库 下载 安装 连接 环境配置 全面

数据库就是储存和管理数据的仓库&#xff0c;对数据进行增删改查操作&#xff0c;其本质是一个软件。 首先数据有两种&#xff0c;一种是关系型数据库&#xff0c;另一种是非关系型数据库。 关系型数据库是以表的形式来存储数据&#xff0c;表和表之间可以有很多复杂的关系&a…

通俗易懂玩Qt:时间滑动选择器实现(内附主要源码)

时间滑动选择器实现 组件说明&#xff1a; 本组件命名为时间滑动选择器&#xff0c;主要运用于 arm 平台下的触摸屏上&#xff0c;虽然 QT 自带有时间选择组件&#xff0c;但是对触摸屏的使用并不友好&#xff0c;为了提升项目界面的交互性&#xff0c;于是就有了时间滑动选择器…

【深海王国】初中生也能画的电路板?番外1:Arduino其他家族成员的拓展板开发(1)

Hi~ (o^^o)♪, 各位深海王国的同志们&#xff0c;早上下午晚上凌晨好呀~ 辛苦工作的你今天也辛苦啦(/≧ω) 今天大都督为大家带来电路板的番外系列——初中生也能画的电路板&#xff1f;番外1&#xff1a;Arduino其他家族成员的拓展板开发&#xff0c;带你给其他Arduino家族成…

数据库漫游记:表、视图、函数、存储过程及触发器之跨平台兼容性分析(上)

先言之 &#x1f31f;余撰此文&#xff0c;乃为导引初窥数据库之学人&#xff0c;俾其明了表、视图、函数、存储过程及触发器之义理&#xff0c;及其于诸般平台之上创建、修改与废弃之法式。盖初学之人&#xff0c;常陷于迷雾之中&#xff0c;难辨东西&#xff0c;故须详述而明…

lombok使用@slf4j 运行时提示找不到符号log(Missing POM for org.projectors:lombok:jar)

1.问题表现 原本是之前搭建好的工程&#xff0c;只是换了个开发环境重新启动就不行了。一直编译不通过&#xff01; 可以看到IDEA其实是引入了依赖的 都没有出现红色波浪线 <mapstruct.version>1.5.5.Final</mapstruct.version> <lombok.version>1.18.30<…

鸿蒙(API 12 Beta2版)NDK开发【JSVM-API使用规范】

JSVM-API使用规范 生命周期管理 【规则】 合理使用OH_JSVM_OpenHandleScope和OH_JSVM_CloseHandleScope管理JSVM_Value的生命周期&#xff0c;做到生命周期最小化&#xff0c;避免发生内存泄漏问题。 每个JSVM_Value属于特定的HandleScope&#xff0c;HandleScope通过OH_JSV…

【Python实战因果推断】71_图因果模型6

目录 Positivity Assumption An Identification Example with Data Confounding Bias Positivity Assumption 调整公式同样强调了正则性&#xff08;positivity&#xff09;的重要性。因为你正在对治疗和结果之间的差异在X的条件下求平均&#xff0c;你必须确保对于所有X的…

【32单片机篇】项目:WIFI天气预报

一、项目需求 使用 ESP8266 通过 HTTP 获取天气数据&#xff08;心知天气&#xff09;&#xff0c;并显示在 OLED 屏幕上。 按键 1 &#xff1a;循环切换今天/明天/后天天气数据&#xff1b; 按键 2 &#xff1a;更新天气 二、项目框图 三、硬件部分 四、项目源码及实现 1.项…

MySQL是怎样运行的——第1章 初识MySQL

文章目录 1. 1 MySQL的客户端/服务器架构1.2 安装MySQL&#xff08;略&#xff09;1.3 启动MySQL服务器程序1.4 启动MySQL客户端程序1.5 客户端与服务器连接的过程1.6 服务器处理客户端请求 1. 1 MySQL的客户端/服务器架构 MySQL的运行过程就是C/S架构。多个客户端程序连接到服…

洛谷 P1868 饥饿的奶牛

原题 题目描述 有一条奶牛冲出了围栏&#xff0c;来到了一处圣地&#xff08;对于奶牛来说&#xff09;&#xff0c;上面用牛语写着一段文字。 现用汉语翻译为&#xff1a; 有 N 个区间&#xff0c;每个区间x,y 表示提供的x∼y 共y−x1 堆优质牧草。你可以选择任意区间但不…

dockerfile定制镜像 docker-compose编排容器

1 dockerfile dockerfile本质上是利用了Linux系统的挂载&#xff08;UnionFS&#xff09;&#xff0c;将多个目录挂载到同一目录下&#xff0c;实现镜像的层叠式结构&#xff0c;从而实现功能聚合。 1.1 一个最简单的程序 package mainimport "fmt"func main() {f…

【leetcode详解】覆盖所有点的最少矩形数目(C++思路详解)

思路详解&#xff1a; 0. 题目情境并未限制矩形高度&#xff0c;故矩形数目的判断只和点的横坐标有关 1. 为了不重不漏地考虑到所有点&#xff0c;故笔者选择首先将二维数组中的点按横坐标的大小排序 //说明&#xff1a;本来笔者以为需要自定义sort排序&#xff0c;后来发现…