目录
一、准备工作
请按 【SpringBoot深入浅出系列】SpringBoot之实现微信支付(开发前准备) 做好开发前准备工作。
二、编码实现
1.项目说明
新建 Spring Initializr 项目 wxpay,项目下新建 config、controller、service、utils 类,实现微信支付。
项目目录结构:
2.创建 Spring Initializr 项目 wxpay
(1)添加依赖
添加 fastjson、wxpay-sdk、httpclient 依赖,pom.xml 文件内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.chaoyue</groupId>
<artifactId>wxpay</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>wxpay</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
(2)添加配置
application.yml 文件中添加如下配置:
server:
port: 8081
servlet:
# 应用的访问路径
context-path: /
wxpay:
appid: #微信公众号或者小程序等的 appid
secret: # 微信公众号或者小程序等的密钥
mchid: # 微信支付商户号
mchkey: # 微信支付商户密钥
noticeUrl: http://***************** #回调地址//必须是外网地址,带域名的
(3)新建配置类 WxpayConfig
@Component
@Configuration
@ConfigurationProperties(prefix = "wxpay")
public class WxpayConfig {
private static String appid; //微信公众号或者小程序等的 appid
private static String secret; //微信公众号或者小程序等的密钥
private static String mchid; //微信支付商户号
private static String mchkey; //微信支付商户密钥
private static String noticeUrl; //回调地址
public WxpayConfig() {
}
public static String getAppid() {
return appid;
}
public void setAppid(String appid) {
WxpayConfig.appid = appid;
}
public static String getSecret() {
return secret;
}
public void setSecret(String secret) {
WxpayConfig.secret = secret;
}
public static String getMchid() {
return mchid;
}
public void setMchid(String mchid) {
WxpayConfig.mchid = mchid;
}
public static String getMchkey() {
return mchkey;
}
public void setMchkey(String mchkey) {
WxpayConfig.mchkey = mchkey;
}
public static String getNoticeUrl() {
return noticeUrl;
}
public void setNoticeUrl(String noticeUrl) {
WxpayConfig.noticeUrl = noticeUrl;
}
}
(4)新建服务接口类 WxService
public interface WxService {
String payBack(String resXml);
Map doUnifiedOrder(HttpServletRequest request,int price,String openId) throws Exception;
}
(5)新建接口实现类 WxServiceImpl
@Service
public class WxServiceImpl implements WxService {
public static final String NOTIFY_URL = WxpayConfig.getNoticeUrl(); //必须是外网地址,带域名的
public static final String TRADE_TYPE_APP = "JSAPI";//类型 APP、JSAPI......
@Override
public String payBack(String resXml) {
WxConfigUtil config = null;
try {
config = new WxConfigUtil();
} catch (Exception e) {
e.printStackTrace();
}
WXPay wxpay = new WXPay(config);
String xmlBack = "";
Map<String, String> notifyMap = null;
try {
notifyMap = WXPayUtil.xmlToMap(resXml); // 调用官方SDK转换成map类型数据
if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {//验证签名是否有效,有效则进一步处理
String return_code = notifyMap.get("return_code");//状态
String out_trade_no = notifyMap.get("out_trade_no");//商户订单号
if (return_code.equals("SUCCESS")) {
if (out_trade_no != null) {
// 注意特殊情况:订单已经退款,但收到了支付结果成功的通知,不应把商户的订单状态从退款改成支付成功
// 注意特殊情况:微信服务端同样的通知可能会多次发送给商户系统,所以数据持久化之前需要检查是否已经处理过了,处理了直接返回成功标志
//业务数据持久化
System.err.println("微信手机支付回调成功订单号:{}");
System.err.println(out_trade_no);
xmlBack = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
System.err.println("微信手机支付回调失败订单号:{}");
System.err.println(out_trade_no);
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
}
return xmlBack;
} else {
// 签名错误,如果数据里没有sign字段,也认为是签名错误
//失败的数据要不要存储?
System.err.println("手机支付回调通知签名错误");
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
return xmlBack;
}
} catch (Exception e) {
System.err.println("手机支付回调通知签名错误");
System.err.println(e);
xmlBack = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
return xmlBack;
}
@Override
public Map doUnifiedOrder(HttpServletRequest request, int price, String openId) throws Exception {
try {
WxConfigUtil config = new WxConfigUtil();
WXPay wxpay = new WXPay(config);
Map<String, String> data = new HashMap<>();
//生成商户订单号,不可重复
data.put("appid", config.getAppID());
data.put("mch_id", config.getMchID());
data.put("nonce_str", WXPayUtil.generateNonceStr());
String body = "订单支付";
data.put("body", body);
data.put("out_trade_no", System.currentTimeMillis()+ "");
data.put("total_fee", String.valueOf(price));
data.put("openid", openId);
//自己的服务器IP地址
data.put("spbill_create_ip", request.getRemoteAddr());
//异步通知地址(请注意必须是外网)
data.put("notify_url", WxpayConfig.getNoticeUrl());
//交易类型
data.put("trade_type", TRADE_TYPE_APP);
//附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据
data.put("attach", "");
data.put("sign", WXPayUtil.generateSignature(data, config.getKey(),
WXPayConstants.SignType.MD5));
//使用官方API请求预付订单
Map<String, String> response = wxpay.unifiedOrder(data);
System.out.println(response);
if ("SUCCESS".equals(response.get("return_code"))) {//主要返回以下5个参数
Map<String, String> param = new HashMap<>();
param.put("appId", config.getAppID());
param.put("nonceStr", WXPayUtil.generateNonceStr());
param.put("package", "prepay_id="+ response.get("prepay_id"));
param.put("signType", "MD5");
param.put("timeStamp", System.currentTimeMillis() / 1000 + "");
param.put("sign", WXPayUtil.generateSignature(param, config.getKey(),
WXPayConstants.SignType.MD5));
param.put("partnerId", response.get("mch_id"));
param.put("prepayId", response.get("prepay_id"));
System.out.println(param);
return param;
}
} catch (Exception e) {
e.printStackTrace();
throw new Exception("下单失败");
}
throw new Exception("下单失败");
}
}
(6)新建微信接口类 WxConfigUtil
public class WxConfigUtil implements WXPayConfig {
private byte[] certData;
public static final String APP_ID = WxpayConfig.getAppid();
public static final String KEY = WxpayConfig.getMchkey();
public static final String MCH_ID = WxpayConfig.getMchid();
public WxConfigUtil() throws Exception {
String certPath = ClassUtils.getDefaultClassLoader().getResource("").getPath()+"/weixin/apiclient_cert.p12";//从微信商户平台下载的安全证书存放的路径
File file = new File(certPath);
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
}
@Override
public String getAppID() {
return WxpayConfig.getAppid();
}
//parnerid,商户号
@Override
public String getMchID() {
return WxpayConfig.getMchid();
}
@Override
public String getKey() {
return WxpayConfig.getMchkey();
}
@Override
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
public int getHttpConnectTimeoutMs() {
return 8000;
}
@Override
public int getHttpReadTimeoutMs() {
return 10000;
}
}
3.前端编码
前端使用 uniapp 开发,后续展开。