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

微信支付二

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

微信支付二

目录

配置文件

MyBatisPlusConfig

 WxPayConfig

Swagger2config

商品订单管理 

OrderInfoController

 OrderInfoService

OrderInfoServiceImpl

商品管理 

controller层

Service层

 Impl层 

网站微信支付API

controller层

Service层

Impl层 

 退款 功能编写

工具类 

HttpClientUtils

HttpUtils

 OrderNoUtils

 R

 YMl配置文件

 wxpay.properties

总结


 


配置文件

MyBatisPlusConfig
package weixinpay.pay.config;


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@MapperScan("weixinpay.pay.mapper")
@EnableTransactionManagement//启用事务管理
public class MyBatisPlusConfig {
}

 WxPayConfig
package weixinpay.pay.config;

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.ScheduledUpdateCertificatesVerifier;
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.util.PemUtil;
import lombok.Data;
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 java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;

import static jdk.nashorn.internal.runtime.regexp.joni.Config.log;


@Slf4j
@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
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;

    

    private PrivateKey getPrivateKey(String filename) {

        try {
            return PemUtil.loadPrivateKey(new FileInputStream(filename));
        } catch (FileNotFoundException e) {
            throw new RuntimeException("私钥文件不存在", e);
        }
    }

    
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier() {

        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));

        return verifier;
    }


    
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier 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;
    }
}

Swagger2config

由于我用的PostMan进行调试的可能Swagger2后面写的不全

package weixinpay.pay.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import springfox.documentation.builders.ApiInfoBuilder;

import springfox.documentation.spi.DocumentationType;

import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;



@Configuration
@EnableSwagger2
public class Swagger2config {


    @Bean
    public Docket docket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder().title("微信支付案例").build());
    }
}

商品订单管理 

OrderInfoController
package weixinpay.pay.controller;

import org.springframework.web.bind.annotation.*;
import weixinpay.pay.entity.OrderInfo;
import weixinpay.pay.enums.OrderStatus;
import weixinpay.pay.service.OrderInfoService;
import weixinpay.pay.vo.R;

import javax.annotation.Resource;
import java.util.List;

@CrossOrigin
@RestController
@RequestMapping
public class OrderInfoController {

    @Resource
    private OrderInfoService orderInfoService;

    @GetMapping("/list")
    public R list() {
        List list = orderInfoService.listOrderByCreateTimeDesc();
        return R.ok().data("list", list);
    }

    
    @GetMapping("query-order-status/{orderNo}")
    public R queryOrderStatus(@PathVariable String orderNo) {
        String orderStatus = orderInfoService.getOrderStatus(orderNo);
        if (OrderStatus.SUCCESS.getType().equals(orderStatus)) {
            return R.ok().setMessage("支付成功");
        }
        return R.ok().setCode(101).setMessage("支付中...");
    }


}

 OrderInfoService
package weixinpay.pay.service;


import com.baomidou.mybatisplus.extension.service.IService;
import weixinpay.pay.entity.OrderInfo;
import weixinpay.pay.enums.OrderStatus;

import java.util.List;

public interface OrderInfoService extends IService {

    OrderInfo createOrderByProductId(Long productId);

    void saveCodeUrl(String orderNo ,String codeUrl);

    List listOrderByCreateTimeDesc();

    void updateStatueByOrderNo(String orderNo, OrderStatus orderStatus);

    String getOrderStatus(String orderNo);


    List getNoPayOrderByDuration(int minutes);

    void updateStatusByOrderNo(String orderNo, OrderStatus refundProcessing);

    OrderInfo getOrderByOrderNo(String orderNo);
}

OrderInfoServiceImpl
package weixinpay.pay.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import weixinpay.pay.entity.OrderInfo;
import weixinpay.pay.entity.Product;
import weixinpay.pay.enums.OrderStatus;
import weixinpay.pay.mapper.OrderInfoMapper;
import weixinpay.pay.mapper.ProductMapper;
import weixinpay.pay.service.OrderInfoService;
import weixinpay.pay.util.OrderNoUtils;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
@Slf4j
@Service
public class OrderInfoServiceImpl extends ServiceImpl implements OrderInfoService {

    @Resource
    private ProductMapper productMapper;
//    @Resource
//    private OrderInfoMapper orderInfoMapper;

    @Override
    public OrderInfo createOrderByProductId(Long productId) {

        //查找已存在但未支付的订单
        OrderInfo orderInfo = this.getNoPayOrderByProductId(productId);
        if (orderInfo !=null ){
            return  orderInfo;
        }

        //获取商品信息
        Product product = productMapper.selectById(productId);
    //生成订单
        orderInfo = new OrderInfo();
        orderInfo.setTitle(product.getTitle());
        orderInfo.setOrderNo(OrderNoUtils.getOrderNo());//订单号
        orderInfo.setProductId(productId);
        orderInfo.setTotalFee(product.getPrice());//分
        orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());
        baseMapper.insert(orderInfo);
        return orderInfo;

    }

    

    @Override
    public void saveCodeUrl(String orderNo, String codeUrl) {

        QueryWrapper queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no",orderNo);

       OrderInfo orderInfo = new OrderInfo();

        orderInfo.setCodeUrl(codeUrl);

        baseMapper.update(orderInfo,queryWrapper);
    }

    @Override
    public List listOrderByCreateTimeDesc() {
         QueryWrapper queryWrapper =new QueryWrapper().orderByDesc("create_time");
         return baseMapper.selectList(queryWrapper);
    }

    
    @Override
    public void updateStatueByOrderNo(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);

    }

    @Override
    public String getOrderStatus(String orderNo) {
        QueryWrapper queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no",orderNo);
        OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
        if (orderInfo == null){
            return null;
        }
        return orderInfo.getOrderStatus();
    }

    
    @Override
    public List getNoPayOrderByDuration(int minutes) {

        Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));

        QueryWrapper queryWrapper = new QueryWrapper<>();
       queryWrapper.eq("order_status",OrderStatus.NOTPAY.getType());
       queryWrapper.le("create_time",instant);
        List orderInfoList = baseMapper.selectList(queryWrapper);

        return orderInfoList;
    }

    @Override
    public void updateStatusByOrderNo(String orderNo, OrderStatus refundProcessing) {

    }

    
    @Override
    public OrderInfo getOrderByOrderNo(String orderNo) {

        QueryWrapper queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no", orderNo);
        OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);

        return orderInfo;
    }

    
    private OrderInfo getNoPayOrderByProductId(Long productId) {

        QueryWrapper queryWrapper = new QueryWrapper<>();
    //productId是否等于product_id
        queryWrapper.eq("product_id",productId);
        queryWrapper.eq("order_status",OrderStatus.NOTPAY.getType());
    //        queryWrapper.eq("user_id",userId);
        OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);
        return orderInfo;
    }
}

商品管理 

controller层
package weixinpay.pay.controller;


import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;
import weixinpay.pay.entity.Product;
import weixinpay.pay.service.ProductService;
import weixinpay.pay.vo.R;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
@CrossOrigin
@Api(tags = "商品管理")
@RestController
@RequestMapping("/api/product")
public class ProductController {


    @Resource
    private ProductService productService;

    @GetMapping("/test")
    public R test(){

        return R.ok().data("message","hello").data("now",new Date());
    }
    @GetMapping("/list")
    public R list(){
       List list =productService.list();
       return R.ok().data("productList",list);

    }
}

Service层

由于我们使用的MybatisPuls 它给我们预定了一下增删改查等方法so

package weixinpay.pay.service;


import com.baomidou.mybatisplus.extension.service.IService;
import weixinpay.pay.entity.Product;

public interface ProductService extends IService {

}

 Impl层 
package weixinpay.pay.service.impl;


import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import weixinpay.pay.entity.Product;
import weixinpay.pay.mapper.ProductMapper;
import weixinpay.pay.service.ProductService;

@Service
public class ProductServiceImpl extends ServiceImpl implements ProductService {

}

网站微信支付API controller层
package weixinpay.pay.controller;


import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import weixinpay.pay.service.WxPayService;
import weixinpay.pay.util.HttpUtils;
import weixinpay.pay.util.WechatPay2ValidatorForRequest;
import weixinpay.pay.vo.R;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;

@CrossOrigin//跨域
@RestController
@RequestMapping("/api/wx-pay")
@Api(tags = "网站微信支付API")
@Slf4j
public class WxPayController {

    @Resource
    private WxPayService wxPayService;
    @Resource
    private Verifier verifier;

    
    @ApiOperation("调用统一下单API,生成支付二维码")
    @PostMapping("/native/{productId}")
    public R nativePay(@PathVariable Long productId) throws Exception {

        log.info("发起支付请求 v3");

        //返回支付二维码连接和订单号
        Map map = wxPayService.nativePayV2(productId);

        return R.ok().setData(map);
    }

    @PostMapping("/native/notify")
    public String nativeNotify(HttpServletResponse response, HttpServletRequest request) throws IOException, GeneralSecurityException {
        Gson gson = new Gson();
        //应答对象
        HashMap map = new HashMap<>();


        //处理通知参数
        String body = HttpUtils.readData(request);

        Map bodyMap = gson.fromJson(body, HashMap.class);
        String requestId = (String) bodyMap.get("id");
        log.info("支付通知的id ===> {}", requestId);
        //log.info("支付通知的完整数据 ===> {}", body);
        //签名验证
        WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier, requestId, body);

        if (!wechatPay2ValidatorForRequest.validate(request)) {

            log.error("通知验签失败");
            //失败应答
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "通知验签失败");
            return gson.toJson(map);
        }

        //处理订单
        wxPayService.processOrder(bodyMap);
        //成功应答
        response.setStatus(200);
        map.put("code", "SUCCESS");
        map.put("massage", "成功");
        return gson.toJson(map);
    }

    
    @GetMapping("/cancel/{orderNo}")
    public R cancelOrderNo(@PathVariable String orderNo) throws IOException {
        log.info("订单取消");
        wxPayService.cancelOrderNO(orderNo);
        return R.ok().setMessage("订单取消");
    }

    
    @GetMapping
    public R queryOrder(@PathVariable String orderNo) throws Exception {
        log.info("查询订单");
        String result=wxPayService.queryOrder(orderNo);

        return R.ok().setMessage("查询成功").data("result", result);
    }

    public  R refund(@PathVariable String orderNo,@PathVariable String reason) throws IOException {
    log.info("申请退款");
    wxPayService.refund(orderNo,reason);
    return  R.ok();
    }

}
Service层
package weixinpay.pay.service;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Map;

public interface WxPayService {
    Map nativePayV2(Long productId) throws Exception;

    void processOrder(Map bodyMap) throws GeneralSecurityException;

    void cancelOrderNO(String orderNo) throws IOException;

    String queryOrder(String orderNo) throws Exception;

    void checkOrderStatus(String orderNo) throws Exception;

    void refund(String orderNo, String reason) throws IOException;

    void checkRefundStatus(String refundNo) throws IOException;
}

Impl层 
package weixinpay.pay.service.impl;

import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
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.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import weixinpay.pay.config.WxPayConfig;
import weixinpay.pay.entity.OrderInfo;
import weixinpay.pay.entity.RefundInfo;
import weixinpay.pay.enums.OrderStatus;
import weixinpay.pay.enums.wxpay.WxApiType;
import weixinpay.pay.enums.wxpay.WxNotifyType;
import weixinpay.pay.enums.wxpay.WxRefundStatus;
import weixinpay.pay.enums.wxpay.WxTradeState;
import weixinpay.pay.service.OrderInfoService;
import weixinpay.pay.service.PaymentInfoService;
import weixinpay.pay.service.RefundInfoService;
import weixinpay.pay.service.WxPayService;
import weixinpay.pay.vo.R;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {

    private final ReentrantLock lock = new ReentrantLock();
    @Resource
    private WxPayConfig wxPayConfig;
    @Resource
    private CloseableHttpClient wxPayClient;
    @Resource
    private OrderInfoService orderInfoService;
    @Resource
    private PaymentInfoService paymentInfoService;
    @Resource
    private RefundInfoService refundsInfoService;

    
    @Override
    public Map nativePayV2(Long productId) throws Exception {
        log.info("调用统一下单API");
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);
        String codeUrl = orderInfo.getCodeUrl();

        if (orderInfo != null && !StringUtils.isEmpty(codeUrl)) {
            log.info("订单已存在,二维码以保存");
            //返回二维码
            Map map = new HashMap<>();
            map.put("codeUrl", codeUrl);
            map.put("orderNo", orderInfo.getOrderNo());
            return map;
        }

        //调用统一下单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());
        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");

        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 = wxPayClient.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(Map bodyMap) throws GeneralSecurityException {
        log.info("处理订单");
        String plainText = decryFromResource(bodyMap);
        //将明文转为Map
        Gson gson = new Gson();
        HashMap plaintTextMap = gson.fromJson(plainText, HashMap.class);
        String orderNo = (String) plaintTextMap.get("out_trade_no");

        

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

                //模拟通知并发
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

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

        }
    }

    @Override
    public void cancelOrderNO(String orderNo) throws IOException {
        //关闭订单号
        this.closeOrder(orderNo);
        //更新订单号
        orderInfoService.updateStatueByOrderNo(orderNo, OrderStatus.CANCEL);
    }

    

    @Override
    public String queryOrder(String orderNo) throws Exception {
        log.info("查询接口调用===>", orderNo);
        String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());

        HttpGet httpGet = new HttpGet();
        httpGet.setHeader("Accept", "application/json");

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


        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功, 查询退款返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                throw new RuntimeException("查询退款异常, 响应码 = " + statusCode + ", 查询退款返回结果 = " + bodyAsString);
            }

            return bodyAsString;

        } finally {
            response.close();
        }
    }

    @Override
    public void checkOrderStatus(String orderNo) throws Exception {
        log.warn("根据订单号核实订单状态===>{}", orderNo);

        //调用微信支付查单接口
        String result = this.queryOrder(orderNo);
        Gson gson = new Gson();
        HashMap resultMap = gson.fromJson(result, HashMap.class);
        //获取微信支付端的订单状态
        String tradeState = resultMap.get("trade_state");
        if (WxTradeState.SUCCESS.getType().equals(tradeState)) {

            log.warn("核实订单已经支付===>", orderNo);
            //如果确定订单已经支付则更新本地订单状态
            orderInfoService.updateStatueByOrderNo(orderNo, OrderStatus.SUCCESS);
            //记录支付日志
            paymentInfoService.createPaymentInfo(result);

        }
        if (WxTradeState.NOTPAY.getType().equals(tradeState)){
            log.warn("核实订单未支付 ===> {}", orderNo);

            this.cancelOrderNO(orderNo);
            orderInfoService.updateStatueByOrderNo(orderNo,OrderStatus.CLOSED);

        }






    }

    @Override
    public void refund(String orderNo, String reason) throws IOException {
    log.info("创造退款单记录");

        RefundInfo refundsInfo  = refundsInfoService.createRefundByOrderNo(orderNo,reason);

        log.info("调用退款API");

//调用统一下单API
        String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
        HttpPost httpPost =new HttpPost(url);
        //请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap<>();
        paramsMap.put("out_trade_no", orderNo);//订单编号
        paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号
        paramsMap.put("reason",reason);//退款原因
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址

        Map amountMap = new HashMap();
        amountMap.put("refund", refundsInfo.getRefund());//退款金额
        amountMap.put("total", refundsInfo.getTotalFee());//原订单金额
        amountMap.put("currency", "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 = wxPayClient.execute(httpPost);

        try {

            //解析响应结果
            String bodyAsString = EntityUtils.toString(response.getEntity());

            int statusCode = response.getStatusLine().getStatusCode();

            if (statusCode == 200) {
                log.info("成功, 退款返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                throw new RuntimeException("退款异常, 响应码 = " + statusCode+ ", 退款返回结果 = " + bodyAsString);
            }

            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);

            //更新退款单
            refundsInfoService.updateRefund(bodyAsString);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            response.close();
        }
    }

    @Override
    public void checkRefundStatus(String refundNo) throws IOException {

        log.warn("根据退款单号核实退款单状态 ===> {}", refundNo);

        //调用查询退款单接口
        String result = this.queryRefund(refundNo);

        //组装json请求体字符串
        Gson gson = new Gson();
        Map resultMap = gson.fromJson(result, HashMap.class);

        //获取微信支付端退款状态
        String status = resultMap.get("status");

        String orderNo = resultMap.get("out_trade_no");

        if (WxRefundStatus.SUCCESS.getType().equals(status)) {

            log.warn("核实订单已退款成功 ===> {}", refundNo);

            //如果确认退款成功,则更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);

            //更新退款单
            refundsInfoService.updateRefund(result);
        }

        if (WxRefundStatus.ABNORMAL.getType().equals(status)) {

            log.warn("核实订单退款异常  ===> {}", refundNo);

            //如果确认退款成功,则更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL);

            //更新退款单
            refundsInfoService.updateRefund(result);
        }
    }

    private String queryRefund(String refundNo) throws IOException {
        log.info("查询退款接口调用 ===> {}", refundNo);

        String url =  String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
        url = wxPayConfig.getDomain().concat(url);

        //创建远程Get 请求对象
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");

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

        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                log.info("成功, 查询退款返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                log.info("成功");
            } else {
                throw new RuntimeException("查询退款异常, 响应码 = " + statusCode+ ", 查询退款返回结果 = " + bodyAsString);
            }

            return bodyAsString;

        } finally {
            response.close();
        }
    }


    private void closeOrder(String orderNo) throws IOException {
        log.info("关单接口的调用,订单号===>{}", orderNo);
        //创建远程对象
        String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url);
        HttpPost httpPost = new HttpPost(url);
        //组装Json请求体
        Gson gson = new Gson();
        HashMap paramsMap = new HashMap<>();
        paramsMap.put("mchid", wxPayConfig.getMchId());
        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 = wxPayClient.execute(httpPost);

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

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            response.close();
        }
    }


    
    private String decryFromResource(Map bodyMap) throws GeneralSecurityException {
        log.info("密文解密");

        //通知数据
        Map resourceMap = (Map) bodyMap.get("resource");

        //数据密文
        String ciphertext = resourceMap.get("ciphertext");
        //随机串
        String nonce = resourceMap.get("nonce");
        //附加数据
        log.info("密文===>{}", ciphertext);
        String associatedData = resourceMap.get("associated_data");
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);

        log.info("明文===>{}", plainText);
        return plainText;


    }
}

 退款 功能编写

由于退款的service serviceImpl 写入商品订单管理了,这里就不单独写了

package weixinpay.pay.task;


import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import weixinpay.pay.entity.OrderInfo;
import weixinpay.pay.entity.RefundInfo;
import weixinpay.pay.service.OrderInfoService;
import weixinpay.pay.service.RefundInfoService;
import weixinpay.pay.service.WxPayService;

import javax.annotation.Resource;
import java.util.List;

@Slf4j
@Component
public class WxPayTask {

    @Resource
    private OrderInfoService orderInfoService;

    @Resource
    private WxPayService wxPayService;

    @Resource
    private RefundInfoService refundInfoService;

    
    //@Scheduled(cron = "0/3 * * * * ?")
    public void task1(){
        log.info("task1 被执行......");
    }

    
    @Scheduled(cron = "0/30 * * * * ?")
    public void orderConfirm() throws Exception {
        log.info("orderConfirm 被执行......");

        List orderInfoList = orderInfoService.getNoPayOrderByDuration(5);

        for (OrderInfo orderInfo : orderInfoList) {
            String orderNo = orderInfo.getOrderNo();
            log.warn("超时订单 ===> {}", orderNo);

            //核实订单状态:调用微信支付查单接口
            wxPayService.checkOrderStatus(orderNo);
        }
    }


    
    @Scheduled(cron = "0/30 * * * * ?")
    public void refundConfirm() throws Exception {
        log.info("refundConfirm 被执行......");

        //找出申请退款超过5分钟并且未成功的退款单
        List refundInfoList = refundInfoService.getNoRefundOrderByDuration(1);

        for (RefundInfo refundInfo : refundInfoList) {
            String refundNo = refundInfo.getRefundNo();
            log.warn("超时未退款的退款单号 ===> {}", refundNo);

            //核实订单状态:调用微信支付查询退款接口
            wxPayService.checkRefundStatus(refundNo);
        }
    }

}

工具类 

HttpClientUtils
package com.atguigu.paymentdemo.util;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;


public class HttpClientUtils {
	private String url;
	private Map param;
	private int statusCode;
	private String content;
	private String xmlParam;
	private boolean isHttps;

	public boolean isHttps() {
		return isHttps;
	}

	public void setHttps(boolean isHttps) {
		this.isHttps = isHttps;
	}

	public String getXmlParam() {
		return xmlParam;
	}

	public void setXmlParam(String xmlParam) {
		this.xmlParam = xmlParam;
	}

	public HttpClientUtils(String url, Map param) {
		this.url = url;
		this.param = param;
	}

	public HttpClientUtils(String url) {
		this.url = url;
	}

	public void setParameter(Map map) {
		param = map;
	}

	public void addParameter(String key, String value) {
		if (param == null)
			param = new HashMap();
		param.put(key, value);
	}

	public void post() throws ClientProtocolException, IOException {
		HttpPost http = new HttpPost(url);
		setEntity(http);
		execute(http);
	}

	public void put() throws ClientProtocolException, IOException {
		HttpPut http = new HttpPut(url);
		setEntity(http);
		execute(http);
	}

	public void get() throws ClientProtocolException, IOException {
		if (param != null) {
			StringBuilder url = new StringBuilder(this.url);
			boolean isFirst = true;
			for (String key : param.keySet()) {
				if (isFirst) {
					url.append("?");
					isFirst = false;
				}else {
					url.append("&");
				}
				url.append(key).append("=").append(param.get(key));
			}
			this.url = url.toString();
		}
		HttpGet http = new HttpGet(url);
		execute(http);
	}

	
	private void setEntity(HttpEntityEnclosingRequestBase http) {
		if (param != null) {
			List nvps = new LinkedList();
			for (String key : param.keySet())
				nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
			http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
		}
		if (xmlParam != null) {
			http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
		}
	}

	private void execute(HttpUriRequest http) throws ClientProtocolException,
			IOException {
		CloseableHttpClient httpClient = null;
		try {
			if (isHttps) {
				SSLContext sslContext = new SSLContextBuilder()
						.loadTrustMaterial(null, new TrustStrategy() {
							// 信任所有
							public boolean isTrusted(X509Certificate[] chain,
									String authType)
									throws CertificateException {
								return true;
							}
						}).build();
				SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
						sslContext);
				httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
						.build();
			} else {
				httpClient = HttpClients.createDefault();
			}
			CloseableHttpResponse response = httpClient.execute(http);
			try {
				if (response != null) {
					if (response.getStatusLine() != null)
						statusCode = response.getStatusLine().getStatusCode();
					HttpEntity entity = response.getEntity();
					// 响应内容
					content = EntityUtils.toString(entity, Consts.UTF_8);
				}
			} finally {
				response.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			httpClient.close();
		}
	}

	public int getStatusCode() {
		return statusCode;
	}

	public String getContent() throws ParseException, IOException {
		return content;
	}

}

HttpUtils
package com.atguigu.paymentdemo.util;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;


public class HttpUtils {

    
    public static String readData(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 OrderNoUtils
package com.atguigu.paymentdemo.util;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;


public class OrderNoUtils {

    
    public static String getOrderNo() {
        return "ORDER_" + getNo();
    }

    
    public static String getRefundNo() {
        return "REFUND_" + getNo();
    }

    
    public static String getNo() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String newDate = sdf.format(new Date());
        String result = "";
        Random random = new Random();
        for (int i = 0; i < 3; i++) {
            result += random.nextInt(10);
        }
        return newDate + result;
    }

}

 R
package com.atguigu.paymentdemo.vo;

import lombok.Data;
import lombok.experimental.Accessors;

import java.util.HashMap;
import java.util.Map;

@Data
@Accessors(chain = true)
public class R {

    private Integer code; //响应码
    private String message; //响应消息
    private Map data = new HashMap<>();

    public static R ok(){
        R r = new R();
        r.setCode(0);
        r.setMessage("成功");
        return r;
    }

    public static R error(){
        R r = new R();
        r.setCode(-1);
        r.setMessage("失败");
        return r;
    }

    public R data(String key, Object value){
        this.data.put(key, value);
        return this;
    }

}

 YMl配置文件
server:
  port: 8090 #服务端口
Spring :
  application:
    name: Pay #应用的名字
  jackson:
    data-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

#数据库连接
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/payment_demo?serverTimezone=GMT%2B8&characterEncoding=utf-8
    username: root
    password: 123456
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:weixinpay/pay/mapper/xml/*.xml
logging:
  level:
    root: info

 wxpay.properties
# 微信支付相关参数
# 商户号
wxpay.mch-id=1558950191
# 商户API证书序列号
wxpay.mch-serial-no=34345964330B66427E0D3D28826C4993C77E631F

# 商户私钥文件
wxpay.private-key-path=apiclient_key.pem
# APIv3密钥
wxpay.api-v3-key=UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B
# APPID
wxpay.appid=wx74862e0dfcf69954
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
wxpay.notify-domain=https://500c-219-143-130-12.ngrok.io

# APIv2密钥
wxpay.partnerKey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb

 

 

总结

此项目在线支付的一系列流程,项目虽小,但内容十分丰富。麻雀虽小但五脏俱全

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

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

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