微信小程序加入微信支付功能

事前准备

1.准备必要的参数

  1. appid:不多说,懂的自然动
  2. session_key:会话密钥
  3. 商户id:直接找公司的要
  4. 商户API密钥:直接要(需要安装证书,和财付通插件),登录商户平台一目了然,不清楚下边有流程

微信小程序加入微信支付功能_第1张图片
微信小程序加入微信支付功能_第2张图片
微信小程序加入微信支付功能_第3张图片
商户Key获取方法
6.

2.必要的配置

  1. 回调地址必须外网可访问。可以使用内网穿透解决,(下同)
  2. 授权地址即你支付接口的域名。在商户平台支付块设置一下。

3.开发过程

  1. 工具类
package com.loran.lobster.api.comment.wxpay;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.*;


public class WXPayUtil {

    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

    private static final Random RANDOM = new SecureRandom();

    /**
     * 字符流转字符串
     * @param in
     * @return
     */
    public static String InputStream2String(InputStream in,String charsetName) {
        InputStreamReader reader = null;
        try {
            reader = new InputStreamReader(in,charsetName);
        } catch (UnsupportedEncodingException e1) {
            e1.printStackTrace();
        }
        BufferedReader br = new BufferedReader(reader);
        StringBuilder sb = new StringBuilder();
        String line = "";
        try {
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map xmlToMap(String strXML) throws Exception {
        try {
            Map data = new HashMap();
            DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }

    }

    /**
     * 将Map转换为XML格式的字符串
     *
     * @param data Map类型数据
     * @return XML格式的字符串
     * @throws Exception
     */
    public static String mapToXml(Map data) throws Exception {
        org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key: data.keySet()) {
            String value = data.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
        try {
            writer.close();
        }
        catch (Exception ex) {
        }
        return output;
    }


    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key API密钥
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map data, String key) throws Exception {
        return generateSignedXml(data, key, "MD5");
    }

    /**
     * 生成带有 sign 的 XML 格式字符串
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名类型
     * @return 含有sign字段的XML
     */
    public static String generateSignedXml(final Map data, String key, String signType) throws Exception {
        String sign = generateSignature(data, key, signType);
        data.put("sign", sign);
        return mapToXml(data);
    }


    /**
     * 判断签名是否正确
     *
     * @param xmlStr XML格式数据
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
        Map data = xmlToMap(xmlStr);
        if (!data.containsKey("sign") ) {
            return false;
        }
        String sign = data.get("sign");
        return generateSignature(data, key).equals(sign);
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map data, String key) throws Exception {
        return isSignatureValid(data, key, "MD5");
    }

    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。
     *
     * @param data Map类型数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map data, String key, String signType) throws Exception {
        if (!data.containsKey("sign") ) {
            return false;
        }
        String sign = data.get("sign");
        return generateSignature(data, key, signType).equals(sign);
    }

    /**
     * 生成签名
     *
     * @param data 待签名数据
     * @param key API密钥
     * @return 签名
     */
    public static String generateSignature(final Map data, String key) throws Exception {
        return generateSignature(data, key, "MD5");
    }

    /**
     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
     *
     * @param data 待签名数据
     * @param key API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map data, String key, String signType) throws Exception {
        Set keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals("sign")) {
                continue;
            }
            if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).trim()).append("&");
        }
        sb.append("key=").append(key);
        return MD5(sb.toString()).toUpperCase();
    }


    /**
     * 获取随机字符串 Nonce Str
     *
     * @return String 随机字符串
     */
    public static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }


    /**
     * 生成 MD5
     *
     * @param data 待处理数据
     * @return MD5结果
     */
    public static String MD5(String data) throws Exception {
        java.security.MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] array = md.digest(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 生成 HMACSHA256
     * @param data 待处理数据
     * @param key 密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }

    /**
     * 日志
     * @return
     */
    public static Logger getLogger() {
        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
        return logger;
    }

    /**
     * 获取当前时间戳,单位秒
     * @return
     */
    public static long getCurrentTimestamp() {
        return System.currentTimeMillis()/1000;
    }

    /**
     * 获取当前时间戳,单位毫秒
     * @return
     */
    public static long getCurrentTimestampMs() {
        return System.currentTimeMillis();
    }

}

package com.loran.lobster.api.comment.wxpay;

import org.w3c.dom.Document;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/**
 * 2018/7/3
 */
public final class WXPayXmlUtil {
    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);

        return documentBuilderFactory.newDocumentBuilder();
    }

    public static Document newDocument() throws ParserConfigurationException {
        return newDocumentBuilder().newDocument();
    }
}

  1. controller
package com.loran.lobster.api.controller;

import com.loran.lobster.api.comment.HttpRequest;
import com.loran.lobster.api.comment.UniqueOrderGenerate;
import com.loran.lobster.api.comment.wxpay.WXPayUtil;
import com.loran.lobster.api.entity.Address;
import com.loran.lobster.api.service.AddressService;
import com.loran.lobster.api.service.OrderGoodsService;
import com.loran.lobster.api.service.OrderService;
import com.loran.lobster.api.service.UserinfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("pay")
public class WXPayController {

    @Autowired
    private AddressService addressService;
    @Autowired
    private OrderGoodsService orderGoodsService;
    @Autowired
    private OrderService orderService;
    @Autowired
    private UserinfoService userinfoService;
    @Value("${vendor.wx.pay.mch_id}")
    private String mch_id;
    @Value("${vendor.wx.pay.key}")
    private String MerchantKey;
    @Value("${vendor.wx.config.app_id}")
    private String app_id;
    /**
     * @Description 微信浏览器内微信支付/公众号支付(JSAPI)
     * @param request
     * @return Map
     */
    @GetMapping(value="/ordersPay")
    public Map orders(HttpServletRequest request, String openid,Integer addressid) {
        Map result = new HashMap<>();
        try {
            //拼接统一下单地址参数
            Map paraMap = new HashMap();
            //获取请求ip地址
            String ip = request.getHeader("x-forwarded-for");
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ip = request.getHeader("Proxy-Client-IP");
            }
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
                ip = request.getRemoteAddr();
            }
            if(ip.indexOf(",")!=-1){
                String[] ips = ip.split(",");
                ip = ips[0].trim();
            }
            Address address = addressService.findAddress(addressid).get(0);
            String ShippingAddress = address.getAddressReceiving()+address.getAddressHouseNumber();
            paraMap.put("appid", app_id);
            paraMap.put("body", "飞象鲜生-订单结算");
            paraMap.put("attach",ShippingAddress );
            paraMap.put("mch_id", "1532416151");
            paraMap.put("nonce_str", WXPayUtil.generateNonceStr());
            paraMap.put("openid", openid);
            paraMap.put("out_trade_no", UniqueOrderGenerate.getOrderIdByTime());//订单号
            paraMap.put("spbill_create_ip", ip);
            paraMap.put("total_fee","1");
            paraMap.put("trade_type", "JSAPI");
            paraMap.put("notify_url","http://codingqicf.natapp1.cc/pay/payCallback");// 此路径是微信服务器调用支付结果通知路径随意写
            String sign = WXPayUtil.generateSignature(paraMap, MerchantKey);
            paraMap.put("sign", sign);
            String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
            // 统一下单 https://api.mch.weixin.qq.com/pay/unifiedorder
            String unifiedorder_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
            String xmlStr = HttpRequest.sendPost(unifiedorder_url, xml);//发送post请求"统一下单接口"返回预支付id:prepay_id


            // ******************************************
            //
            //  前端调起微信支付必要参数
            //
            // ******************************************
            String prepay_id = "";//预支付id
            if (xmlStr.indexOf("SUCCESS") != -1) {
                Map map = WXPayUtil.xmlToMap(xmlStr);
                prepay_id = (String) map.get("prepay_id");
            }
            String packages = "prepay_id=" + prepay_id;
            Map wxPayMap = new HashMap();
            wxPayMap.put("appId", app_id);
            wxPayMap.put("timeStamp", String.valueOf(WXPayUtil.getCurrentTimestamp()));
            wxPayMap.put("nonceStr", WXPayUtil.generateNonceStr());
            wxPayMap.put("package", packages);
            wxPayMap.put("signType", "MD5");
            String paySign = WXPayUtil.generateSignature(wxPayMap,MerchantKey);
            // ******************************************
            //
            //  返回给前端调起微信支付的必要参数
            //
            // ******************************************
            result.put("prepay_id", prepay_id);
            result.put("sign", paySign);
            result.putAll(wxPayMap);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 回调方法,必须是无参数方法
     * @param request  接收支付参数
     * @param response  告诉微信结果
     * @return
     */
    @PostMapping(value = "/payCallback")
    public String callBack(HttpServletRequest request, HttpServletResponse response){
        //System.out.println("微信支付成功,微信发送的callback信息,请注意修改订单信息");
        InputStream is = null;
        String xmlBack="";
        try {
            is = request.getInputStream();//获取请求的流信息(这里是微信发的xml格式所有只能使用流来读)
            String xml = WXPayUtil.InputStream2String(is, "UTF-8");
            Map notifyMap = WXPayUtil.xmlToMap(xml);//将微信发的xml转map
            System.out.println(notifyMap);
            if(notifyMap.get("return_code").equals("SUCCESS")){
                if(notifyMap.get("result_code").equals("SUCCESS")){
                    String out_trade_no = notifyMap.get("out_trade_no");//订单号
                    if (out_trade_no == null) {
                        System.err.println("微信支付回调失败订单号: {}"+notifyMap);
                        xmlBack = "" + "" + "" + " ";
                        return xmlBack;
                    }
                    /*以下是自己的业务处理------仅做参考
                     *    添加 商品订单表数据。清空购物车。 添加订单信息
                     */





                    xmlBack = "" + "" + "" + " ";
                    return xmlBack;
                }else{
                    xmlBack = "" + "" + "" + " ";
                    return xmlBack;
                }
            }


            //告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可
            response.getWriter().write("");
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("微信支付回调通知失败");
            String result = "" + "" + "" + " ";
            return result;
        }
        return null;
    }

}

  1. 小程序调用。
  /**发起支付 */
  navigateToOrder: function() {
    var that = this;
    var userinfo = wx.getStorageSync("userinfo");
    /**
     * 调用预支付接口
     */
    wx.request({
      url: app.globalData.server_root + "pay/ordersPay",
      data: {
        openid: userinfo.userOpenid,
        addressid: that.data.selectId
      },
      success: function(res) {
        console.log(res.data)
        //调起支付窗口,参数为返回回来的签名
        wx.requestPayment({
          timeStamp: res.data.timeStamp,
          nonceStr: res.data.nonceStr,
          package: res.data.package,
          paySign: res.data.sign,
          signType: res.data.signType,
          success(res) {
            console.log(res.data)
          }
        })
      }
    })

  }
  1. 支付成功调用回调地址设置的接口方法进行业务处理。至此结束

你可能感兴趣的:(工作类)