首页后端开发其他后端知识Java SpringMVC后端开发支付统一下单API如何操作

Java SpringMVC后端开发支付统一下单API如何操作

时间2024-03-28 14:54:03发布访客分类其他后端知识浏览1544
导读:关于“Java SpringMVC后端开发支付统一下单API如何操作”的知识点有一些人不是很理解,对此小编给大家总结了相关内容,文中的内容简单清晰,易于学习与理解,具有一定的参考学习价值,希望能对大家有所帮助,接下来就跟随小编一起学习一下“...
关于“Java SpringMVC后端开发支付统一下单API如何操作”的知识点有一些人不是很理解,对此小编给大家总结了相关内容,文中的内容简单清晰,易于学习与理解,具有一定的参考学习价值,希望能对大家有所帮助,接下来就跟随小编一起学习一下“Java SpringMVC后端开发支付统一下单API如何操作”吧。


 


小程序支付很常用,其实都是走的微信统一下单接口,因为经常使用,所以这里把自己写的小程序后端支付controller记录下来,文档参考微信支付官方文档,地址:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_10& index=1,大家尽量配合微信的官方文档一起看代码,其实很简单的,无非是一个下单接口和一个支付回调的接口,需要注意的是这里需要导微信商户品台配置Native支付回调的接口地址。

配置方法

登录商户平台 --> 产品中心--> 开发配置--> 支付配置--> Native支付回调链接,可参考下图配置。

PaymentController代码

import java.io.BufferedOutputStream;
    
import java.io.BufferedReader;
    
import java.io.InputStreamReader;
    
import java.util.HashMap;
    
import java.util.Map;
    
import java.util.Random;
    

import javax.annotation.Resource;
    
import javax.servlet.http.HttpServletRequest;
    
import javax.servlet.http.HttpServletResponse;
    

import org.springframework.util.StringUtils;
    
import org.springframework.web.bind.annotation.RequestMapping;
    
import org.springframework.web.bind.annotation.RequestMethod;
    
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/payment")
public class PaymentController {
    

    @Resource
    private IPaymentService paymentService;


    @RequestMapping(value = "/wxpay", method = RequestMethod.POST)
    public ResultInfo toPay(HttpServletRequest request) {

        try {
    
            
            String ordercode = "";
    // 商户订单号,自己的订单ID,自己获取
            String openid = "";
    // 用户的openID,自己获取

            // 生成的随机字符串
            String nonce_str = getRandomStringByLength(32);
    
            // 商品名称
            String body = "小程序积分充值";
    
            // 获取客户端的ip地址
            String spbill_create_ip = getIpAddr(request);
    

            // 组装参数,用户生成统一下单接口的签名
            MapString, String>
     packageParams = new HashMap>
    ();
    
            packageParams.put("appid", ICConsants.appid);
    
            packageParams.put("mch_id", ICConsants.mch_id);
    
            packageParams.put("nonce_str", nonce_str);
    
            packageParams.put("body", body);
    
            packageParams.put("out_trade_no", ordercode);
    // 商户订单号,自己的订单ID
            packageParams.put("total_fee", 100   "");
    // 支付金额,这边需要转成字符串类型,否则后面的签名会失败
            packageParams.put("spbill_create_ip", spbill_create_ip);
    
            packageParams.put("notify_url", ICConsants.notify_url);
    // 支付成功后的回调地址
            packageParams.put("trade_type", ICConsants.TRADETYPE);
    // 支付方式
            packageParams.put("openid", openid);
    // 用户的openID,自己获取

            String prestr = PayUtil.createLinkString(packageParams);
     // 把数组所有元素,按照“参数=参数值”的模式用“&
    ”字符拼接成字符串

            // MD5运算生成签名,这里是第一次签名,用于调用统一下单接口
            String mysign = PayUtil.sign(prestr, ICConsants.key, "utf-8").toUpperCase();
    

            // 拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去
            String xml = "xml>
    "   "appid>
    "   ICConsants.appid   "/appid>
    "   "body>
    ![CDATA["   body   "]]>
    /body>
    "
                      "mch_id>
    "   ICConsants.mch_id   "/mch_id>
    "   "nonce_str>
    "   nonce_str   "/nonce_str>
    "
                      "notify_url>
    "   ICConsants.notify_url   "/notify_url>
    "   "openid>
    "   openid
                      "/openid>
    "   "out_trade_no>
    "   ordercode   "/out_trade_no>
    "
                      "spbill_create_ip>
    "   spbill_create_ip   "/spbill_create_ip>
    "   "total_fee>
    "
                      100   "/total_fee>
    "// 支付的金额,单位:分
                      "trade_type>
    "   ICConsants.TRADETYPE   "/trade_type>
    "   "sign>
    "   mysign   "/sign>
    "
                      "/xml>
    ";
    

            // 调用统一下单接口,并接受返回的结果
            String result = PayUtil.httpRequest(ICConsants.pay_url, "POST", xml);
    

            // 将解析结果存储在HashMap中
            MapString, String>
     map = PayUtil.doXMLParse(result);
    

            String return_code = (String) map.get("return_code");
    // 返回状态码
            String result_code = (String) map.get("result_code");
    // 返回状态码

            MapString, Object>
     response = new HashMapString, Object>
    ();
    // 返回给小程序端需要的参数
            if ("SUCCESS".equals(return_code) &
    &
 return_code.equals(result_code)) {
    
                String prepay_id = (String) map.get("prepay_id");
    // 返回的预付单信息
                response.put("nonceStr", nonce_str);
    
                response.put("package", "prepay_id="   prepay_id);
    
                Long timeStamp = System.currentTimeMillis() / 1000;
    
                response.put("timeStamp", timeStamp   "");
    // 这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
                // 拼接签名需要的参数
                String stringSignTemp = "appId="   ICConsants.appid   "&
    nonceStr="   nonce_str   "&
    package=prepay_id="
                          prepay_id   "&
    signType=MD5&
    timeStamp="   timeStamp;
    
                // 再次签名,这个签名用于小程序端调用wx.requesetPayment方法
                String paySign = PayUtil.sign(stringSignTemp, ICConsants.key, "utf-8").toUpperCase();
    

                response.put("paySign", paySign);
    
                response.put("signType", ICConsants.SIGNTYPE);

            }
    

            response.put("appid", ICConsants.appid);
    

            ResultInfo resultInfo = new ResultInfo();
    
            resultInfo.setBody(response);
    
            return resultInfo;


        }
 catch (Exception e) {
    
            e.printStackTrace();
    
            ResultInfo resultInfo = new ResultInfo();
    
            resultInfo.getHead().setErrorCode(ICConsants.ERRORCODE_10001);
    
            resultInfo.getHead().setMessage("订单创建失败:"   e.getMessage());
    
            return resultInfo;

        }

    }


    // 这里是支付回调接口,微信支付成功后会自动调用
    @RequestMapping(value = "/wxnotify", method = RequestMethod.POST)
    public void toNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {

        try {
    
            BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
    
            String line = null;
    
            StringBuilder sb = new StringBuilder();

            while ((line = br.readLine()) != null) {
    
                sb.append(line);

            }
    
            br.close();
    
            // sb为微信返回的xml
            String notityXml = sb.toString();
    
            String resXml = "";
    

            MapString, String>
     map = PayUtil.doXMLParse(notityXml);
    
            String returnCode = (String) map.get("return_code");

            if ("SUCCESS".equals(returnCode)) {
    
                // 验证签名是否正确
                MapString, String>
     validParams = PayUtil.paraFilter(map);
     // 回调验签时需要去除sign和空值参数
                String prestr = PayUtil.createLinkString(validParams);

                // 根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等
                if (PayUtil.verify(prestr, (String) map.get("sign"), ICConsants.key, "utf-8")) {
    

                    //TODO
                    /** 此处添加自己的业务逻辑代码start **/
                    // 注意要判断微信支付重复回调,支付成功后微信会重复的进行回调
                    String out_trade_no = map.get("out_trade_no").toString();
    
                    paymentService.findIntegralModelByCode(out_trade_no);
    
                    
                    /** 此处添加自己的业务逻辑代码end **/

                    // 通知微信服务器已经支付成功
                    resXml = "xml>
    "   "return_code>
    ![CDATA[SUCCESS]]>
    /return_code>
    "
                              "return_msg>
    ![CDATA[OK]]>
    /return_msg>
    "   "/xml>
     ";

                    
                }
 else {
    
                    resXml = "xml>
    "   "return_code>
    ![CDATA[FAIL]]>
    /return_code>
    "
                              "return_msg>
    ![CDATA[签名识别]]>
    /return_msg>
    "   "/xml>
     ";

                }

            }
 else {
    
                resXml = "xml>
    "   "return_code>
    ![CDATA[FAIL]]>
    /return_code>
    "
                          "return_msg>
    ![CDATA[报文为空]]>
    /return_msg>
    "   "/xml>
     ";

            }
    

            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
    
            out.write(resXml.getBytes());
    
            out.flush();
    
            out.close();

        }
 catch (Exception e) {
    
            e.printStackTrace();
    
            String resXml = "xml>
    "   "return_code>
    ![CDATA[FAIL]]>
    /return_code>
    "
                      "return_msg>
    ![CDATA[解析异常]]>
    /return_msg>
    "   "/xml>
     ";
    
            BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
    
            out.write(resXml.getBytes());
    
            out.flush();
    
            out.close();

        }

    }


    // 获取随机字符串
    private String getRandomStringByLength(int length) {
    
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
    
        Random random = new Random();
    
        StringBuffer sb = new StringBuffer();
    
        for (int i = 0;
     i  length;
 i  ) {
    
            int number = random.nextInt(base.length());
    
            sb.append(base.charAt(number));

        }
    
        return sb.toString();

    }


    // 获取IP
    private String getIpAddr(HttpServletRequest request) {
    
        String ip = request.getHeader("X-Forwarded-For");
    
        if (!StringUtils.isEmpty(ip) &
    &
 !"unKnown".equalsIgnoreCase(ip)) {
    
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = ip.indexOf(",");

            if (index != -1) {
    
                return ip.substring(0, index);

            }
 else {
    
                return ip;

            }

        }
    
        ip = request.getHeader("X-Real-IP");
    
        if (!StringUtils.isEmpty(ip) &
    &
 !"unKnown".equalsIgnoreCase(ip)) {
    
            return ip;

        }
    
        return request.getRemoteAddr();

    }

}
    

上面用的 ResultInfo 对象是我自定义的返回类,和小程序端匹配的 JSON 格式对象,大家可以根据自己的需求创建自己的类。我这里就不贴出我的返回类了。


PayUtil代码

import java.io.BufferedReader;
    
import java.io.ByteArrayInputStream;
    
import java.io.IOException;
    
import java.io.InputStream;
    
import java.io.InputStreamReader;
    
import java.io.OutputStream;
    
import java.io.UnsupportedEncodingException;
    
import java.net.HttpURLConnection;
    
import java.net.URL;
    
import java.util.ArrayList;
    
import java.util.Collections;
    
import java.util.HashMap;
    
import java.util.Iterator;
    
import java.util.List;
    
import java.util.Map;
    

import org.apache.commons.codec.digest.DigestUtils;
    
import org.jdom.Document;
    
import org.jdom.Element;
    
import org.jdom.input.SAXBuilder;


public class PayUtil {

    /**
     * 签名字符串
     *
     * @param text          需要签名的字符串
     * @param key           密钥
     * @param input_charset 编码格式
     * @return 签名结果
     */
    public static String sign(String text, String key, String input_charset) {
    
        text = text   "&
    key="   key;
    
        return DigestUtils.md5Hex(getContentBytes(text, input_charset));

    }


    /**
     * 签名字符串
     *
     * @param text          需要签名的字符串
     * @param sign          签名结果
     * @param key           密钥
     * @param input_charset 编码格式
     * @return 签名结果
     */
    public static boolean verify(String text, String sign, String key, String input_charset) {
    
        text = text   "&
    key="   key;
    
        // text = text   key;
    
        String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset)).toUpperCase();
    
        ;

        if (mysign.equals(sign)) {
    
            return true;

        }
 else {
    
            return false;

        }

    }


    /**
     * @param content
     * @param charset
     * @return
     * @throws                              java.security.SignatureException
     * @throws UnsupportedEncodingException
     */
    public static byte[] getContentBytes(String content, String charset) {

        if (charset == null || "".equals(charset)) {
    
            return content.getBytes();

        }

        try {
    
            return content.getBytes(charset);

        }
 catch (UnsupportedEncodingException e) {
    
            throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:"   charset);

        }

    }


    private static boolean isValidChar(char ch) {
    
        if ((ch >
    = ´0´ &
    &
     ch = ´9´) || (ch >
    = ´A´ &
    &
     ch = ´Z´) || (ch >
    = ´a´ &
    &
     ch = ´z´))
            return true;
    
        if ((ch >
    = 0x4e00 &
    &
     ch = 0x7fff) || (ch >
    = 0x8000 &
    &
     ch = 0x952f))
            return true;
    // 简体中文汉字编码
        return false;

    }
    

    /**
     * 除去数组中的空值和签名参数
     *
     * @param sArray 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    public static MapString, String>
     paraFilter(MapString, String>
 sArray) {
    
        MapString, String>
     result = new HashMapString, String>
    ();

        if (sArray == null || sArray.size() = 0) {
    
            return result;

        }

        for (String key : sArray.keySet()) {
    
            String value = sArray.get(key);

            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
                    || key.equalsIgnoreCase("sign_type")) {
    
                continue;

            }
    
            result.put(key, value);

        }
    
        return result;

    }
    

    /**
     * 把数组所有元素排序,并按照“参数=参数值”的模式用“&
    ”字符拼接成字符串
     *
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(MapString, String>
 params) {
    
        ListString>
     keys = new ArrayList>
    (params.keySet());
    
        Collections.sort(keys);
    
        String prestr = "";
    
        for (int i = 0;
     i  keys.size();
 i  ) {
    
            String key = keys.get(i);
    
            String value = params.get(key);

            if (i == keys.size() - 1) {
    // 拼接时,不包括最后一个&
    字符
                prestr = prestr   key   "="   value;

            }
 else {
    
                prestr = prestr   key   "="   value   "&
    ";

            }

        }
    
        return prestr;

    }


    /**
     * @param requestUrl    请求地址
     * @param requestMethod 请求方法
     * @param outputStr     参数
     */
    public static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
    
        // 创建SSLContext
        StringBuffer buffer = null;

        try {
    
            URL url = new URL(requestUrl);
    
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    
            conn.setRequestMethod(requestMethod);
    
            conn.setDoOutput(true);
    
            conn.setDoInput(true);
    
            conn.connect();

            // 往服务器端写内容
            if (null != outputStr) {
    
                OutputStream os = conn.getOutputStream();
    
                os.write(outputStr.getBytes("utf-8"));
    
                os.close();

            }
    
            // 读取服务器端返回的内容
            InputStream is = conn.getInputStream();
    
            InputStreamReader isr = new InputStreamReader(is, "utf-8");
    
            BufferedReader br = new BufferedReader(isr);
    
            buffer = new StringBuffer();
    
            String line = null;

            while ((line = br.readLine()) != null) {
    
                buffer.append(line);

            }
    
            br.close();

        }
 catch (Exception e) {
    
            e.printStackTrace();

        }
    
        return buffer.toString();

    }


    public static String urlEncodeUTF8(String source) {
    
        String result = source;

        try {
    
            result = java.net.URLEncoder.encode(source, "UTF-8");

        }
 catch (UnsupportedEncodingException e) {
    
            // TODO Auto-generated catch block
            e.printStackTrace();

        }
    
        return result;

    }
    

    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
     *
     * @param strxml
     * @return
     * @throws             org.jdom2.JDOMException
     * @throws IOException
     */
    public static MapString, String>
 doXMLParse(String strxml) throws Exception {

        if (null == strxml || "".equals(strxml)) {
    
            return null;

        }
    

        MapString, String>
     m = new HashMapString, String>
    ();
    
        InputStream in = String2Inputstream(strxml);
    
        SAXBuilder builder = new SAXBuilder();
    
        Document doc = builder.build(in);
    
        Element root = doc.getRootElement();
    
        List list = root.getChildren();
    
        Iterator it = list.iterator();

        while (it.hasNext()) {
    
            Element e = (Element) it.next();
    
            String k = e.getName();
    
            String v = "";
    
            List children = e.getChildren();

            if (children.isEmpty()) {
    
                v = e.getTextNormalize();

            }
 else {
    
                v = getChildrenText(children);

            }
    

            m.put(k, v);

        }
    

        // 关闭流
        in.close();
    

        return m;

    }


    /**
     * 获取子结点的xml
     *
     * @param children
     * @return String
     */
    public static String getChildrenText(List children) {
    
        StringBuffer sb = new StringBuffer();

        if (!children.isEmpty()) {
    
            Iterator it = children.iterator();

            while (it.hasNext()) {
    
                Element e = (Element) it.next();
    
                String name = e.getName();
    
                String value = e.getTextNormalize();
    
                List list = e.getChildren();
    
                sb.append(""   name   ">
    ");

                if (!list.isEmpty()) {
    
                    sb.append(getChildrenText(list));

                }
    
                sb.append(value);
    
                sb.append("/"   name   ">
    ");

            }

        }
    

        return sb.toString();

    }


    public static InputStream String2Inputstream(String str) {
    
        return new ByteArrayInputStream(str.getBytes());

    }

}


以及常亮内ICConsants

public class ICConsants {
    
    
    //小程序appid
    public static final String appid = "";
    
    //微信支付的商户id
    public static final String mch_id = "";
    
    //微信支付的商户密钥
    public static final String key = "";
    
    //支付成功后的服务器回调url,这里填PayController里的回调函数地址
    public static final String notify_url = "";
    
    //签名方式,固定值
    public static final String SIGNTYPE = "MD5";
    
    //交易类型,小程序支付的固定值为JSAPI
    public static final String TRADETYPE = "JSAPI";
    
    //微信统一下单接口地址
    public static final String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    
}
    


这里说明一下,微信支付的商户密钥是在商户平台 --> 账户中心 --> API安全--> API密钥 --> 设置API密钥处设置,设置一个32位的字符。


到此这篇关于“Java SpringMVC后端开发支付统一下单API如何操作”的文章就介绍到这了,感谢各位的阅读,更多相关Java SpringMVC后端开发支付统一下单API如何操作内容,欢迎关注网络资讯频道,小编将为大家输出更多高质量的实用文章!

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!


若转载请注明出处: Java SpringMVC后端开发支付统一下单API如何操作
本文地址: https://pptw.com/jishu/655012.html
用JavaScript怎样写简易的选项卡切换功能 自定义微信小程序修改checkbox如何实现?

游客 回复需填写必要信息