栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java实现微信支付及注意点

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java实现微信支付及注意点

本文采用springboot+微信v3+mybatis-plus来实现的微信支付,
详情请参考微信官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml
下面的异步消息通知,必须是在公网ip才能接收到,不然,是接收不到消息的

首先第一步,依赖
 
        
            com.github.wechatpay-apiv3
            wechatpay-apache-httpclient
            0.4.5
        
        
        
            com.github.wxpay
            wxpay-sdk
            0.0.3
        
第二步创建wx支付的属性文件wxpay.properties,

下面的所有属性都可以在微信支付平台中获取,具体的就不做介绍,如果实在不懂,可以私聊或者留言
商户私钥文件apiclient_key.pem存放在resource目录下即可

# 微信支付相关参数
# 商户号
wxpay.mch-id=161387xxxx
# 商户API证书序列号
wxpay.mch-serial-no=7D6432494D17BB46xxxxxxxxxxxxxxxxxxxxxxx
# 商户私钥文件
wxpay.private-key-path=apiclient_key.pem
# APIv3密钥
wxpay.api-v3-key=AbdAWozW8RWCFDxxxxxxxxxxxxxxxxxx
# APPID
wxpay.appid=wx9afbd6xxxxxxxxxx
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
wxpay.notify-domain=http://127.0.0.1:8090
第三步创建配置文件

配置文件中会读取wxpay.properties的属性,便于后面的使用,
超级注意:在获取私钥文件时,如果是本地获取采用PemUtil.loadPrivateKey(new FileInputStream(filename));即可,但是当你打包到liunx服务器上部署时,就会报错,找不到死要文件,所以需要采用我下面未注释的方法区获取私钥文件,

package com.lcj.config;

import com.lcj.test.test;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.exception.ParseException;
import com.wechat.pay.contrib.apache.httpclient.exception.ValidationException;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.ClassPathResource;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;

import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;
import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE;


@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {

    // 商户号
    private String mchId;

    // 商户API证书序列号
    private String mchSerialNo;

    // 商户私钥文件
    private String privateKeyPath;

    // APIv3密钥
    private String apiV3Key;

    // APPID
    private String appid;

    // 微信服务器地址
    private String domain;

    // 接收结果通知地址
    private String notifyDomain;

    // APIv2密钥
    private String partnerKey;

    
    private PrivateKey getPrivateKey(String filename) {
        log.info("私钥路径为=========》:"+filename);
        try {
            //liunx系统运行找不到文件路径,需要采用这种方式加载
            ClassPathResource resource = new ClassPathResource(filename);
            InputStream inputStream = resource.getInputStream();
//            return PemUtil.loadPrivateKey(new FileInputStream(filename));
            return PemUtil.loadPrivateKey(inputStream);
        } catch (FileNotFoundException e) {
            throw new RuntimeException("私钥文件不存在", e);
        } catch (IOException e) {
            throw new RuntimeException("IOException", e);
        }
    }

    
    @Bean
    public Verifier getVerifier() throws NotFoundException, HttpCodeException, GeneralSecurityException, IOException {

//        log.info("获取签名验证器");

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);
        
        //私钥签名对象
//        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);

        //身份认证对象
//        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        // 使用定时更新的签名验证器,不需要传入证书
//        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
//                wechatPay2Credentials,
//                apiV3Key.getBytes(StandardCharsets.UTF_8));
        
        //获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        // 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
                new PrivateKeySigner(mchSerialNo, privateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
        // 从证书管理器中获取verifier
        Verifier verifier = certificatesManager.getVerifier(mchId);

        return verifier;
    }


    public final boolean notificationHandler(HttpServletRequest req, Verifier verifier, String body) {
        boolean result = false;
        try {
            // 构建request,传入必要参数
            NotificationRequest request = new NotificationRequest.Builder()
                    .withSerialNumber(req.getHeader(WECHAT_PAY_SERIAL))
                    .withNonce(req.getHeader(WECHAT_PAY_NONCE))
                    .withTimestamp(req.getHeader(WECHAT_PAY_TIMESTAMP))
                    .withSignature(req.getHeader(WECHAT_PAY_SIGNATURE))
                    .withBody(body)
                    .build();
            NotificationHandler handler = new NotificationHandler(verifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
            // 验签和解析请求体
            Notification notification = handler.parse(request);
//            Assert.assertNotNull(notification);
            System.out.println(notification.toString());
            result = true;
        } catch (ValidationException e) {
            e.printStackTrace();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return result;
    }
    
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getwxPayClient(Verifier verifier) {

//        log.info("获取httpClient");

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();

        return httpClient;
    }

    
    @Bean(name = "wxPayNoSignClient")
    public CloseableHttpClient getWxPayNoSignClient(){

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        //用于构造HttpClient
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                //设置商户信息
                .withMerchant(mchId, mchSerialNo, privateKey)
                //无需进行签名验证、通过withValidator((response) -> true)实现
                .withValidator((response) -> true);

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();

        log.info("== getWxPayNoSignClient END ==");

        return httpClient;
    }
}

上面就是把准备工作做完了,完成了HttpClient的创建,签名验证器的创建等。下面就开始进行接口开发,后面的代码只涉及下单接口,和异步消息通知,其他接口就照猫画虎即可
我这里还是先提供一下,本人使用的数据库脚本


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_order_info
-- ----------------------------
DROP TABLE IF EXISTS `t_order_info`;
CREATE TABLE `t_order_info`  (
  `id` bigint(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '订单id',
  `title` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单标题',
  `order_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商户订单编号',
  `user_id` bigint(20) NULL DEFAULT NULL COMMENT '用户id',
  `product_id` bigint(20) NULL DEFAULT NULL COMMENT '支付产品id',
  `total_fee` int(11) NULL DEFAULT NULL COMMENT '订单金额(分)',
  `code_url` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单二维码连接',
  `order_status` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '订单状态',
  `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
  `payment_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 27 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_order_info
-- ----------------------------

-- ----------------------------
-- Table structure for t_payment_info
-- ----------------------------
DROP TABLE IF EXISTS `t_payment_info`;
CREATE TABLE `t_payment_info`  (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '支付记录id',
  `order_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商户订单编号',
  `transaction_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付系统交易编号',
  `payment_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付类型',
  `trade_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '交易类型',
  `trade_state` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '交易状态',
  `payer_total` int(11) NULL DEFAULT NULL COMMENT '支付金额(分)',
  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '通知参数',
  `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_payment_info
-- ----------------------------

-- ----------------------------
-- Table structure for t_product
-- ----------------------------
DROP TABLE IF EXISTS `t_product`;
CREATE TABLE `t_product`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商Bid',
  `title` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商品名称',
  `price` int(11) NULL DEFAULT NULL COMMENT '价格(分)',
  `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_product
-- ----------------------------
INSERT INTO `t_product` VALUES (1, 'Java程', 1, '2022-04-25 16:48:32', '2022-04-25 16:48:32');
INSERT INTO `t_product` VALUES (2, '大数据课程', 1, '2022-04-25 16:48:32', '2022-04-25 16:48:32');
INSERT INTO `t_product` VALUES (3, '前端端程', 1, '2022-04-25 16:48:32', '2022-04-25 16:48:32');
INSERT INTO `t_product` VALUES (4, 'UI程', 1, '2022-04-25 16:48:32', '2022-04-25 16:48:32');

-- ----------------------------
-- Table structure for t_refund_info
-- ----------------------------
DROP TABLE IF EXISTS `t_refund_info`;
CREATE TABLE `t_refund_info`  (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '款单id',
  `order_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商户订单编号',
  `refund_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '商户退款单编号',
  `refund_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '支付系统退款单号',
  `total_fee` int(11) NULL DEFAULT NULL COMMENT '原订单金额(分)',
  `refund` int(11) NULL DEFAULT NULL COMMENT '退款金额(分)',
  `reason` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '退款原因',
  `refund_status` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '退款状态',
  `content_return` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '申请退款返回参数',
  `content_notify` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '退款结果通知参数',
  `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_refund_info
-- ----------------------------

SET FOREIGN_KEY_CHECKS = 1;

第四步实现微信网站下单

微信网站下单,我采用的是Native支付支付实现的,
下单支付的逻辑流程,
首先,我们先要创建订单
其次,将订单信息返回,并封装
然后,将订单信息作为参数,调用微信接口,进行支付
再然后,判断接口返回的状态,并将微信接口返回的支付url保存到数据库一份,
最后,将url返回给前端
那我这里就直接上代码
WxPayController

public class WxPayController {
 @Resource
    private WxPayService wxPayService;

    @Resource
    private Verifier verifier;

    @Resource
    private WxPayConfig wxPayConfig;


    @ApiOperation("调用同一下单API,生产二维码")
    @PostMapping("native/{productId}")
    public R nativePay(@PathVariable Long productId) throws IOException {
        log.info("发起支付请求");
        Map map = wxPayService.nativePay(productId);
        return R.ok().setData(map);
    }


    
    @ApiOperation("支付通知")
    @PostMapping("/native/notify")
    public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {

        Gson gson = new Gson();

        HashMap map = null;//应答对象
        try {
            map = new HashMap<>();

//        处理通知参数
            String body = HttpUtils.readData(request);
            HashMap bodyMap = gson.fromJson(body, HashMap.class);
            String requestId = (String) bodyMap.get("id");
            log.info("支付通知的id====>{}", requestId);
//            log.info("支付通知的完整数据====>{}",body);

            //签名验证
//            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier, body, requestId);
            //if (!wechatPay2ValidatorForRequest.validate(request)) {
            //签名验证
            if (!wxPayConfig.notificationHandler(request, verifier, body)) {
                log.error("通知验签失败");
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "通知验签失败");
                return gson.toJson(map);
            }

            log.info("通知验签成功");

            //处理订单
            wxPayService.processOrder(bodyMap);

            //应答超时
            //模拟接收微信端的重复通知
            //TimeUnit.SECONDS.sleep(5);

            //成功的应答
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "成功");
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "失败");
        }
        return gson.toJson(map);
    }
    }

接下来就是WxpayServerImpl

@Slf4j
@Service
public class WxPayServiceIml implements WxPayService {

    @Resource
    private WxPayConfig wxPayConfig;

    @Resource
    private CloseableHttpClient wxPayNoSignClient;

    @Resource
    private OrderInfoService orderInfoService;

    @Resource
    private PaymentInfoService paymentInfoService;

    @Resource
    private RefundInfoService refundsInfoService;

    private ReentrantLock lock = new ReentrantLock();

    
    @Override
    public Map  nativePay(Long productId) throws IOException {
        log.info("生成订单");
        //生成订单
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayType.WXPAY.getType());
        //获取二维码地址
        String codeUrl = orderInfo.getCodeUrl();
        //判断订单存在且二维码地址不为空
        if (!ObjectUtils.isEmpty(orderInfo) && StringUtils.hasText(codeUrl)) {
            log.info("订单已存在,二维码已保存");
            //返回二维码
            Map map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfo.getOrderNo());
            return map;
        }

        //订单存入数据库
        log.info("调用统一下单API");
        //TODO 调用同一下单接口API
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("appid", wxPayConfig.getAppid());//APPID
        paramsMap.put("mchid", wxPayConfig.getMchId());//商户号
        paramsMap.put("description", orderInfo.getTitle());//订单标题
        paramsMap.put("out_trade_no", orderInfo.getOrderNo());//订单编号
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));//支付信息通知
        Map amountMap = new HashMap();
        amountMap.put("total", orderInfo.getTotalFee());//支付金额
        amountMap.put("currency", "CNY");//CNY:人民币

        paramsMap.put("amount", amountMap);
        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求参数 ===> {}" + jsonParams);


        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayNoSignClient.execute(httpPost);

        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
            if (statusCode == 200) { //处理成功
                log.info("成功, 返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功,无返回Body
                log.info("成功");
            } else {
                log.info("Native下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }
            //响应结果
            Map resultMap = gson.fromJson(bodyAsString, HashMap.class);
            //二维码
            codeUrl = resultMap.get("code_url");

            //保存二维码
            String orderNo = orderInfo.getOrderNo();
            orderInfoService.saveCodeUrl(orderNo, codeUrl);

            //返回二维码
            Map map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfo.getOrderNo());
            return map;
        } finally {
            response.close();
        }
    }
@Override
    public void processOrder(HashMap bodyMap) throws GeneralSecurityException {
        log.info("处理订单");
        //解密报文
        String plainText = decryptFromResource(bodyMap);
        Gson gson = new Gson();
        //将明文转换为map
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String orderNo = (String) plainTextMap.get("out_trade_no");

        
        //尝试获取锁,成功则返回true,获取失败则返回false,不必一直等待锁的释放
        if (lock.tryLock()) {
            try {
                //处理重复的通知
                //接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。
                String orderStatus = orderInfoService.getOrderStatus(orderNo);
                //判断订单不是未支付状态直接返回;
                if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
                    return;
                }

                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
                //记录支付日志
                paymentInfoService.createPaymentInfo(plainText);
            } finally {
                //主动释放锁
                lock.unlock();
            }
        }


    }

OrderInfoServiceImpl 订单类

@Service
    public class OrderInfoServiceImpl extends ServiceImpl implements OrderInfoService {

    @Resource
    private ProductMapper productMapper;

    @Override
    public OrderInfo createOrderByProductId(Long productId,String paymentType) {

        //根据productId查到已存在的未支付订单
        OrderInfo orderInfo = this.getNoPayOrderByProductId(productId,paymentType);
        if (!ObjectUtils.isEmpty(orderInfo)) {
            return orderInfo;
        }

        Product product = productMapper.selectById(productId);
        //生成订单
        orderInfo = new OrderInfo();
        orderInfo.setTitle(product.getTitle());//设置标题
        orderInfo.setOrderNo(OrderNoUtils.getOrderNo());//设置订单号
        orderInfo.setProductId(productId);//设置商品id
        orderInfo.setTotalFee(product.getPrice());//设置金额,单位是分
        orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());//设置订单状态
        orderInfo.setPaymentType(paymentType);
        baseMapper.insert(orderInfo);
        return orderInfo;
    }
     
    @Override
    public String getOrderStatus(String orderNo) {
        QueryWrapper queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no", orderNo);
        OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
        if (ObjectUtils.isEmpty(orderInfo)) {
            return null;
        }
        return orderInfo.getOrderStatus();
    }
    
    @Override
    public void updateStatusByOrderNo(String orderNo, OrderStatus orderStatus) {

        log.info("订单状态更新成了====>{}", orderStatus.getType());
        QueryWrapper queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no", orderNo);
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setOrderStatus(orderStatus.getType());
        baseMapper.update(orderInfo, queryWrapper);
    }
    }

PaymentInfoServiceImpl 支付日记记录类

@Service
@Slf4j
public class PaymentInfoServiceImpl extends ServiceImpl implements PaymentInfoService {
    
    @Override
    public void createPaymentInfo(String plainText) {
        log.info("记录支付日志");
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        //订单号
        String orderNo = (String) plainTextMap.get("out_trade_no");
        //业务编号
        String transactionId = (String) plainTextMap.get("transaction_id");
        //支付类型
        String tradeType = (String) plainTextMap.get("trade_type");
        //交易状态
        String tradeState = (String) plainTextMap.get("trade_state");

        Map amount = (Map) plainTextMap.get("amount");
        //用户实际支付金额
        int payerTotal = ((Double) amount.get("payer_total")).intValue();

        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setOrderNo(orderNo);
        paymentInfo.setPaymentType(PayType.WXPAY.getType());
        paymentInfo.setTransactionId(transactionId);
        paymentInfo.setTradeType(tradeType);
        paymentInfo.setTradeState(tradeState);
        paymentInfo.setPayerTotal(payerTotal);
        paymentInfo.setContent(plainText);

        baseMapper.insert(paymentInfo);
    }
    }

接下来就是手机H5支付了,H5支付特别简单,但是微信返回的接口是无法直接访问的,必须是你在微信支付平台申请绑定的域名进行跳转之后才能够进行访问,否则就会报错。什么意思呢,就是你的项目必须在你绑定的域名的服务器启动,才能够跳转成功微信的H5支付接口

 
    @ApiOperation("微信H5支付")
    @PostMapping("/h5Pay/{productId}")
    public R h5pay(HttpServletRequest request , @PathVariable Long productId) {
        log.info("发起支付请求");

        Map map = null;
        try {
            map = wxPayService.h5pay(productId, request);
        } catch (IOException e) {
            e.printStackTrace();
            new RuntimeException(e.getMessage());
        }
        return R.ok().setData(map);
    }
  @Override
    public Map h5pay(Long productId, HttpServletRequest request) throws IOException {
        log.info("生成订单");
        //生成订单
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayType.WXMOBILEPAY.getType());
        String codeUrl = orderInfo.getCodeUrl();
        //如果已存在订单,且订单未支付,将订单的二维码直接返回给用户
        if (orderInfo != null && codeUrl != null) {
            log.info("订单已存在,二维码已保存");
            //返回二维码
            Map map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfo.getOrderNo());
            return map;
        }
        //订单入库
        log.info("调用同一下单API");

        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.H5_PAY.getType()));
        //请求body的参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("appid", wxPayConfig.getAppid());//APPID
        paramsMap.put("mchid", wxPayConfig.getMchId());//商户号
        paramsMap.put("description", orderInfo.getTitle());//订单标题
        paramsMap.put("out_trade_no", orderInfo.getOrderNo());//订单编号
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));//支付信息通知
        Map amountMap = new HashMap();
        amountMap.put("total", orderInfo.getTotalFee());//支付金额
        amountMap.put("currency", "CNY");//CNY:人民币

        paramsMap.put("amount", amountMap);
        Map sceneInfoMap = new HashMap();
        String requestIp = CommonUtils.getIpAddr(request);
        sceneInfoMap.put("payer_client_ip",requestIp);
        paramsMap.put("scene_info",sceneInfoMap);
        Map h5InfoMap = new HashMap();
        h5InfoMap.put("type","Wap");
        sceneInfoMap.put("h5_info",h5InfoMap);
        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);

        log.info("请求参数 ===> {}", jsonParams);


        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayNoSignClient.execute(httpPost);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
            //响应结果
            Map resultMap = gson.fromJson(bodyAsString, HashMap.class);


            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
            if (statusCode == 200) { //处理成功
                log.info("成功, 返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功,无返回Body
                log.info("成功");
            } else {
                log.info("Native下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
                throw new IOException(resultMap.get("message"));
            }

            //二维码
            codeUrl = resultMap.get("h5_url");

            //保存二维码
            String orderNo = orderInfo.getOrderNo();
            orderInfoService.saveCodeUrl(orderNo, codeUrl);

            //返回二维码
            Map map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderNo);
            return map;
        } finally {
            response.close();
        }
    }

是不是很简单,但是如果没研究过的话,会有很多坑,让人触不及防。
最后在贴上上面用到的状态类

@AllArgsConstructor
@Getter
public enum WxApiType {

    
    NATIVE_PAY("/v3/pay/transactions/native"),
    
    H5_PAY("/v3/pay/transactions/h5"),
    
    NATIVE_PAY_V2("/pay/unifiedorder"),
    
    ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),

    
    CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),

    
    DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),

    
    DOMESTIC_REFUNDS_QUERY("/v3/refund/domestic/refunds/%s"),

    
    TRADE_BILLS("/v3/bill/tradebill"),

    
    FUND_FLOW_BILLS("/v3/bill/fundflowbill");


    
    private final String type;
}

@AllArgsConstructor
@Getter
public enum WxNotifyType {

	
	NATIVE_NOTIFY("/api/wx-pay/native/notify"),

	
	NATIVE_NOTIFY_V2("/api/wx-pay-v2/native/notify"),

	
	REFUND_NOTIFY("/api/wx-pay/refunds/notify");

	
	private final String type;
}
@AllArgsConstructor
@Getter
public enum WxTradeState {

    
    SUCCESS("SUCCESS"),

    
    NOTPAY("NOTPAY"),

    
    CLOSED("CLOSED"),

    
    REFUND("REFUND");

    
    private final String type;
}

@AllArgsConstructor
@Getter
public enum OrderStatus {
    
    NOTPAY("未支付"),


    
    SUCCESS("支付成功"),

    
    CLOSED("超时已关闭"),

    
    CANCEL("用户已取消"),

    
    REFUND_PROCESSING("退款中"),

    
    REFUND_SUCCESS("已退款"),

    
    REFUND_ABNORMAL("退款异常");

    
    private final String type;
}
@AllArgsConstructor
@Getter
public enum PayType {
    
    WXPAY("微信"),


    
    ALIPAY("支付宝"),

    
    ALIMOBILEPAY("支付宝-手机"),
    
    WXMOBILEPAY("微信-手机");



    
    private final String type;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/885096.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号