暢購商城第十二天

第12章 微信支付

學習目標

  • 能夠說出微信支付開發的整體思路

  • 生成支付二維碼

  • 查詢支付狀態

  • 實現支付日誌的生成與訂單狀態的修改、刪除訂單

  • 支付狀態回查

  • MQ處理支付回調狀態

  • 定時處理訂單狀態

1 開發準備

1.1 開發文檔

微信支付接口調用的整體思路:

按API要求組裝參數,以XML方式發送(POST)給微信支付接口(URL),微信支付接口也是以XML方式給予響應。程序根據返回的結果(其中包括支付URL)生成二維碼或判斷訂單狀態。

在線微信支付開發文檔:

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

如果你不能聯網,請查閱講義配套資源 (資源\配套軟件\微信掃碼支付\開發文檔)

我們在本章課程中會用到”統一下單”和”查詢訂單”兩組API

1. appid:微信公衆賬號或開放平臺APP的唯一標識
2. mch_id:商戶號  (配置文件中的partner)
3. partnerkey:商戶密鑰
4. sign:數字簽名, 根據微信官方提供的密鑰和一套算法生成的一個加密信息, 就是爲了保證交易的安全性

1.2 微信支付模式回顧

模式二
在這裏插入圖片描述
業務流程說明:

1.商戶後臺系統根據用戶選購的商品生成訂單。
2.用戶確認支付後調用微信支付【統一下單API】生成預支付交易;
3.微信支付系統收到請求後生成預支付交易單,並返回交易會話的二維碼鏈接code_url。
4.商戶後臺系統根據返回的code_url生成二維碼。
5.用戶打開微信“掃一掃”掃描二維碼,微信客戶端將掃碼內容發送到微信支付系統。
6.微信支付系統收到客戶端請求,驗證鏈接有效性後發起用戶支付,要求用戶授權。
7.用戶在微信客戶端輸入密碼,確認支付後,微信客戶端提交授權。
8.微信支付系統根據用戶授權完成支付交易。
9.微信支付系統完成支付交易後給微信客戶端返回交易結果,並將交易結果通過短信、微信消息提示用戶。微信客戶端展示支付交易結果頁面。
10.微信支付系統通過發送異步消息通知商戶後臺系統支付結果。商戶後臺系統需回覆接收情況,通知微信後臺系統不再發送該單的支付通知。
11.未收到支付通知的情況,商戶後臺系統調用【查詢訂單API】。
12.商戶確認訂單已支付後給用戶發貨。

1.3 微信支付SDK

微信支付提供了SDK, 大家下載後打開源碼,install到本地倉庫。
在這裏插入圖片描述
課程配套的本地倉庫已經提供jar包,所以安裝SDK步驟省略。

使用微信支付SDK,在maven工程中引入依賴

<!--微信支付-->
<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
</dependency>

我們主要會用到微信支付SDK的以下功能:

獲取隨機字符串

WXPayUtil.generateNonceStr()

MAP轉換爲XML字符串(自動添加簽名)

 WXPayUtil.generateSignedXml(param, partnerkey)

XML字符串轉換爲MAP

WXPayUtil.xmlToMap(result)

爲了方便微信支付開發,我們可以在changgou-common工程下引入依賴

<!--微信支付-->
<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>0.0.3</version>
</dependency>

1.4 HttpClient工具類

HttpClient是Apache Jakarta Common下的子項目,用來提供高效的、最新的、功能豐富的支持HTTP協議的客戶端編程工具包,並且它支持HTTP協議最新的版本和建議。HttpClient已經應用在很多的項目中,比如Apache Jakarta上很著名的另外兩個開源項目Cactus和HTMLUnit都使用了HttpClient。

HttpClient通俗的講就是模擬了瀏覽器的行爲,如果我們需要在後端向某一地址提交數據獲取結果,就可以使用HttpClient.

關於HttpClient(原生)具體的使用不屬於我們本章的學習內容,我們這裏這裏爲了簡化HttpClient的使用,提供了工具類HttpClient(對原生HttpClient進行了封裝)

HttpClient工具類代碼:

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;
    }
}

HttpClient工具類使用的步驟

HttpClient client=new HttpClient(請求的url地址);
client.setHttps(true);//是否是https協議
client.setXmlParam(xmlParam);//發送的xml數據
client.post();//執行post請求
String result = client.getContent(); //獲取結果

將HttpClient工具包放到common工程下並引入依賴,引入依賴後就可以直接使用上述的工具包了。

<!--httpclient支持-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
</dependency>

1.5 支付微服務搭建

(1)創建changgou-service-pay

創建支付微服務changgou-service-pay,只要實現支付相關操作。

(2)application.yml

創建application.yml,配置文件如下:

server:
  port: 18092
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:
  appid: wx8397f8696b538317
  partner: 1473426802
  partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
  notifyurl: http://www.itcast.cn

appid: 微信公衆賬號或開放平臺APP的唯一標識

partner:財付通平臺的商戶賬號

partnerkey:財付通平臺的商戶密鑰

notifyurl: 回調地址

(3)啓動類創建

changgou-service-pay中創建com.changgou.WeixinPayApplication,代碼如下:

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableEurekaClient
public class WeixinPayApplication {

    public static void main(String[] args) {
        SpringApplication.run(WeixinPayApplication.class,args);
    }
}

2 微信支付二維碼生成

2.1需求分析與實現思路

在支付頁面上生成支付二維碼,並顯示訂單號和金額

用戶拿出手機,打開微信掃描頁面上的二維碼,然後在微信中完成支付
在這裏插入圖片描述

2.2 實現思路

我們通過HttpClient工具類實現對遠程支付接口的調用。

接口鏈接:https://api.mch.weixin.qq.com/pay/unifiedorder

具體參數參見“統一下單”API, 構建參數發送給統一下單的url ,返回的信息中有支付url,根據url生成二維碼,顯示的訂單號和金額也在返回的信息中。

2.3 代碼實現

(1)業務層

新增com.changgou.service.WeixinPayService接口,代碼如下:

public interface WeixinPayService {
    /*****
     * 創建二維碼
     * @param out_trade_no : 客戶端自定義訂單編號
     * @param total_fee    : 交易金額,單位:分
     * @return
     */
    public Map createNative(String out_trade_no, String total_fee);
}

創建com.changgou.service.impl.WeixinPayServiceImpl類,併發送Post請求獲取預支付信息,包含二維碼掃碼支付地址。代碼如下:

@Service
public class WeixinPayServiceImpl implements WeixinPayService {

    @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 out_trade_no : 客戶端自定義訂單編號
     * @param total_fee    : 交易金額,單位:分
     * @return
     */
    @Override
    public Map createNative(String out_trade_no, String total_fee){
        try {
            //1、封裝參數
            Map param = new HashMap();
            param.put("appid", appid);                              //應用ID
            param.put("mch_id", partner);                           //商戶ID號
            param.put("nonce_str", WXPayUtil.generateNonceStr());   //隨機數
            param.put("body", "暢購");                            	//訂單描述
            param.put("out_trade_no",out_trade_no);                 //商戶訂單號
            param.put("total_fee", total_fee);                      //交易金額
            param.put("spbill_create_ip", "127.0.0.1");           //終端IP
            param.put("notify_url", notifyurl);                    //回調地址
            param.put("trade_type", "NATIVE");                     //交易類型

            //2、將參數轉成xml字符,並攜帶簽名
            String paramXml = WXPayUtil.generateSignedXml(param, partnerkey);

            ///3、執行請求
            HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
            httpClient.setHttps(true);
            httpClient.setXmlParam(paramXml);
            httpClient.post();

            //4、獲取參數
            String content = httpClient.getContent();
            Map<String, String> stringMap = WXPayUtil.xmlToMap(content);
            System.out.println("stringMap:"+stringMap);

            //5、獲取部分頁面所需參數
            Map<String,String> dataMap = new HashMap<String,String>();
            dataMap.put("code_url",stringMap.get("code_url"));
            dataMap.put("out_trade_no",out_trade_no);
            dataMap.put("total_fee",total_fee);

            return dataMap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

(2) 控制層

創建com.changgou.controller.WeixinPayController,主要調用WeixinPayService的方法獲取創建二維碼的信息,代碼如下:

@RestController
@RequestMapping(value = "/weixin/pay")
@CrossOrigin
public class WeixinPayController {

    @Autowired
    private WeixinPayService weixinPayService;

    /***
     * 創建二維碼
     * @return
     */
    @RequestMapping(value = "/create/native")
    public Result createNative(String outtradeno, String money){
        Map<String,String> resultMap = weixinPayService.createNative(outtradeno,money);
        return new Result(true, StatusCode.OK,"創建二維碼預付訂單成功!",resultMap);
    }
}

這裏我們訂單號通過隨機數生成,金額暫時寫死,後續開發我們再對接業務系統得到訂單號和金額

Postman測試http://localhost:18092/weixin/pay/create/native?outtradeno=No000000001&money=1
在這裏插入圖片描述

打開支付頁面/pay.html,修改value路徑,然後打開,會出現二維碼,可以掃碼試試
在這裏插入圖片描述
測試如下:
在這裏插入圖片描述

3 檢測支付狀態

3.1 需求分析

當用戶支付成功後跳轉到成功頁面
在這裏插入圖片描述
當返回異常時跳轉到錯誤頁面
在這裏插入圖片描述

3.2 實現思路

我們通過HttpClient工具類實現對遠程支付接口的調用。

接口鏈接:https://api.mch.weixin.qq.com/pay/orderquery

具體參數參見“查詢訂單”API, 我們在controller方法中輪詢調用查詢訂單(間隔3秒),當返回狀態爲success時,我們會在controller方法返回結果。前端代碼收到結果後跳轉到成功頁面。

3.3 代碼實現

(1)業務層

修改com.changgou.service.WeixinPayService,新增方法定義

/***
 * 查詢訂單狀態
 * @param out_trade_no : 客戶端自定義訂單編號
 * @return
 */
public Map queryPayStatus(String out_trade_no);

在com.changgou.pay.service.impl.WeixinPayServiceImpl中增加實現方法

/***
 * 查詢訂單狀態
 * @param out_trade_no : 客戶端自定義訂單編號
 * @return
 */
@Override
public Map queryPayStatus(String out_trade_no) {
    try {
        //1.封裝參數
        Map param = new HashMap();
        param.put("appid",appid);                            //應用ID
        param.put("mch_id",partner);                         //商戶號
        param.put("out_trade_no",out_trade_no);              //商戶訂單編號
        param.put("nonce_str",WXPayUtil.generateNonceStr()); //隨機字符

        //2、將參數轉成xml字符,並攜帶簽名
        String paramXml = WXPayUtil.generateSignedXml(param,partnerkey);

        //3、發送請求
        HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
        httpClient.setHttps(true);
        httpClient.setXmlParam(paramXml);
        httpClient.post();

        //4、獲取返回值,並將返回值轉成Map
        String content = httpClient.getContent();
        return WXPayUtil.xmlToMap(content);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

(2)控制層

com.changgou.controller.WeixinPayController新增方法,用於查詢支付狀態,代碼如下:

上圖代碼如下:

/***
 * 查詢支付狀態
 * @param outtradeno
 * @return
 */
@GetMapping(value = "/status/query")
public Result queryStatus(String outtradeno){
    Map<String,String> resultMap = weixinPayService.queryPayStatus(outtradeno);
    return new Result(true,StatusCode.OK,"查詢狀態成功!",resultMap);
}

4 訂單狀態操作準備工作

4.1 需求分析

在這裏插入圖片描述
我們現在系統還有個問題需要解決:支付後訂單狀態沒有改變

流程回顧:

1.用戶下單之後,訂單數據會存入到MySQL中,同時會將訂單對應的支付日誌存入到Redis,以List+Hash的方式存儲。
2.用戶下單後,進入支付頁面,支付頁面調用支付系統,從微信支付獲取二維碼數據,並在頁面生成支付二維碼。
3.用戶掃碼支付後,微信支付服務器會通調用前預留的回調地址,並攜帶支付狀態信息。
4.支付系統接到支付狀態信息後,將支付狀態信息發送給RabbitMQ
5.訂單系統監聽RabbitMQ中的消息獲取支付狀態,並根據支付狀態修改訂單狀態
6.爲了防止網絡問題導致notifyurl沒有接到對應數據,定時任務定時獲取Redis中隊列數據去微信支付接口查詢狀態,並定時更新對應狀態。

需要做的工作:

1.創建訂單時,同時將訂單信息放到Redis中,以List和Hash各存一份
2.實現回調地址接收支付狀態信息
3.將訂單支付狀態信息發送給RabbitMQ
4.訂單系統中監聽支付狀態信息,如果是支付成功,修改訂單狀態,如果是支付失敗,刪除訂單(或者改成支付失敗)
5.防止網絡異常無法接收到回調地址的支付信息,定時任務從Redis List中讀取數據判斷是否支付,如果支付了,修改訂單狀態,如果未支付,將支付信息放入隊列,下次再檢測,如果支付失敗刪除訂單(或者改成支付失敗)。

4.2 Redis存儲訂單信息

每次添加訂單後,會根據訂單檢查用戶是否是否支付成功,我們不建議每次都操作數據庫,每次操作數據庫會增加數據庫的負載,我們可以選擇將用戶的訂單信息存入一份到Redis中,提升讀取速度。

修改changgou-service-order微服務的com.changgou.order.service.impl.OrderServiceImpl類中的add方法,如果是線上支付,將用戶訂單數據存入到Redis中,由於每次創建二維碼,需要用到訂單編號 ,所以也需要將添加的訂單信息返回。
在這裏插入圖片描述
上圖代碼如下:

/**
 * 增加Order
 * 金額校驗:後臺校驗
 * @param order
 */
@Override
public Order add(Order order){
    //...略

    //修改庫存
    skuFeign.decrCount(order.getUsername());

    //添加用戶積分
    userFeign.addPoints(2);

    //線上支付,記錄訂單
    if(order.getPayType().equalsIgnoreCase("1")){
        //將支付記錄存入到Reids namespace  key  value
        redisTemplate.boundHashOps("Order").put(order.getId(),order);
    }

    //刪除購物車信息
    //redisTemplate.delete("Cart_" + order.getUsername());

    return order;
}

修改com.changgou.order.controller.OrderController的add方法,將訂單對象返回,因爲頁面需要獲取訂單的金額和訂單號用於創建二維碼,代碼如下:
在這裏插入圖片描述

4.3 修改訂單狀態

訂單支付成功後,需要修改訂單狀態並持久化到數據庫,修改訂單的同時,需要將Redis中的訂單刪除,所以修改訂單狀態需要將訂單日誌也傳過來,實現代碼如下:

修改com.changgou.order.service.OrderService,添加修改訂單狀態方法,代碼如下:

/***
 * 根據訂單ID修改訂單狀態
 * @param transactionid 交易流水號
 * @param orderId
 */
void updateStatus(String orderId,String transactionid);

修改com.changgou.order.service.impl.OrderServiceImpl,添加修改訂單狀態實現方法,代碼如下:

/***
 * 訂單修改
 * @param orderId
 * @param transactionid  微信支付的交易流水號
 */
@Override
public void updateStatus(String orderId,String transactionid) {
    //1.修改訂單
    Order order = orderMapper.selectByPrimaryKey(orderId);
    order.setUpdateTime(new Date());    //時間也可以從微信接口返回過來,這裏爲了方便,我們就直接使用當前時間了
    order.setPayTime(order.getUpdateTime());    //不允許這麼寫
    order.setTransactionId(transactionid);  //交易流水號
    order.setPayStatus("1");    //已支付
    orderMapper.updateByPrimaryKeySelective(order);

    //2.刪除Redis中的訂單記錄
    redisTemplate.boundHashOps("Order").delete(orderId);
}

4.4 刪除訂單

如果用戶訂單支付失敗了,或者支付超時了,我們需要刪除用戶訂單,刪除訂單的同時需要回滾庫存,這裏回滾庫存我們就不實現了,作爲同學們的作業。實現如下:

修改changgou-service-order的com.changgou.order.service.OrderService,添加刪除訂單方法,我們只需要將訂單id傳入進來即可實現,代碼如下:

/***
 * 刪除訂單操作
 * @param id
 */
void deleteOrder(String id);

修改changgou-service-order的com.changgou.order.service.impl.OrderServiceImpl,添加刪除訂單實現方法,代碼如下:

/***
 * 訂單的刪除操作
 */
@Override
public void deleteOrder(String id) {
    //改狀態
    Order order = (Order) redisTemplate.boundHashOps("Order").get(id);
    order.setUpdateTime(new Date());
    order.setPayStatus("2");    //支付失敗
    orderMapper.updateByPrimaryKeySelective(order);

    //刪除緩存
    redisTemplate.boundHashOps("Order").delete(id);
}

5 支付信息回調

5.1 接口分析

每次實現支付之後,微信支付都會將用戶支付結果返回到指定路徑,而指定路徑是指創建二維碼的時候填寫的notifyurl參數,響應的數據以及相關文檔參考一下地址:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8

5.1.1 返回參數分析

通知參數如下:

字段名 變量名 必填 類型 示例值 描述
返回狀態碼 return_code String(16) SUCCESS SUCCESS
返回信息 return_msg String(128) OK OK

以下字段在return_code爲SUCCESS的時候有返回

字段名 變量名 必填 類型 示例值 描述
公衆賬號ID appid String(32) wx8888888888888888 微信分配的公衆賬號ID(企業號corpid即爲此appId)
業務結果 result_code String(16) SUCCESS SUCCESS/FAIL
商戶訂單號 out_trade_no String(32) 1212321211201407033568112322 商戶系統內部訂單號
微信支付訂單號 transaction_id String(32) 1217752501201407033233368018 微信支付訂單號

5.1.2 響應分析

回調地址接收到數據後,需要響應信息給微信服務器,告知已經收到數據,不然微信服務器會再次發送4次請求推送支付信息。

字段名 變量名 必填 類型 示例值 描述
返回狀態碼 return_code String(16) SUCCESS 請按示例值填寫
返回信息 return_msg String(128) OK 請按示例值填寫

舉例如下:

<xml>
  <return_code><![CDATA[SUCCESS]]></return_code>
  <return_msg><![CDATA[OK]]></return_msg>
</xml>

5.2 回調接收數據實現

修改changgou-service-pay微服務的com.changgou.pay.controller.WeixinPayController,添加回調方法,代碼如下:

/***
 * 支付回調
 * @param request
 * @return
 */
@RequestMapping(value = "/notify/url")
public String notifyUrl(HttpServletRequest request){
    InputStream inStream;
    try {
        //讀取支付回調數據
        inStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        outSteam.close();
        inStream.close();
        // 將支付回調數據轉換成xml字符串
        String result = new String(outSteam.toByteArray(), "utf-8");
        //將xml字符串轉換成Map結構
        Map<String, String> map = WXPayUtil.xmlToMap(result);

        //響應數據設置
        Map respMap = new HashMap();
        respMap.put("return_code","SUCCESS");
        respMap.put("return_msg","OK");
        return WXPayUtil.mapToXml(respMap);
    } catch (Exception e) {
        e.printStackTrace();
        //記錄錯誤日誌
    }
    return null;
}

6 MQ處理支付回調狀態

6.1 業務分析

在這裏插入圖片描述
支付系統是獨立於其他系統的服務,不做相關業務邏輯操作,只做支付處理,所以回調地址接收微信服務返回的支付狀態後,立即將消息發送給RabbitMQ,訂單系統再監聽支付狀態數據,根據狀態數據做出修改訂單狀態或者刪除訂單操作。

6.2 發送支付狀態

(1)集成RabbitMQ

修改支付微服務,集成RabbitMQ,添加如下依賴:

<!--加入ampq-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

這裏我們建議在後臺手動創建隊列,並綁定隊列。如果使用程序創建隊列,可以按照如下方式實現。

修改application.yml,配置支付隊列和交換機信息,代碼如下:

#位置支付交換機和隊列
mq:
  pay:
    exchange:
      order: exchange.order
    queue:
      order: queue.order
    routing:
      key: queue.order

創建隊列以及交換機並讓隊列和交換機綁定,修改com.changgou.WeixinPayApplication,添加如下代碼:

/***
 * 創建DirectExchange交換機
 * @return
 */
@Bean
public DirectExchange basicExchange(){
    return new DirectExchange(env.getProperty("mq.pay.exchange.order"), true,false);
}

/***
 * 創建隊列
 * @return
 */
@Bean(name = "queueOrder")
public Queue queueOrder(){
    return new Queue(env.getProperty("mq.pay.queue.order"), true);
}

/****
 * 隊列綁定到交換機上
 * @return
 */
@Bean
public Binding basicBinding(){
    return BindingBuilder.bind(queueOrder()).to(basicExchange()).with(env.getProperty("mq.pay.routing.key"));
}

6.2.2 發送MQ消息

修改回調方法,在接到支付信息後,立即將支付信息發送給RabbitMQ,代碼如下:
在這裏插入圖片描述
上圖代碼如下:

@Value("${mq.pay.exchange.order}")
private String exchange;
@Value("${mq.pay.queue.order}")
private String queue;
@Value("${mq.pay.routing.key}")
private String routing;

@Autowired
private WeixinPayService weixinPayService;

@Autowired
private RabbitTemplate rabbitTemplate;

/***
 * 支付回調
 * @param request
 * @return
 */
@RequestMapping(value = "/notify/url")
public String notifyUrl(HttpServletRequest request){
    InputStream inStream;
    try {
        //讀取支付回調數據
        inStream = request.getInputStream();
        ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outSteam.write(buffer, 0, len);
        }
        outSteam.close();
        inStream.close();
        // 將支付回調數據轉換成xml字符串
        String result = new String(outSteam.toByteArray(), "utf-8");
        //將xml字符串轉換成Map結構
        Map<String, String> map = WXPayUtil.xmlToMap(result);
        //將消息發送給RabbitMQ
        rabbitTemplate.convertAndSend(exchange,routing, JSON.toJSONString(map));

        //響應數據設置
        Map respMap = new HashMap();
        respMap.put("return_code","SUCCESS");
        respMap.put("return_msg","OK");
        return WXPayUtil.mapToXml(respMap);
    } catch (Exception e) {
        e.printStackTrace();
        //記錄錯誤日誌
    }
    return null;
}

6.3 監聽MQ消息處理訂單

在訂單微服務中,我們需要監聽MQ支付狀態消息,並實現訂單數據操作。

6.3.1 集成RabbitMQ

在訂單微服務中,先集成RabbitMQ,再監聽隊列消息。

在pom.xml中引入如下依賴:

<!--加入ampq-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

在application.yml中配置rabbitmq配置,代碼如下:
在這裏插入圖片描述
在application.yml中配置隊列名字,代碼如下:

#位置支付交換機和隊列
mq:
  pay:
    queue:
      order: queue.order

6.3.2 監聽消息修改訂單

在訂單微服務於中創建com.changgou.order.consumer.OrderPayMessageListener,並在該類中consumeMessage方法,用於監聽消息,並根據支付狀態處理訂單,代碼如下:

@Component
@RabbitListener(queues = {"${mq.pay.queue.order}"})
public class OrderPayMessageListener {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private OrderService orderService;

    /***
     * 接收消息
     */
    @RabbitHandler
    public void consumeMessage(String msg){
        //將數據轉成Map
        Map<String,String> result = JSON.parseObject(msg,Map.class);

        //return_code=SUCCESS
        String return_code = result.get("return_code");
        //業務結果
        String result_code = result.get("result_code");

        //業務結果 result_code=SUCCESS/FAIL,修改訂單狀態
        if(return_code.equalsIgnoreCase("success") ){
            //獲取訂單號
            String outtradeno = result.get("out_trade_no");
            //業務結果
            if(result_code.equalsIgnoreCase("success")){
                if(outtradeno!=null){
                    //修改訂單狀態  out_trade_no
                    orderService.updateStatus(outtradeno,result.get("transaction_id"));
                }
            }else{
                //訂單刪除
                orderService.deleteOrder(outtradeno);
            }
        }

    }
}

7 定時處理訂單狀態(學員完成)

7.1 業務分析

在現實場景中,可能會出現這麼種情況,就是用戶支付後,有可能暢購服務網絡不通或者服務器掛了,此時會導致回調地址無法接收到用戶支付狀態,這時候我們需要取微信服務器查詢。所以我們之前訂單信息的ID存入到了Redis隊列,主要用於解決這種網絡不可達造成支付狀態無法回調獲取的問題。

實現思路如下:

1.每次下單,都將訂單存入到Reids List隊列中
2.定時每5秒檢查一次Redis 隊列中是否有數據,如果有,則再去查詢微信服務器支付狀態
3.如果已支付,則修改訂單狀態
4.如果沒有支付,是等待支付,則再將訂單存入到Redis隊列中,等會再次檢查
5.如果是支付失敗,直接刪除訂單信息並修改訂單狀態
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章