第01章-準(zhǔn)備工作
1、微信支付產(chǎn)品介紹
參考資料:產(chǎn)品中心 - 微信支付商戶平臺(tái) (qq.com)
付款碼支付、JSAPI支付、小程序支付、Native支付、APP支付、刷臉支付
1.1、付款碼支付
用戶展示微信錢包內(nèi)的“付款碼”給商家,商家掃描后直接完成支付,適用于線下面對(duì)面收銀的場景。
1.2、JSAPI支付
- 線下場所:商戶展示一個(gè)支付二維碼,用戶使用微信掃描二維碼后,輸入需要支付的金額,完成支付。
- 公眾號(hào)場景:用戶在微信內(nèi)進(jìn)入商家公眾號(hào),打開某個(gè)頁面,選擇某個(gè)產(chǎn)品,完成支付。
- PC網(wǎng)站場景:在網(wǎng)站中展示二維碼,用戶使用微信掃描二維碼,輸入需要支付的金額,完成支付。
特點(diǎn):用戶在客戶端輸入支付金額
1.3、小程序支付
在微信小程序平臺(tái)內(nèi)實(shí)現(xiàn)支付的功能。
1.4、Native支付
Native支付是指商戶展示支付二維碼,用戶再用微信“掃一掃”完成支付的模式。這種方式適用于PC網(wǎng)站。
特點(diǎn):商家預(yù)先指定支付金額
1.5、APP支付
商戶通過在移動(dòng)端獨(dú)立的APP應(yīng)用程序中集成微信支付模塊,完成支付。
1.6、刷臉支付
用戶在刷臉設(shè)備前通過攝像頭刷臉、識(shí)別身份后進(jìn)行的一種支付方式。
2、接口版本
微信支付企業(yè)主流的API版本有v2和v3,課程中我們使用微信支付APIv3。
V2和V3的比較
相比較而言,APIv2比APIv3安全性更高,但是APIv2中有一些功能在APIv3中尚未完整實(shí)現(xiàn),具體參考如下API字典頁面:API字典概覽 | 微信支付商戶平臺(tái)文檔中心 (qq.com)
3、接入指引
3.1、獲取開發(fā)參數(shù)
如果需要獨(dú)立申請和開通微信支付功能,可以參考如下官方文檔。開通微信支付后,才能獲取相關(guān)的開發(fā)參數(shù)以及商戶公鑰和商戶私鑰文件。
參考資料:微信支付接入指引 - 微信支付商戶平臺(tái) (qq.com)
3.2、配置開發(fā)參數(shù)
在service-order
服務(wù)的resources目錄中創(chuàng)建wxpay.properties
這個(gè)文件定義了在“接入指引”的步驟中我們提前準(zhǔn)備的微信支付相關(guān)的參數(shù),例如商戶號(hào)、APPID、API秘鑰等等
# 微信支付相關(guān)參數(shù)
wxpay:
mch-id: 1558950191 #商戶號(hào)
mch-serial-no: 34345964330B66427E0D3D28826C4993C77E631F # 商戶API證書序列號(hào)
private-key-path: D:/project/yygh/cert/apiclient_key.pem # 商戶私鑰文件
api-v3-key: UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B # APIv3密鑰
appid: wx74862e0dfcf69954 # APPID
notify-url: https://7d92-115-171-63-135.ngrok.io/api/order/wxpay/payment/notify # 接收支付結(jié)果通知地址
notify-refund-url: http://agxnyzl04y90.ngrok.xiaomiqiu123.top/api/order/wxpay/refunds/notify # 接收退款結(jié)果通知地址
3.3、復(fù)制商戶私鑰
將商戶私鑰文件復(fù)制到配置文件指定的目錄下:
資料:資料>微信支付>商戶證書>apiclient_key.pem
private-key-path: D:/project/yygh/cert/apiclient_key.pem # 商戶私鑰文件
3.4、證書密鑰使用說明(了解)
參考文檔:APIv3證書與密鑰使用說明
一個(gè)完整的請求和響應(yīng)的流程:
- 商戶使用商戶私鑰對(duì)請求進(jìn)行簽名,發(fā)送給微信支付平臺(tái),平臺(tái)使用商戶公鑰進(jìn)行簽名驗(yàn)證。
- 微信支付平臺(tái)使用平臺(tái)私鑰對(duì)響應(yīng)進(jìn)行簽名,商戶使用微信支付平臺(tái)公鑰對(duì)響應(yīng)進(jìn)行驗(yàn)簽。
第02章-訂單支付
1、微信支付平臺(tái)證書的獲取
1.1、引入SDK
參考文檔:SDK&工具
我們可以使用官方提供的 SDK wechatpay-java
在service-order微服務(wù)中添加依賴:
<!--微信支付APIv3-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.6</version>
</dependency>
1.2、讀取支付參數(shù)
在config 包中 創(chuàng)建 WxPayConfig.java
package com.atguigu.syt.order.config;
@Configuration
@PropertySource("classpath:wxpay.properties") //讀取配置文件
@ConfigurationProperties(prefix="wxpay") //讀取wxpay節(jié)點(diǎn)
@Data
public class WxPayConfig {
// 商戶號(hào)
private String mchId;
// 商戶API證書序列號(hào)
private String mchSerialNo;
// 商戶私鑰文件
private String privateKeyPath;
// APIv3密鑰
private String apiV3Key;
// APPID
private String appid;
// 接收支付結(jié)果通知地址
private String notifyUrl;
// 接收退款結(jié)果通知地址
private String notifyRefundUrl;
}
1.3、自動(dòng)更新微信支付平臺(tái)證書
在 API 請求過程中,客戶端需使用微信支付平臺(tái)證書,驗(yàn)證服務(wù)器應(yīng)答的真實(shí)性和完整性。我們使用自動(dòng)更新平臺(tái)證書的配置類 RSAAutoCertificateConfig
。每個(gè)商戶號(hào)只能創(chuàng)建一個(gè) RSAAutoCertificateConfig
。
在WxPayConfig
中添加如下方法:
/**
* 獲取微信支付配置對(duì)象
* @return
*/
@Bean
public RSAAutoCertificateConfig getConfig(){
return new RSAAutoCertificateConfig.Builder()
.merchantId(mchId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(mchSerialNo)
.apiV3Key(apiV3Key)
.build();
}
RSAAutoCertificateConfig
通過 RSAAutoCertificateProvider
自動(dòng)下載微信支付平臺(tái)證書。 同時(shí),RSAAutoCertificateProvider
會(huì)啟動(dòng)一個(gè)后臺(tái)線程,定時(shí)更新證書(目前設(shè)計(jì)為60分鐘),以實(shí)現(xiàn)證書過期時(shí)的新老證書平滑切換。
常見錯(cuò)誤:引入商戶私鑰后如果項(xiàng)目無法啟動(dòng),則需要升級(jí)JDK版本,并重新配置idea編譯和運(yùn)行環(huán)境到最新版本的JDK。建議升級(jí)到1.8.0_300以上
2、生成支付二維碼
2.1、Native支付流程
參考文檔:業(yè)務(wù)流程時(shí)序圖
2.2、Controller
在service-order中創(chuàng)建FrontWXPayController
package com.atguigu.syt.order.controller.front;
@Api(tags = "微信支付接口")
@RestController
@RequestMapping("/front/order/wxpay")
public class FrontWXPayController {
@Resource
private WxPayService wxPayService;
@Resource
private AuthContextHolder authContextHolder;
@ApiOperation("獲取支付二維碼url")
@ApiImplicitParam(name = "outTradeNo",value = "訂單號(hào)", required = true)
@GetMapping("/auth/nativePay/{outTradeNo}")
public Result<String> nativePay(@PathVariable String outTradeNo, HttpServletRequest request, HttpServletResponse response) {
//校驗(yàn)用戶登錄狀態(tài)
authContextHolder.checkAuth(request, response);
String codeUrl = wxPayService.createNative(outTradeNo);
return Result.ok(codeUrl);
}
}
2.3、Service
SDK參考代碼:wechatpay-java/NativePayServiceExample.java at main · wechatpay-apiv3/wechatpay-java · GitHub
具體代碼示例如下:
Native下單API參數(shù)參考:Native下單API
接口:OrderInfoService
/**
* 根據(jù)訂單號(hào)獲取訂單
* @param outTradeNo
* @return
*/
OrderInfo selectByOutTradeNo(String outTradeNo);
實(shí)現(xiàn):OrderInfoServiceImpl
@Override
public OrderInfo selectByOutTradeNo(String outTradeNo) {
LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OrderInfo::getOutTradeNo, outTradeNo);
return baseMapper.selectOne(queryWrapper);
}
接口:WxPayService
package com.atguigu.syt.order.service;
public interface WxPayService {
/**
* 獲取支付二維碼utl
* @param outTradeNo
* @return
*/
String createNative(String outTradeNo);
}
實(shí)現(xiàn):WxPayServiceImpl
package com.atguigu.syt.order.service.impl;
@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {
@Resource
private RSAAutoCertificateConfig rsaAutoCertificateConfig;
@Resource
private WxPayConfig wxPayConfig;
@Resource
private OrderInfoService orderInfoService;
@Override
public String createNative(String outTradeNo) {
// 初始化服務(wù)
NativePayService service = new NativePayService.Builder().config(rsaAutoCertificateConfig).build();
// 調(diào)用接口
try {
//獲取訂單
OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);
PrepayRequest request = new PrepayRequest();
// 調(diào)用request.setXxx(val)設(shè)置所需參數(shù),具體參數(shù)可見Request定義
request.setAppid(wxPayConfig.getAppid());
request.setMchid(wxPayConfig.getMchId());
request.setDescription(orderInfo.getTitle());
request.setOutTradeNo(outTradeNo);
request.setNotifyUrl(wxPayConfig.getNotifyUrl());
Amount amount = new Amount();
//amount.setTotal(orderInfo.getAmount().multiply(new BigDecimal(100)).intValue());
amount.setTotal(1);//1分錢
request.setAmount(amount);
// 調(diào)用接口
PrepayResponse prepayResponse = service.prepay(request);
return prepayResponse.getCodeUrl();
} catch (HttpException e) { // 發(fā)送HTTP請求失敗
// 調(diào)用e.getHttpRequest()獲取請求打印日志或上報(bào)監(jiān)控,更多方法見HttpException定義
log.error(e.getHttpRequest().toString());
throw new GuiguException(ResultCodeEnum.FAIL);
} catch (ServiceException e) { // 服務(wù)返回狀態(tài)小于200或大于等于300,例如500
// 調(diào)用e.getResponseBody()獲取返回體打印日志或上報(bào)監(jiān)控,更多方法見ServiceException定義
log.error(e.getResponseBody());
throw new GuiguException(ResultCodeEnum.FAIL);
} catch (MalformedMessageException e) { // 服務(wù)返回成功,返回體類型不合法,或者解析返回體失敗
// 調(diào)用e.getMessage()獲取信息打印日志或上報(bào)監(jiān)控,更多方法見MalformedMessageException定義
log.error(e.getMessage());
throw new GuiguException(ResultCodeEnum.FAIL);
}
}
}
可見,使用 SDK 并不需要計(jì)算請求簽名和驗(yàn)證應(yīng)答簽名。
3、前端整合
3.1、api
創(chuàng)建api/wxpay.js
import request from '~/utils/request'
export default {
//獲取支付二維碼url
nativePay(outTradeNo) {
return request({
url: `/front/order/wxpay/auth/nativePay/${outTradeNo}`,
method: 'get'
})
},
}
3.2、修改show.vue
引入api
import wxpayApi from '~/api/wxpay'
添加data數(shù)據(jù)
codeUrl: null, //微信支付二維碼
isPayShow: false, //不顯示登錄二維碼組件
payText: '支付'
修改按鈕
將
<div class="v-button" @click="pay()">支付</div>
修改為
<div class="v-button" @click="pay()">{{payText}}</div>
添加方法
//支付
pay() {
//防止重復(fù)提交
if(this.isPayShow) return
this.isPayShow = true
this.payText = '支付中.....'
//顯示二維碼
wxpayApi.nativePay(this.orderInfo.outTradeNo).then((response) => {
this.codeUrl = response.data
this.dialogPayVisible = true
})
},
//關(guān)閉對(duì)話框
closeDialog(){
//恢復(fù)支付按鈕
this.isPayShow = false
this.payText = '支付'
}
用二維碼組件替換img圖片
<img src="二維碼鏈接" alt="" />
替換成
<qriously :value="codeUrl" :size="220"/>
第03章-查詢支付結(jié)果
1、支付查單
參考文檔:微信支付查單接口
商戶可以主動(dòng)調(diào)用微信支付查單接口,同步訂單狀態(tài)。
調(diào)用查詢訂單接口,如果支付成功則更新訂單狀態(tài)
并添加交易記錄
,如果支付尚未成功則輪詢查單
1.1、更新訂單狀態(tài)
OrderInfoService接口
/**
* 根據(jù)訂單號(hào)更新訂單狀態(tài)
* @param outTradeNo
* @param status
*/
void updateStatus(String outTradeNo, Integer status);
OrderInfoServiceImpl實(shí)現(xiàn)
@Override
public void updateStatus(String outTradeNo, Integer status) {
LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(OrderInfo::getOutTradeNo, outTradeNo);
OrderInfo orderInfo = new OrderInfo();
orderInfo.setOrderStatus(status);
baseMapper.update(orderInfo, queryWrapper);
}
1.2、添加交易記錄
PaymentInfoService接口
/**
* 保存交易記錄
* @param orderInfo
* @param transaction
*/
void savePaymentInfo(OrderInfo orderInfo, Transaction transaction);
實(shí)現(xiàn):PaymentInfoServiceImpl
@Override
public void savePaymentInfo(OrderInfo orderInfo, Transaction transaction) {
PaymentInfo paymentInfo = new PaymentInfo();
paymentInfo.setOrderId(orderInfo.getId());
paymentInfo.setPaymentType(PaymentTypeEnum.WEIXIN.getStatus());
paymentInfo.setOutTradeNo(orderInfo.getOutTradeNo());//數(shù)據(jù)庫字段的長度改成32
paymentInfo.setSubject(orderInfo.getTitle());
paymentInfo.setTotalAmount(orderInfo.getAmount());
paymentInfo.setPaymentStatus(PaymentStatusEnum.PAID.getStatus());
paymentInfo.setTradeNo(transaction.getTransactionId());
paymentInfo.setCallbackTime(new Date());
paymentInfo.setCallbackContent(transaction.toString());
baseMapper.insert(paymentInfo);
}
1.3、查單Controller
FrontWXPayController
@ApiOperation("查詢支付狀態(tài)")
@ApiImplicitParam(name = "outTradeNo",value = "訂單id", required = true)
@GetMapping("/queryPayStatus/{outTradeNo}")
public Result queryPayStatus(@PathVariable String outTradeNo) {
//調(diào)用查詢接口
boolean success = wxPayService.queryPayStatus(outTradeNo);
if (success) {
return Result.ok().message("支付成功");
}
return Result.ok().message("支付中").code(250);
}
1.4、查單Service
接口:WxPayService
/**
* 查詢訂單支付狀態(tài)
* @param outTradeNo
* @return
*/
boolean queryPayStatus(String outTradeNo);
實(shí)現(xiàn):WxPayServiceImpl
@Resource
private PaymentInfoService paymentInfoService;
@Override
public boolean queryPayStatus(String outTradeNo) {
// 初始化服務(wù)
NativePayService service = new NativePayService.Builder().config(rsaAutoCertificateConfig).build();
// 調(diào)用接口
try {
QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
// 調(diào)用request.setXxx(val)設(shè)置所需參數(shù),具體參數(shù)可見Request定義
request.setOutTradeNo(outTradeNo);
request.setMchid(wxPayConfig.getMchId());
// 調(diào)用接口
Transaction transaction = service.queryOrderByOutTradeNo(request);
Transaction.TradeStateEnum tradeState = transaction.getTradeState();
//支付成功
if(tradeState.equals(Transaction.TradeStateEnum.SUCCESS)){
OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);
//更新訂單狀態(tài)
orderInfoService.updateStatus(outTradeNo, OrderStatusEnum.PAID.getStatus());
//記錄支付日志
paymentInfoService.savePaymentInfo(orderInfo, transaction);
//通知醫(yī)院修改訂單狀態(tài)
//組裝參數(shù)
HashMap<String, Object> paramsMap = new HashMap<>();
paramsMap.put("hoscode", orderInfo.getHoscode());
paramsMap.put("hosOrderId", orderInfo.getHosOrderId());
paramsMap.put("timestamp", HttpRequestHelper.getTimestamp());
paramsMap.put("sign", HttpRequestHelper.getSign(paramsMap, "8af52af00baf6aec434109fc17164aae"));
//發(fā)送請求
JSONObject jsonResult = HttpRequestHelper.sendRequest(paramsMap, "http://localhost:9998/order/updatePayStatus");
//解析響應(yīng)
if(jsonResult.getInteger("code") != 200) {
log.error("查單失敗,"
+ "code:" + jsonResult.getInteger("code")
+ ",message:" + jsonResult.getString("message")
);
throw new GuiguException(ResultCodeEnum.FAIL.getCode(), jsonResult.getString("message"));
}
//返回支付結(jié)果
return true;//支付成功
}
return false;//支付中
} catch (HttpException e) { // 發(fā)送HTTP請求失敗
// 調(diào)用e.getHttpRequest()獲取請求打印日志或上報(bào)監(jiān)控,更多方法見HttpException定義
log.error(e.getHttpRequest().toString());
throw new GuiguException(ResultCodeEnum.FAIL);
} catch (ServiceException e) { // 服務(wù)返回狀態(tài)小于200或大于等于300,例如500
// 調(diào)用e.getResponseBody()獲取返回體打印日志或上報(bào)監(jiān)控,更多方法見ServiceException定義
log.error(e.getResponseBody());
throw new GuiguException(ResultCodeEnum.FAIL);
} catch (MalformedMessageException e) { // 服務(wù)返回成功,返回體類型不合法,或者解析返回體失敗
// 調(diào)用e.getMessage()獲取信息打印日志或上報(bào)監(jiān)控,更多方法見MalformedMessageException定義
log.error(e.getMessage());
throw new GuiguException(ResultCodeEnum.FAIL);
}
}
2、前端整合
2.1、修改request.js
utils/request.js的響應(yīng)攔截器中增加對(duì)250狀態(tài)的判斷
}else if (response.data.code !== 200) {
修改為
}else if (response.data.code !== 200 && response.data.code !== 250) {
2.2、api
在api/wxpay.js中添加的方法
//查詢訂單
queryPayStatus(outTradeNo) {
return request({
url: `/front/order/wxpay/queryPayStatus/${outTradeNo}`,
method: 'get'
})
},
3.3、show.vue輪詢查單
js中修改pay方法:每隔3秒查單
添加queryPayStatus方法
完善closeDialog方法:停止定時(shí)器文章來源:http://www.zghlxwxcb.cn/news/detail-492663.html
//發(fā)起支付
pay(){
if(this.isPayShow) return
this.isPayShow = true
this.payText = '支付中.....'
wxpayApi.nativePay(this.orderInfo.outTradeNo).then((response) => {
this.codeUrl = response.data
this.dialogPayVisible = true
//每隔3秒查單
this.timer = setInterval(()=>{
console.log('輪詢查單:' + new Date())
this.queryPayStatus()
}, 3000)
})
},
//查詢訂單狀態(tài)
queryPayStatus(){
wxpayApi.queryPayStatus(this.orderInfo.outTradeNo).then(response => {
if(response.code == 250){
return
}
//清空定時(shí)器
clearInterval(this.timer)
window.location.reload()
})
},
//關(guān)閉對(duì)話框
closeDialog(){
//停止定時(shí)器
clearInterval(this.timer)
//恢復(fù)支付按鈕
this.isPayShow = false
this.payText = '支付'
}
3、查單應(yīng)答超時(shí)
3.1、測試應(yīng)答超時(shí)
//應(yīng)答超時(shí)
//設(shè)置響應(yīng)超時(shí),支付成功后沒有及時(shí)響應(yīng),可能繼續(xù)輪詢查單,此時(shí)數(shù)據(jù)庫會(huì)記錄多余的支付日志
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//返回支付結(jié)果
return true;//支付成功
3.2、處理重復(fù)通知
支付成功后,判斷訂單狀態(tài):文章來源地址http://www.zghlxwxcb.cn/news/detail-492663.html
OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);
//處理支付成功后重復(fù)查單
//保證接口調(diào)用的冪等性:無論接口被調(diào)用多少次,產(chǎn)生的結(jié)果是一致的
Integer orderStatus = orderInfo.getOrderStatus();
if (OrderStatusEnum.UNPAID.getStatus().intValue() != orderStatus.intValue()) {
return true;//支付成功、關(guān)單、。。。
}
//更新訂單狀態(tài)
//記錄支付日志
......
到了這里,關(guān)于網(wǎng)站怎么接入微信掃碼支付?的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!