一、創建微服務
1、導入依賴
<!--微信支付-->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<!--httpclient支持-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
2、需要的工具類
HttpClient:http/https相關操作
public class HttpClient {
private String url;
private Map<String, String> param;
private int statusCode;
private String content;
private String xmlParam;
private boolean isHttps;
public boolean isHttps() {
return isHttps;
}
public void setHttps(boolean isHttps) {
this.isHttps = isHttps;
}
public String getXmlParam() {
return xmlParam;
}
public void setXmlParam(String xmlParam) {
this.xmlParam = xmlParam;
}
public HttpClient(String url, Map<String, String> param) {
this.url = url;
this.param = param;
}
public HttpClient(String url) {
this.url = url;
}
public void setParameter(Map<String, String> map) {
param = map;
}
public void addParameter(String key, String value) {
if (param == null)
param = new HashMap<String, String>();
param.put(key, value);
}
public void post() throws ClientProtocolException, IOException {
HttpPost http = new HttpPost(url);
setEntity(http);
execute(http);
}
public void put() throws ClientProtocolException, IOException {
HttpPut http = new HttpPut(url);
setEntity(http);
execute(http);
}
public void get() throws ClientProtocolException, IOException {
if (param != null) {
StringBuilder url = new StringBuilder(this.url);
boolean isFirst = true;
for (String key : param.keySet()) {
if (isFirst) {
url.append("?");
}else {
url.append("&");
}
url.append(key).append("=").append(param.get(key));
}
this.url = url.toString();
}
HttpGet http = new HttpGet(url);
execute(http);
}
/**
* set http post,put param
*/
private void setEntity(HttpEntityEnclosingRequestBase http) {
if (param != null) {
List<NameValuePair> nvps = new LinkedList<NameValuePair>();
for (String key : param.keySet()) {
nvps.add(new BasicNameValuePair(key, param.get(key))); // 參數
}
http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 設置參數
}
if (xmlParam != null) {
http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
}
}
private void execute(HttpUriRequest http) throws ClientProtocolException,
IOException {
CloseableHttpClient httpClient = null;
try {
if (isHttps) {
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, new TrustStrategy() {
// 信任所有
@Override
public boolean isTrusted(X509Certificate[] chain,
String authType)
throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext);
httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
.build();
} else {
httpClient = HttpClients.createDefault();
}
CloseableHttpResponse response = httpClient.execute(http);
try {
if (response != null) {
if (response.getStatusLine() != null) {
statusCode = response.getStatusLine().getStatusCode();
}
HttpEntity entity = response.getEntity();
// 響應內容
content = EntityUtils.toString(entity, Consts.UTF_8);
}
} finally {
response.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
httpClient.close();
}
}
public int getStatusCode() {
return statusCode;
}
public String getContent() throws ParseException, IOException {
return content;
}
}
3、配置文件:application.yml
server:
port: 18090
spring:
application:
name: pay
main:
allow-bean-definition-overriding: true
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
#hystrix 配置
hystrix:
command:
default:
execution:
timeout:
#如果enabled設置為false,則請求超時交給ribbon控制
enabled: true
isolation:
strategy: SEMAPHORE
#微信支付信息配置
weixin:
#應用ID
appid: wx8397f8696b538317
#商戶號
partner: 1473426802
#密鑰
partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
#支付回調地址 通知地址
notifyurl: https://www.cnblogs.com/chawaner/
4、SpringBoot啟動類
/**
* @Author TeaBowl
* @Date 2021/2/1 10:47
* @Version 1.0
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableEurekaClient
public class WeiXinPayApplication {
public static void main(String[] args) {
SpringApplication.run(WeiXinPayApplication.class,args);
}
}
5、控制層
/**
* @Author TeaBowl
* @Date 2021/2/1 12:01
* @Version 1.0
*/
@RestController
@RequestMapping(value = "/weixin/pay")
public class WeiXinPayController {
@Autowired
private WeixinPayService weixinPayService;
/**
* 創建二維碼
* @param parameterMap:用戶訂單信息
* @return
*/
@RequestMapping(value = "/create/native")
public Result createNative(@RequestParam Map<String,String> parameterMap){
Map<String,String> resultMap = weixinPayService.createnative(parameterMap);
return new Result(true, StatusCode.OK,"創建二維碼預付訂單成功!",resultMap);
}
}
6、應用層
/**
* @Author TeaBowl
* @Date 2021/2/1 11:01
* @Version 1.0
*/
public interface WeixinPayService {
/**
* 創建二維碼
* @param parameterMap:用戶訂單信息
* @return
*/
Map createnative(Map<String,String> parameterMap);
}
/**
* @Author TeaBowl
* @Date 2021/2/1 11:00
* @Version 1.0
*/
@Service
public class WeixinPayServiceImpl implements WeixinPayService {
//應用ID
@Value("${weixin.appid}")
private String appid;
//商戶號
@Value("${weixin.partner}")
private String partner;
//密鑰
@Value("${weixin.partnerkey}")
private String partnerkey;
//通知地址
@Value("${weixin.notifyurl}")
private String notifyurl;
/**
* 創建二維碼
*
* @param parameterMap:用戶訂單信息
* @return
*/
@Override
public Map createnative(Map<String, String> parameterMap) {
try {
//遠程調用
//創建一個Map集合存放請求參數
Map<String, String> paramMap = new HashMap<>();
//添加數據
//應用ID
paramMap.put("appid", appid);
//商戶號
paramMap.put("mch_id", partner);
//隨機字符串
paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
//商品描述
paramMap.put("body", "茶碗兒購物是真的好");
//訂單號
paramMap.put("out_trade_no", parameterMap.get("outtradeno"));
//交易總金額,單位為:分
paramMap.put("total_fee", parameterMap.get("totalfee"));
//終端IP
paramMap.put("spbill_create_ip", "127.0.0.1");
//通知地址,添URL地址
paramMap.put("notify_url", notifyurl);
//交易類型,NATIVE
paramMap.put("trade_type", "NATIVE");
//簽名
//paramMap.put("sign","");
//Map轉為XML數據,可以自帶簽名
//將請求參數集合,轉為帶有簽名的XML數據格式
String xmlStr = WXPayUtil.generateSignedXml(paramMap, partnerkey);
//URL地址,微信支付接口鏈接
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//提交方式:HTTPS
//創建HttpClient對象,對url請求地址進行操作
HttpClient httpClient = new HttpClient(url);
//設置提交方式為HTTPS
httpClient.setHttps(true);
//提交參數,參數以XML數據格式提交
httpClient.setXmlParam(xmlStr);
//執行請求
//發送XML數據,使用post請求
httpClient.post();
//獲取返回的數據,此時返回的數據為XML數據格式
String content = httpClient.getContent();
System.out.println("content:" + content);
//返回數據轉成Map
Map<String, String> resultMap = WXPayUtil.xmlToMap(content);
return resultMap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
二、測試
1、瀏覽器輸入請求地址:http://localhost:18090/weixin/pay/create/native?outtradeno=198999&totalfee=101
2、參數解釋:
outtradeno:訂單號
totalfee:總金額
3、瀏覽器請求結果
{
"flag": true,
"code": 20000,
"message": "創建二維碼預付訂單成功!",
"data": {
"nonce_str": "pUPI4MeBq1ikJbOJ",
"code_url": "weixin://wxpay/bizpayurl?pr=qJVBnYYzz",
"appid": "wx8397f8696b538317",
"sign": "7BCED5501AD892D5490A109DADE3383F",
"trade_type": "NATIVE",
"return_msg": "OK",
"result_code": "SUCCESS",
"mch_id": "1473426802",
"return_code": "SUCCESS",
"prepay_id": "wx011403161517455f3f2880f7e02e920000"
}
}
三、生成二維碼
1、使用qrious.js寫一個頁面pay.html,用於生成二維碼
<html>
<head>
<title>二維碼入門小demo</title>
<!--1.引入js 2. 創建一個img標簽 用來存儲顯示二維碼的圖片 3.創建js對象 4.設置js對象的配置項-->
<script src="qrious.js"> </script>
</head>
<body>
<img id="myqrious" >
</body>
<script>
var qrious = new QRious({
element:document.getElementById("myqrious"),// 指定的是圖片所在的DOM對象
size:250,//指定圖片的像素大小
level:'H',//指定二維碼的容錯級別(H:可以恢復30%的數據)
value:'weixin://wxpay/bizpayurl?pr=qJVBnYYzz'//指定二維碼圖片代表的真正的值
})
</script>
</html>
2、打開pay.html,顯示一個二維碼,微信掃碼支付
四、查詢支付狀態
1、控制層添加方法(controller)
/**
* 微信支付狀態查詢
* @param outtradeno:商戶訂單號,由微信服務器生成
* @return
*/
@GetMapping(value = "/status/query")
public Result queryStatus(String outtradeno){
//查詢微信支付狀態
Map map = weixinPayService.queryStatus(outtradeno);
return new Result(true, StatusCode.OK,"微信支付狀態查詢成功!",map);
}
2、應用層添加方法(service、serviceImpl)
/**
* 查詢微信支付狀態
* @param outtradeno:商戶訂單號,由微信服務器生成
* @return
*/
Map queryStatus(String outtradeno);
/**
* 查詢微信支付狀態
* @param outtradeno:商戶訂單號,由微信服務器生成
* @return
*/
@Override
public Map queryStatus(String outtradeno) {
try {
//遠程調用
//創建一個Map集合存放請求參數
Map<String, String> paramMap = new HashMap<>();
//添加數據
//應用ID
paramMap.put("appid", appid);
//商戶號
paramMap.put("mch_id", partner);
//隨機字符串
paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
//訂單號
paramMap.put("out_trade_no", outtradeno);
//簽名
//paramMap.put("sign","");
//Map轉為XML數據,可以自帶簽名
//將請求參數集合,轉為帶有簽名的XML數據格式
String xmlStr = WXPayUtil.generateSignedXml(paramMap, partnerkey);
//URL地址,微信查詢訂單接口鏈接
String url = "https://api.mch.weixin.qq.com/pay/orderquery";
//提交方式:HTTPS
//創建HttpClient對象,對url請求地址進行操作
HttpClient httpClient = new HttpClient(url);
//設置提交方式為HTTPS
httpClient.setHttps(true);
//提交參數,參數以XML數據格式提交
httpClient.setXmlParam(xmlStr);
//執行請求
//發送XML數據,使用post請求
httpClient.post();
//獲取返回的數據,此時返回的數據為XML數據格式
String content = httpClient.getContent();
//System.out.println("content:" + content);
//返回數據轉成Map
Map<String, String> resultMap = WXPayUtil.xmlToMap(content);
return resultMap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
3、瀏覽器輸入請求地址:http://localhost:18090/weixin/pay/status/query?outtradeno=198999
參數解釋:outtradeno:訂單號
{
"flag": true,
"code": 20000,
"message": "微信支付狀態查詢成功!",
"data": {
"nonce_str": "9FMEOJb0nhdJzGOQ",
"device_info": "",
"out_trade_no": "198999",
"trade_state": "NOTPAY",
"appid": "wx8397f8696b538317",
"total_fee": "101",
"sign": "1C86209F252D64254542EFC5481FD0D0",
"trade_state_desc": "訂單未支付",
"return_msg": "OK",
"result_code": "SUCCESS",
"mch_id": "1473426802",
"return_code": "SUCCESS"
}
}
因為上面測試支付二維碼時候,我沒支付,所以查詢訂單支付狀態顯示“訂單未支付”。
五、支付結果回調
1、控制層添加方法
/**
* 支付結果通知回調方法
* @param request
* @return
*/
@RequestMapping(value = "/notify/url")
public String notifyurl(HttpServletRequest request) throws Exception {
//獲取網絡輸入流,也就是網絡輸入流格式的通知結果
ServletInputStream inputStream = request.getInputStream();
//創建一個OutputStream輸出流->輸入文件
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
//定義緩沖區
byte[] buffer = new byte[1024];
//初始化一個數據長度
int len = 0;
//往緩沖區里讀文件,數據長度 不等於-1說明有數據
while ((len = inputStream.read(buffer))!=-1){
//緩沖區中的數據 寫入到 輸出流對象中
//從0開始讀到長度最后一位
byteArrayOutputStream.write(buffer,0,len);
}
//將byteArrayOutputStream字節流轉為字節數組
//這就是微信支付結果的字節數組
byte[] byteArray = byteArrayOutputStream.toByteArray();
//字節數組 轉為 xml字符串
String xmlResult = new String(byteArray, "Utf-8");
System.out.println("xmlResult:\n"+xmlResult);
//xml字符串 轉為 Map
Map<String, String> resultMap = WXPayUtil.xmlToMap(xmlResult);
System.out.println("resultMap:\n"+resultMap);
//返回結果
String result = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
return result;
}
2、配置文件中修改回調地址為動態調用
#微信支付信息配置
weixin:
#應用ID
appid: wx8397f8696b538317
#商戶號
partner: 1473426802
#密鑰
partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
#支付回調地址 通知地址
#外網域名:http://19453k43d4.51vip.biz:32375
notifyurl: http://19453k43d4.51vip.biz:32375/weixin/pay/notify/url
3、創建二維碼
瀏覽器輸入請求:http://19453k43d4.51vip.biz:32375/weixin/pay//create/native?outtradeno=1999&totalfee=1
{
"flag": true,
"code": 20000,
"message": "創建二維碼預付訂單成功!",
"data": {
"nonce_str": "pUPI4MeBq1ikJbOJ",
"code_url": "weixin://wxpay/bizpayurl?pr=qJVBnYYzd",
"appid": "wx8397f8696b538317",
"sign": "7BCED5501AD892D5490A109DADE3383F",
"trade_type": "NATIVE",
"return_msg": "OK",
"result_code": "SUCCESS",
"mch_id": "1473426802",
"return_code": "SUCCESS",
"prepay_id": "wx011403161517455f3f2880f7e02e920000"
}
}
4、修改pay.html中的value為上步的code_url
<html>
<head>
<title>二維碼入門小demo</title>
<!--1.引入js 2. 創建一個img標簽 用來存儲顯示二維碼的圖片 3.創建js對象 4.設置js對象的配置項-->
<script src="qrious.js"> </script>
</head>
<body>
<img id="myqrious" >
</body>
<script>
var qrious = new QRious({
element:document.getElementById("myqrious"),// 指定的是圖片所在的DOM對象
size:250,//指定圖片的像素大小
level:'H',//指定二維碼的容錯級別(H:可以恢復30%的數據)
value:'weixin://wxpay/bizpayurl?pr=qJVBnYYzd'//指定二維碼圖片代表的真正的值
})
</script>
</html>
5、雙擊打開pay.html,生成一個二維碼,掃碼支付后;
瀏覽器輸入請求地址:http://19453k43d4.51vip.biz:32375/weixin/pay/status/query?outtradeno=1999
{
"flag": true,
"code": 20000,
"message": "微信支付狀態查詢成功!",
"data": {
"transaction_id": "4200000517202002187004756359",
"nonce_str": "ZxVmHMCc1mVr8rxR",
"trade_state": "SUCCESS",
"bank_type": "OTHERS",
"openid": "oNpSGwaqHv74waDX0BLPNrFiYIUo",
"sign": "3DC6A5346C914DE745DBBB7796039BC1",
"return_msg": "OK",
"fee_type": "CNY",
"mch_id": "1473426802",
"cash_fee": "1",
"out_trade_no": "1999",
"cash_fee_type": "CNY",
"appid": "wx8397f8696b538317",
"total_fee": "1",
"trade_state_desc": "支付成功",
"trade_type": "NATIVE",
"result_code": "SUCCESS",
"attach": "",
"time_end": "20200218153715",
"is_subscribe": "N",
"return_code": "SUCCESS"
}
}
6、查看控制台輸出,有一個支付結果通知
六、MQ消息中間件監聽
微信服務返回的支付狀態,發送給MQ消息中間件
1、在支付系統中導入依賴
<!--加入ampq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、配置文件application.yml中添加信息
rabbitmq:
host: 服務器地址
port: 端口
username: 用戶名
password: 密碼
#位置支付交換機和隊列
mq:
pay:
exchange:
order: exchange.order
queue:
order: queue.order
routing:
key: queue.order
實際開發中登錄“服務器地址:15672”手動創建交換機和隊列
3、創建隊列綁定交換機配置
/**
* @Author TeaBowl
* @Date 2021/2/3 11:26
* @Version 1.0
*/
@Configuration
public class MQConfig {
/**
* 讀取配置文件中的信息
*/
@Autowired
private Environment env;
/**
* 創建隊列
* @return
*/
@Bean
public Queue OrderQueue (){
//參數:隊列名
return new Queue(env.getProperty("mq.pay.queue.order"));
}
/**
* 創建交換機
* @return
*/
@Bean
public Exchange OrderExchange (){
//參數:交換機名、是否持久化、是否自動刪除
return new DirectExchange(env.getProperty("mq.pay.exchange.order"),true,false);
}
/**
* 隊列綁定交換機
* @param orderQueue:隊列
* @param orderExchange:交換機
* @return
*/
@Bean
public Binding orderQueueExchange(Queue orderQueue,Exchange orderExchange){
//隊列綁定交換機
return BindingBuilder.bind(orderQueue).to(orderExchange).with(env.getProperty("mq.pay.routing.key")).noargs();
}
}
4、controller中注入MQ操作對象
//注入MQ操作對象
@Autowired
private RabbitTemplate rabbitTemplate;
5、修改controller中支付結果通知回調方法
/**
* 支付結果通知回調方法
* @param request
* @return
*/
@RequestMapping(value = "/notify/url")
public String notifyurl(HttpServletRequest request) throws Exception {
//獲取網絡輸入流,也就是網絡輸入流格式的通知結果
ServletInputStream inputStream = request.getInputStream();
//創建一個OutputStream輸出流->輸入文件
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
//定義緩沖區
byte[] buffer = new byte[1024];
//初始化一個數據長度
int len = 0;
//往緩沖區里讀文件,數據長度 不等於-1說明有數據
while ((len = inputStream.read(buffer))!=-1){
//緩沖區中的數據 寫入到 輸出流對象中
//從0開始讀到長度最后一位
byteArrayOutputStream.write(buffer,0,len);
}
//將byteArrayOutputStream字節流轉為字節數組
//這就是微信支付結果的字節數組
byte[] byteArray = byteArrayOutputStream.toByteArray();
//字節數組 轉為 xml字符串
String xmlResult = new String(byteArray, "Utf-8");
System.out.println("xmlResult:\n"+xmlResult);
//xml字符串 轉為 Map
Map<String, String> resultMap = WXPayUtil.xmlToMap(xmlResult);
System.out.println("resultMap:\n"+resultMap);
//發送支付結果給MQ
rabbitTemplate.convertAndSend("exchange.order","queue.order", JSON.toJSONString(resultMap));
//返回結果
String result = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
return result;
}
訂單系統監聽MQ消息
1、在訂單系統中導入依賴
<!--加入ampq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2、配置文件application.yml中添加信息
rabbitmq:
host: 服務器地址
port: 端口
username: 用戶名
password: 密碼
#位置支付交換機和隊列
mq:
pay:
#交換機
exchange:
order: exchange.order
#隊列
queue:
order: queue.order
routing:
key: queue.order
3、創建監聽MQ信息配置
/**
* @Author TeaBowl
* @Date 2021/2/3 12:02
* @Version 1.0
* 監聽MQ信息
*/
@Component
@RabbitListener(queues = "${mq.pay.queue.order}") //監聽隊列
public class OrderMessageListener {
/**
* 支付結果監聽
* @param message:支付結果
*/
@RabbitHandler
public void getMeaaage(String message){
//支付結果回調通知,Json格式轉為Map
Map<String, String> resultMap = JSON.parseObject(message, Map.class);
System.out.println("監聽到的支付結果信息:\n"+resultMap);
//從MQ消息中間件中獲取 支付操作的通信狀態
//通信標識:return_code 狀態:SUCCESS/FAIL
String return_code = resultMap.get("return_code");
//如果支付操作的通信成功
if (return_code.equals("SUCCESS")){
//從MQ消息中間件中獲取 支付操作的業務結果
//業務結果:result_code 狀態:SUCCESS/FAIL
String result_code = resultMap.get("result_code");
//商戶訂單號:out_trade_no
String out_trade_no = resultMap.get("out_trade_no");
//如果支付操作的業務結果為成功 修改訂單狀態
if (result_code.equals("SUCCESS")){
//從MQ消息中間件中獲取信息
//微信支付訂單號:transaction_id
String transaction_id = resultMap.get("transaction_id");
}else {
//如果支付失敗,關閉支付,取消訂單,回滾庫存
}
}
}
}
4、創建二維碼實
a. 瀏覽器請求地址:http://19453k43d4.51vip.biz:32375/weixin/pay/create/native?outtradeno=1769&totalfee=1
{
"flag": true,
"code": 20000,
"message": "創建二維碼預付訂單成功!",
"data": {
"nonce_str": "QlLrzt4vdsKNck1d",
"code_url": "weixin://wxpay/bizpayurl?pr=MjNPE2Dzz",
"appid": "wx8397f8696b538317",
"sign": "CD80E7E68014CF70C36535E231E0FF14",
"trade_type": "NATIVE",
"return_msg": "OK",
"result_code": "SUCCESS",
"mch_id": "1473426802",
"return_code": "SUCCESS",
"prepay_id": "wx03125509016851a5fca270399354320000"
}
}
b. 修改pay.html中的支付地址為code_url地址
<html>
<head>
<title>二維碼入門小demo</title>
<!--1.引入js
2. 創建一個img標簽 用來存儲顯示二維碼的圖片
3.創建js對象 4.設置js對象的配置項-->
<script src="qrious.js"> </script>
</head>
<body>
<img id="myqrious" >
</body>
<script>
var qrious = new QRious({
element:document.getElementById("myqrious"),// 指定的是圖片所在的DOM對象
size:250,//指定圖片的像素大小
level:'H',//指定二維碼的容錯級別(H:可以恢復30%的數據)
value:'weixin://wxpay/bizpayurl?pr=MjNPE2Dzz'//指定二維碼圖片代表的真正的值
})
</script>
</html>
c. 二維碼圖片
5、掃碼支付后。查看瀏覽器RabbitMQ后台:“服務器地址:15672/#/queues”
查看消息:
七、修改訂單狀態
1、在訂單工程中進行修改
應用層接口(OrderService)中,添加接口
/**
* 修改訂單狀態
* @param outtradeno:用戶訂單號
* @param paytime:支付時間
* @param transactionid:交易流水號
*/
void updataStatus(String outtradeno,String paytime,String transactionid) throws Exception;
應用層接口實現類(OrderServiceImpl)中,添加方法實現
/**
* 修改訂單狀態
* @param outtradeno:用戶訂單號
* @param paytime:支付時間
* @param transactionid:交易流水號
*/
@Override
public void updataStatus(String outtradeno, String paytime, String transactionid) throws Exception {
//使用時間轉換工具,轉換時間格式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
Date payTimeInfo = simpleDateFormat.parse(paytime);
//根據用戶訂單號,查詢訂單 order訂單信息封裝類
Order order = orderMapper.selectByPrimaryKey(outtradeno);
//修改訂單信息
//設置交易時間
order.setPayTime(payTimeInfo);
//設置支付狀態:0未支付,1已支付,2支付失敗
order.setPayStatus("1");
//設置交易流水號
order.setTransactionId(transactionid);
//信息更新到數據庫訂單表中
orderMapper.updateByPrimaryKeySelective(order);
}
2、刪除訂單,回滾庫存
在訂單支付后,如果支付失敗,訂單數據表中的訂單信息就沒必要保留了,商品庫存也需要回滾到原來的數量;
為了方便二次查詢,訂單並不是真的刪除了,而是修改了狀態,這叫邏輯刪除。
應用層接口(OrderService)中,添加接口
/**
* 支付失敗,刪除[修改狀態]訂單,回滾庫存
* @param outtradeno:用戶訂單號
*/
void deleteOrder(String outtradeno);
應用層接口實現類(OrderServiceImpl)中,添加方法實現
/**
* 支付失敗,刪除[修改狀態]訂單,回滾庫存
* @param outtradeno:用戶訂單號
*/
@Override
public void deleteOrder(String outtradeno) {
//根據用戶訂單號,查詢訂單 order訂單信息封裝類
Order order = orderMapper.selectByPrimaryKey(outtradeno);
//修改狀態
//設置更新時間
order.setUpdateTime(new Date());
//設置支付狀態:0未支付,1已支付,2支付失敗
order.setPayStatus("2");
//信息更新到數據庫訂單表中
orderMapper.updateByPrimaryKeySelective(order);
//回滾庫存->調用商品微服務 暫略
}
3、對接監聽
修改MQ信息監聽配置OrderMessageListener
注入訂單應用的操作對象
@Autowired
private OrderService orderService;
修改支付結果監聽方法
/**
* 支付結果監聽
* @param message:支付結果
*/
@RabbitHandler
public void getMeaaage(String message) throws Exception {
//支付結果回調通知,Json格式轉為Map
Map<String, String> resultMap = JSON.parseObject(message, Map.class);
System.out.println("監聽到的支付結果信息:\n"+resultMap);
//從MQ消息中間件中獲取 支付操作的通信狀態
//通信標識:return_code 狀態:SUCCESS/FAIL
String return_code = resultMap.get("return_code");
//如果支付操作的通信成功
if (return_code.equals("SUCCESS")){
//從MQ消息中間件中獲取 支付操作的業務結果
//業務結果:result_code 狀態:SUCCESS/FAIL
String result_code = resultMap.get("result_code");
//商戶訂單號:out_trade_no
String out_trade_no = resultMap.get("out_trade_no");
//如果支付操作的業務結果為成功 修改訂單狀態
if (result_code.equals("SUCCESS")){
//修改訂單狀態
//參數:用戶訂單號、支付完成時間、交易流水號
orderService.updataStatus(out_trade_no,resultMap.get("time_end"),resultMap.get("transaction_id"));
}else {
//如果支付失敗,關閉支付,取消訂單,回滾庫存
//關閉支付 暫略
//支付失敗,刪除[修改狀態]訂單,回滾庫存
orderService.deleteOrder(out_trade_no);
}
}
}
4、測試
創建二維碼信息,瀏覽器輸入請求:http://localhost:18090/weixin/pay/create/native?outtradeno=1355321180126445568&totalfee=1
{
"flag": true,
"code": 20000,
"message": "創建二維碼預付訂單成功!",
"data": {
"nonce_str": "Dc7zjYlcdvPxienu",
"code_url": "weixin://wxpay/bizpayurl?pr=s2V4HM3zz",
"appid": "wx8397f8696b538317",
"sign": "7D75E71E44AD669496C14684A66D8501",
"trade_type": "NATIVE",
"return_msg": "OK",
"result_code": "SUCCESS",
"mch_id": "1473426802",
"return_code": "SUCCESS",
"prepay_id": "wx04014823728571d0a1350cf856a41e0000"
}
}
修改pay.html的支付地址
<html>
<head>
<title>二維碼入門小demo</title>
<!--1.引入js
2. 創建一個img標簽 用來存儲顯示二維碼的圖片
3.創建js對象 4.設置js對象的配置項-->
<script src="qrious.js"> </script>
</head>
<body>
<img id="myqrious" >
</body>
<script>
var qrious = new QRious({
element:document.getElementById("myqrious"),// 指定的是圖片所在的DOM對象
size:250,//指定圖片的像素大小
level:'H',//指定二維碼的容錯級別(H:可以恢復30%的數據)
value:'weixin://wxpay/bizpayurl?pr=s2V4HM3zz'//指定二維碼圖片代表的真正的值
})
</script>
</html>
雙擊pay.html生成二維碼
掃碼支付成功后,已經監聽到了信息
數據庫中的訂單表,也根據監聽到的消息進行了改變
八、延時隊列配置
創建訂單后,延長支付時間
1、訂單系統中,創建延時隊列配置類
/**
* @Author TeaBowl
* @Date 2021/2/4 11:11
* @Version 1.0
* 延時隊列配置
* Queue1->Queue2
*/
@Configuration
public class QueueConfig {
/**
* 創建Queue1
* 延時隊列會過期,過期后將數據發送給Queue2
* 死信:就是超過有效時間仍沒有讀取的數據,就是放棄讀取的數據
*/
@Bean
public Queue orderDelayQueue() {
return QueueBuilder
//設置隊列名
.durable("orderDelayQueue")
/**
* 死信交換機->新交換機
* Queue1消息超時,進入死信隊列,綁定死信交換機x-dead-letter-exchange
* 參數:死信交換機、被綁定的新交換機
*/
.withArgument("x-dead-letter-exchange","orderListenerExchange")
/**
* 死信路由Key->新路由Key
* Queue1綁定死信交換機后,路由key就是x-dead-letter-routing-key
* orderListenerQueue是新的路由key,名稱就是隊列Queue2的名稱
*/
.withArgument("x-dead-letter-routing-key","orderListenerQueue")
.build();
}
/**
* 創建Queue2
* Queue1過期后,進入死信交換機x-dead-letter-exchange
* Queue1數據綁定新的交換機orderListenerExchange
* 路由到隊列Queue2
* @return
*/
@Bean
public Queue orderListenerQueue() {
//參數:隊列名、是否持久化
return new Queue("orderListenerQueue",true);
}
/**
* 創建交換機
* @return
*/
@Bean
public Exchange orderListenerExchange(){
return new DirectExchange("orderListenerExchange");
}
/**
* 隊列Queue2綁定新交換機
* @param orderListenerQueue:隊列Queue2
* @param orderListenerExchange:新的交換機
* @return
*/
@Bean
public Binding orderListenerBinding(Queue orderListenerQueue,Exchange orderListenerExchange){
return BindingBuilder
//新隊列
.bind(orderListenerQueue)
//新交換機
.to(orderListenerExchange)
//新路由key
.with("orderListenerQueue")
.noargs();
}
}
2、修改訂單應用層實現類OrderServiceImpl
注入MQ操作對象
@Autowired
private RabbitTemplate rabbitTemplate;
修改添加訂單方法
/**
* 增加Order 添加訂單實現 就是把購物車里的商品信息生成一個訂單
* 訂單表:tb_order 統計信息(訂單里的商品)
* 明細表:tb_order_item 商品詳細信息
* 訂單添加1次,明細添加多次
*
* @param order:前端接收的訂單信息
*/
@Override
public void add(Order order) {
/**
* 未實現
* 1.價格校驗
* 2.當前購物車和訂單捆綁了,沒有拆開
*/
//生成訂單id
order.setId(String.valueOf(idWorker.nextId()));
/**
* 獲取訂單明細——>購物車集合
* 獲取登錄名:order.getUsername()
* orderItems:明細對象集合
*/
List<OrderItem> orderItems = new ArrayList<>();
/**
* 獲取勾選的商品ID,需要下單的商品,將要下單的商品ID從購物車中移除
* 勾選了的id集合:order.getSkuIds()
*/
for (Long skuId : order.getSkuIds()) {
//根據商品id,查詢要下單的商品
orderItems.add((OrderItem) redisTemplate.boundHashOps("Cart_" + order.getUsername()).get(skuId));
//根據商品id,從購物車中刪除要下單的商品
redisTemplate.boundHashOps("Cart_" + order.getUsername()).delete(skuId);
}
//初始化總數量
int totalNum = 0;
//初始化總金額
int totalMoney = 0;
//封裝Map<String,Integer> 封裝庫存遞減數據
Map<String, Integer> decrmap = new HashMap<>();
//循環明細 orderItems:購物車里的所有商品
for (OrderItem orderItem : orderItems) {
//獲取總數量 orderItem:購物車里,其中一種商品
//totalNum:購物車里,商品的總數量
totalNum += orderItem.getNum();
//totalMoney:購物車里,商品的總金額
totalMoney += orderItem.getMoney();
//設置訂單明細id
orderItem.setId(String.valueOf(idWorker.nextId()));
//設置訂單明細所屬的訂單 order.getId():訂單id
orderItem.setOrderId(order.getId());
//是否退貨 0:未退貨,1:已退貨
orderItem.setIsReturn("0");
//封裝遞減數據 參數:商品ID、訂單中的商品數量
decrmap.put(orderItem.getSkuId().toString(),orderItem.getNum());
}
//1、添加訂單
//創建訂單時間:當前時間
order.setCreateTime(new Date());
//修改訂單時間:當前時間
order.setUpdateTime(order.getCreateTime());
//訂單來源
// 1:web,2:app,3:微信公眾號,4:微信小程序 5 H5手機頁面
order.setSourceType("1");
//訂單狀態
//0:未完成,1:已完成,2:已退貨
order.setOrderStatus("0");
//支付狀態
//0:未支付,1:已支付,2:支付失敗
order.setPayStatus("0");
//訂單是否刪除 0:未刪除,1:已刪除
order.setIsDelete("0");
/**
* 訂單里,購買商品的總數量=每個商品的總數量之和
* 1.獲取訂單明細——>購物車集合
* 2.循環訂單明細,獲取每個商品的購買數量
* 3.所有商品的數量求和
*/
order.setTotalNum(totalNum);
//訂單里,商品的總金額=每個商品的總金額之和
order.setTotalMoney(totalMoney);
//實付金額,實付金額金額=每個商品的總金額之和
//存在優惠時,金額會變動,此處無優惠,商品總金額=實付金額
order.setPayMoney(totalMoney);
//2、添加明細
//添加訂單信息
orderMapper.insertSelective(order);
//循環添加訂單明細信息
for (OrderItem orderItem : orderItems) {
//根據訂單,循環添加訂單明細信息
orderItemMapper.insertSelective(orderItem);
}
/**
* 庫存遞減
* decrmap:要減掉的數據
*/
skuFeign.decrCount(decrmap);
//為用戶添加積分活躍度,+1
userFeign.addPoints(1);
//設置延時消息發送時間
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("創建訂單時間:"+simpleDateFormat.format(new Date()));
//添加訂單
rabbitTemplate.convertAndSend("orderDelayQueue", (Object) order.getId(),
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//設置延時讀取 10s
message.getMessageProperties().setExpiration("10000");
return message;
}
});
}
3、創建過期消息監聽
/**
* @Author TeaBowl
* @Date 2021/2/4 12:35
* @Version 1.0
* 過期消息監聽,監聽隊列Queue2
*/
@Component
@RabbitListener(queues = "orderListenerQueue")
public class DelayMessageListener {
/**
* 延時隊列監聽
* @param message
*/
@RabbitHandler
public void getDelayMessage(String message){
//設置延時消息發送時間
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.print("監聽到消息的時間:\n"+simpleDateFormat.format(new Date()));
System.out.println("監聽到的消息:\n"+message);
}
}
結果: