# 通道對接
## 通道對接
該文檔適合技術人員閱讀,可以幫助技術人員快速對接上游通道。我們這里將上游通道區分為支付通道和代付通道。
### 1. 支付通道
#### (1)支付流程
商戶下單后,xxpay-pay項目中的`PayOrderController`類中的`payOrder`方法負責接收商戶下單請求,該方法中會根據商戶的下單請求參數,得到具體的支付通道名稱,然后調用該通道的`pay`方法完成上游通道的下單操作,具體代碼提下如下。
```
<pre class="calibre25">```
payOrderId <span class="token">=</span> payOrder<span class="token1">.</span><span class="token2">getPayOrderId</span><span class="token1">(</span><span class="token1">)</span><span class="token1">;</span>
String channelId <span class="token">=</span> payOrder<span class="token1">.</span><span class="token2">getChannelId</span><span class="token1">(</span><span class="token1">)</span><span class="token1">;</span>
String channelName <span class="token">=</span> channelId<span class="token1">.</span><span class="token2">substring</span><span class="token1">(</span><span class="token3">0</span><span class="token1">,</span> channelId<span class="token1">.</span><span class="token2">indexOf</span><span class="token1">(</span><span class="token4">"_"</span><span class="token1">)</span><span class="token1">)</span><span class="token1">;</span>
AbstractRes res <span class="token">=</span> localDynamicChannelService<span class="token1">.</span><span class="token2">callPaymentMethod</span><span class="token1">(</span>channelName<span class="token1">,</span> payOrder<span class="token1">,</span> <span class="token3">true</span><span class="token1">)</span><span class="token1">;</span>
```
```
通過`localDynamicChannelService.callPaymentMethod(channelName, payOrder, true);`可以從spring容器中得到該通道的實例,然后調用具體的下單方法。
在命名通道類名的時候,格式須為:`通道名稱+PaymentService`,如:`AlipayPaymentService`為支付寶通道。
在XxPay Pro中,已經支持內置支付通道和動態通道(可通過接口商店安裝),具體代碼邏輯參考類`LocalDynamicChannelService`中的業務邏輯。
- 內置支付通道:指在xxpay-pay項目中硬編碼實現,開發好需要部署class文件,重啟項目。
- 動態支付通道:指動態開發的支付接口,打包成jar文件,在運營平臺自動導入即可,項目可不用重啟,直接可使用。
#### (2)內置支付通道實現
在xxpay-pay項目中的`channel`目錄下,須為通道創建一個獨立的目錄。

比如這里以威富通為例,威富通就是一個具體的上游通道,給它定義名稱為`swiftpay`,一般名字的定義來自通道的品牌名稱。
然后在該目錄下創建具體的支付實現類,名字為:`SwiftpayPaymentService`,類的名字必須是`通道名稱+PaymentService`,首字母大寫。然后該類繼承自`BasePayment`,重寫`pay`方法即可。
一般一個通道會對應多種支付方式,比如威富通會有微信掃碼支付,支付寶掃碼支付,統一條碼支付等。那么每種支付方式需要對應一個支付接口,我們這樣命名:`通道名稱+_+支付方式`,如威富通微信掃碼支付我們定義為:`swiftpay_wxpay_native`。
在pay方法中,我們會根據商戶選擇的支付方式,對應到上游通道的實現方法中,可以參考威富通的支付對接實現。具體的每種支付方式,需要參考上游通道的接口和demo來實現。

#### (3)通道支付回調
一般正規的支付流程,都是在用戶支付成功后,上游通道會回調接口中上傳的回調地址,那么我們需要處理上游過來的回調請求。
同樣的,也是在支付通道目錄下,創建一個支付回調的實現類,類的名字為:`通道名稱+PayNotifyService`,如威富通支付的回調類名為:`SwiftpayPayNotifyService`,該類繼承自`BasePayNotify`,重寫`doNotify`方法即可。
在調用上游通道支付接口時,我們會指定回調地址,那么在我們系統中,通過統一的方式獲取支付回調地址,具體代碼如下。
```
<pre class="calibre25">```
<span class="token5">// 前端頁面跳轉通知地址</span>
paramMap<span class="token1">.</span><span class="token2">put</span><span class="token1">(</span><span class="token4">"returnUrl"</span><span class="token1">,</span> super<span class="token1">.</span><span class="token2">getReturnUrl</span><span class="token1">(</span><span class="token2">getChannelName</span><span class="token1">(</span><span class="token1">)</span><span class="token1">,</span> dbConfig<span class="token1">)</span><span class="token1">)</span><span class="token1">;</span>
<span class="token5">// 后臺異步回調通知地址</span>
paramMap<span class="token1">.</span><span class="token2">put</span><span class="token1">(</span><span class="token4">"notifyUrl"</span><span class="token1">,</span> super<span class="token1">.</span><span class="token2">getNotifyUrl</span><span class="token1">(</span><span class="token2">getChannelName</span><span class="token1">(</span><span class="token1">)</span><span class="token1">,</span> dbConfig<span class="token1">)</span><span class="token1">)</span><span class="token1">;</span>
```
```
notify地址格式為:`http://支付系統地址/notify/通道名稱/notify_res.htm`,如威富通的回調地址為:`http://pay.xx.com/notify/swiftpay/notify_res.htm`,每個通道獲取到的通知地址,通道名稱是對應自己的。
通知的入口類為:`NotifyPayController`,實現代碼為:
```
<pre class="calibre25">```
_log<span class="token1">.</span><span class="token2">info</span><span class="token1">(</span><span class="token4">"====== 開始接收{}支付回調通知 ======"</span><span class="token1">,</span> channel<span class="token1">)</span><span class="token1">;</span>
<span class="token5">// 驗證回調是否在白名單</span>
String notifyIp <span class="token">=</span> IPUtility<span class="token1">.</span><span class="token2">getClientIp</span><span class="token1">(</span>request<span class="token1">)</span><span class="token1">;</span>
String checkResult <span class="token">=</span> <span class="token2">notifyCheck</span><span class="token1">(</span>channel<span class="token1">,</span> notifyIp<span class="token1">)</span><span class="token1">;</span>
<span class="token6">if</span> <span class="token1">(</span>checkResult <span class="token">!=</span> <span class="token6">null</span><span class="token1">)</span> <span class="token6">return</span> checkResult<span class="token1">;</span>
<span class="token5">//獲取通道對象示例</span>
Object instance <span class="token">=</span> localDynamicChannelService<span class="token1">.</span><span class="token2">getPayNotifyInterface</span><span class="token1">(</span>channel<span class="token1">.</span><span class="token2">toLowerCase</span><span class="token1">(</span><span class="token1">)</span><span class="token1">)</span><span class="token1">;</span>
<span class="token6">if</span><span class="token1">(</span>instance <span class="token6">instanceof</span> <span class="token2">PayNotifyInterface</span><span class="token1">)</span><span class="token1">{</span>
PayNotifyInterface payNotifyInterface <span class="token">=</span> <span class="token1">(</span>PayNotifyInterface<span class="token1">)</span>instance<span class="token1">;</span>
<span class="token6">if</span><span class="token1">(</span>payNotifyInterface <span class="token">==</span> <span class="token6">null</span><span class="token1">)</span><span class="token1">{</span>
<span class="token6">return</span> ApiBuilder<span class="token1">.</span><span class="token2">bizError</span><span class="token1">(</span><span class="token4">"支付渠道類型[channel="</span><span class="token">+</span>channel<span class="token">+</span><span class="token4">"]實例化異常"</span><span class="token1">)</span><span class="token1">.</span><span class="token2">toJSONString</span><span class="token1">(</span><span class="token1">)</span><span class="token1">;</span>
<span class="token1">}</span>
JSONObject retObj <span class="token">=</span> payNotifyInterface<span class="token1">.</span><span class="token2">doNotify</span><span class="token1">(</span>request<span class="token1">)</span><span class="token1">;</span>
String notifyRes <span class="token">=</span> retObj<span class="token1">.</span><span class="token2">getString</span><span class="token1">(</span>PayConstant<span class="token1">.</span>RESPONSE_RESULT<span class="token1">)</span><span class="token1">;</span>
_log<span class="token1">.</span><span class="token2">info</span><span class="token1">(</span><span class="token4">"響應給{}:{}"</span><span class="token1">,</span> channel<span class="token1">,</span> notifyRes<span class="token1">)</span><span class="token1">;</span>
_log<span class="token1">.</span><span class="token2">info</span><span class="token1">(</span><span class="token4">"====== 完成接收{}支付回調通知 ======"</span><span class="token1">,</span> channel<span class="token1">)</span><span class="token1">;</span>
<span class="token6">return</span> notifyRes<span class="token1">;</span>
<span class="token1">}</span>
```
```
原理同支付下單流程類似,也是得到具體的通道,然后從spring容器中得到具體的通知實例,調用`doNotify`方法。
#### (4)通道參數定義
一般每個支付通道都會對應一些配置,比如商戶ID,私鑰,網關地址等信息。我們需要根據上游通道的接口文檔,抽象出具體的配置字段,然后定義配置類。
一般類的名稱命名為`通道名稱+Config`,比如威富通的配置類為`SwiftpayConfig`。
威富通的配置包括:商戶ID,商戶key,網關請求地址,那么我們的代碼是這樣的。
```
<pre class="calibre25">```
public class <span class="token2">SwiftpayConfig</span> <span class="token1">{</span>
<span class="token5">// 商戶ID</span>
private String mchId<span class="token1">;</span>
<span class="token5">// 商戶Key</span>
private String key<span class="token1">;</span>
<span class="token5">// 請求地址</span>
private String reqUrl<span class="token1">;</span>
public <span class="token2">SwiftpayConfig</span><span class="token1">(</span><span class="token1">)</span><span class="token1">{</span><span class="token1">}</span>
public <span class="token2">SwiftpayConfig</span><span class="token1">(</span>String payParam<span class="token1">)</span> <span class="token1">{</span>
Assert<span class="token1">.</span><span class="token2">notNull</span><span class="token1">(</span>payParam<span class="token1">,</span> <span class="token4">"init swiftpay config error"</span><span class="token1">)</span><span class="token1">;</span>
JSONObject object <span class="token">=</span> JSONObject<span class="token1">.</span><span class="token2">parseObject</span><span class="token1">(</span>payParam<span class="token1">)</span><span class="token1">;</span>
this<span class="token1">.</span>mchId <span class="token">=</span> object<span class="token1">.</span><span class="token2">getString</span><span class="token1">(</span><span class="token4">"mchId"</span><span class="token1">)</span><span class="token1">;</span>
this<span class="token1">.</span>key <span class="token">=</span> object<span class="token1">.</span><span class="token2">getString</span><span class="token1">(</span><span class="token4">"key"</span><span class="token1">)</span><span class="token1">;</span>
this<span class="token1">.</span>reqUrl <span class="token">=</span> object<span class="token1">.</span><span class="token2">getString</span><span class="token1">(</span><span class="token4">"reqUrl"</span><span class="token1">)</span><span class="token1">;</span>
<span class="token1">}</span>
public String <span class="token2">getMchId</span><span class="token1">(</span><span class="token1">)</span> <span class="token1">{</span>
<span class="token6">return</span> mchId<span class="token1">;</span>
<span class="token1">}</span>
public void <span class="token2">setMchId</span><span class="token1">(</span>String mchId<span class="token1">)</span> <span class="token1">{</span>
this<span class="token1">.</span>mchId <span class="token">=</span> mchId<span class="token1">;</span>
<span class="token1">}</span>
public String <span class="token2">getKey</span><span class="token1">(</span><span class="token1">)</span> <span class="token1">{</span>
<span class="token6">return</span> key<span class="token1">;</span>
<span class="token1">}</span>
public void <span class="token2">setKey</span><span class="token1">(</span>String key<span class="token1">)</span> <span class="token1">{</span>
this<span class="token1">.</span>key <span class="token">=</span> key<span class="token1">;</span>
<span class="token1">}</span>
public String <span class="token2">getReqUrl</span><span class="token1">(</span><span class="token1">)</span> <span class="token1">{</span>
<span class="token6">return</span> reqUrl<span class="token1">;</span>
<span class="token1">}</span>
public void <span class="token2">setReqUrl</span><span class="token1">(</span>String reqUrl<span class="token1">)</span> <span class="token1">{</span>
this<span class="token1">.</span>reqUrl <span class="token">=</span> reqUrl<span class="token1">;</span>
<span class="token1">}</span>
<span class="token1">}</span>
```
```
在調用上游通道接口時,當需要使用配置參數時,我們可以這樣使用。
```
<pre class="calibre25">```
SwiftpayConfig swiftpayConfig <span class="token">=</span> <span class="token6">new</span> <span class="token2">SwiftpayConfig</span><span class="token1">(</span><span class="token2">getPayParam</span><span class="token1">(</span>payOrder<span class="token1">)</span><span class="token1">)</span><span class="token1">;</span>
map<span class="token1">.</span><span class="token2">put</span><span class="token1">(</span><span class="token4">"mch_id"</span><span class="token1">,</span> swiftpayConfig<span class="token1">.</span><span class="token2">getMchId</span><span class="token1">(</span><span class="token1">)</span><span class="token1">)</span><span class="token1">;</span>
```
```
#### (5)通道接口配置
以上支付通道的支付接口,回調接口都已經實現,這時需要在運營平臺創建通道接口配置,才可使用。
`創建支付接口類型`
進入:運營平臺 > 支付配置 > 支付接口類型 > 新增接口類型

- 接口類型代碼:這個和支付通道的通道名稱是一致的,如:swiftpay。
- 接口類型名稱:對應上游支付通道名稱,如:威富通支付。
- 配置定義描述:這里是和我們上面提到的通道配置類對應,內容為json格式,描述了生成該通道配置賬戶界面的表單內容。可使用官方工具生成:<https://www.jeequan.com/dev/tool.html>
- 回調IP白名單:如果配置了IP,那么只有該IP下的回調才會通過
- 訂單超時時間:如果配置了,那么過了超時時間,系統會自動將訂單關閉
`創建支付接口`
進入:運營平臺 > 支付配置 > 支付接口 > 新增支付接口

- 接口代碼:這里對應我們我們為每個支付通道定義的支付接口,如威富通的微信掃碼支付,名字為:swiftpay\_wxpay\_native。
- 接口類型:選擇對應的通道名稱。
- 支付類型:根據具體的支付場景,選擇對應的支付類型。
`通道賬號配置`
進入:運營平臺 > 支付配置 > 支付通道 > 子賬戶

賬戶配置的表單,就來自上面配置的支付接口類型的配置定義描述,也對應通道配置類中的屬性。
### 2. 代付通道
代付通道的實現參考支付通道即可,這里調用的是上游的代付接口。下面給出幾個核心邏輯類,相信聰明你的一定知道如何對接的。
代付下單入口類:`AgentpayController`
轉賬實現類名稱格式:`通道名稱+TransService`,重寫`trans`方法。
轉賬判斷是否成功很關鍵,一定要在`明確成功`或`失敗`時,才可以設置代付最終業務結果。
對接上游通道代付接口時,如果判斷代付接口,一般有三種方式:
1. 調代付接口直接同步響應結果,明確告訴代付已經成功,該種最簡單直接處理業務即可。
2. 調代付接口時,上游通道同步返回申請成功,需要主動查詢代付結果。在我們系統,可通過如下代碼實現。
```
<pre class="calibre25">```
<span class="token5">// 交易處理中</span>
_log<span class="token1">.</span><span class="token2">info</span><span class="token1">(</span><span class="token4">"{} >>> 轉賬處理中"</span><span class="token1">,</span> logPrefix<span class="token1">)</span><span class="token1">;</span>
JSONObject msgObj <span class="token">=</span> <span class="token6">new</span> <span class="token2">JSONObject</span><span class="token1">(</span><span class="token1">)</span><span class="token1">;</span>
msgObj<span class="token1">.</span><span class="token2">put</span><span class="token1">(</span><span class="token4">"count"</span><span class="token1">,</span> <span class="token3">1</span><span class="token1">)</span><span class="token1">;</span>
msgObj<span class="token1">.</span><span class="token2">put</span><span class="token1">(</span><span class="token4">"transOrderId"</span><span class="token1">,</span> transOrderId<span class="token1">)</span><span class="token1">;</span>
msgObj<span class="token1">.</span><span class="token2">put</span><span class="token1">(</span><span class="token4">"channelName"</span><span class="token1">,</span> <span class="token2">getChannelName</span><span class="token1">(</span><span class="token1">)</span><span class="token1">)</span><span class="token1">;</span>
mq4TransQuery<span class="token1">.</span><span class="token2">send</span><span class="token1">(</span>msgObj<span class="token1">.</span><span class="token2">toJSONString</span><span class="token1">(</span><span class="token1">)</span><span class="token1">,</span> <span class="token3">10</span> <span class="token">*</span> <span class="token3">1000</span><span class="token1">)</span><span class="token1">;</span> <span class="token5">// 10秒后查詢</span>
<span class="token6">return</span> QueryRetMsg<span class="token1">.</span><span class="token2">waiting</span><span class="token1">(</span><span class="token1">)</span><span class="token1">;</span>
```
```
通過mq方式,設置延遲查詢,直到查詢到成功或失敗。可參考項目中杉德代付的代碼:`SandpayTransService`。
3. 調代付接口時,上游通道同步返回申請成功,需要接收上游通道的異步回調,確認代付結果。在我們系統中,需要實現回調方法接口。
增加回調處理類,名稱格式為:`通道名稱 + TransNotifyService`,重寫doNotify方法。可參考項目中雙乾代付的代碼:`SqpayTransNotifyService`。
`如果還是搞不定,可以聯系售后技術支持哦!`