[微信支付] SpringMVC开发小案例

最近写了一个微信开发java版的,

先从微信支付官方(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1)下载demo(java版),

1、服务号相关信息,填上相关的参数


/**
 * 服务号相关信息
 */
public class ConfigUtil {

    
    public final static String APPID = "";
  
    public final static String MCH_ID = "";// 商户号
   
    public final static String API_KEY = "";// API密钥




}

2、因为微信支付的金额单位是元,所以在传输的时候需要将元转换为分,以下是转换包

import java.text.NumberFormat;
import java.text.ParseException;


//金额转换
public class moneyUtil{


    //分转元
    public static String changeY2F(Double amount){
        String currency =  amount.toString();
        int index = currency.indexOf(".");
        int length = currency.length();
        Long amLong = 0l;
        if(index == -1){
            amLong = Long.valueOf(currency+"00");
        }else if(length - index >= 3){
            amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", ""));
        }else if(length - index == 2){
            amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0);
        }else{
            amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00");
        }
        return amLong.toString();
    }


    //元转分
    public static String fenToYuan(String amount){
        NumberFormat format = NumberFormat.getInstance();
        try{
            Number number = format.parse(amount);
            double temp = number.doubleValue() / 100.0;
            format.setGroupingUsed(false);
            // 设置返回的小数部分所允许的最大位数
            format.setMaximumFractionDigits(2);
            amount = format.format(temp);
        } catch (ParseException e){
            e.printStackTrace();
        }
        return amount;
    }

}

3、其中也sign用到了md5加密,还有16位随机字符串,获取ip地址方法

    //md5大写
    public static String MD5Encode(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException {

        StringBuilder sign = new StringBuilder();
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] bytes = md.digest(str.getBytes("utf-8"));

        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex.toUpperCase());
        }
        return sign.toString();
    }


    /**
     * 创建随机字符串16位
     */
    public static String CreateNoncestr() {
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        String res = "";
        for (int i = 0; i < 16; i++) {
            Random rd = new Random();
            res += chars.charAt(rd.nextInt(chars.length() - 1));
        }
        return res;
    }

 /**获取ip地址*/
    public static String getIpAddr(HttpServletRequest request) {
        InetAddress addr = null;
        try {
            addr = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            return request.getRemoteAddr();
        }
        byte[] ipAddr = addr.getAddress();
        String ipAddrStr = "";
        for (int i = 0; i < ipAddr.length; i++) {
            if (i > 0) {
                ipAddrStr += ".";
            }
            ipAddrStr += ipAddr[i] & 0xFF;
        }
        return ipAddrStr;
    }

4、下单接口,因为自己第一次尝试写,其中有些没有用到方法,代码比较杂乱

//1、下单
    @RequestMapping("/createNative")
    public Object createNative(@RequestParam("outTradeNo") String outTradeNo, @RequestParam("totalFee") String totalFee,
                                                                        @RequestParam("body") String body,HttpServletRequest request)throws Exception {
        //解决中文乱码
        body  = new String(body.getBytes("iso-8859-1"), "utf-8");

        try {
            request.setCharacterEncoding("UTF-8");
            SortedMap<String , String> req = new TreeMap<String, String>();
            req.put("appid", ConfigUtil.APPID);
            req.put("mch_id", ConfigUtil.MCH_ID);

            req.put("out_trade_no", outTradeNo);
            totalFee= moneyUtil.changeY2F(Double.valueOf(totalFee));
            req.put("total_fee", totalFee);
            req.put("body",body);

            // 32位随机字符串
            String random = PayCommonUtil.CreateNoncestr();

            req.put("nonce_str", random);
            // 获取ip地址
            req.put("spbill_create_ip", PayCommonUtil.getIpAddr(request));

            String notify_url = "http://www.huiyouar.com/WeiXinSmpay/WeixinNotify";
            req.put("notify_url",notify_url );
            req.put("trade_type", "NATIVE");

            String sppend = "appid="+ConfigUtil.APPID+"&body="+body+"&mch_id="+ConfigUtil.MCH_ID+
                    "&nonce_str="+random+"&notify_url="+notify_url+"&out_trade_no="+outTradeNo+
                    "&spbill_create_ip="+PayCommonUtil.getIpAddr(request)+
                    "&total_fee="+totalFee+"&trade_type=NATIVE&key="+ConfigUtil.API_KEY;


            String sign = PayCommonUtil.MD5Encode(sppend);
            req.put("sign", sign);


            //生成XML
            String xmlBody = WXPayUtil.generateSignedXml(req, ConfigUtil.API_KEY);

            String sendurl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
            String result = PayCommonUtil.httpsRequest(sendurl, "POST", xmlBody);

            Map<String, String> resultMap = WXPayUtil.xmlToMap(result);

            if(resultMap.get("result_code").equals("SUCCESS")){
                return resultMap.get("code_url");
            }else{
                return JSON.toJSONString(resultMap);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "请求异常";
        }
    }

以上代码可以就实现微信支付了,

接下来是微信支付查询接口

1、

//3、查询
@SneakyThrows
@RequestMapping("/queryOrder")
public String queryOrder(@RequestParam("out_trade_no") String out_trade_no){

    SortedMap<String, String> parameters = new TreeMap<String, String>();
    parameters.put("appid", ConfigUtil.APPID);
    parameters.put("mch_id", ConfigUtil.MCH_ID);
    // 随机字符串
    String randomString = PayCommonUtil.CreateNoncestr();
    parameters.put("nonce_str", randomString);
    parameters.put("out_trade_no", out_trade_no);

    //签名校验
    String append = "appid="+ConfigUtil.APPID+"&mch_id="+ConfigUtil.MCH_ID+"&nonce_str="+randomString+"&out_trade_no="+out_trade_no+"&key="+ConfigUtil.API_KEY;
    String sign = PayCommonUtil.MD5Encode(append);
    parameters.put("sign", sign);

    //生成XML
    String requestXML = WXPayUtil.generateSignedXml(parameters, ConfigUtil.API_KEY);


    String url = "https://api.mch.weixin.qq.com/pay/orderquery";
    String result = PayCommonUtil.httpsRequest(url, "POST", requestXML);

    //返回结果
    Map<String, String> resultMap = WXPayUtil.xmlToMap(result);
    String return_code =resultMap.get("return_code");
    String result_code =resultMap.get("result_code");
    String trade_state =resultMap.get("trade_state");
    if(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")&&trade_state.equals("SUCCESS")){
        return "支付成功";
    }else if(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")&&trade_state.equals("REFUND")){
        return "转入退款 ";
    }else if(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")&&trade_state.equals("NOTPAY")){
        return "订单未支付 ";
    }else if(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")&&trade_state.equals("CLOSED")){
        return "订单已关闭 ";
    }else if(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")&&trade_state.equals("REVOKED")){
        return "已撤销(刷卡支付)  ";
    }else if(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")&&trade_state.equals("USERPAYING")){
        return "用户支付中  ";
    }else if(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")&&trade_state.equals("PAYERROR")){
        return "支付失败(其他原因,如银行返回失败)";
    }else if(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")&&trade_state.equals("PAYERROR")){
        return "支付失败(其他原因,如银行返回失败)";
    }else if(return_code.equals("SUCCESS")&&result_code.equals("SUCCESS")&&trade_state.equals("ACCEPT")){
        return "已接收,等待扣款";
    }else if(return_code.equals("SUCCESS")&&result_code.equals("FAIL")&&resultMap.get("err_code_des").equals("订单不存在")){
        return "订单不存在";
    }else {
        return String.valueOf(resultMap);
    }

}

2、关闭订单接口

//2、关闭订单
@RequestMapping("/closeOrder")
public String closeOrder(@RequestParam("out_trade_no") String out_trade_no) throws Exception {

    SortedMap<String, String> parameters = new TreeMap<String, String>();
    parameters.put("appid", ConfigUtil.APPID);
    parameters.put("mch_id", ConfigUtil.MCH_ID);
    // 随机字符串
    String randomString = PayCommonUtil.CreateNoncestr();
    parameters.put("nonce_str", randomString);
    parameters.put("out_trade_no", out_trade_no);

    String append = "appid="+ConfigUtil.APPID+"&mch_id="+ConfigUtil.MCH_ID+"&nonce_str="+randomString+"&out_trade_no="+out_trade_no+"&key="+ConfigUtil.API_KEY;

    String sign = PayCommonUtil.MD5Encode(append);
    parameters.put("sign", sign);

    //生成XML
    String requestXML = WXPayUtil.generateSignedXml(parameters, ConfigUtil.API_KEY);


    String url = "https://api.mch.weixin.qq.com/pay/closeorder";
    String result = PayCommonUtil.httpsRequest(url, "POST", requestXML);

    Map<String, String> resultMap = WXPayUtil.xmlToMap(result);
    String  resultCode = resultMap.get("return_code");
    if(resultCode.equals("SUCCESS")){
        return "订单取消成功";
    }else{
        return "订单取消失败";
    }


}

 3、退款接口有些麻烦,他需要用到证书,不然会报错,百度查了告诉我是缺少证书,加载进来就可以实现功能了,

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
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;


public class CommonUtil {
    private static int socketTimeout = 10000;// 连接超时时间,默认10秒
    private static int connectTimeout = 30000;// 传输超时时间,默认30秒
    private static RequestConfig requestConfig;// 请求器的配置
    private static CloseableHttpClient httpClient;// HTTP请求器

    public static String postData(String url, String xmlObj, String path) {
        try {
            // 加载证书
            initCert(path);
        } catch (Exception e) {
            e.printStackTrace();
        }
        String result = null;
        HttpPost httpPost = new HttpPost(url);
        // 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
        StringEntity postEntity = new StringEntity(xmlObj, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.setEntity(postEntity);
        // 根据默认超时限制初始化requestConfig
        requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();
        // 设置请求器的配置
        httpPost.setConfig(requestConfig);
        try {
            HttpResponse response = null;
            try {
                response =  httpClient.execute(httpPost);
            }  catch (IOException e) {
                e.printStackTrace();
            }
            HttpEntity entity = response.getEntity();
            try {
                result = EntityUtils.toString(entity, "UTF-8");
            }  catch (IOException e) {
                e.printStackTrace();
            }
        } finally {
            httpPost.abort();
        }
        return result;
    }

    /**
     * 加载证书
     *
     */
    @SuppressWarnings("deprecation")
    private static void initCert(String path) throws Exception {
        // 证书密码,默认为商户ID
        String key = ConfigUtil.MCH_ID;
        // 指定读取证书格式为PKCS12
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        // 读取本机存放的PKCS12证书文件
        FileInputStream instream = new FileInputStream(new File(path));
        try {
            // 指定PKCS12的密码(商户ID)
            keyStore.load(instream, key.toCharArray());
        } finally {
            instream.close();
        }
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, key.toCharArray()).build();
        // 指定TLS版本
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext, new String[] { "TLSv1" }, null,
                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        // 设置httpclient的SSLSocketFactory
        httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
    }

}

退款接口

//4、退款-----证书
@SneakyThrows
@RequestMapping("/refundOrder")
public String refundOrder(@RequestParam("out_trade_no") String out_trade_no,@RequestParam("out_refund_no")  String out_refund_no,
                          @RequestParam("total_fee")  String total_fee,@RequestParam("refund_fee")String refund_fee){

    SortedMap<String, String> parameters = new TreeMap<String, String>();
    parameters.put("appid", ConfigUtil.APPID);
    parameters.put("mch_id", ConfigUtil.MCH_ID);
    // 随机字符串
    String randomString = PayCommonUtil.CreateNoncestr();
    parameters.put("nonce_str", randomString);

    parameters.put("out_trade_no", out_trade_no);
    //退款订单号
    parameters.put("out_refund_no", out_refund_no);
    //退款金额
    total_fee = chang2F.changeY2F(Double.valueOf(total_fee));
    parameters.put("total_fee", total_fee);
    refund_fee = moneyUtil.changeY2F(Double.valueOf(refund_fee));
    parameters.put("refund_fee", refund_fee);

    //签名校验
    String append = "appid="+ConfigUtil.APPID+"&mch_id="+ConfigUtil.MCH_ID+"&nonce_str="+randomString+"&out_refund_no="+out_refund_no +"&out_trade_no="+
                                                        out_trade_no+"&refund_fee="+refund_fee+"&total_fee="+total_fee+"&key="+ConfigUtil.API_KEY;

    String sign = PayCommonUtil.MD5Encode(append);
    parameters.put("sign", sign);

    //生成XML
    String requestXML = WXPayUtil.generateSignedXml(parameters, ConfigUtil.API_KEY);
    String imgPath = "证书目录"+"apiclient_cert.p12";

    String url = "https://api.mch.weixin.qq.com/secapi/pay/refund";

    String result =   CommonUtil.postData(url,requestXML,imgPath);

    Map<String, String> resultMap = WXPayUtil.xmlToMap(result);
    String return_code = resultMap.get("return_code");
    if(return_code.equals("SUCCESS")){
        return "退款申请接收成功";
    }else{
        return JSON.toJSONString(resultMap);
    }

}

 退款查询接口

//5、退款查询
@SneakyThrows
@RequestMapping("/refundqueryOrder")
public String refundqueryOrder(@RequestParam("out_trade_no") String out_trade_no){

    SortedMap<String, String> parameters = new TreeMap<String, String>();
    parameters.put("appid", ConfigUtil.APPID);
    parameters.put("mch_id", ConfigUtil.MCH_ID);
    String ramdomString = PayCommonUtil.CreateNoncestr();
    parameters.put("nonce_str",ramdomString );// 随机字符串
    parameters.put("out_trade_no", out_trade_no);

    String append = "appid="+ConfigUtil.APPID+"&mch_id="+ConfigUtil.MCH_ID+"&nonce_str="+ramdomString+"&out_trade_no="+out_trade_no+"&key="+ConfigUtil.API_KEY;

    String sign = PayCommonUtil.MD5Encode(append);
    parameters.put("sign", sign);

    //生成XML
    String requestXML = WXPayUtil.generateSignedXml(parameters, ConfigUtil.API_KEY);

    String url = "https://api.mch.weixin.qq.com/pay/refundquery";
    String result = PayCommonUtil.httpsRequest(url, "POST", requestXML);

    Map<String, String> resultMap = WXPayUtil.xmlToMap(result);
    return JSON.toJSONString(resultMap);

}

还有一些就是springmvc配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jee="http://www.springframework.org/schema/jee"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
		http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd" >
	
	 <!-- 1.扫描Controller的包-->
    <context:component-scan base-package="com.saihui.controller"/>
    <!-- 2.配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!-- 2.1 页面前缀 -->
        <property name="prefix" value="/WEB-INF/"/>
        <!-- 2.2 页面后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 3.开启mvc注解驱动-->
    <mvc:annotation-driven/>
    
        <mvc:annotation-driven>
        <!--设置响应输出字符集-->
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/html;charset=utf-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
</beans>

 web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

<servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

    <!-- Spring字符集过滤器 -->
    <filter>
        <filter-name>SpringEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>SpringEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

因为我下单接口返回的是链接,如果需要生成图片的话,以下是util

package com.saihui.util;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Hashtable;

public class ImageUtil {
    public static String image(String trl) {
        //二维码宽度
        int width=300;
        //二维码高度
        int height=300;
        //定义二维码图片格式
        String format="png";

        //定义二维码属性
        Hashtable<EncodeHintType,String> his=new Hashtable<EncodeHintType,String>();
        //二维码内容的编码格式
        his.put(EncodeHintType.CHARACTER_SET,"utf-8");
        //定义二维码边距
        his.put(EncodeHintType.MARGIN,"1");


        //创建位矩阵对象并且生成二维码对应的位矩阵对象
        //BarcodeFormat.QR_CODE  生成图片类型为QRCode
        BitMatrix bitMatrix= null;
        try {
            bitMatrix = new MultiFormatWriter().encode(trl, BarcodeFormat.QR_CODE,width,height);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //二维码生成路径
        String path="D:\\";
        //二维码生成名字
        String name="myBlog.png";

        Path path2= Paths.get(path,name);
        //生成二维码
        try {
            MatrixToImageWriter.writeToPath(bitMatrix, format, path2);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("二维码生成成功");
        return format;
    }


}

 在controller里改成out.write(ImageUtil.image("微信支付返回的链接"));就可以了,把@RestController改成@Controller

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring MVC 是一个基于Java的框架,用于构建Web应用程序。Spring的特性包括依赖注入、切面编程等。微信支付 V3微信支付的最新版本,它为商家提供了更好的体验和更高的安全性。退款是在客户发生问题或商家必须解决问题时,将付款金额退回到客户账户的一种方式,而 BinaryWang 是一个开发团队,专注于微信开发和开源工具,其提供了一些与微信支付相关的Java工具类库。 采用Spring MVC开发微信支付 V3 的退款功能可以带来许多优势。Spring MVC框架提供了丰富的工具,包括文件上传、JSON 数据绑定、RESTful方案等,可以极大地简化开发工作。另外,使用Spring的依赖注入来管理支付的服务可以大大提高代码的可维护性和可扩展性。同时,由于退款功能涉及到安全性问题,使用Spring的切面编程可以轻松地实现安全审计、日志记录等功能,使得退款操作更加安全和可靠。 而 BinaryWang 提供的微信支付V3 Java工具类库,可以极大地简化开发者的支付流程。该工具类库提供了支持签名和验签功能的API,可以方便地处理交易数据,并支持自动转换格式。除此之外,该工具类库提供的API还支持查询订单状态、退款查询、下载交易数据等等,使得开发者能够更轻松地实现微信支付的功能,同时保证了支付安全性和可靠性。 总之,采用Spring MVC与微信支付V3的退款功能,结合 BinaryWang 提供的Java工具类库,可以使开发者更加轻松地实现退款功能,并保证了支付的安全性和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值