微信支付扫码模式二

模式一和模式二提供了两种不同的能力,适用于不同的场景,看商户具体的需求。

两种模式,在支付的流程中,有一定的共同的流程:
1,生成订单。
2,用户支付。

差别在于:
模式一,先扫码,再生成订单。
模式二,先生成订单,再扫码。

而 生成订单,代表着 本次支付给商户的金额是否是已经确定了。
在模式一中,用户扫描的二维码,此时可以还没有确定实际要支付的金额。
在模式二中,用户扫描的二维码,金额已经是确定的。

可以这么理解,模式一中的二维码,是商品的二维码。
模式二中的二维码,是 订单的二维码,也因为这个是订单的二维码,所以必须要有时效性。


那么这两个场景的玩法,可以有一个明显的差别,
模式一,更适合无人职守的自动售卖机。所有的商品都有一个固定的二维码,价格相对稳定,当用户使用微信支付扫描了二维码,微信再请求自动售卖机的服务提供商的 后台接口,注意,这个请求中,是包含了商品ID以及用户信息的,这样,商户系统就可以根据 商品ID,以及用户的身份,再来确定用户实际要支付的金额。

模式二,更适合有人职守的,支付金额非常不确定的场合。比如,你去饭馆吃饭,虽然每个菜的金额是固定的,但一桌子饭菜的金额不固定,甚至是你还可能使用饭馆事先发放的代金券。这个时候,就需要收银员,预先创建一个订单,确定好金额,然后你再来扫描这个二维码来支付

 

该模块采用到的技术栈包括 spring boot 、spring jpa 、gradle、spock、springCloud contracts......包括产品,订单,折扣以及支付

首先创建产品表

CREATE TABLE `app_product` (
  `id`    VARCHAR(32) PRIMARY KEY,
  `name`  VARCHAR(48) NOT NULL,
  `price` DOUBLE      NOT NULL,
  `type`  VARCHAR(24) NOT NULL
)

建立对应Entity

@Entity
@Table(name = "app_product")
@Getter
@Setter
public class AppProduct {

    @Id
    private String id;

    private String name;

    private double price;

    @Enumerated(EnumType.STRING)
    private AppProductType type;
}

建立对应的返回值DTO

@Getter
@Setter
public class AppProductDTO {

    private String id;

    private double price;

    private String name;

    private AppProductType type;

   private List<AppProductDiscountDTO> appProductDiscountDTOs;

}
枚举产品类型

public enum AppProductType {
    VIP,
}
@Repository
public interface AppProductRepository extends JpaRepository<AppProduct, String> {

    List<AppProduct> findAllByType(AppProductType type);

}
后面才发现的Collector和stream写法,函数式编程代码更简化,因为有checkstyle的缘故,详情请看JAVA8文档,所以有些超长的代码换到了下一行

public class AppProductService {
    private final AppProductRepository appProductRepository;

    private final OrderMapper orderMapper;

    private final AppProductDiscountRepository appProductDiscountRepository;

    @Autowired
    public AppProductService(AppProductRepository appProductRepository,
                             OrderMapper orderMapper,
                             AppProductDiscountRepository appProductDiscountRepository) {
        this.appProductRepository = appProductRepository;
        this.orderMapper = orderMapper;
        this.appProductDiscountRepository = appProductDiscountRepository;
    }

    public List<AppProductDTO> getVipProducts() {
        List<AppProduct> vipProducts = appProductRepository.findAllByType(AppProductType.VIP);
        if (vipProducts == null || vipProducts.size() == 0) {
            throw new UnprocessableEntityException("The vip products is empty ");
        }
        Map<String, List<AppProductDiscount>> productDiscounts =
                getAppProductDiscounts(vipProducts);
        return toAppProductDTOs(vipProducts, productDiscounts);
    }

    private List<AppProductDTO>
    toAppProductDTOs(List<AppProduct> vipProducts,
                     Map<String, List<AppProductDiscount>> productDiscounts) {
        return orderMapper.mapList(vipProducts, AppProductDTO.class)
                .stream()
                .peek(item -> {
                    List<AppProductDiscountDTO> discounts =
                            orderMapper.mapList(productDiscounts.get(item.getId()),
                                    AppProductDiscountDTO.class);
                    item.setAppProductDiscountDTOs(discounts);
                }).collect(Collectors.toList());
    }

    private Map<String, List<AppProductDiscount>> getAppProductDiscounts(
            List<AppProduct> appProducts) {
        List<String> productIds = appProducts
                .stream()
                .map(AppProduct::getId)
                .collect(Collectors.toList());
        Date now = new Date();
        return appProductDiscountRepository
                .findAllByAppProductIdInAndIsDisabledIsFalseAndStartTimeBeforeAndEndTimeAfter(
                        productIds, now, now)
                .stream()
                .collect(Collectors.groupingBy(AppProductDiscount::getAppProductId));
    }
public class AppProductController {

    private final AppProductService appProductService;

    @Autowired
    public AppProductController(AppProductService appProductService) {
        this.appProductService = appProductService;
    }

    @GetMapping(value = "/vip")
    public List<AppProductDTO> getVipProducts() {
        return appProductService.getVipProducts();
    }
}

下面是折扣代码

@Entity
@Table(name = "app_product_discount")
@Getter
@Setter
public class AppProductDiscount {

    @Id
    private String id;

    private String appProductId;

    private Integer productNumber;

    private Double discount;

    private Date startTime;

    private Date endTime;

    private boolean isDisabled;

    private Date createdTime;
}
public interface AppProductDiscountRepository extends JpaRepository<AppProductDiscount, String> {

    List<AppProductDiscount>
    findAllByAppProductIdInAndIsDisabledIsFalseAndStartTimeBeforeAndEndTimeAfter(
            List<String> appProductId, Date startTime, Date endTime);

    AppProductDiscount findByAppProductIdAndProductNumberAndIsDisabledIsFalse(
            String appProductId, int productNumber);

}

订单代码

public enum OrderStatus {
    WAITING_PAYMENT,PAID,
}
public class Order {

    @Id
    private String id;

    private Long userId;

    private double paymentPrice;

    private double totalPrice;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    @Enumerated(EnumType.STRING)
    private PaymentWayType paymentWay;

    private Date createdTime;

    private Date updatedTime;
}
@Service
@Transactional
public class OrderService {
private final OrderRepository orderRepository;
    private final AppProductRepository appProductRepository;
    private final OrderMapper orderMapper;
    private final AuthenticationFacade authenticationFacade;
    private final AppProductDiscountRepository appProductDiscountRepository;
    private final OrderAppProductRepository orderAppProductRepository;

    @Autowired
    public OrderService(OrderRepository orderRepository,
                        AppProductRepository appProductRepository,
                        OrderMapper orderMapper,
                        AuthenticationFacade authenticationFacade,
                        AppProductDiscountRepository appProductDiscountRepository,
                        OrderAppProductRepository orderAppProductRepository) {
        this.orderRepository = orderRepository;
        this.appProductRepository = appProductRepository;
        this.orderMapper = orderMapper;
        this.authenticationFacade = authenticationFacade;
        this.appProductDiscountRepository = appProductDiscountRepository;
        this.orderAppProductRepository = orderAppProductRepository;
    }

    @Transactional
    public OrderDTO createOrder(OrderCommand command) {
        String appProductId = command.getAppProductId();
        int appProductNumber = command.getAppProductNumber();
        AppProduct appProduct = appProductRepository.findOne(appProductId);
        if (appProduct == null) {
            throw new UnprocessableEntityException("appProduct is not exists");
        }
        AppProductDiscount discount = appProductDiscountRepository
                .findByAppProductIdAndProductNumberAndIsDisabledIsFalse(
                        appProductId, appProductNumber);
        double paymentPrice = getPaymentPrice(appProduct, discount, appProductNumber);
        if (command.getPaymentPrice() != paymentPrice) {
            throw new UnprocessableEntityException("Invalid paymentPrice");
        }
        Order order = saveOrder(paymentPrice, appProduct, appProductNumber);
        saveOrderAppProduct(order, appProduct, discount, appProductNumber);

        return orderMapper.map(order, OrderDTO.class);
    }

    private Order saveOrder(double paymentPrice, AppProduct appProduct, int appProductNumber) {
        Order order = new Order();
        order.setId(IdUtil.createUuid());
        order.setUserId(authenticationFacade.getUserId());
        order.setPaymentPrice(paymentPrice);
        order.setTotalPrice(appProduct.getPrice() * appProductNumber);
        order.setStatus(OrderStatus.WAITING_PAYMENT);
        order.setCreatedTime(new Date());
        order.setUpdatedTime(new Date());
        return orderRepository.save(order);
    }

    private void saveOrderAppProduct(Order order, AppProduct appProduct,
                                     AppProductDiscount discount, int appProductNumber) {
        OrderAppProduct orderAppProduct = new OrderAppProduct();
        orderAppProduct.setId(IdUtil.createUuid());
        orderAppProduct.setAppProductId(appProduct.getId());
        orderAppProduct.setAppProductName(appProduct.getName());
        orderAppProduct.setAppProductPrice(appProduct.getPrice());
        orderAppProduct.setOrderId(order.getId());
        orderAppProduct.setAppProductNumber(appProductNumber);
        if (discount != null) {
            orderAppProduct.setAppProductDiscountId(discount.getId());
            orderAppProduct.setAppProductDiscountDistcount(discount.getDiscount());
        }
        orderAppProductRepository.save(orderAppProduct);
    }

    private double getPaymentPrice(AppProduct appProduct,
                                   AppProductDiscount discount, int productNumber) {
        double paymentPrice = appProduct.getPrice() * productNumber;
        if (discount != null) {
            paymentPrice = discount.getProductNumber()
                    * discount.getDiscount()
                    * appProduct.getPrice();
        }
        BigDecimal b = new BigDecimal(paymentPrice);
        return b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
    }
}

中间还有个订单产品产生的一个中间模块

对于微信支付,刚开始比较傻,还是用的老一套,自己封装xml,后来看了源码才发现好多东西微信已经封装好了,我们只需要传入参数,并按开发文档流程进行就好了,省了不少东西,也有效的减少了自己的测试。中间陷入了微信服务商号和普通商户号的误区

首先配置好一些基本参数:appId,mchId,mchKey,notifyUrl以及tradeType

@Configuration
@ConditionalOnClass(WxPayService.class)
public class WxPayConfiguration {
    @Value("${wechat.pay.appId}")
    private String appId;
    @Value("${wechat.pay.mchId}")
    private String mchId;
    @Value("${wechat.pay.mchKey}")
    private String mchKey;
    @Value("${wechat.pay.notifyUrl}")
    private String notifyUrl;
    @Value("${wechat.pay.tradeType}")
    private String tradeType;

    @Bean
    @ConditionalOnMissingBean
    public WxPayConfig payConfig() {
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setAppId(appId);
        payConfig.setMchId(mchId);
        payConfig.setMchKey(mchKey);
        payConfig.setNotifyUrl(notifyUrl);
        payConfig.setTradeType(tradeType);
        return payConfig;
    }

    @Bean
    public WxPayService wxPayService(WxPayConfig payConfig) {
        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
        return wxPayService;
    }
}

接着就是生产二维码以及扫码过后的回调通知,比起自己拼装的xml一系类工具复杂程度简直不可比拟

public class WechatPayService implements Loggable {
    private final OrderRepository orderRepository;

    private final QrcodeUtil qrcodeUtil;
    @Value("${wechat.qwcode.path}")
    public String rootPath;
    @Value("${wechat.pay.spbillCreateIp}")
    private String spbillCreateIp;
    @Resource(name = "wxPayService")
    private WxPayService wxService;

    @Autowired
    public WechatPayService(OrderRepository orderRepository,
                            QrcodeUtil qrcodeUtil,
                            WxPayService wxService) {
        this.orderRepository = orderRepository;
        this.qrcodeUtil = qrcodeUtil;
        this.wxService = wxService;
    }
//回调通知
    public WxPayNotifyResponse wechatNotify(String xmlData)
            throws WxPayException {
        WxPayNotifyResponse response = new WxPayNotifyResponse();
        WxPayOrderNotifyResult wxPayOrderNotifyResult = wxService
                .parseOrderNotifyResult(xmlData);
        WxPayOrderQueryResult confirmResult = wxService.queryOrder(
                null, wxPayOrderNotifyResult.getOutTradeNo());
        if ("SUCCESS".equals(confirmResult.getResultCode())) {
            Order order = orderRepository.findOne(confirmResult.getOutTradeNo());
            order.setStatus(OrderStatus.PAID);
            order.setPaymentWay(PaymentWayType.WX);
            orderRepository.save(order);
            response.setReturnCode("SUCCESS");
        } else {
            response.setReturnCode("FAIL");
            response.setReturnMsg("OutTradeNo is Empty");
        }
        return response;
    }
//生成二维码
    public WechatPaymentDTO createPayment(WechatPayCommand wechatPayCommand) throws Exception {
        Order order = orderRepository.findOne(wechatPayCommand.getOrderId());
        if (order == null) {
            throw new UnprocessableEntityException("order is not exists");
        }
        WxPayUnifiedOrderRequest request = getWxPayUnifiedOrderRequest(order);
        WxPayUnifiedOrderResult wxPayUnifiedOrderResult;
        try {
            wxPayUnifiedOrderResult = wxService.unifiedOrder(request);
        } catch (WxPayException e) {
            error("wechat pay failed", e);
            throw e;
        }
        byte[] qrCodeUrl = wxService.createScanPayQrcodeMode2(
                wxPayUnifiedOrderResult.getCodeURL(), null, null);
        WechatPaymentDTO wechatPaymentDTO = new WechatPaymentDTO();
        wechatPaymentDTO.setId(request.getOutTradeNo());
        wechatPaymentDTO.setQrCodeUrl(qrCodeUrl);
        return wechatPaymentDTO;
    }
//微信请求参数配置
    private WxPayUnifiedOrderRequest getWxPayUnifiedOrderRequest(Order order) {
        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
        request.setOutTradeNo(IdUtil.createUuid());
        request.setBody("VIP会员充值");
        request.setProductId(order.getId());
        request.setTotalFee((int) (order.getPaymentPrice() * 100));
        request.setSpbillCreateIp(spbillCreateIp);
        return request;
    }

猜你喜欢

转载自blog.csdn.net/qq_37001674/article/details/81488573