微信支付App版——Java

时间:2024-01-23 20:28:57

这是我第一次接触支付

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

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

对于我来说是一个挑战,虽然有微信的支付文档  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 (C),
* 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>
<groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId>
<version>2.0.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>3.0.52.ALL</version>
</dependency>
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>




如遇到bug可联系我 我们一起清除bug让自己的知识更丰富