这是我第一次接触支付

现在随着社会的发展,很多用户都在用第三方支付

前段时间公要做微信支付和支付宝支付

对于我来说是一个挑战,虽然有微信的支付文档  https://pay.weixin.qq.com/wiki/doc/api/index.html

我阅读了一下感觉挺简单的 但还是踩了两天坑才做出来

首先我们需要看一下统一下单接口需要的参数

也许第一次接触的人不是很清楚

接下来一步一步的操作

开打 https://pay.weixin.qq.com/wiki/doc/api/index.html

选择App支付进入

打开API列表后,第一条就是 统一下单

首先看一下所需要的参数,相信大家看了请求参数就明白了

但是有一点的是 商户号mch_id 一定要写对(我和我们公司的ios小姐姐联调时就犯这个错了,她很坚信的告诉我ID是对的)

这里的签名是需要加密,需要二次加密

之前也看了一些文档代码、以及询问朋友,结果经过测试都是有误,因为一些jia包引用类不全,自己整理了一套  希望能帮到大家

/**
• Description
• Copyright ©,
• FileName: WeiXinController
• Author: QiMing
• Date: 2019/1/22 16:05
 */
 package com.mopin.morepick.api.controller.pay.wechartPay;import com.github.pagehelper.util.StringUtil;
 import com.mopin.morepick.dto.ResponseCode;
 import com.mopin.morepick.dto.ResponseMessage;
 import com.mopin.morepick.service.orders.OrdersService;
 import io.swagger.annotations.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;import java.util.Map;
@Api(tags = “微信付款”, description = “微信付款模块”)
 @ApiResponses(value = {
 @ApiResponse(code = -1, message = “未知错误”),
 @ApiResponse(code = 200, message = “数据获取成功或操作失败”),
 @ApiResponse(code = 40002, message = “参数错误”)})
 @RestController
 @RequestMapping("/buypay")
 public class WeiXinController {
/**
 * 订单
 */
@Autowired
private OrdersService ordersService;

/**
 * @param pkOrders, ip
 * @return com.mopin.morepick.dto.ResponseMessage
 * @Description 统一下单(微信)
 * @author QiMing 
 * @date 2018/9/5 17:14
 */
@RequestMapping(value = "/getcreate", method = RequestMethod.POST)
@ApiOperation(value = "统一下单")
public ResponseMessage getcreate(@RequestParam @ApiParam(value = "订单主键 ", name = "pkOrders", required = true) String pkOrders,
                                 @RequestParam @ApiParam(value = "ip ", name = "ip", required = true) String ip,
                                 @RequestParam @ApiParam(value = "用户 ", name = "pkUser", required = true) String pkUser) {

    if (StringUtil.isNotEmpty(pkOrders) && StringUtil.isNotEmpty(ip)) {
        Map<String, Object> o = ordersService.crate(pkOrders, ip, pkUser);
        if (o.size() == 7) {
            return ResponseMessage.createSuccessResult().setData(o).setMsg("获取数据成功");
        } else if (o.size() == 2) {
            return ResponseMessage.createErrorResult(ResponseCode.PARAMS_ERROR_CODE, "无此订单,或拼单尚未完成");
        }

    }
    return ResponseMessage.createErrorResult(ResponseCode.PARAMS_ERROR_CODE, "参数错误");
}


service层我就不写了 相信大家知道怎么创建
接下来是实现层

/**
• @Description 微信下单
• @param pkOrders
• @return com.mopin.morepick.model.orders.Orders
• @author QiMing
• @date 2018/9/4 14:03
 */
 @Override
 public Map<String, Object> crate(String pkOrders, String Ip, String pkUser) {
String Body = “测试”;
 String Appid = WXPayConfig.appid;
 String Mch_id = WXPayConfig.mch_id;
 String NOstr = CommonUtil.getRandomString(32);
/**
  • 这个地方我想说一下
  • 价钱可能定义的是Double类型 它可能会失去准确度
  • 因为微信支付只接受两位小数的数据
  • 比如你数据库中的值是38.88 是需要乘100的
  • 但是有的时候数据库中的值是38 当你支付时会出现报错,说你价钱格式不对 那就是double失去精准度了
  • 在文章结尾我会附上工具类

  • 大家可能会看到一些参数怎么来的 比如 personalOrder.getPrice() personalOrder.getOrdersNo();这些呢是我从数据库中提取的希望不要困扰到大家
•  */
 String price = String.valueOf(Math.round(personalOrder.getPrice()*100));
 BigDecimal Price = new BigDecimal(price);String OrderNo = personalOrder.getOrdersNo();
 String Notify_url = WXPayConfig.notify_url;
 SortedMap<String, Object> map = new TreeMap<>();
 map.put(“appid”, Appid);
 map.put(“mch_id”, Mch_id);
 map.put(“nonce_str”, NOstr);
 map.put(“body”, Body);
 String key = WXPayConfig.key;
 map.put(“attach”, “测试”);
 map.put(“out_trade_no”, OrderNo);
 map.put(“total_fee”, Price);
 map.put(“spbill_create_ip”, Ip);
 map.put(“notify_url”, Notify_url);
 map.put(“trade_type”, WXPayConfig.trade_type);String Sign = CommonUtil.signatures(map, key);//密钥
 map.put(“sign”, Sign);
 String xml = CommonUtil.getRequestXml(map);
 String result = “”;
 try {
 result = CommonUtil.post(WXPayConfig.unifiedorder, xml);
 } catch (Exception e) {
 e.getMessage();
 }
 /------------------------------------------/Map<String, String> map11 = null;
 String prepay_id = null;
 try {
 map11 = CommonUtil.doXMLParse(result);
 String return_code = map11.get(“return_code”);
 if (return_code.contains(“SUCCESS”)) {
 prepay_id = map11.get(“prepay_id”);//获取到prepay_id
 }
 } catch (JDOMException | IOException e) {
 e.printStackTrace();
 }/_____________________________________________/
Map<String, Object> map1 = new HashMap<>();
/——————————————二次——————————————————/
 long timestamp = DateUtil.tenten();
 SortedMap<String, Object> smap = new TreeMap<>();
 smap.put(“appid”, Appid);
 smap.put(“partnerid”, Mch_id);
 smap.put(“prepayid”, prepay_id);
 smap.put(“package”, “Sign=WXPay”);
 smap.put(“timestamp”, timestamp);
 smap.put(“noncestr”, NOstr);
 String sign = CommonUtil.signatures(smap, key);//密钥
 /————————————————————————————————/map1.put(“appid”, Appid);
 map1.put(“mch_id”, Mch_id);
 map1.put(“body”, Body);
 map1.put(“prepay_id”, prepay_id);
 map1.put(“nocestr”, NOstr);
 map1.put(“sign”, sign);
 map1.put(“timestamp”, timestamp);
 return map1;
 }

微信工具类之一
public class WXPayConfig {

/**
 * 开放平台对应的appid
 */
public static final String appid = ".....";

/**
 * 微信支付分配的商户号
 */
public static final String mch_id = "....";

/**
 * key
 */
public static final String key = ".....";

/**
 * 退款接口
 */
public static final String refundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";

/**
 * 回调地址
 */
public static final String notify_url = "https://。。。。";//回调接口需要用https访问通的

/**
 * 交易类型
 */
public static final String trade_type = "APP";

/**
 * 统一下单接口
 */
public static final String unifiedorder = "https://api.mch.weixin.qq.com/pay/unifiedorder";

/**
 * 查询订单接口
 */
public static final String orderquery = "https://api.mch.weixin.qq.com/pay/orderquery";

/**
 * 关闭订单 订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟。
 */
public static final String closeOrder = "https://api.mch.weixin.qq.com/pay/closeorder";

/**
 * 查询退款 如果单个支付订单部分退款次数超过20次请使用退款单号查询
 */
public static final String refundquery = "https://api.mch.weixin.qq.com/pay/refundquery";

/**
 * 下载对账单
 */
public static final String downloadbill = "https://api.mch.weixin.qq.com/pay/downloadbill";

/**
 * 下载资金对账单
 */
public static final String downloadfundflow = "https://api.mch.weixin.qq.com/pay/downloadfundflow";

/**
 * 交易保障
 */
public static final String report = "https://api.mch.weixin.qq.com/payitil/report";

/**
 * 证书地址
 */
//public static final String address = "";//本地

public static final String address = "";//linux  发布上线时一定要用你上传在linux上的地址


微信工具类之一

import org.apache.commons.lang3.StringUtils;
 import org.apache.http.Consts;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.ssl.SSLContexts;
 import org.apache.http.util.EntityUtils;
 import org.jdom2.Document;
 import org.jdom2.Element;
 import org.jdom2.JDOMException;
 import org.jdom2.input.SAXBuilder;import javax.net.ssl.SSLContext;
 import java.io.;
 import java.net.URI;
 import java.security.KeyStore;
 import java.security.MessageDigest;
 import java.text.SimpleDateFormat;
 import java.util.;public class CommonUtil {
/**
 * @Description 随机字符串生成
 * @param  length
 * @return  java.lang.String
 * @author  QiMing 
 * @date  2018/9/6 15:25
 */
public static String getRandomString(int length) { //length表示生成字符串的长度
    String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    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();
}

/**
 * @Description 请求下单xml组装
 * @param   parameters
 * @return  java.lang.String
 * @author  QiMing 
 * @date  2018/9/6 15:24
 */
public static String getRequestXml(SortedMap<String, Object> parameters) {

    StringBuffer sb = new StringBuffer();
    sb.append("<xml>");
    Set es = parameters.entrySet();
    Iterator it = es.iterator();
    while (it.hasNext()) {
        Map.Entry entry = (Map.Entry) it.next();
        String key = (String) entry.getKey();
        Object value = entry.getValue();
        if ("appid".equalsIgnoreCase(key)||"attach".equalsIgnoreCase(key)
                || "body".equalsIgnoreCase(key)||
                "mch_id".equalsIgnoreCase(key)||
                "nonce_str".equalsIgnoreCase(key)||
                "out_trade_no".equalsIgnoreCase(key)||
                "total_fee".equalsIgnoreCase(key)||
                "spbill_create_ip".equalsIgnoreCase(key)||
                "notify_url".equalsIgnoreCase(key)||
                "trade_type".equalsIgnoreCase(key)||
                "sign".equalsIgnoreCase(key)) {
            sb.append("<" + key + ">" + "<![CDATA[" + value + "]]></" + key + ">");
        } else {
            sb.append("<" + key + ">" + value + "</" + key + ">");
        }
    }
    sb.append("</xml>");
    return sb.toString();
}

/**
 * @Description 退款
 * @param   parameters
 * @return  java.lang.String
 * @author  QiMing 
 * @date  2018/9/6 15:24
 */
public static String getRefundXml(SortedMap<String, Object> parameters) {

    StringBuffer sb = new StringBuffer();
    sb.append("<xml>");
    Set es = parameters.entrySet();
    Iterator it = es.iterator();
    while (it.hasNext()) {
        Map.Entry entry = (Map.Entry) it.next();
        String key = (String) entry.getKey();
        Object value = entry.getValue();
        if ("appid".equalsIgnoreCase(key)||
            "mch_id".equalsIgnoreCase(key)||
            "nonce_str".equalsIgnoreCase(key)||
            "out_trade_no".equalsIgnoreCase(key)||
            "out_refund_no".equalsIgnoreCase(key)||
            "total_fee".equalsIgnoreCase(key)||
            "refund_fee".equalsIgnoreCase(key)||
            "sign".equalsIgnoreCase(key)) {
            sb.append("<" + key + ">" + "<![CDATA[" + value + "]]></" + key + ">");
        } else {
            sb.append("<" + key + ">" + value + "</" + key + ">");
        }
    }
    sb.append("</xml>");
    return sb.toString();
}


/**
 * 微信支付加密工具
 */
public static String signatures(SortedMap<String, Object> map, String key) {
    Set<String> keySet = map.keySet();
    String[] str = new String[map.size()];
    StringBuilder tmp = new StringBuilder();
    // 进行字典排序
    str = keySet.toArray(str);
    Arrays.sort(str);
    for (int i = 0; i < str.length; i++) {
        String t = str[i] + "=" + map.get(str[i]) + "&";
        tmp.append(t);
    }
    if (StringUtils.isNotBlank(key)) {
        tmp.append("key=" + key);
    }
    String tosend = tmp.toString();
    MessageDigest md = null;
    byte[] bytes = null;
    try {

        md = MessageDigest.getInstance("MD5");
        bytes = md.digest(tosend.getBytes("UTF-8"));
    } catch (Exception e) {
        e.printStackTrace();
    }

    String singe = byteToStr(bytes);
    return singe.toUpperCase();

}
public static String byteToStr(byte[] byteArray) {
    String strDigest = "";
    for (int i = 0; i < byteArray.length; i++) {
        strDigest += byteToHexStr(byteArray[i]);
    }
    return strDigest;
}

public static String byteToHexStr(byte bytes) {
    char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
    char[] tempArr = new char[2];
    tempArr[0] = Digit[(bytes >>> 4) & 0X0F];
    tempArr[1] = Digit[bytes & 0X0F];

    String s = new String(tempArr);
    return s;
}

/**
 * 验证回调签名
 * @return
 */
public static boolean isTenpaySign(Map<String, String> map, String apiKey) throws UnsupportedEncodingException {
    String charset = "utf-8";
    String signFromAPIResponse = map.get("sign");
    if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {
        System.out.println("API返回的数据签名数据不存在,有可能被第三方篡改!!!");
        return false;
    }
    System.out.println("服务器回包里面的签名是:" + signFromAPIResponse);
    //过滤空 设置 TreeMap
    SortedMap<String, String> packageParams = new TreeMap<>();
    for (String parameter : map.keySet()) {
        String parameterValue = map.get(parameter);
        String v = "";
        if (null != parameterValue) {
            v = parameterValue.trim();
        }
        packageParams.put(parameter, v);
    }

    StringBuffer sb = new StringBuffer();
    Set es = packageParams.entrySet();
    Iterator it = es.iterator();
    while (it.hasNext()) {
        Map.Entry entry = (Map.Entry) it.next();
        String k = (String) entry.getKey();
        String v = (String) entry.getValue();
        if (!"sign".equals(k) && null != v && !"".equals(v)) {
            sb.append(k + "=" + v + "&");
        }
    }
    sb.append("key=" + apiKey);

    //将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较

    //算出签名
    String resultSign = "";
    String tobesign = sb.toString();
    if (null == charset || "".equals(charset)) {
        resultSign = MD5Util.MD5Encode(tobesign, charset).toUpperCase();
    } else {
        resultSign = MD5Util.MD5Encode(tobesign, charset).toUpperCase();
    }
    String tenpaySign = packageParams.get("sign").toUpperCase();
    return tenpaySign.equals(resultSign);
}



/**
 * @Description 退款的请求方法
 * @param   requestUrl, requestMethod, outputStr
 * @return  java.lang.String
 * @author  QiMing 
 * @date  2018/9/6 15:23
 */
public static String httpsRequest2(String requestUrl, String requestMethod, String outputStr) throws Exception {

    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    StringBuilder res = new StringBuilder("");
    FileInputStream instream = new FileInputStream(new File(WXPayConfig.address));
    try {
        keyStore.load(instream, WXPayConfig.mch_id.toCharArray());
    } finally {
        instream.close();
    }

    // Trust own CA and all self-signed certs
    SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, WXPayConfig.mch_id.toCharArray()).build();
    // Allow TLSv1 protocol only
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
    CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
    try {

        HttpPost httpost = new HttpPost(requestUrl);
        httpost.addHeader("Connection", "keep-alive");
        httpost.addHeader("Accept", "*/*");
        httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        httpost.addHeader("Host", "api.mch.weixin.qq.com");
        httpost.addHeader("X-Requested-With", "XMLHttpRequest");
        httpost.addHeader("Cache-Control", "max-age=0");
        httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
        StringEntity entity2 = new StringEntity(outputStr, Consts.UTF_8);
        httpost.setEntity(entity2);
        System.out.println("executing request" + httpost.getRequestLine());

        CloseableHttpResponse response = httpclient.execute(httpost);

        try {
            HttpEntity entity = response.getEntity();

            System.out.println("----------------------------------------");
            System.out.println(response.getStatusLine());
            if (entity != null) {
                System.out.println("Response content length: " + entity.getContentLength());
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));
                String text = "";
                res.append(text);
                while ((text = bufferedReader.readLine()) != null) {
                    res.append(text);
                    System.out.println(text);
                }

            }
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
    } finally {
        httpclient.close();
    }
    return res.toString();

}

/**
 * @Description xml解析
 * @param   strxml
 * @return  java.util.Map
 * @author  QiMing 
 * @date  2018/9/6 15:25
 */
public static Map doXMLParse(String strxml) throws JDOMException, IOException {

    strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");

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

    Map<String,Object> m = new HashMap<>();

    InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
    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;
}


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 String post(String url, String str)throws Exception {
    // 处理请求地址
    URI uri = new URI(url);
    HttpPost post = new HttpPost(uri);
    post.setEntity(new StringEntity(str,"UTF-8"));
    CloseableHttpClient client = HttpClients.createDefault();

    // 执行请求
    HttpResponse response = client.execute(post);

    if (response.getStatusLine().getStatusCode() == 200) {
        // 处理请求结果
        StringBuffer buffer = new StringBuffer();
        InputStream in = null;
        try {
            in = response.getEntity().getContent();
            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(in));
            String line = null;
            while ((line = reader.readLine()) != null) {
                buffer.append(line);
            }

        } finally {
            // 关闭流
            if (in != null)
                in.close();
        }

        return buffer.toString();
    } else {
        return null;
    }

}


接下来这个工具类就是处理价钱的问题了

import java.math.RoundingMode;
 import java.text.DecimalFormat;
 import java.text.NumberFormat;public class DoubleUtil {
/**
 * @Title NonRounding
 * @Description 不保留四舍五入
 * @param   d
 * @return  double
 * @author  QiMing
 * @date  2019/1/3 16:45
 */
public static double NonRounding(double d){
    NumberFormat nf = NumberFormat.getNumberInstance();
    // 保留两位小数
    nf.setMaximumFractionDigits(2);
    // 如果不需要四舍五入,可以使用RoundingMode.DOWN
    nf.setRoundingMode(RoundingMode.UP);
    double dou = Double.valueOf(nf.format(d));
    return dou;
}

/**
 * @Title rounding
 * @Description 四舍五入
 * @param   d
 * @return  double
 * @author  QiMing
 * @date  2019/1/3 16:49
 */
public static double rounding(double d){

    DecimalFormat df = new DecimalFormat("#.00");
    double dou = Double.valueOf(df.format(d));
    return dou;
}


到此把数据给到Ios端即可
这是一些需要的jar包

dependency>
 org.jdom
 jdom2
 2.0.5


org.apache.httpcomponents httpclient 4.5.3 com.alipay.sdk alipay-sdk-java 3.0.52.ALL com.github.wxpay wxpay-sdk 0.0.3