您現在的位置是:首頁 > 垂釣

接入微信支付詳細流程,前後端具體程式碼,SpringBoot+Vue實戰(二)

  • 由 美女電影 發表于 垂釣
  • 2022-05-11
簡介insert(paymentInfo)

serial no是出廠編號嗎

本專案引入了Swagger,Lombok,集成了mp,以及日誌和引入微信支付的sdk,有興趣的小夥伴可以私下去了解一下配置資訊,本文不在多講。

後端專案總體結構:

接入微信支付詳細流程,前後端具體程式碼,SpringBoot+Vue實戰(二)

一。建立統一返回結果R類,在開發中很多專案都會建立統一結果類用來返回資料

@Data

@Accessors

(chain =

true

public

class

R {

private

Integer code;

//響應碼

private

String

message;

//響應訊息

private

Map<

String

Object

> data =

new

HashMap<>();

public

static

R ok(){

R r =

new

R();

r。setCode(

0

);

r。setMessage(

“成功”

);

return

r;

}

public

static

R error(){

R r =

new

R();

r。setCode(

-1

);

r。setMessage(

“失敗”

);

return

r;

}

public

R data(

String

key,

Object

value){

this

。data。put(key, value);

return

this

}

}

二。建立並定義實體類(BaseEntity是父類,其他繼承BaseEntity。注:Mapper介面繼承BaseMapper<>,業務層介面繼承IService<>,業務層實現類繼承ServiceImpl<,>)

接入微信支付詳細流程,前後端具體程式碼,SpringBoot+Vue實戰(二)

這裡只展示OrderInfo訂單實體類,如下

接入微信支付詳細流程,前後端具體程式碼,SpringBoot+Vue實戰(二)

三。為了開發方便,這裡定義微信支付中的列舉常量,並新增工具類,這裡只展示訂單狀態列舉

接入微信支付詳細流程,前後端具體程式碼,SpringBoot+Vue實戰(二)

訂單狀態列舉

@AllArgsConstructor

@Getter

public

enum

OrderStatus {

/**

* 未支付

*/

NOTPAY(

“未支付”

),

/**

* 支付成功

*/

SUCCESS(

“支付成功”

),

/**

* 已關閉

*/

CLOSED(

“超時已關閉”

),

/**

* 已取消

*/

CANCEL(

“使用者已取消”

),

/**

* 退款中

*/

REFUND_PROCESSING(

“退款中”

),

/**

* 已退款

*/

REFUND_SUCCESS(

“已退款”

),

/**

* 退款異常

*/

REFUND_ABNORMAL(

“退款異常”

);

/**

* 型別

*/

private

final

String

type

}

工具類:

接入微信支付詳細流程,前後端具體程式碼,SpringBoot+Vue實戰(二)

四。引入微信支付引數,將wxpay。properties 複製到resources目錄中,這個檔案定義了之前我們準備的微信支付相關的引數,例如商戶號、APPID、API秘鑰等等,並將下載的私鑰檔案複製到專案根目錄,新建config包建

WxPayConfig

類用來讀取配置檔案獲取引數。

接入微信支付詳細流程,前後端具體程式碼,SpringBoot+Vue實戰(二)

WxPayConfig

@Configuration

@PropertySource

“classpath:wxpay。properties”

//讀取配置檔案

@ConfigurationProperties

(prefix=

“wxpay”

//讀取wxpay節點

@Data

//使用set方法將wxpay節點中的值填充到當前類的屬性中

@Slf4j

public

class

WxPayConfig {

// 商戶號

private

String

mchId;

// 商戶API證書序列號

private

String

mchSerialNo;

// 商戶私鑰檔案

private

String

privateKeyPath;

// APIv3金鑰

private

String

apiV3Key;

// APPID

private

String

appid;

// 微信伺服器地址

private

String

domain;

// 接收結果通知地址

private

String

notifyDomain;

// APIv2金鑰

private

String

partnerKey;

/**

* 獲取商戶的私鑰檔案

* @param filename

* @return

*/

private

PrivateKey getPrivateKey(

String

filename){

try

{

return

PemUtil。loadPrivateKey(

new

FileInputStream(filename));

}

catch

(FileNotFoundException e) {

throw

new

RuntimeException(

“私鑰檔案不存在”

, e);

}

}

/**

* 獲取簽名驗證器

* @return

*/

@Bean

public

ScheduledUpdateCertificatesVerifier getVerifier(){

log。info(

“獲取簽名驗證器”

);

//獲取商戶私鑰

PrivateKey privateKey = getPrivateKey(privateKeyPath);

//私鑰簽名物件

PrivateKeySigner privateKeySigner =

new

PrivateKeySigner(mchSerialNo, privateKey);

//身份認證物件

WechatPay2Credentials wechatPay2Credentials =

new

WechatPay2Credentials(mchId, privateKeySigner);

// 使用定時更新的簽名驗證器,不需要傳入證書

ScheduledUpdateCertificatesVerifier verifier =

new

ScheduledUpdateCertificatesVerifier(

wechatPay2Credentials,

apiV3Key。getBytes(StandardCharsets。UTF_8));

return

verifier;

}

/**

* 獲取http請求物件

* @param verifier

* @return

*/

@Bean

(name =

“wxPayClient”

public

CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){

log。info(

“獲取httpClient”

);

//獲取商戶私鑰

PrivateKey privateKey = getPrivateKey(privateKeyPath);

WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder。create()

。withMerchant(mchId, mchSerialNo, privateKey)

。withValidator(

new

WechatPay2Validator(verifier));

// 。。。 接下來,你仍然可以透過builder設定各種引數,來配置你的HttpClient

// 透過WechatPayHttpClientBuilder構造的HttpClient,會自動的處理簽名和驗籤,並進行證書自動更新

CloseableHttpClient httpClient = builder。build();

return

httpClient;

}

/**

* 獲取HttpClient,無需進行應答簽名驗證,跳過驗籤的流程

*/

@Bean

(name =

“wxPayNoSignClient”

public

CloseableHttpClient getWxPayNoSignClient(){

//獲取商戶私鑰

PrivateKey privateKey = getPrivateKey(privateKeyPath);

//用於構造HttpClient

WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder。create()

//設定商戶資訊

。withMerchant(mchId, mchSerialNo, privateKey)

//無需進行簽名驗證、透過withValidator((response) -> true)實現

。withValidator((response) ->

true

);

// 透過WechatPayHttpClientBuilder構造的HttpClient,會自動的處理簽名和驗籤,並進行證書自動更新

CloseableHttpClient httpClient = builder。build();

log。info(

“== getWxPayNoSignClient END ==”

);

return

httpClient;

}

}

五。Native支付流程

接入微信支付詳細流程,前後端具體程式碼,SpringBoot+Vue實戰(二)

簡述:

1.使用者點選確認支付,後臺系統生成訂單,在後端資料庫表中生成一條記錄

2.商戶後臺系統呼叫統一下單API進行下單(Native下單介面)

3.微信支付系統收成請求後,生成預支付交易連結,並返回給商戶後臺系統

4.商戶後臺系統將連結生成二維碼圖片code_url,並展示給使用者

5.使用者開啟微信客戶端掃描二維碼,並提交掃碼連結給微信支付系統

6.驗證連結有效性,並要求使用者支付授權

7.授權即支付密碼,使用者確認支付,支付密碼,提交授權

8.微信支付系統驗證授權,完成支付交易

9.微信支付系統返回微信客戶端支付結果(訊息提示)

10.同時非同步告知商戶後臺系統(主動發起),用於修改系統狀態,並告知微信支付系統

11.為防止10網路異常掛掉或者沒有執行,導致使用者收到通知但商戶後臺系統沒有更新,利用查詢訂單API查詢訂單狀態,返回支付結果

12.發貨操作

六。主要

業務程式碼:

1。生成訂單,需要判斷是非已經存在

@Override

public

OrderInfo createOrderByProductId(

Long

productId) {

//查詢已存在但未支付的訂單

OrderInfo orderInfo =

this

。getNoPayOrderByProductId(productId);

if

( orderInfo !=

null

){

return

orderInfo;

}

//獲取商品資訊

Product product = productMapper。selectById(productId);

//生成訂單

orderInfo = new OrderInfo();

orderInfo。setTitle(product。getTitle());

orderInfo。setOrderNo(OrderNoUtils。getOrderNo());

//訂單號

orderInfo。setProductId(productId);

orderInfo。setTotalFee(product。getPrice());

//分

orderInfo。setOrderStatus(OrderStatus。NOTPAY。getType());

baseMapper。insert(orderInfo);

return

orderInfo;

}

2。記錄支付日誌

/**

* 記錄支付日誌

* @param plainText

*/

@Override

public

void

createPaymentInfo(

String

plainText) {

log。info(

“記錄支付日誌”

);

Gson gson =

new

Gson();

HashMap plainTextMap = gson。fromJson(plainText, HashMap。class);

//訂單號

String

orderNo = (

String

)plainTextMap。get(

“out_trade_no”

);

//業務編號

String

transactionId = (

String

)plainTextMap。get(

“transaction_id”

);

//支付型別

String

tradeType = (

String

)plainTextMap。get(

“trade_type”

);

//交易狀態

String

tradeState = (

String

)plainTextMap。get(

“trade_state”

);

//使用者實際支付金額

Map

<

String

Object

> amount = (

Map

)plainTextMap。get(

“amount”

);

Integer payerTotal = ((Double) amount。get(

“payer_total”

))。intValue();

PaymentInfo paymentInfo =

new

PaymentInfo();

paymentInfo。setOrderNo(orderNo);

paymentInfo。setPaymentType(PayType。WXPAY。getType());

paymentInfo。setTransactionId(transactionId);

paymentInfo。setTradeType(tradeType);

paymentInfo。setTradeState(tradeState);

paymentInfo。setPayerTotal(payerTotal);

paymentInfo。setContent(plainText);

baseMapper。insert(paymentInfo);

}

3。建立訂單,呼叫Native支付介面介面,生成二維碼並儲存

/**

* 建立訂單,呼叫Native支付介面

* @param productId

* @return code_url 和 訂單號

* @throws Exception

*/

@Transactional(rollbackFor = Exception。class)

@Override

public

Map

<

String

Object

> nativePay(Long productId) throws Exception {

log。info(

“生成訂單”

);

//生成訂單

OrderInfo orderInfo = orderInfoService。createOrderByProductId(productId);

String

codeUrl = orderInfo。getCodeUrl();

if

(orderInfo !=

null

&& !StringUtils。isEmpty(codeUrl)){

log。info(

“訂單已存在,二維碼已儲存”

);

//返回二維碼

Map

<

String

Object

> map =

new

HashMap<>();

map。put(

“codeUrl”

, codeUrl);

map。put(

“orderNo”

, orderInfo。getOrderNo());

return

map;

}

log。info(

“呼叫統一下單API”

);

//呼叫統一下單API

HttpPost httpPost =

new

HttpPost(wxPayConfig。getDomain()。concat(WxApiType。NATIVE_PAY。getType()));

// 請求body引數

Gson gson =

new

Gson();

Map

paramsMap =

new

HashMap();

paramsMap。put(

“appid”

, wxPayConfig。getAppid());

paramsMap。put(

“mchid”

, wxPayConfig。getMchId());

paramsMap。put(

“description”

, orderInfo。getTitle());

paramsMap。put(

“out_trade_no”

, orderInfo。getOrderNo());

paramsMap。put(

“notify_url”

, wxPayConfig。getNotifyDomain()。concat(WxNotifyType。NATIVE_NOTIFY。getType()));

Map

amountMap =

new

HashMap();

amountMap。put(

“total”

, orderInfo。getTotalFee());

amountMap。put(

“currency”

“CNY”

);

paramsMap。put(

“amount”

, amountMap);

//將引數轉換成json字串

String

jsonParams = gson。toJson(paramsMap);

log。info(

“請求引數 ===> {}”

+ jsonParams);

StringEntity entity =

new

StringEntity(jsonParams,

“utf-8”

);

entity。setContentType(

“application/json”

);

httpPost。setEntity(entity);

httpPost。setHeader(

“Accept”

“application/json”

);

//完成簽名並執行請求

CloseableHttpResponse response = wxPayClient。execute(httpPost);

try

{

String

bodyAsString = EntityUtils。toString(response。getEntity());

//響應體

int statusCode = response。getStatusLine()。getStatusCode();

//響應狀態碼

if

(statusCode ==

200

) {

//處理成功

log。info(

“成功, 返回結果 = ”

+ bodyAsString);

}

else

if

(statusCode ==

204

) {

//處理成功,無返回Body

log。info(

“成功”

);

}

else

{

log。info(

“Native下單失敗,響應碼 = ”

+ statusCode+

“,返回結果 = ”

+ bodyAsString);

throw

new

IOException(

“request failed”

);

}

//響應結果

Map

<

String

String

> resultMap = gson。fromJson(bodyAsString, HashMap。class);

//二維碼

codeUrl = resultMap。get(

“code_url”

);

//儲存二維碼

String

orderNo = orderInfo。getOrderNo();

orderInfoService。saveCodeUrl(orderNo, codeUrl);

//返回二維碼

Map

<

String

Object

> map =

new

HashMap<>();

map。put(

“codeUrl”

, codeUrl);

map。put(

“orderNo”

, orderInfo。getOrderNo());

return

map;

}

finally

{

response。close();

}

}

儲存二維碼

/**

* 儲存訂單二維碼

* @param orderNo

* @param codeUrl

*/

@Override

public

void

saveCodeUrl(

String

orderNo,

String

codeUrl) {

QueryWrapper queryWrapper =

new

QueryWrapper<>();

queryWrapper。eq(

“order_no”

, orderNo);

OrderInfo orderInfo =

new

OrderInfo();

orderInfo。setCodeUrl(codeUrl);

baseMapper。update(orderInfo, queryWrapper);

}

4。退款

/**

* 退款

* @param orderNo

* @param reason

* @throws IOException

*/

@Transactional

(rollbackFor = Exception。class)

@Override

public

void

refund(

String

orderNo,

String

reason) throws Exception {

log。info(

“建立退款單記錄”

);

//根據訂單編號建立退款單

RefundInfo refundsInfo = refundsInfoService。createRefundByOrderNo(orderNo, reason);

log。info(

“呼叫退款API”

);

//呼叫統一下單API

String

url = wxPayConfig。getDomain()。concat(WxApiType。DOMESTIC_REFUNDS。getType());

HttpPost httpPost =

new

HttpPost(url);

// 請求body引數

Gson gson =

new

Gson();

Map paramsMap =

new

HashMap();

paramsMap。put(

“out_trade_no”

, orderNo);

//訂單編號

paramsMap。put(

“out_refund_no”

, refundsInfo。getRefundNo());

//退款單編號

paramsMap。put(

“reason”

,reason);

//退款原因

paramsMap。put(

“notify_url”

, wxPayConfig。getNotifyDomain()。concat(WxNotifyType。REFUND_NOTIFY。getType()));

//退款通知地址

Map amountMap =

new

HashMap();

amountMap。put(

“refund”

, refundsInfo。getRefund());

//退款金額

amountMap。put(

“total”

, refundsInfo。getTotalFee());

//原訂單金額

amountMap。put(

“currency”

“CNY”

);

//退款幣種

paramsMap。put(

“amount”

, amountMap);

//將引數轉換成json字串

String

jsonParams = gson。toJson(paramsMap);

log。info(

“請求引數 ===> {}”

+ jsonParams);

StringEntity entity =

new

StringEntity(jsonParams,

“utf-8”

);

entity。setContentType(

“application/json”

);

//設定請求報文格式

httpPost。setEntity(entity);

//將請求報文放入請求物件

httpPost。setHeader(

“Accept”

“application/json”

);

//設定響應報文格式

//完成簽名並執行請求,並完成驗籤

CloseableHttpResponse response = wxPayClient。execute(httpPost);

try

{

//解析響應結果

String

bodyAsString = EntityUtils。toString(response。getEntity());

int statusCode = response。getStatusLine()。getStatusCode();

if

(statusCode ==

200

) {

log。info(

“成功, 退款返回結果 = ”

+ bodyAsString);

}

else

if

(statusCode ==

204

) {

log。info(

“成功”

);

}

else

{

throw

new

RuntimeException(

“退款異常, 響應碼 = ”

+ statusCode+

“, 退款返回結果 = ”

+ bodyAsString);

}

//更新訂單狀態

orderInfoService。updateStatusByOrderNo(orderNo, OrderStatus。REFUND_PROCESSING);

//更新退款單

refundsInfoService。updateRefund(bodyAsString);

}

finally

{

response。close();

}

}

七。本專案用到了定時任務

*

根據訂單號查詢微信支付查單介面,核實訂單狀態

*

如果訂單已支付,則更新商戶端訂單狀態,並記錄支付日誌

*

如果訂單未支付,則呼叫關單介面關閉訂單,並更新商戶端訂單狀態

從第0秒開始每隔30秒執行1次,查詢建立超過5分鐘,並且未支付的訂單

*

獲取微信支付端退款狀態

*

如果確認退款成功,則更新訂單狀態

從第0秒開始每隔30秒執行1次,查詢建立超過5分鐘,並且未成功的退款單

@Slf

4j

@Component

public

class

WxPayTask

{

@Resource

private

OrderInfoService orderInfoService;

@Resource

private

WxPayService wxPayService;

@Resource

private

RefundInfoService refundInfoService;

/**

* 秒 分 時 日 月 周

* 以秒為例

* *:每秒都執行

* 1-3:從第1秒開始執行,到第3秒結束執行

* 0/3:從第0秒開始,每隔3秒執行1次

* 1,2,3:在指定的第1、2、3秒執行

* ?:不指定

* 日和周不能同時制定,指定其中之一,則另一個設定為?

*/

//@Scheduled(cron = “0/3 * * * * ?”)

public

void

task1

()

{

log。info(

“task1 被執行……”

);

}

/**

* 從第0秒開始每隔30秒執行1次,查詢建立超過5分鐘,並且未支付的訂單

*/

@Scheduled

(cron =

“0/30 * * * * ?”

public

void

orderConfirm

()

throws

Exception

{

log。info(

“orderConfirm 被執行……”

);

List orderInfoList = orderInfoService。getNoPayOrderByDuration(

1

);

for

(OrderInfo orderInfo : orderInfoList) {

String orderNo = orderInfo。getOrderNo();

log。warn(

“超時訂單 ===> {}”

, orderNo);

//核實訂單狀態:呼叫微信支付查單介面

wxPayService。checkOrderStatus(orderNo);

}

}

/**

* 從第0秒開始每隔30秒執行1次,查詢建立超過5分鐘,並且未成功的退款單

*/

@Scheduled

(cron =

“0/30 * * * * ?”

public

void

refundConfirm

()

throws

Exception

{

log。info(

“refundConfirm 被執行……”

);

//找出申請退款超過5分鐘並且未成功的退款單

List refundInfoList = refundInfoService。getNoRefundOrderByDuration(

1

);

for

(RefundInfo refundInfo : refundInfoList) {

String refundNo = refundInfo。getRefundNo();

log。warn(

“超時未退款的退款單號 ===> {}”

, refundNo);

//核實訂單狀態:呼叫微信支付查詢退款介面

wxPayService。checkRefundStatus(refundNo);

}

}

前端程式碼為vue,下載依賴npm install建議國內映象,執行npm run serve

前端專案:

接入微信支付詳細流程,前後端具體程式碼,SpringBoot+Vue實戰(二)

api:訪問後端介面的地址,引數,請求方法

接入微信支付詳細流程,前後端具體程式碼,SpringBoot+Vue實戰(二)

具體前端頁面不再介紹

原始碼地址:git@gitee。com:liu_88/wxzf。git

Top