場景
微信小程式、百度小程式、頭條小程式.. 每種小程式都有不同格式的回撥資料,不同的驗籤方式,不同的回撥應答,但業務邏輯是相同的,不可能為每種都寫一套程式碼。找了一圈沒發現這方面的元件,於是寫了一個簡單的 demo,已經調通,僅供參考。
分析
- 商戶業務邏輯單獨拿出來。
- 支付回撥歸納為五步: 非同步通知的原始資料->驗籤->提取必要資料->執行商戶邏輯->返回應答。
檔案概況
回撥處理檔案 demo
商戶業務邏輯類 demo
1. 商戶業務邏輯契約類
interface
<?php
namespace App\Library\Notify;
interface PayNotifyInterface
{
/**
* 非同步回撥檢驗完成後,回撥客戶端的業務邏輯
* 業務邏輯處理,必須實現該類。
*
* @param array $data 經過處理後返回的統一格式的回撥資料
* @return boolean
*/
public function notifyProcess(array $data);
}
商戶業務邏輯類實現契約類
<?php
namespace App\Service;
use App\Library\Notify\PayNotifyInterface;
use Illuminate\Support\Facades\Log;
use App\Model\Pay;
class PaymentNotify implements PayNotifyInterface
{
public function notifyProcess(array $data)
{
$pay_type = $data['pay_type'];
switch ($pay_type) {
case 'case1':
$result = $this->case1($data); // 型別1
break;
case 'case2':
$result = $this->case2($data); // 型別2
break;
default:
$result = false;
}
return $result;
}
public function case1($data)
{
// 檢查訂單狀態
$pay = Pay::where('order_no',$data['order_no'])->first();
if ( !$pay || $pay->pay_status != 0 || $pay->pay_amount != $data['pay_amount'] ) { return false; }
// ...業務邏輯
return true;
}
public function case2($data)
{
// ...
}
}
/**
返回給商戶業務邏輯的資料
{"notify_time":"2020-05-13 16:15:10",
"order_no":"20200513576982804164",
"pay_amount":"3",
"transaction_id":"82058950219732",
"trade_state":"2",
"return_data":{"pay_type":"commonpub"},
"pay_type":"commonpub",
"raw_data"(原始資料):{"unitPrice":"3","orderId":"82058950219732","payTime":"1589357710","dealId":"431111113","tpOrderId":"20200513576982804164","count":"1","totalMoney":"3","hbBalanceMoney":"0","userId":"4211111093","promoMoney":"0","promoDetail":"","hbMoney":"0","giftCardMoney":"0","payMoney":"3","payType":"1117","returnData":{"pay_type":"commonpub"},"partnerId":"6111101","rsaSign":"TdHNFqt\/0bPwA2cQ8nbNG9iJL8GkEHG0Iwfk4iVYhUZ3lRYEhYx7qzYaL3easzoglTtVedsUv+WtrNmLx6ufcf2EcS86HXP2wmf5wPNxfZJk+XDzkgwqYHU1u9pWNcxn2hwdLB1NONpRogBHlsY112wAPpRGVZJtjtuzYf+2Kcs=","status":"2"}}
*/
2. 通用回撥處理類
<?php
namespace App\Library\Notify;
use App\Library\Notify\PayNotifyInterface;
/**
* 各小程式支付平臺的不同實現 需要繼承該類
* 獲取非同步通知的原始資料-》驗籤-》執行商戶邏輯-》返回應答
*/
abstract class NotifyAbstract
{
/**
* 入口
* @param PayNotifyInterface $notify
* final 不能被繼承
*/
final public function handle(PayNotifyInterface $notify)
{
// 獲取非同步通知的原始資料
$notifyData = $this->getNotifyData();
if ($notifyData === false) {
return $this->replyNotify(false, '獲取通知資料失敗');
}
// 驗籤
$checkRet = $this->checkNotifyData($notifyData);
if ($checkRet === false) {
return $this->replyNotify(false, '返回資料驗籤失敗,可能資料被篡改');
}
// 回撥商戶的業務邏輯
$flag = $this->callback($notify, $notifyData);
if ($flag) {
$msg = 'OK';
} else {
$msg = '商戶邏輯呼叫出錯';
}
// 應答
return $this->replyNotify($flag, $msg);
}
/**
* 回撥商戶的業務邏輯,根據返回的 true 或者 false
* @param PayNotifyInterface $notify
* @param array $notifyData 原始回撥資料
*
* @return boolean
*/
protected function callback(PayNotifyInterface $notify, array $notifyData)
{
$data = $this->getRetData($notifyData); // 提取必要資料返回商戶
if ($data === false) {
return false;
}
return $notify->notifyProcess($data); //處理商戶業務邏輯
}
/**
* 獲取回撥通知資料 進行簡單處理 返回陣列
*
* 如果獲取資料失敗,返回false
*
* @return array|false
*/
abstract public function getNotifyData();
/**
* 檢查非同步通知的資料是否合法
*
* 如果檢查失敗,返回false
*
* @param array $data 由 $this->getNotifyData() 返回的原始回撥陣列
* @return boolean
*/
abstract public function checkNotifyData(array $data);
/**
* 向客戶端返回必要統一的資料
* @param array $data 由 $this->getNotifyData() 返回的原始回撥陣列
* @return array|false
*/
abstract protected function getRetData(array $data);
/**
* 根據返回結果,回答支付機構。是否回撥通知成功
* @param boolean $flag 每次返回的bool值
* @param string $msg 通知資訊,錯誤原因
* @return mixed
*/
abstract protected function replyNotify($flag, $msg = 'OK');
}
各支付平臺繼承 NotifyAbstract 以百度小程式為例
<?php
namespace App\Library\Notify;
use App\Library\Notify\NotifyAbstract;
use App\Library\BaiduSign;
use Illuminate\Support\Facades\Log;
/**
回撥原始資料
{"unitPrice":"3","orderId":"82058950219732","payTime":"1589357710","dealId":"43111113","tpOrderId":"20200513576982804164","count":"1","totalMoney":"3","hbBalanceMoney":"0","userId":"4226669093","promoMoney":"0","promoDetail":"","hbMoney":"0","giftCardMoney":"0","payMoney":"3","payType":"1117","returnData":"{\"pay_type\":\"commonpub\"}","partnerId":"6011111","rsaSign":"TdHNFqt\/0bPwA2cQ8nbNG9iJL8GkEHG0Iwfk4iVYhUZ3lRYEhYx7qzYaL3easzoglTtVedsUv+WtrNmLx6ufcf2EcS86HXP2wmf5wPNxfZJk+XDzkgwqYHU1u9pWNcxn2hwdLB1NONpRogBHlsY112wAPpRGVZJtjtuzYf+2Kcs=","status":"2"}
*/
class BdNotify extends NotifyAbstract
{
// 獲取原始回撥資料 轉成陣列 array | false
public function getNotifyData()
{
$data = $_POST;
if (empty($data) || ! is_array($data)) {
return false;
}
return $data;
}
// 支付狀態 及 驗籤 bool
public function checkNotifyData(array $data)
{
if( $data['status'] != 2 ){
Log::error('百度支付回撥資料檢查:支付狀態不為2');
return false;
}
$flag = (new BaiduSign() )->checkSignWithRsa($data);
return $flag;
}
// 獲取必要欄位 返回商戶業務邏輯 array | false
protected function getRetData(array $data)
{
$data['returnData'] = isset($data['returnData']) ? json_decode($data['returnData'],true) : [];
if( !isset($data['returnData']['pay_type']) ){
Log::error('百度支付獲取必要欄位:缺少 pay_type 引數');
return false;
}
// 注意 這裡向業務邏輯返回統一的格式
$retData = [
'notify_time' => date('Y-m-d H:i:s',$data['payTime']),
'order_no' => $data['tpOrderId'],
'pay_amount' => $data['totalMoney'],
'transaction_id'=> $data['orderId'],
'trade_state' => $data['status'],
'return_data' => $data['returnData'],
'pay_type' => $data['returnData']['pay_type'],
'raw_data' => $data, //原始資料
];
return $retData;
}
// 應答支付平臺
protected function replyNotify($flag, $msg = 'OK')
{
Log::error('百度支付回撥應答:'.$msg);
$ret['errno'] = 0;
if ($flag) {
$ret['msg'] = 'success';
$ret['data'] = ['isConsumed'=>2];
} else {
$ret['msg'] = 'fail';
$ret['data'] = ['isConsumed'=>2,'isErrorOrder'=>1];
}
return $ret;
}
}
在回撥方法中呼叫
<?php
namespace App\Http\Controllers\Api\V1;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Library\Notify\BdNotify; // 百度小程式回撥類
use App\Service\PaymentNotify; // 商戶業務邏輯類
use Illuminate\Support\Facades\Log;
class NotifyController extends Controller
{
public function index($platform)
{
if (!in_array($platform, ['baidu'])) {
return '暫不支援';
}
$callback = new PaymentNotify();
switch ($platform) {
case 'baidu':
$ret = (new BdNotify())->handle($callback);
break;
}
return $ret;
}
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結