话不多说上效果图
|
|
微信小程序支付
1、需要企业或者个体工商户申请,个人主体小程序不支持开通微信支付
2、有商户号以及商户key
想具体了解微信支付准备工作的可以去了解这篇博客
那么开始进入正题
统一下单需要的参数
想要具体了解可以看官方文档
Java后端代码
需要引入的依赖
如有依赖缺失可以在下方评论,一定在看到的第一时间回复
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
PayTest.java 支付类
doXMLParse静态方法是copy的一位大佬的,出处已经记不得了
先将参数都写死进行测试走一遍流程之后个人感觉会清晰的多
后续进行该动工作也很轻松
import com.example.live.utils.HttpClientUtils;
import com.example.servicebase.R;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.util.DigestUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.*;
@RestController
@CrossOrigin
@RequestMapping("/user/login")
public class PayTest {
@GetMapping("test")
public R test() throws Exception {
String appid="小程序的appid"; //小程序的appid
String body="test"; //商品描述
String mch_id="商户号"; //商户号
String nonce_str= String.valueOf(UUID.randomUUID()).replace("-","");//随机字符串
String notify_url="支付结果通知回调";//支付结果通知回调
String openid="openid用户唯一标识"; //openid用户唯一标识
String out_trade_no=String.valueOf(UUID.randomUUID()).replace("-","");//商户订单号 只是测试实际建议使用时间戳
String spbill_create_ip="192.168.0.109";//调用微信支付的人的IP 实际需要调用获取用户ip的方法 网上很容易找
int total_fee=1;//订单价格 以分为单位
String trade_type="JSAPI"; //交易类型 小程序取值为JSAPI
//拼接要加密的字符串 需要根据参数名ASCII字典序排序 末尾记得拼接商户key
String stringA="appid="+appid+"&body="+body+"&mch_id="+mch_id+"&nonce_str="+nonce_str
+"¬ify_url="+notify_url+"&openid="+openid+"&out_trade_no="+out_trade_no
+"&spbill_create_ip="+spbill_create_ip+"&total_fee="+total_fee+"&trade_type="+trade_type
+"&key=你的商户key;
//将拼接好的字符串进行MD5加密 第一次签名
String sign=DigestUtils.md5DigestAsHex(stringA.getBytes(StandardCharsets.UTF_8)).toUpperCase();
//微信只接受xml格式的
String xml = "<xml>" + "<appid>" + appid + "</appid>"
+ "<body>"+ body +"</body>"
+ "<mch_id>" + mch_id + "</mch_id>"
+ "<nonce_str>" + nonce_str + "</nonce_str>"
+ "<notify_url>" +notify_url + "</notify_url>"
+ "<openid>" + openid + "</openid>"
+ "<out_trade_no>" + out_trade_no + "</out_trade_no>"
+ "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>"
+ "<total_fee>" + total_fee + "</total_fee>"
+ "<trade_type>" + trade_type + "</trade_type>"
+ "<sign>" + sign + "</sign>"
+ "</xml>";
//发送一个post请求
String content = HttpClientUtils.postParameters("https://api.mch.weixin.qq.com/pay/unifiedorder",xml,"UTF-8");
//微信返回xml格式数据 我们将数据进行解析
Map<String, String> pay2 =doXMLParse(content);
//获取当前系统的时间戳
final long time = System.currentTimeMillis() / 1000;
//拼接需要参与签名的参数 注意参数的大小写 nonceStr需要与微信返回的值保持一致
String paySign1 = "appId=" + pay2.get("appid") + "&nonceStr=" + pay2.get("nonce_str") + "&package=prepay_id="+pay2.get("prepay_id")
+"&signType=MD5"+"&timeStamp="+time+"&key=你的商户key;
//进行二次签名
String sign2=DigestUtils.md5DigestAsHex(paySign1.getBytes(StandardCharsets.UTF_8)).toUpperCase();
//封装参数返回给小程序端
Map<String,String> pay=new HashMap<>();
pay.put("nonceStr",pay2.get("nonce_str"));
pay.put("package","prepay_id="+pay2.get("prepay_id"));
pay.put("paySign",sign2);
pay.put("signType","MD5");
pay.put("timeStamp", String.valueOf(time));
return R.ok().data("pay",pay);
}
//解析xml格式数据的工具类的静态方法
public static Map<String, String> doXMLParse(String strxml) {
Map<String, String> m = new HashMap<String, String>();
try {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if (null == strxml || "".equals(strxml)) {
return null;
}
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();
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
} catch (IOException e) {
} catch (JDOMException e) {
}
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();
}
}
HttpClientUtils.java 请求工具类
import org.apache.commons.io.IOUtils;
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.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
//http工具类
/**
* 依赖的jar包有:commons-lang-2.6.jar、httpclient-4.3.2.jar、httpcore-4.3.1.jar、commons-io-2.4.jar
* @author zhaoyb
*
*/
public class HttpClientUtils {
public static final int connTimeout=10000;
public static final int readTimeout=10000;
public static final String charset="UTF-8";
private static HttpClient client = null;
static {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(128);
cm.setDefaultMaxPerRoute(128);
client = HttpClients.custom().setConnectionManager(cm).build();
}
public static String postParameters(String url, String parameterStr,String charset) throws ConnectTimeoutException, SocketTimeoutException, Exception{
return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
}
/**
* 发送一个 Post 请求, 使用指定的字符集编码.
*
* @param url
* @param body RequestBody
* @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
* @param charset 编码
* @param connTimeout 建立链接超时时间,毫秒.
* @param readTimeout 响应超时时间,毫秒.
* @return ResponseBody, 使用指定的字符集编码.
* @throws ConnectTimeoutException 建立链接超时异常
* @throws SocketTimeoutException 响应超时
* @throws Exception
*/
public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
throws ConnectTimeoutException, SocketTimeoutException, Exception {
HttpClient client = null;
HttpPost post = new HttpPost(url);
String result = "";
try {
if (StringUtils.isNotBlank(body)) {
HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
post.setEntity(entity);
}
// 设置参数
Builder customReqConf = RequestConfig.custom();
if (connTimeout != null) {
customReqConf.setConnectTimeout(connTimeout);
}
if (readTimeout != null) {
customReqConf.setSocketTimeout(readTimeout);
}
post.setConfig(customReqConf.build());
HttpResponse res;
if (url.startsWith("https")) {
// 执行 Https 请求.
client = createSSLInsecureClient();
res = client.execute(post);
} else {
// 执行 Http 请求.
client = HttpClientUtils.client;
res = client.execute(post);
}
result = IOUtils.toString(res.getEntity().getContent(), charset);
} finally {
post.releaseConnection();
if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
((CloseableHttpClient) client).close();
}
}
return result;
}
/**
* 创建 SSL连接
* @return
* @throws GeneralSecurityException
*/
private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
@Override
public void verify(String host, SSLSocket ssl)
throws IOException {
}
@Override
public void verify(String host, X509Certificate cert)
throws SSLException {
}
@Override
public void verify(String host, String[] cns,
String[] subjectAlts) throws SSLException {
}
});
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (GeneralSecurityException e) {
throw e;
}
}
}
小程序端代码
// 点击 支付
async handleOrderPay() {
try{
if(!wx.getStorageSync("address")){
await showToast({ title: "您还未选择收货地址",icon:'none' });
}else{
//调用统一下单接口
const res=await request({url:`user/login/test`})
const res2=JSON.parse(res.data.data.content)
//拿到接口的返回值去调用微信的接口
const res3=await requestPayment(res2)
await showToast({ title: "支付成功" });
wx.navigateTo({
url: '/pages/order/index?type=2'
});
}catch(error){
await showToast({title:"支付失败"})
}
}