今日目標:
(1)掌握二維碼生成插件 qrious 的使用
(2)理解微信支付開發的整體思路
(3)調用微信支付接口(統一下單)生成支付二維碼
(4)調用微信接口(查詢訂單)查詢支付狀態
(5)實現支付日誌的生成與訂單狀態的修改
目錄
1、工程搭建
1.1 建立支付服務接口工程(pay-interface)
參考其他服務層接口
1.2 建立支付服務實現工程(pay-service)
(1)依賴pay-interface和common,其他依賴參考其他服務層工程
(2)添加微信支付 SDK 依賴
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
(3)在common工程中放入HttpClient的工具類,並在pom.xml文件中引入依賴
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
(4)添加微信支付配置文件
(5)引入 qrious 的js文件到cart-web的plugins目錄中
2、微信支付二維碼生成
2.1 需求分析
在支付頁面上生成支付二維碼,並顯示訂單號和金額
用戶拿出手機,打開微信掃描頁面上的二維碼,然後在微信中完成支付
2.2 後端
(1)服務層接口(pay-interface),新建WinxinPayService接口
package com.pinyougou.pay.service;
import java.util.Map;
/**
* 微信支付接口
* Author xushuai
* Description
*/
public interface WeixinPayService {
/**
* 生成本地支付的數據信息
*
* @param out_trade_no 外部訂單號
* @param total_fee 金額,單位爲:分
* @return java.util.Map
*/
Map createNative(String out_trade_no, String total_fee);
}
(2)服務層實現(pay-service),新建WinxinPayServiceImpl實現WinxinPayService
package com.pinyougou.pay.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.github.wxpay.sdk.WXPayUtil;
import com.pinyougou.pay.service.WeixinPayService;
import org.springframework.beans.factory.annotation.Value;
import util.HttpClient;
import java.util.HashMap;
import java.util.Map;
/**
* 微信支付接口實現
* Author xushuai
* Description
*/
@Service
public class WeixinPayServieImpl implements WeixinPayService {
@Value("${appid}")
private String WEIXIN_APPID;
@Value("${partner}")
private String WEIXIN_PARTNER;
@Value("${partnerkey}")
private String WEIXIN_PARTNERKEY;
@Value("${notifyurl}")
private String WEIXIN_NOTIFYURL;
@Override
public Map createNative(String out_trade_no, String total_fee) {
try {
// 1.封裝微信支付請求參數
Map<String, String> param = buildParam(out_trade_no, total_fee);
// 2.使用請求參數生成請求xml數據
String signXml = WXPayUtil.generateSignedXml(param, WEIXIN_PARTNERKEY);
System.out.println("xml數據:" + signXml);
// 3.發送請求
HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
httpClient.setHttps(true);
httpClient.setXmlParam(signXml);
httpClient.post();
// 4.獲取請求響應結果
String response = httpClient.getContent();
Map<String, String> responseMap = WXPayUtil.xmlToMap(response);
System.out.println("響應結果:" + responseMap);
// 5.生成返回結果
if (responseMap.get("return_code").equals("SUCCESS")) {
Map resultMap = new HashMap();
resultMap.put("code_url", responseMap.get("code_url"));// 二維碼鏈接地址
resultMap.put("out_trade_no", out_trade_no);// 訂單號
resultMap.put("total_fee", total_fee);// 金額
return resultMap;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 構造微信支付請求參數
*
* @param out_trade_no 外部訂單號
* @param total_fee 金額
*/
private Map<String, String> buildParam(String out_trade_no, String total_fee) {
Map<String, String> param = new HashMap<>();
// 公衆賬號ID
param.put("appid", WEIXIN_APPID);
// 商戶號
param.put("mch_id", WEIXIN_PARTNER);
// 設備號(自定義參數,可以爲終端設備號(門店號或收銀設備ID),PC網頁或公衆號內支付可以傳"WEB")
//param.put("device_info", "WEB");
// 隨機字符串(隨機字符串,長度要求在32位以內。推薦隨機數生成算法)
param.put("nonce_str", WXPayUtil.generateNonceStr());
// 商品描述
param.put("body", "品優購");
// 商品詳情
//param.put("detail", WEIXIN_APPID);
// 商戶訂單號
param.put("out_trade_no", out_trade_no);
// 標價金額
param.put("total_fee", total_fee);
// 終端IP
param.put("spbill_create_ip", "127.0.0.1");
// 通知地址
param.put("notify_url", "http://test.itcast.cn");
// 交易類型
param.put("trade_type", "NATIVE");
return param;
}
}
(3)控制層(cart-web),新建PayController
package com.pinyougou.cart.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.pinyougou.pay.service.WeixinPayService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import util.IdWorker;
import java.util.Map;
/**
* 支付控制層
* Author xushuai
* Description
*/
@RestController
@RequestMapping("/pay")
public class PayController {
@Reference
private WeixinPayService weixinPayService;
/**
* 生成二維碼數據
*
* @return java.util.Map
*/
@RequestMapping("/createNative")
public Map createNative(){
IdWorker idWorker = new IdWorker();
return weixinPayService.createNative(String.valueOf(idWorker.nextId()), "1");
}
}
2.3 前端
(1)新建payService.js
app.service('payService',function($http){
//本地支付
this.createNative=function(){
return $http.get('pay/createNative.do');
}
});
(2)新建payController.js
app.controller('payController' ,function($scope ,payService){
//本地生成二維碼
$scope.createNative=function(){
payService.createNative().success(
function(response){
$scope.money= (response.total_fee/100).toFixed(2) ; //金額
$scope.out_trade_no= response.out_trade_no;//訂單號
//二維碼
var qr = new QRious({
element:document.getElementById('qrious'),
size:250,
level:'H',
value:response.code_url
});
}
);
}
});
(3)在pay.html頁面中引入JS文件和基礎指令
(4)設置展示二維碼的img標籤的id爲:qrious
(5)綁定變量,展示訂單ID和訂單總金額
3、檢測支付狀態
3.1 需求分析
當用戶支付成功後跳轉到成功頁面
當返回異常時跳轉到錯誤頁面
3.2 後端
(1)服務層接口(pay-interface),在WinxinPayService新增方法
/**
* 查詢訂單支付狀態
*
* @param out_trade_no 外部訂單號
* @return java.util.Map
*/
Map queryPayStatus(String out_trade_no);
(2)服務層實現(pay-service),在WinxinPayServiceImpl新增實現
@Override
public Map queryPayStatus(String out_trade_no) {
try {
// 1.封裝請求參數
Map<String, String> param = buildQueryPayStatusParam(out_trade_no);
// 2.使用請求參數生成請求xml數據
String signXml = WXPayUtil.generateSignedXml(param, WEIXIN_PARTNERKEY);
System.out.println("xml數據:" + signXml);
// 3.發送請求
HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
httpClient.setHttps(true);
httpClient.setXmlParam(signXml);
httpClient.post();
// 4.獲取請求結果
String response = httpClient.getContent();
Map<String, String> responseMap = WXPayUtil.xmlToMap(response);
System.out.println("響應結果:" + response);
return responseMap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 構造查詢支付狀態的請求參數
*
* @param out_trade_no 商戶訂單號
* @return java.util.Map<java.lang.String,java.lang.String>
*/
private Map<String, String> buildQueryPayStatusParam(String out_trade_no) {
Map<String, String> param = new HashMap<>();
// 公衆賬號ID
param.put("appid", WEIXIN_APPID);
// 商戶號
param.put("mch_id", WEIXIN_PARTNER);
// 商戶訂單號
param.put("out_trade_no", out_trade_no);
// 隨機字符串
param.put("nonce_str", WXPayUtil.generateNonceStr());
return param;
}
(3)控制層(cart-web),在PayController中新增方法
/**
* 查詢訂單狀態(三秒查詢一次,直到支付成功)
*
* @param out_trade_no 商家訂單號
* @return entity.Result
*/
@RequestMapping("/queryPayStatus")
public Result queryPayStatus(String out_trade_no){
Result result = null;
while (true) {
// 查詢訂單狀態
Map map = weixinPayService.queryPayStatus(out_trade_no);
if (map == null) {
result = Result.error("支付出錯");
break;
}
if (map.get("trade_state").equals("SUCCESS")) {
result = Result.success("支付成功");
break;
}
try {
// 三秒查詢一次訂單狀態
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return result;
}
3.3 前端
(1)在payService.js中新增方法
//查詢支付狀態
this.queryPayStatus = function (out_trade_no) {
return $http.get('pay/queryPayStatus.do?out_trade_no=' + out_trade_no);
}
(2)在payController.js中新增方法
//查詢支付狀態
queryPayStatus = function (out_trade_no) {
payService.queryPayStatus(out_trade_no).success(
function (response) {
if (response.success) {
location.href = "paysuccess.html";
} else {
location.href = "payfail.html";
}
}
);
}
(3)在生成二維碼成功響應後,執行查詢訂單狀態的方法
3.4 二維碼超時處理
(1)後端:在控制層中的PayController中的查詢訂單狀態方法加入邏輯
(2)前端:當二維碼已過期時,重新生成二維碼
3.5 支付成功顯示支付金額
(1)前端:修改payController中支付成功後的邏輯
(2)前端:在payController中引入$location 服務
(3)前端:在payController中新增方法
//獲取金額
$scope.getMoney=function(){
return $location.search()['money'];
}
(4)前端:在paysuccess.html中引入相關js文件和基礎指令
(5)前端:頁面綁定變量展示支付金額
4、支付日誌
4.1 需求分析
我們現在系統還有兩個問題需要解決:
- 系統中無法查詢到支付記錄
- 支付後訂單狀態沒有改變
我們現在就來解決這兩個問題。
實現思路:
(1)在用戶下訂單時,判斷如果爲微信支付,就想支付日誌表添加一條記錄,信息包括支付總金額、訂單ID(多個)、用戶ID 、下單時間等信息,支付狀態爲0(未支付)
(2)生成的支付日誌對象放入redis中,以用戶ID作爲key,這樣在生成支付二維碼時就可以從redis中提取支付日誌對象中的金額和訂單號。
(3)當用戶支付成功後,修改支付日誌的支付狀態爲1(已支付),並記錄微信傳遞給我們的交易流水號。根據訂單ID(多個)修改訂單的狀態爲2(已付款)。
4.2 插入支付日誌
(1)生成訂單時,生成支付日誌,修改OrderServiceImpl中的add方法(order-service)
/**
* 增加
*/
@Override
public void add(TbOrder order) {
// 1.從redis中提取購物車列表
List<Cart> cartList = (List<Cart>) redisTemplate.boundHashOps("cartList").get(order.getUserId());
List<String> orderIdList = new ArrayList();//訂單ID列表
double total_money = 0;//總金額 (元)
// 2.循環購物車保存訂單
for (Cart cart : cartList) {
// 生成訂單對象
TbOrder tbOrder = buildOrder(order);
// 設置商家ID
tbOrder.setSellerId(cart.getSellerId());
// 合計金額
double money = 0;
// 循環購物車明細
for (TbOrderItem orderItem : cart.getOrderItemList()) {
// 補全訂單明細數據
orderItem.setOrderId(tbOrder.getOrderId());
orderItem.setSellerId(cart.getSellerId());
orderItem.setId(idWorker.nextId());
// 保存
orderItemMapper.insert(orderItem);
// 累加合計金額
money += orderItem.getTotalFee().doubleValue();
}
// 設置合計金額到訂單
tbOrder.setPayment(BigDecimal.valueOf(money));
// 保存
orderMapper.insert(tbOrder);
// 添加到訂單列表
orderIdList.add(tbOrder.getOrderId() + "");
// 累加到總金額
total_money += money;
}
if ("1".equals(order.getPaymentType())) {//如果是微信支付
TbPayLog payLog = new TbPayLog();
String outTradeNo = idWorker.nextId() + "";//支付訂單號
payLog.setOutTradeNo(outTradeNo);//支付訂單號
payLog.setCreateTime(new Date());//創建時間
//訂單號列表,逗號分隔
String ids = orderIdList.toString().replace("[", "").replace("]", "").replace(" ", "");
payLog.setOrderList(ids);//訂單號列表,逗號分隔
payLog.setPayType("1");//支付類型
payLog.setTotalFee((long) (total_money * 100));//總金額(分)
payLog.setTradeState("0");//支付狀態
payLog.setUserId(order.getUserId());//用戶ID
payLogMapper.insert(payLog);//插入到支付日誌表
redisTemplate.boundHashOps("payLog").put(order.getUserId(), payLog);//放入緩存
}
// 3.清除購物車中的數據
redisTemplate.boundHashOps("cartList").delete(order.getUserId());
}
4.3 讀取支付日誌
(1)後端:服務層接口(order-interface),在OrderService中新增方法
/**
* 獲取指定用戶的支付日誌
*
* @param username 用戶登錄名
* @return com.pinyougou.pojo.TbPayLog
*/
TbPayLog searchPayLogFromRedis(String username);
(2)後端:服務層實現(order-service),在OrderServiceImpl中實現該方法
@Override
public TbPayLog searchPayLogFromRedis(String username) {
return (TbPayLog) redisTemplate.boundHashOps("payLog").get(username);
}
(3)後端:控制層(cart-web),修改PayController中的createNative方法的邏輯
/**
* 生成二維碼數據
*
* @return java.util.Map
*/
@RequestMapping("/createNative")
public Map createNative(){
// 獲取當前登錄用戶名
String username = SecurityContextHolder.getContext().getAuthentication().getName();
// 從緩存中取出支付日誌
TbPayLog tbPayLog = orderService.searchPayLogFromRedis(username);
if (tbPayLog != null) {
return weixinPayService.createNative(tbPayLog.getOutTradeNo(), String.valueOf(tbPayLog.getTotalFee()));
}
return new HashMap();
}
5、支付成功,修改訂單狀態
(1)後端:服務層接口(order-interface),在OrderService中新增方法
/**
* 修改訂單狀態
*
* @param out_trade_no 支付訂單號
* @param transaction_id 微信返回的交易流水號
*/
void updateOrderStatus(String out_trade_no, String transaction_id);
(2)後端:服務層實現(order-service),在OrderServiceImpl中實現該方法
@Override
public void updateOrderStatus(String out_trade_no, String transaction_id) {
//1.修改支付日誌狀態
TbPayLog payLog = payLogMapper.selectByPrimaryKey(out_trade_no);
payLog.setPayTime(new Date());
payLog.setTradeState("1");//已支付
payLog.setTransactionId(transaction_id);//交易號
payLogMapper.updateByPrimaryKey(payLog);
//2.修改訂單狀態
String orderList = payLog.getOrderList();//獲取訂單號列表
String[] orderIds = orderList.split(",");//獲取訂單號數組
for (String orderId : orderIds) {
TbOrder order = orderMapper.selectByPrimaryKey(Long.parseLong(orderId));
if (order != null) {
order.setStatus(TbOrder.STATUS_PAY);//已付款
orderMapper.updateByPrimaryKey(order);
}
}
//清除redis緩存數據
redisTemplate.boundHashOps("payLog").delete(payLog.getUserId());
}
(3)後端:控制層(cart-web),在支付成功調用該方法