天天看點

JAVA商城項目(微服務架構)——第19天 下單

0.學習目标

會調用訂單系統接口

實作訂單結算功能

實作微信支付功能

1.訂單系統接口

我們不做開發,隻講解

1.1.導入訂單服務

把課前資料提供的leyou-order複制到D:\heima\code\leyou目錄。

然後在工程内導入:

JAVA商城項目(微服務架構)——第19天 下單

然後導入module:

JAVA商城項目(微服務架構)——第19天 下單

選擇導入module:

JAVA商城項目(微服務架構)——第19天 下單

選擇目錄中的 ly-order:

JAVA商城項目(微服務架構)——第19天 下單

打開父工程leyou的pom檔案,添加ly-order子產品:

JAVA商城項目(微服務架構)——第19天 下單

1.2.Swagger-UI

1.2.1.什麼是OpenAPI

随着網際網路技術的發展,現在的網站架構基本都由原來的後端渲染,變成了:前端渲染、前後端分離的形态,而且前端技術和後端技術在各自的道路上越走越遠。? 前端和後端的唯一聯系,變成了API接口;API文檔變成了前後端開發人員聯系的紐帶,變得越來越重要。

沒有API文檔工具之前,大家都是手寫API文檔的,在什麼地方書寫的都有,而且API文檔沒有統一規範和格式,每個公司都不一樣。這無疑給開發帶來了災難。

OpenAPI規範(OpenAPI Specification 簡稱OAS)是Linux基金會的一個項目,試圖通過定義一種用來描述API格式或API定義的語言,來規範RESTful服務開發過程。目前V3.0版本的OpenAPI規範已經釋出并開源在github上 。

官網:https://github.com/OAI/OpenAPI-Specification

1.2.2.什麼是swagger?

OpenAPI是一個編寫API文檔的規範,然而如果手動去編寫OpenAPI規範的文檔,是非常麻煩的。而Swagger就是一個實作了OpenAPI規範的工具集。

官網:https://swagger.io/

看官方的說明:

JAVA商城項目(微服務架構)——第19天 下單

Swagger包含的工具集:

Swagger編輯器: Swagger Editor允許您在浏覽器中編輯YAML中的OpenAPI規範并實時預覽文檔。 
Swagger UI: Swagger UI是HTML,Javascript和CSS資産的集合,可以從符合OAS标準的API動态生成漂亮的文檔。 
Swagger Codegen:允許根據OpenAPI規範自動生成API用戶端庫(SDK生成),伺服器存根和文檔。 
Swagger Parser:用于解析來自Java的OpenAPI定義的獨立庫 
Swagger Core:與Java相關的庫,用于建立,使用和使用OpenAPI定義 
Swagger Inspector(免費): API測試工具,可讓您驗證您的API并從現有API生成OpenAPI定義 
SwaggerHub(免費和商業): API設計和文檔,為使用OpenAPI的團隊建構。 
           

1.2.3.快速入門

SpringBoot已經內建了Swagger,使用簡單注解即可生成swagger的API文檔。

1)引入依賴

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.8.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.8.0</version>
</dependency>
           

2)編寫配置

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .host("http://order.leyou.com")
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.leyou.order.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("樂優商城訂單系統")
                .description("樂優商城訂單系統接口文檔")
                .version("1.0")
                .build();
    }
}
           

3)接口聲明

在controller的每個handler上添加接口說明注解:

@RestController
@RequestMapping("order")
@Api("訂單服務接口")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @Autowired
    private PayHelper payHelper;

    /**
     * 建立訂單
     *
     * @param order 訂單對象
     * @return 訂單編号
     */
    @PostMapping
    @ApiOperation(value = "建立訂單接口,傳回訂單編号", notes = "建立訂單")
    @ApiImplicitParam(name = "order", required = true, value = "訂單的json對象,包含訂單條目和物流資訊")
    public ResponseEntity<Long> createOrder(@RequestBody @Valid Order order) {
        Long id = this.orderService.createOrder(order);
        return new ResponseEntity<>(id, HttpStatus.CREATED);
    }

    /**
     * 分頁查詢目前使用者訂單
     *
     * @param status 訂單狀态
     * @return 分頁訂單資料
     */
    @GetMapping("list")
    @ApiOperation(value = "分頁查詢目前使用者訂單,并且可以根據訂單狀态過濾", 
                  notes = "分頁查詢目前使用者訂單")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "page", value = "目前頁", 
                          defaultValue = "1", type = "Integer"),
        @ApiImplicitParam(name = "rows", value = "每頁大小", 
                          defaultValue = "5", type = "Integer"),
        @ApiImplicitParam(
            name = "status", 
            value = "訂單狀态:1未付款,2已付款未發貨,3已發貨未确認,4已确認未評價,5交易關閉,6交易成功,已評價", type = "Integer"),
    })
    public ResponseEntity<PageResult<Order>> queryUserOrderList(
        @RequestParam(value = "page", defaultValue = "1") Integer page,
        @RequestParam(value = "rows", defaultValue = "5") Integer rows,
        @RequestParam(value = "status", required = false) Integer status) {
        PageResult<Order> result = this.orderService.queryUserOrderList(page, rows, status);
        if (result == null) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
        return ResponseEntity.ok(result);
    }
}
           

常用注解說明:

/**
 @Api:修飾整個類,描述Controller的作用
 @ApiOperation:描述一個類的一個方法,或者說一個接口
 @ApiParam:單個參數描述
 @ApiModel:用對象來接收參數
 @ApiProperty:用對象接收參數時,描述對象的一個字段
 @ApiResponse:HTTP響應其中1個描述
 @ApiResponses:HTTP響應整體描述
 @ApiIgnore:使用該注解忽略這個API
 @ApiError :發生錯誤傳回的資訊
 @ApiImplicitParam:一個請求參數
 @ApiImplicitParams:多個請求參數
 */
           

4)啟動測試

啟動服務,然後通路:http://localhost:8089/swagger-ui.html

JAVA商城項目(微服務架構)——第19天 下單

點選order-controller,檢視接口資訊:

JAVA商城項目(微服務架構)——第19天 下單

點選任意一個接口,即可看到詳細資訊:

JAVA商城項目(微服務架構)——第19天 下單

1.3.測試接口

1.3.1.建立訂單接口

可以通過頁面看到接口資訊:

請求方式:POST 
請求路徑:/order 
請求參數:包含訂單、訂單詳情等資料的json對象。 
傳回結果:訂單編号 
           

點選Try It Out來測試:

JAVA商城項目(微服務架構)——第19天 下單

輸入資料:

{
  "totalPay": 236800,
  "postFee": 0,
  "paymentType": 2,
  "actualPay": 236800,
  "buyerMessage": null,
  "buyerNick": "huge",
  "orderDetails": [
    {
      "skuId": 3893493,
      "num": 1,
      "title": "蘋果(Apple)iPhone 6 (A1586) 16GB 金色 移動聯通電信4G手機3",
      "price": 236800,
      "ownSpec": "{\"機身顔色\":\"鑽雕藍\",\"記憶體\":\"4GB\",\"機身存儲\":\"64GB\"}",
      "image": "http://image.leyou.com/images/9/4/1524297342728.jpg"
    }
  ],
  "receiver": "鋒哥",
  "receiverMobile": "15800000000",
  "receiverState": "上海",
  "receiverCity": "上海",
  "receiverDistrict": "浦東新簽",
  "receiverAddress": "航頭鎮航頭路18号傳智播客3号樓",
  "receiverZip": "210000",
  "invoiceType": 0,
  "sourceType":2
}
           

然後點選execute:

JAVA商城項目(微服務架構)——第19天 下單

結果:

JAVA商城項目(微服務架構)——第19天 下單

下單需要登入,通過登入生成token:

JAVA商城項目(微服務架構)——第19天 下單

把token的值手動加入到浏覽器的cookie中:

JAVA商城項目(微服務架構)——第19天 下單
JAVA商城項目(微服務架構)——第19天 下單

添加成功,響應訂單編号。但是和資料庫儲存的訂單編号不太一樣(後幾位不一樣,浏覽器展示長整型會出現精度損失)

1.3.2.生成ID的方式

訂單id的特殊性

訂單資料非常龐大,将來一定會做分庫分表。那麼這種情況下, 要保證id的唯一,就不能靠資料庫自增,而是自己來實作算法,生成唯一id。

雪花算法

這裡的訂單id是通過一個工具類生成的:

JAVA商城項目(微服務架構)——第19天 下單

而工具類所采用的生成id算法,是由Twitter公司開源的snowflake(雪花)算法。

簡單原理

雪花算法會生成一個64位的二進制資料,為一個Long型。(轉換成字元串後長度最多19位) ,其基本結構:

JAVA商城項目(微服務架構)——第19天 下單
第一位:為未使用

第二部分:41位為毫秒級時間(41位的長度可以使用69年)

第三部分:5位datacenterId和5位workerId(10位的長度最多支援部署1024個節點)

第四部分:最後12位是毫秒内的計數(12位的計數順序号支援每個節點每毫秒産生4096個ID序号)
           

snowflake生成的ID整體上按照時間自增排序,并且整個分布式系統内不會産生ID碰撞(由datacenter和workerId作區分),并且效率較高。經測試snowflake每秒能夠産生26萬個ID。

配置

為了保證不重複,我們給每個部署的節點都配置機器id:

leyou:
  worker:
    workerId: 1
    datacenterId: 1
           

加載屬性:

@ConfigurationProperties(prefix = "leyou.worker")
public class IdWorkerProperties {

    private long workerId;// 目前機器id

    private long datacenterId;// 序列号

    public long getWorkerId() {
        return workerId;
    }

    public void setWorkerId(long workerId) {
        this.workerId = workerId;
    }

    public long getDatacenterId() {
        return datacenterId;
    }

    public void setDatacenterId(long datacenterId) {
        this.datacenterId = datacenterId;
    }
}
           

編寫配置類:

@Configuration
@EnableConfigurationProperties(IdWorkerProperties.class)
public class IdWorkerConfig {

    @Bean
    public IdWorker idWorker(IdWorkerProperties prop) {
        return new IdWorker(prop.getWorkerId(), prop.getDatacenterId());
    }
}
           

使用:

JAVA商城項目(微服務架構)——第19天 下單

1.3.2.查詢訂單接口

接口說明:

請求方式:GET 
請求路徑:/order/{id} 
請求參數:id,訂單編号 
傳回結果:Order,訂單的json對象 
           

測試:

JAVA商城項目(微服務架構)——第19天 下單

結果:

JAVA商城項目(微服務架構)——第19天 下單

1.3.3.更新訂單狀态

接口說明:

請求參數:PUT 
請求路徑:/order/{id}/{status} 
請求參數: 
id:訂單編号,String類型,不能為空 
status:訂單狀态,不能為空 
傳回結果:null 
           

測試:

JAVA商城項目(微服務架構)——第19天 下單

結果:

JAVA商城項目(微服務架構)——第19天 下單

資料庫中也發生了改變:

JAVA商城項目(微服務架構)——第19天 下單

1.3.4.分頁查詢訂單

接口說明:

請求方式:Get 
請求路徑:/order/list 
請求參數: 
page:目前頁,Integer類型,預設為1 
rows:每頁大小,Integer類型,預設為5 
status:訂單狀态,String類型,預設查詢全部狀态訂單 
傳回結果:PageResult 對象,包含下面屬性: 
total:總條數 
items:目前頁訂單數組 
訂單對象 
           

測試:

JAVA商城項目(微服務架構)——第19天 下單

結果:

JAVA商城項目(微服務架構)——第19天 下單

1.3.5.生成微信付款連結

接口說明:

請求方式:Get 
請求路徑:/order/url/{id} 
請求參數:id,訂單編号 
傳回結果:String類型,生成的微信支付連結 
           

測試:

JAVA商城項目(微服務架構)——第19天 下單

結果:

JAVA商城項目(微服務架構)——第19天 下單

微信支付工具

PayHelper

@Component
public class PayHelper {

    private WXPay wxPay;

    private static final Logger logger = LoggerFactory.getLogger(PayHelper.class);

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private OrderService orderService;

    public PayHelper(PayConfig payConfig) {
        // 真實開發時
        wxPay = new WXPay(payConfig);
        // 測試時
        // wxPay = new WXPay(payConfig, WXPayConstants.SignType.MD5, true);
    }

    public String createPayUrl(Long orderId) {
        String key = "ly.pay.url." + orderId;
        try {
            String url = this.redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(url)) {
                return url;
            }
        } catch (Exception e) {
            logger.error("查詢緩存付款連結異常,訂單編号:{}", orderId, e);
        }

        try {
            Map<String, String> data = new HashMap<>();
            // 商品描述
            data.put("body", "樂優商城測試");
            // 訂單号
            data.put("out_trade_no", orderId.toString());
            //貨币
            data.put("fee_type", "CNY");
            //金額,機關是分
            data.put("total_fee", "1");
            //調用微信支付的終端IP(estore商城的IP)
            data.put("spbill_create_ip", "127.0.0.1");
            //回調位址
            data.put("notify_url", "http://test.leyou.com/wxpay/notify");
            // 交易類型為掃碼支付
            data.put("trade_type", "NATIVE");
            //商品id,使用假資料
            data.put("product_id", "1234567");

            Map<String, String> result = this.wxPay.unifiedOrder(data);

            if ("SUCCESS".equals(result.get("return_code"))) {
                String url = result.get("code_url");
                // 将付款位址緩存,時間為10分鐘
                try {
                    this.redisTemplate.opsForValue().set(key, url, 10, TimeUnit.MINUTES);
                } catch (Exception e) {
                    logger.error("緩存付款連結異常,訂單編号:{}", orderId, e);
                }
                return url;
            } else {
                logger.error("建立預交易訂單失敗,錯誤資訊:{}", result.get("return_msg"));
                return null;
            }
        } catch (Exception e) {
            logger.error("建立預交易訂單異常", e);
            return null;
        }
    }

    /**
     * 查詢訂單狀态
     *
     * @param orderId
     * @return
     */
    public PayState queryOrder(Long orderId) {
        Map<String, String> data = new HashMap<>();
        // 訂單号
        data.put("out_trade_no", orderId.toString());
        try {
            Map<String, String> result = this.wxPay.orderQuery(data);
            if (result == null) {
                // 未查詢到結果,認為是未付款
                return PayState.NOT_PAY;
            }
            String state = result.get("trade_state");
            if ("SUCCESS".equals(state)) {
                // success,則認為付款成功

                // 修改訂單狀态
                this.orderService.updateStatus(orderId, 2);
                return PayState.SUCCESS;
            } else if (StringUtils.equals("USERPAYING", state)
                       || StringUtils.equals("NOTPAY", state)) {
                // 未付款或正在付款,都認為是未付款
                return PayState.NOT_PAY;
            } else {
                // 其它狀态認為是付款失敗
                return PayState.FAIL;
            }
        } catch (Exception e) {
            logger.error("查詢訂單狀态異常", e);
            return PayState.NOT_PAY;
        }
    }
}
           

跟支付相關的其它幾個類:

JAVA商城項目(微服務架構)——第19天 下單

1.3.6.查詢支付狀态

接口說明:

請求方式: Get 
請求路徑: /state/{id} 
請求參數: id,訂單編号 
傳回結果:0, 未查詢到支付資訊 1,支付成功 2,支付失敗(查詢失敗,或者訂單過期) 
           

1.3.6.1.未付款

未付款時查詢,測試:

JAVA商城項目(微服務架構)——第19天 下單

結果:

JAVA商城項目(微服務架構)——第19天 下單

因為尚未付款,是以查詢傳回0。

1.3.6.2.付款

通過JS把連結變成二維碼。

找到課前資料提供的JS頁面:

JAVA商城項目(微服務架構)——第19天 下單

進入,并輸入剛剛生成的位址:

JAVA商城項目(微服務架構)——第19天 下單

1.3.6.3.已付款

掃碼支付,然後再次查詢:

JAVA商城項目(微服務架構)——第19天 下單

狀态碼為1,代表支付成功了!

2.訂單結算頁

2.1.頁面跳轉

在購物車頁面的最下方,有一個去結算按鈕:

JAVA商城項目(微服務架構)——第19天 下單

當點選結算,我們應該跳轉到訂單結算頁,即:getOrderInfo.html

JAVA商城項目(微服務架構)——第19天 下單

檢視購物車的結算按鈕:

JAVA商城項目(微服務架構)——第19天 下單

可以看到,位址是正确的。但是隻有登入使用者才可以去結算付款,是以我們不能直接跳轉,而是在跳轉前校驗使用者的登入狀态,如果發現是未登入,應該重定向到登入頁!

我們給這個按鈕綁定點選事件:

JAVA商城項目(微服務架構)——第19天 下單

事件中判斷登入狀态,進行頁面跳轉:

toOrderInfo() {
    // 判斷是否登入
    ly.verifyUser().then(() => {
        // 已登入
        window.location.href = "/getOrderInfo.html"
    }).catch(() => {
        // 未登入
        window.location.href = "/login.html?returnUrl=" + window.location.href;
    })
}
           

登入後測試:

JAVA商城項目(微服務架構)——第19天 下單

此處頁面需要渲染的内容主要包含3部分:

收貨人資訊

支付方式

商品資訊

2.2.收貨人資訊(作業)

JAVA商城項目(微服務架構)——第19天 下單

這裡的收貨人資訊肯定是目前登入使用者的收貨位址。是以需要根據目前登入使用者去查詢,目前我們在頁面是寫的假資料:

JAVA商城項目(微服務架構)——第19天 下單

大家可以在在背景提供位址的增删改查接口,然後頁面加載時根據目前登入使用者查詢,而後指派給addresses即可。

2.3.支付方式

支付方式有2種:

微信支付 
貨到付款 
           

與我們訂單資料中的paymentType關聯:

JAVA商城項目(微服務架構)——第19天 下單

是以我們可以在Vue執行個體中定義一個屬性來記錄支付方式:

JAVA商城項目(微服務架構)——第19天 下單

然後在頁面渲染時與這個變量關聯:

JAVA商城項目(微服務架構)——第19天 下單

2.4.商品清單

效果圖:

JAVA商城項目(微服務架構)——第19天 下單

這裡的送貨清單,其實就是購物車中使用者選擇的要付款的商品

是以,我們需要在購物車跳轉過來的同時,攜帶選中的購物車的資訊

2.4.1.購物車資訊擷取

我們修改cart.html中的頁面跳轉邏輯,把使用者選中的購物車資訊傳遞過來:

JAVA商城項目(微服務架構)——第19天 下單

然後在created鈎子函數中擷取購物車資料,儲存到本地屬性,要注意的是,我們應該在擷取資料前校驗使用者登入狀态,如果發現未登入,則直接重定向到登入頁:

JAVA商城項目(微服務架構)——第19天 下單

然後重新加載頁面,檢視控制台:

JAVA商城項目(微服務架構)——第19天 下單

2.4.2.頁面渲染

要修改的頁面位置:每一個li就是一件商品

JAVA商城項目(微服務架構)——第19天 下單

我們修改為:

<ul class="send-detail">
    <li v-for="(cart,index) in carts" :key="index">
        <div class="sendGoods">
            <ul class="yui3-g">
                <li class="yui3-u-1-6">
                    <span><img width="70px" height="70px" :src="cart.image"/></span>
                </li>
                <li class="yui3-u-7-12">
                    <div class="desc">{{cart.title}}</div>
                    <div class="seven">
                        <span v-for="(v) in JSON.parse(cart.ownSpec)">{{v + "  "}} </span>
                    </div>
                </li>
                <li class="yui3-u-1-12">
                    <div class="price">¥{{ly.formatPrice(cart.price * cart.num)}}</div>
                </li>
                <li class="yui3-u-1-12">
                    <div class="num">{{cart.num}}</div>
                </li>
                <li class="yui3-u-1-12">
                    <div class="exit">有貨</div>
                </li>
            </ul>
        </div>
    </li>
</ul>
           

2.5.總金額

另外在商品清單下面,還有一個總金額的計算:

JAVA商城項目(微服務架構)——第19天 下單

可以看出這裡主要有4個資料:

總金額:totalPay 
優惠返現:discount 
運費:postFee 
實付金額:actualPay 
           

不過我們沒有做優惠活動,另外運費需要結合物流系統來計算,暫時我們都設定為0,在order屬性中寫死:

JAVA商城項目(微服務架構)——第19天 下單

我們通過計算屬性來得到totalPay和actualPay值:

computed: {
    totalNum(){
        return this.carts.reduce((c1, c2) => c1 + c2.num, 0)
    },
    totalPay(){
        return this.carts.reduce((c1, c2) => c1 + c2.price * c2.num, 0);
    },
    actualPay(){
        return this.totalPay + this.order.postFee - this.order.discount;
    }
},
           

然後在頁面渲染:

JAVA商城項目(微服務架構)——第19天 下單

效果:

JAVA商城項目(微服務架構)——第19天 下單

2.6.送出訂單

2.6.1.頁面送出

來看下訂單接口所需要的資料:

JAVA商城項目(微服務架構)——第19天 下單

分為3部分,分别是

訂單本身的基本資訊
總金額 
實付金額 
付款類型 
買家資訊就是目前使用者 
訂單詳情
           

就是購物車中的商品,不過購物車資料會多出一個userId,我們去除即可:

JAVA商城項目(微服務架構)——第19天 下單
物流資訊
目前使用者選中的物流位址資訊 
給送出按鈕綁定事件:
           
JAVA商城項目(微服務架構)——第19天 下單

然後編寫方法,組織資料并送出:

methods: {
    submit() {
        // 把購物車資料處理成訂單詳情
        const orderDetails = this.carts.map(({userId, ...rest}) => rest);
        // 處理物流資訊
        const addr = this.addresses[this.selectedAddress];
        const obj = {
            receiver: addr.name,
            receiverState: addr.state,
            receiverCity: addr.city,
            receiverAddress: addr.address,
            receiverDistrict: addr.district,
            receiverMobile: addr.phone,
            receiverZip: addr.zipCode
        };
        // 複制到訂單對象
        Object.assign(this.order, obj, {
            orderDetails,
            totalPay: this.totalPay,
            actualPay: this.actualPay,
        });
        // 送出訂單
        ly.http.post("/order", this.order).then(({data}) => {
            // 線上支付,需要到付款頁
            window.location = "pay.html?orderId=" + data;
        }).catch((resp) => {
            alert("訂單送出失敗,可能是缺貨!")
        })
    }
},
           

2.6.2.精度損失問題

在頁面點選送出測試:

JAVA商城項目(微服務架構)——第19天 下單

成功生成訂單!

然後看頁面跳轉:

JAVA商城項目(微服務架構)——第19天 下單

好像有什麼不對?訂單号的最後2位不正确啊!

這其實是因為JS的長整數精度有限,java的Long類型資料超出了範圍,是以出現了精度損失。

我們背景傳回的是Json的字元串,在axios内部會自動調用 JSON.parse()方法把json字元串轉為JS資料,就會出現進度損失。如果不進行轉換,依然當做字元串來使用,就不會有問題了。

是以,我們重寫axios對響應的處理回調函數:

JAVA商城項目(微服務架構)——第19天 下單

再次測試,就OK了。

接下來就輪到支付了。

3.微信支付

3.1.介紹

微信支付官方文檔:https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F

JAVA商城項目(微服務架構)——第19天 下單

我們選擇開發文檔,而後進入選擇頁面:

JAVA商城項目(微服務架構)——第19天 下單

選擇掃碼支付:

JAVA商城項目(微服務架構)——第19天 下單

此處我們使用模式二來開發:

3.2.開發流程

模式二與模式一相比,流程更為簡單,不依賴設定的回調支付URL。

商戶背景系統先調用微信支付的統一下單接口,微信背景系統傳回連結參數code_url;

商戶背景系統将code_url值生成二維碼圖檔,使用者使用微信用戶端掃碼後發起支付。

注意:code_url有效期為2小時,過期後掃碼不能再發起支付。 
           

流程圖:

JAVA商城項目(微服務架構)——第19天 下單

這裡我們把商戶(我們)要做的事情總結一下:

1、商戶生成訂單 
2、商戶調用微信下單接口,擷取預交易的連結 
3、商戶将連結生成二維碼圖檔,展示給使用者; 
4、使用者支付并确認 
5、支付結果通知: 
微信異步通知商戶支付結果,商戶告知微信支付接收情況 
商戶如果沒有收到通知,可以調用接口,查詢支付狀态 
6、如果支付成功,發貨,修改訂單狀态 
           

在前面的業務中,我們已經完成了:

1、生成訂單 
接下來,我們需要做的是:

2、調用微信接口,生成連結。 
3、并且生成二維碼圖檔 
           

3.3.生成二維碼

3.3.1.生成預交易連結

我們先根據訂單的編号,調用背景服務,生成交易連結,而後才能根據連結生成二維碼。

在頁面發起請求:

var payVm = new Vue({
    el:"#payVm",
    data:{
        ly,
        orderId:0,// 訂單編号
    },
    created(){
        // 判斷登入狀态
        ly.http.get("/auth/verify").then(() => {
            // 擷取訂單編号
            this.orderId = ly.getUrlParam("orderId");
            // 擷取請求連結
            ly.http.get("/order/url/" + this.orderId)
                .then(resp => {
                    console.log(resp.data);
                })
        }.catch(() => {
            // 未登入,跳轉至登入頁
             location.href = "/login.html?returnUrl=" + location.href;
        })
    },
    components: {
        shortcut: () => import("./js/pages/shortcut.js")
    }
});
           

背景已經定義好生成付款位址的接口。

JAVA商城項目(微服務架構)——第19天 下單

重新整理頁面檢視:

JAVA商城項目(微服務架構)——第19天 下單

3.3.2.生成二維碼

這裡我們使用一個生成二維碼的JS插件:qrcode,官網:https://github.com/davidshimjs/qrcodejs

JAVA商城項目(微服務架構)——第19天 下單

我們把課這個js腳本引入到項目中:

JAVA商城項目(微服務架構)——第19天 下單

官方使用案例:

JAVA商城項目(微服務架構)——第19天 下單

然後在頁面引用:

JAVA商城項目(微服務架構)——第19天 下單

頁面定義一個div,用于展示二維碼:

JAVA商城項目(微服務架構)——第19天 下單

然後擷取到付款連結後,根據連結生成二維碼:

JAVA商城項目(微服務架構)——第19天 下單
// 判斷登入狀态
ly.http.get("/auth/verify").then(() => {
    // 擷取訂單編号
    this.orderId = ly.getUrlParam("orderId");
    // 擷取請求連結
    ly.http.get("/order/url/" + this.orderId)
        .then(resp => {
            new QRCode(document.getElementById("qrImage"), {
                text: resp.data,
                width: 250,
                height: 250,
                colorDark: "#000000",
                colorLight: "#ffffff",
                correctLevel: QRCode.CorrectLevel.H
            });
        })
}).catch(() => {
    // 未登入,跳轉至登入頁
    location.href = "/login.html?returnUrl=" + location.href;
})
           

重新整理頁面,檢視效果:

JAVA商城項目(微服務架構)——第19天 下單

此時,客戶用手機掃描二維碼,可以看到付款頁面。

3.4.付款狀态查詢

跳轉到支付頁面後,我們等待使用者付款,付款完成則跳轉到付款成功頁面。

3.4.1.頁面循環查詢支付狀态

不過,因為不清楚使用者何時會付款,是以這裡采用循環的方式,不斷請求判斷是否支付成功。

// 開啟定時任務,查詢付款狀态
const taskId = setInterval(() => {
    ly.http.get("/order/state/" + this.orderId)
        .then(resp => {
        let i = resp.data;
        if (i === 1) {
            // 付款成功
            clearInterval(taskId);
            // 跳轉到付款成功頁
            location.href = "/paysuccess.html?orderId=" + this.orderId;
        } else if (i === 2) {
            // 付款失敗
            clearInterval(taskId);
            // 跳轉到付款失敗頁
            location.href = "/payfail.html";
        }
    })
}, 3000);
           

3.4.2.付款成功頁面

當付款成功後,自動跳轉到付款成功頁面:

JAVA商城項目(微服務架構)——第19天 下單