《苍穹外卖》模拟微信支付解决办法

目录

概要

代码实现

模拟微信支付后端实现

数据库设计

 application.yml配置文件

 pom.xml文件

 entity实体类

 controller类

 service及其实现类

 统一返回结果类

《苍穹外卖》后端代码改动

微信支付工具类

 OrderServiceImpl类

 PayNotifyController.java类

 小程序端代码改动

小结

概要

最近在做《苍穹外卖》项目练手练手时,由于无法创建商家号,所以无法直接体验原生的微信支付流程。浏览了网上很多解决方案,基本都是从小程序后端直接返回模拟数据给小程序,以达到“跳过支付”的目的。但为了深入理解和体验微信支付整个流程,我新建了一个微信支付模块(也可以直接新建一个项目),让后端和小程序直接与其交互,从而实现完整的微信支付流程。

代码实现

模拟微信支付后端实现

数据库设计

        新建数据库wechat_pay,用于存储微信支付相关信息(实际可能是NoSQL数据库,为了方便所以直接使用关系型数据库)。prepay_order表用来存储预支付订单信息,transaction_record用来存储实际交易记录。两张表根据微信开放文档设计,很多字段在本项目中可能不会使用到(因为本人对签名算法了解有限,暂未模拟签名验证)。

create schema wechat_pay;

create table prepay_order
(
    id           bigint auto_increment comment '预支付id'
        primary key,
    total        decimal(9, 2) not null,
    currency     varchar(16)   not null comment '金额单位',
    out_trade_no varchar(32)   not null comment '商户系统内部订单号',
    openid       varchar(128)  not null comment '微信用户唯一标识',
    description  varchar(127)  not null comment '商品描述',
    notify_url   varchar(255)  null comment '支付回调通知URL',
    create_time  datetime      null comment '创建时间',
    appid        varchar(32)   not null comment '公众号id',
    mchid        varchar(32)   not null comment '直连商户号'
)
    comment '预支付订单';

create table transaction_record
(
    id           bigint auto_increment comment '交易id'
        primary key,
    total        decimal(9, 2) not null comment '交易金额',
    currency     varchar(16)   not null comment '金额单位',
    out_trade_no varchar(32)   not null comment '商户系统内部订单号',
    openid       varchar(128)  not null comment '微信用户唯一标识',
    description  varchar(127)  not null comment '商品描述',
    create_time  datetime      null comment '交易时间',
    appid        varchar(32)   not null comment '公众号id',
    mchid        varchar(32)   not null comment '直连商户号'
)
    comment '交易记录';

 application.yml配置文件

        为了避免和商户后端服务端口号冲突,这里配置为8081,因为我是用的mybatis-plus,所以也进行了相关配置(根据实际需要配置)

server:
  port: 8081
spring:
  main:
    allow-circular-references: true
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/wechat_pay?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
      username: root
      password: ********

mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.wechat.entity
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: false
  global-config:
    db-config:
      id-type: auto
      update-strategy: not_null

 pom.xml文件

        使用和商户后端同样的springboot版本和依赖版本,避免版本错误。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wechat</groupId>
    <artifactId>wechat-pay</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>wechat-pay</name>
    <description>wechat-pay</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.32</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.7</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

 entity实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 微信支付后端预支付订单
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PrepayOrder implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long id;
    //金额
    private BigDecimal total;
    //金额单位
    private String currency;
    //商户系统订单号
    private String outTradeNo;
    //openid
    private String openid;
    //商品描述
    private String description;
    //回调通知地址
    private String notifyUrl;
    //创建时间
    private LocalDateTime createTime;
    private String appid;
    private String mchid;
}
package com.wechat.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
 * 微信支付后端交易记录
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TransactionRecord implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long id;
    //金额
    private BigDecimal total;
    //金额单位
    private String currency;
    //商户系统订单号
    private String outTradeNo;
    //openid
    private String openid;
    //商品描述
    private String description;
    //创建时间
    private LocalDateTime createTime;
    private String appid;
    private String mchid;
}

         实际中这些参数都是用来签名验证的,虽然没有实现,但都做了保留。

package com.wechat.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 小程序微信支付传递的数据模型(模拟微信支付后台数据模型)
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PaymentDTO {
    private String timeStamp;  //时间戳,从 1970 年 1 月 1 日 00:00:00 至今的秒数,即当前的时间
    private String nonceStr;   //随机字符串,长度为32个字符以下
    private Long prepayId;    //统一下单接口返回的 prepay_id 参数值
    private String signType;  //MD5签名算法,应与后台下单时的值一致
    private String paySign;   //签名
}

 controller类

package com.wechat.controller;

import com.alibaba.fastjson.JSONObject;
import com.wechat.entity.PaymentDTO;
import com.wechat.entity.PrepayOrder;
import com.wechat.entity.TransactionRecord;
import com.wechat.result.PayResult;
import com.wechat.result.Result;
import com.wechat.service.PrepayOrderService;
import com.wechat.service.TransactionRecordService;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.time.LocalDateTime;

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

    @Autowired
    private PrepayOrderService prepayOrderService;
    @Autowired
    private TransactionRecordService transactionRecordService;
    /**
     * 模拟微信支付后端返回预支付交易标识
     * 商家后端发送post请求,请求体中携带的是封装的JSONObject对象,包含金额、商家号、订单号、appid、订单描述、下单微信用户的openid、通知地址
     * @return
     */
    @PostMapping("/jsapi")
    public PayResult jsapi(@RequestBody JSONObject body){
        log.info("模拟微信预支付交易{}",body);//{"amount":{"total":1,"currency":"CNY"},"mchid":"123456","out_trade_no":"1733894836304","appid":"wx*********","description":"外卖订单","notify_url":"http://localhost:8080/notify/paySuccess","payer":{"openid":"*******"}}
        //将该预支付单加入数据库
        PrepayOrder prepayOrder = new PrepayOrder();
        prepayOrder.setAppid(body.getString("appid"));
        prepayOrder.setMchid(body.getString("mchid"));
        prepayOrder.setDescription(body.getString("description"));
        prepayOrder.setNotifyUrl(body.getString("notify_url"));
        prepayOrder.setOutTradeNo(body.getString("out_trade_no"));
        prepayOrder.setOpenid(body.getJSONObject("payer").getString("openid"));
        prepayOrder.setTotal(body.getJSONObject("amount").getBigDecimal("total"));
        prepayOrder.setCurrency(body.getJSONObject("amount").getString("currency"));
        prepayOrder.setCreateTime(LocalDateTime.now());
        prepayOrderService.save(prepayOrder);
        return new PayResult(prepayOrder.getId(),"SUCCESS");
    }

    /**
     * 小程序用户发起微信支付
     * 模拟微信支付后端服务
     * 支付成功后通知商家后端
     * @return
     */
    @PostMapping("/payment")
    public Result payment(@RequestBody PaymentDTO paymentDTO) throws IOException {
        log.info("模拟微信正式支付交易{}",paymentDTO);
        //根据预支付订单id获取商户系统订单号
        PrepayOrder prepayOrder = prepayOrderService.getById(paymentDTO.getPrepayId());
        //如果该预支付订单查不到,则返回错误信息
        if(prepayOrder == null){
            return Result.error("支付失败");
        } else {
            //获取客户端向商户后端发送通知
            CloseableHttpClient httpClient = HttpClients.createDefault();
            String outTradeNo = prepayOrder.getOutTradeNo();
            //生成支付记录
            TransactionRecord transactionRecord = new TransactionRecord();
            BeanUtils.copyProperties(prepayOrder, transactionRecord);
            transactionRecordService.save(transactionRecord);
            //删除预支付订单
            prepayOrderService.removeById(prepayOrder);
            //支付成功,向商家后端推送结果
            HttpPost httpPost = new HttpPost(prepayOrder.getNotifyUrl());
            httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
            httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("out_trade_no", outTradeNo);
            jsonObject.put("transaction_id", transactionRecord.getId());
            httpPost.setEntity(new StringEntity(jsonObject.toJSONString(), "UTF-8"));
            httpClient.execute(httpPost);

            //给小程序返回结果
            return Result.success("支付成功");
        }

    }
}

 service及其实现类

package com.wechat.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.wechat.entity.PrepayOrder;

public interface PrepayOrderService extends IService<PrepayOrder> {
}
package com.wechat.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.wechat.entity.TransactionRecord;

public interface TransactionRecordService extends IService<TransactionRecord> {
}
package com.wechat.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wechat.entity.PrepayOrder;
import com.wechat.mapper.PrepayOrderMapper;
import com.wechat.service.PrepayOrderService;
import org.springframework.stereotype.Service;

@Service
public class PrepayOrderServiceImp extends ServiceImpl<PrepayOrderMapper, PrepayOrder> implements PrepayOrderService {
}
package com.wechat.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wechat.entity.TransactionRecord;
import com.wechat.mapper.TransactionRecordMapper;
import com.wechat.service.TransactionRecordService;
import org.springframework.stereotype.Service;

@Service
public class TransactionRecordServiceImpl extends ServiceImpl<TransactionRecordMapper, TransactionRecord> implements TransactionRecordService {
}

 统一返回结果类

package com.wechat.result;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 模拟微信支付后端返回的预支付交易标识
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PayResult {
    private Long prepay_id;
    private String code;
}
package com.wechat.result;

import lombok.Data;

import java.io.Serializable;

/**
 * 后端统一返回结果
 * @param <T>
 */
@Data
public class Result<T> implements Serializable {

    private Integer code; //编码:1成功,0和其它数字为失败
    private String msg; //错误信息
    private T data; //数据

    public static <T> Result<T> success() {
        Result<T> result = new Result<T>();
        result.code = 1;
        return result;
    }

    public static <T> Result<T> success(T object) {
        Result<T> result = new Result<T>();
        result.data = object;
        result.code = 1;
        return result;
    }

    public static <T> Result<T> error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }
}

《苍穹外卖》后端代码改动

微信支付工具类

        这里没有使用微信支付提供的httpClient,因为它需要读取并解析两个密钥文件(pem),并自动进行签名。接口请求地址也换成了我们自己实现的模拟微信支付端地址。签名等可以随便写,配置文件中的商户id,密钥等都可以随便配置,因为并没有使用到,只是做了保留。

package com.sky.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.properties.WeChatProperties;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.ArrayList;


/**
 * 微信支付工具类
 */
@Component
public class MyWeChatPayUtil {

    //微信支付下单接口地址
    public static final String JSAPI = "http://localhost:8081/pay/transactions/jsapi";
    //申请退款接口地址
    public static final String REFUNDS = "http://localhost:8081/refund/domestic/refunds";
    @Autowired
    private WeChatProperties weChatProperties;

    /**
     * 发送post方式请求
     *
     * @param url
     * @param body
     * @return
     */
    private String post(String url, String body) throws Exception {
        CloseableHttpClient httpClient = HttpClients.createDefault();

        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
        httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
        httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
        httpPost.setEntity(new StringEntity(body, "UTF-8"));

        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            return bodyAsString;
        } finally {
            httpClient.close();
            response.close();
        }
    }

    /**
     * 发送get方式请求
     *
     * @param url
     * @return
     */
    private String get(String url) throws Exception {
        CloseableHttpClient httpClient = HttpClients.createDefault();

        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
        httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
        httpGet.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());

        CloseableHttpResponse response = httpClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            return bodyAsString;
        } finally {
            httpClient.close();
            response.close();
        }
    }

    /**
     * jsapi下单
     *
     * @param orderNum    商户订单号
     * @param total       总金额
     * @param description 商品描述
     * @param openid      微信用户的openid
     * @return
     */
    private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appid", weChatProperties.getAppid());
        jsonObject.put("mchid", weChatProperties.getMchid());
        jsonObject.put("description", description);
        jsonObject.put("out_trade_no", orderNum);
        jsonObject.put("notify_url", weChatProperties.getNotifyUrl());

        JSONObject amount = new JSONObject();
        amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("currency", "CNY");

        jsonObject.put("amount", amount);

        JSONObject payer = new JSONObject();
        payer.put("openid", openid);

        jsonObject.put("payer", payer);

        String body = jsonObject.toJSONString();
        return post(JSAPI, body);
    }

    /**
     * 小程序支付
     *
     * @param orderNum    商户订单号
     * @param total       金额,单位 元
     * @param description 商品描述
     * @param openid      微信用户的openid
     * @return
     */
    public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
        //统一下单,生成预支付交易单
        String bodyAsString = jsapi(orderNum, total, description, openid);
        //解析返回结果
        JSONObject jsonObject = JSON.parseObject(bodyAsString);
        String prepayId = jsonObject.getString("prepay_id");
        //如果成功生成预支付交易单,经过签名验证封装后返回给前端
        if (prepayId != null) {
            String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
            String nonceStr = RandomStringUtils.randomNumeric(32);
            //二次签名,调起支付需要重新签名(签名省略)
            //构造数据给微信小程序,用于调起微信支付
            JSONObject jo = new JSONObject();
            jo.put("timeStamp", timeStamp);
            jo.put("nonceStr", nonceStr);
            jo.put("package", prepayId);
            jo.put("signType", "RSA");
            jo.put("paySign", "123456789");

            return jo;
        }
        return jsonObject;
    }

    /**
     * 申请退款
     *
     * @param outTradeNo    商户订单号
     * @param outRefundNo   商户退款单号
     * @param refund        退款金额
     * @param total         原订单金额
     * @return
     */
    public String refund(String outTradeNo, String outRefundNo, BigDecimal refund, BigDecimal total) throws Exception {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("out_trade_no", outTradeNo);
        jsonObject.put("out_refund_no", outRefundNo);

        JSONObject amount = new JSONObject();
        amount.put("refund", refund.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("currency", "CNY");

        jsonObject.put("amount", amount);
        jsonObject.put("notify_url", weChatProperties.getRefundNotifyUrl());

        String body = jsonObject.toJSONString();

        //调用申请退款接口
        return post(REFUNDS, body);
    }
}

 OrderServiceImpl类

package com.sky.service.impl;

@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private MyWeChatPayUtil myWeChatPayUtil;


    /**
     * 订单支付
     *
     * @param ordersPaymentDTO
     * @return
     */
    public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) {
        // 当前登录用户id
        Long userId = BaseContext.getCurrentId();
        User user = userMapper.selectById(userId);
        // 根据订单号查询订单金额
        String orderNumber = ordersPaymentDTO.getOrderNumber();
        LambdaQueryWrapper<Orders> wrapper = new LambdaQueryWrapper<Orders>()
                .eq(Orders::getUserId, userId)
                .eq(Orders::getNumber, orderNumber)
                .select(Orders::getAmount);
        Orders order = baseMapper.selectOne(wrapper);
        //调用微信支付接口,生成预支付交易单
        JSONObject jsonObject = null;
        try {
            jsonObject = myWeChatPayUtil.pay(
                    ordersPaymentDTO.getOrderNumber(), //商户订单号
                    order.getAmount(), //支付金额,单位 元
                    "外卖订单", //商品描述
                    user.getOpenid() //微信用户的openid
            );
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
            throw new OrderBusinessException("该订单已支付");
        }

        OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
        vo.setPackageStr(jsonObject.getString("package"));

        return vo;
    }

    /**
     * 支付成功,修改订单状态
     *
     * @param outTradeNo
     */
    public void paySuccess(String outTradeNo) {
        // 根据根据订单号更新订单的状态、支付方式、支付状态、结账时间
        LambdaUpdateWrapper<Orders> wrapper = new LambdaUpdateWrapper<Orders>()
                .eq(outTradeNo != null, Orders::getNumber, outTradeNo)
                .set(Orders::getStatus, Orders.TO_BE_CONFIRMED)
                .set(Orders::getPayStatus, Orders.PAID)
                .set(Orders::getCheckoutTime, LocalDateTime.now());
        baseMapper.update(wrapper);
    }
}

 PayNotifyController.java类

        同样,这里也没有用到解密。

package com.sky.controller.user;

import com.alibaba.druid.support.json.JSONUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.properties.WeChatProperties;
import com.sky.service.OrderService;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.entity.ContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;

/**
 * 支付回调相关接口
 */
@RestController
@RequestMapping("/notify")
@Slf4j
public class PayNotifyController {
    @Autowired
    private OrderService orderService;
    @Autowired
    private WeChatProperties weChatProperties;

    /**
     * 支付成功回调
     *
     * @param request
     */
    @RequestMapping("/paySuccess")
    public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //读取数据
        String body = readData(request);
        log.info("支付成功回调:{}", body);

        //数据解密
        //String plainText = decryptData(body);
        //log.info("解密后的文本:{}", plainText);

        JSONObject jsonObject = JSON.parseObject(body);
        String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号
        String transactionId = jsonObject.getString("transaction_id");//微信支付交易号

        log.info("商户平台订单号:{}", outTradeNo);
        log.info("微信支付交易号:{}", transactionId);

        //业务处理,修改订单状态、来单提醒
        orderService.paySuccess(outTradeNo);

        //给微信响应
        responseToWeixin(response);
    }

    /**
     * 读取数据
     *
     * @param request
     * @return
     * @throws Exception
     */
    private String readData(HttpServletRequest request) throws Exception {
        BufferedReader reader = request.getReader();
        StringBuilder result = new StringBuilder();
        String line = null;
        while ((line = reader.readLine()) != null) {
            if (result.length() > 0) {
                result.append("\n");
            }
            result.append(line);
        }
        return result.toString();
    }

    /**
     * 数据解密
     *
     * @param body
     * @return
     * @throws Exception
     */
    private String decryptData(String body) throws Exception {
        JSONObject resultObject = JSON.parseObject(body);
        JSONObject resource = resultObject.getJSONObject("resource");
        String ciphertext = resource.getString("ciphertext");
        String nonce = resource.getString("nonce");
        String associatedData = resource.getString("associated_data");

        AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        //密文解密
        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);

        return plainText;
    }

    /**
     * 给微信响应
     * @param response
     */
    private void responseToWeixin(HttpServletResponse response) throws Exception{
        response.setStatus(200);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("code", "SUCCESS");
        map.put("message", "SUCCESS");
        response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
        response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));
        response.flushBuffer();
    }
}

 小程序端代码改动

        将注释处的代码改成下面的代码即可,原来是直接请求微信支付官方接口,现在是请求我们自己实现的微信支付后端,后端会根据参数中的预支付id查找是否有该预支付订单,如果有,会把支付记录存入数据库,并分别通知前端和后端,做出相应的操作。

//请求模拟微信支付后端,完成支付
                      wx.request({
                        url: 'http://localhost:8081/pay/transactions/payment', //模拟微信支付后端
                        data: {
                          nonceStr: res.data.nonceStr,
                          prepayId: res.data.packageStr,
                          paySign: res.data.paySign,
                          timeStamp: res.data.timeStamp,
                          signType: res.data.signType,
                        },
                        header: {
                          'content-type': 'application/json' // 默认值
                        },
                        method: 'POST',
                        success(res) {
                          console.log(res)
                          //支付成功
                          if(res.data.code == 1){
                            wx.showModal({
                              title: '提示',
                              content: '支付成功',
                              success: function () {
                                uni.redirectTo({
                                  url: '/pages/success/index?orderId=' + _this.orderId
                                });
                              }
                            })
                            console.log('支付成功!')
                          }
                        }
                      })
                      // 调起微信支付
                      // wx.requestPayment({
                      //   nonceStr: res.data.nonceStr,
                      //   package: res.data.packageStr,
                      //   paySign: res.data.paySign,
                      //   timeStamp: res.data.timeStamp,
                      //   signType: res.data.signType,
                      //   success: function (res) {
                      //     wx.showModal({
                      //       title: '提示',
                      //       content: '支付成功',
                      //       success: function () {
                      //         uni.redirectTo({
                      //           url: '/pages/success/index?orderId=' + _this.orderId
                      //         });
                      //       }
                      //     })
                      //     console.log('支付成功!')
                      //   }
                      // })

小结

这里只是做了一个简单的模拟实现,很多地方并不规范,如有问题,欢迎指正!

后续对微信后端接口的调用,都可以在此代码的基础上实现,例如退款等业务。

<think>好的,用户想知道在苍穹外卖项目中如何跳过微信支付。我需要根据提供的引用资料整理出一个清晰的步骤说明。 首先,用户提到的几个引用都提到了不同的修改部分,包括小程序端注释代码、OrderServiceImpl.java的改动,以及数据库的更新方法。我需要把这些步骤分点说明,确保用户能够一步步操作。 需要注意引用中的各个部分,比如引用[1]提到修改小程序端的代码,引用[3]提到了在OrderServiceImpl.java中添加代码,引用[4]则是关于数据库的更新方法。需要将这些步骤整合起来,确保逻辑连贯。 另外,用户可能对技术细节不太熟悉,所以需要用简单的中文解释每个步骤的作用,比如说明修改小程序端是为了绕过向微信发送请求,修改Java代码是为了模拟支付成功,更新数据库是为了保持数据一致性。 可能还需要提醒用户注意事项,比如确保所有相关代码修改到位,测试功能是否正常,以及了解这样做可能带来的问题,比如不符合实际支付流程等。 最后,生成相关问题时要围绕技术实现、可能的问题和应用场景展开,帮助用户进一步理解。</think>根据提供的参考资料,以下是实现苍穹外卖项目跳过微信支付模块的详细步骤: ### 一、核心原理 通过修改小程序端和商户系统的代码逻辑,绕过向微信支付后台发送请求的环节,直接模拟支付成功流程并更新数据库状态[^2]。 ### 二、具体实现步骤 1. **小程序端修改** - 注释或删除触发微信支付的代码段 - 直接调用支付成功回调函数(示例): ```javascript // 原调用微信支付的代码 // wx.requestPayment({...}); // 替换为直接跳转支付成功页面 wx.navigateTo({ url: '/pages/payment/success' }); ``` 2. **服务端代码修改**(OrderServiceImpl.java) ```java // 在payment方法中添加模拟支付成功逻辑 JSONObject jsonObject = new JSONObject(); jsonObject.put("code", "ORDERPAID"); // 模拟支付成功响应 orderService.paySuccess(ordersPaymentDTO.getOrderNumber()); // 触发支付成功处理 ``` 3. **数据库操作更新** ```java @Update("update orders set status = #{orderStatus}, pay_status = #{orderPaidStatus}, checkout_time = #{check_out_time} where id = #{id}") void updateStatus(Integer orderStatus, Integer orderPaidStatus, LocalDateTime check_out_time, Long id); ``` ### 三、实现效果 1. 跳过真实的微信支付接口调用 2. 自动弹出支付成功界面 3. 数据库订单状态更新为已支付[^3] 4. 支付时间等字段正常记录[^4] ### 四、注意事项 1. 需要同步修改前端和后端代码 2. 测试时要清空浏览器缓存 3. 该方法仅适用于开发测试环境 4. 实际生产环境需接入正规支付渠道
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值