建立Laravel專案
一開始先Git,這幾乎是一定要的啊!
下載歐付寶SDK,本篇使用PHP SDK
git clone https://github.com/o-pay/Payment_PHP
|
將SDK移到Laravel裡頭的app底下
cp Payment_PHP/sdk/AllPay.Payment.Integration.php AllPay/app/
|
建立等等測試用的Controller
php artisan make:controller PaymentsController
|
從剛剛的SDK包裡面,複製example到我們的controller裡,本篇使用All的example,如下:
include('AllPay.Payment.Integration.php'); try { $obj = new AllInOne();
$obj->ServiceURL = "https://payment-stage.opay.tw/Cashier/AioCheckOut/V5"; $obj->HashKey = '5294y06JbISpM5x9' ; $obj->HashIV = 'v77hoKGq4kWxNNIS' ; $obj->MerchantID = '2000132'; $obj->EncryptType = EncryptType::ENC_SHA256;
$MerchantTradeNo = "Test".time();
$obj->Send['ReturnURL'] = 'http://gw.grazia.tw/sdk/op_sdk/op_payment/example/simple_ServerReplyPaymentStatus.php' ; $obj->Send['MerchantTradeNo'] = $MerchantTradeNo; $obj->Send['MerchantTradeDate'] = date('Y/m/d H:i:s'); $obj->Send['TotalAmount'] = 2000; $obj->Send['TradeDesc'] = "good to drink"; $obj->Send['ChoosePayment'] = PaymentMethod::ALL;
array_push($obj->Send['Items'], array('Name' => "歐付寶黑芝麻豆漿", 'Price' => (int)"2000", 'Currency' => "元", 'Quantity' => (int) "1", 'URL' => "dedwed"));
$obj->CheckOut(); } catch (Exception $e) { echo $e->getMessage(); }
|
我們將上面一些機敏資訊,移到Laravel的.env檔裡面,如下:
$obj->HashKey = env('HASHKEY');
$obj->HashIV = env('HASHIV');
$obj->MerchantID = env('MERCHANTID');
$obj->Send['ReturnURL'] = env('ALLPAYRETURNURL');
$obj->Send['ClientBackURL'] = $request->ClintBackURL;
|
- .env檔內如下:
ALLPAYRETURNURL=https:
HASHKEY=5294y06JbISpM5x9 HASHIV=v77hoKGq4kWxNNIS MERCHANTID=2000132
|
使用use
代替include
- 刪除
include('AllPay.Payment.Integration.php');
|
- 新增AllPay.Payment.Integration.php到composer.json file
"autoload-dev": { "psr-4": { "Tests\\": "tests/" }, "files": [ "app/Helpers.php", "app/AllPay.Payment.Integration.php" ]
|
- 在terminal下達
- 在controller檔案裡,use新的class
namespace App\Http\Controllers;
use AllInOne; use EncryptType; use Exception; use Illuminate\Http\Request; use PaymentMethod;
|
建立金流訂單 (這部分屬個人表格設計,帶入參數每個人都不同)
因為會一次性的寫入兩張表格,所以這邊會使用 Laravel 的 transaction 來寫入資料
//總金額 $totalAmount = Order::getTotalAmountForPayments($orders); //商品訂單編號 $ordersName = Order::getOrdersNameForPayments($orders); //金流訂單編號 $MerchantTradeNo = time() . Helpers::createAUniqueNumber(); //金流訂單建立時間 $MerchantTradeDate = date('Y/m/d H:i:s'); //金流訂單敘述 $TradeDesc = 'BuyBuyGo'; //數量 $quantity = 1;
//因為同時建立兩張表格,這邊使用Laravel的transaction功能來防止資料庫資料不一 //transaction開始 DB::beginTransaction();
//以下動作需全部完成無錯誤,否則終止並回朔 try { $payment_service_order = new PaymentServiceOrders();
$payment_service_order->user_id = User::getUserID($request); //金流服務商ID $payment_service_order->payment_service_id = $thirdPartyPaymentService->id; $payment_service_order->expiry_time = (new Carbon())->now()->addDay(1)->toDateTimeString(); $payment_service_order->MerchantID = env('MERCHANTID'); $payment_service_order->MerchantTradeNo = $MerchantTradeNo; $payment_service_order->MerchantTradeDate = $MerchantTradeDate; $payment_service_order->TotalAmount = $totalAmount; $payment_service_order->TradeDesc = $TradeDesc; //商品訂單的編號 $payment_service_order->ItemName = $ordersName; $payment_service_order->save();
foreach ($orders as $order) { $order_relations = new OrderRelations(); $order_relations->payment_service_id = $thirdPartyPaymentService->id; $order_relations->payment_service_order_id = $payment_service_order->id; $order_relations->order_id = $order->id; $order_relations->save(); } //若有錯誤,則停止並回朔,回報自訂錯誤訊息 } catch (Exception $e) { DB::rollBack();
return Helpers::result('false', 'Something went wrong with DB', 400); } //若無錯誤,則寫入資料庫 DB::commit();
|
為PaymentsController建立一支API
- 到routes資料夾底下的api.php
- 增加route
Route::post('pay', 'PaymentsController@pay');
|
建立一頁最簡單的的html
- 直接更改Laravel內建welcome.blade的內容,如下:
<!DOCTYPE html> <html> <head> <title>Facebook Login JavaScript Example</title> <meta charset="UTF-8"> </head> <body> // 這邊需輸入PaymentsController的API <form action="/api/pay" method="POST"> @csrf() <input type="checkbox" value="1" name="order_id[]"> <input type="checkbox" value="2" name="order_id[]"> <input type="checkbox" value="3" name="order_id[]"> <input type="hidden" value="https://64b30ea0.ngrok.io/" name="ClintBackURL"> <button type="submit">Submit</button> </form> </body> </html>
|
簡單傳送測試
- 到Laravel首頁,此時此頁面應已經變更為我們剛剛建立的簡單form頁面
- 什麼都不要勾選,點選submit
- 成功到了歐付寶的付款頁面
建立Log
- 為了要知道當我們成功付款之後,歐付寶會回傳什麼給我們,我們需要用Log來看看回傳的東西
- 有沒有一個地方,是所有的請求跟回饋都一定會進出通過,而且可以讓我們控制的? 這似乎是個完美記log的地方
- 我們可以建立一個middleware,然後在middleware裏頭使用Laravel的Log功能,將所有進來的請求跟我們回饋的東西全都記下來
- 建立middleware
- 於terminal頁面
php artisan make:middleware TestLog
|
- 註冊middleware
- 到
/app/Http/Kernel.php
檔案裡頭,加上我們剛剛建立的middleware
protected $middleware = [ \App\Http\Middleware\CheckForMaintenanceMode::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, \App\Http\Middleware\TrustProxies::class, \App\Http\Middleware\TestLog::class, ];
|
$response = $next($request); log::info([ $request->header(), $request->getMethod(), $request->getRequestUri(), $request->all(), $response->getStatusCode(), $response->getContent() ]); return $response;
|
建立public url
- 我們要取得第三方回傳的資訊,所以我們必須要有一個public url來接收第三方的response
- 我們可以使用ngrok來取得public url
- 至ngrok官網安裝ngrok
- 將ngrok變更為可全域執行
- 先取得public url,在terminal中
- 在terminal中,位於AllPay專案資料夾內,開啟本機通道
- 複製ngrok產生的public https url
建立接收的function
- 我們準備要來接歐付寶回傳的訊息了,我們需要建立一個function,並且接到之後,可以在裡面做我們想做的事
- 在PaymentsController裡頭,我們先建一個function
receive
,如下:public function receive() { }
|
- 針對此function,建立一個API
Route::post('pay', 'PaymentsController@pay'); Route::post('receive', 'PaymentsController@receive');
|
設定ReturnURL
- 於.env檔內,如有照之前步驟,應有以下參數,將複製的public url 貼上
ALLPAYRETURNURL=https://163be100.ngrok.io/api/recevie
|
- 你的連結跟我的不一樣哦,別貼我的
付款測試
- 再次連到歐付寶付款頁面
- 登入歐付寶提供的買家測試帳號
- 使用歐付寶提供的測試信用卡付款
- 卡號:
- 有效期限:請大於目前月/年,例:12 / 20
- 末三碼:
- 付款後,我們到log去看一下有沒有收到歐付寶的回饋,於terminal,位於AllPay的資料夾內
cat storage/logs/laravel-2019-02-08.log`
|
- 咦,有收到歐付寶的回饋了!
'MerchantID' => '2000132', 'MerchantTradeNo' => 'Test1549597724', 'PayAmt' => '2000', 'PaymentDate' => '2019/02/08 11:49:03', 'PaymentType' => 'Credit_CreditCard', 'PaymentTypeChargeFee' => '20', 'RedeemAmt' => '0', 'RtnCode' => '1', 'RtnMsg' => '交易成功', 'SimulatePaid' => '0', 'TradeAmt' => '2000', 'TradeDate' => '2019/02/08 11:48:44', 'TradeNo' => '1902081148440800', 'CheckMacValue' => '5B1EE24B0E9D600C65578DD82D3168E2ED56799453577E17E1EBEFC536BD7EAF',
|
驗證
- 試想,如果有人不小心知道了你的API,然後對方也是開發者,他如果跑到你的服務購買商品,然後呼叫你的API結帳,你如何辨別?
- 所以說,第三方跟廠商會有一套只有雙方身份對了,驗證才能通過的機制
- 驗證機制大概就是,所以你傳出去的資料,每一項欄位,會經過一套只有雙方適用的公式下去計算,最後會得出一串CheckMacValue,眼尖的朋友應該已經看到,在我們收到的訊息最下面一個欄位帶的就是這串資料。
- 詳細的公式各位朋友可參照官方網站。
- 由於本篇使用官方的SDK,所以本篇將教大家如何使用官方SDK來驗證收到的資訊
- 首先,我們到官方SDK的檔案中
app/AllPay.Payment.Integration.php
,搜尋名為CheckMacValue
的class
CheckMacValue
class裡頭,有一個名為generate
的function,大家可以看一下它是怎麼寫的,這就是產生這串CheckMacValue的公式。
- 所以說,我們只要通過這套公式,驗證我們收到的訊息之中,除了
CheckMacValue
這個欄位的資訊,那理應得到跟回傳的CheckMacValue
一模一樣的值
- 先取得回傳資訊中,除了
CheckMacValue
之外的所有資訊,我們可以使用以下的code$parameters = $paymentResponse->except('CheckMacValue');
|
- 再來,取得歐付寶回傳的
CheckMacValue
$receivedCheckMacValue = $paymentResponse->CheckMacValue;
|
- 接下來,使用官方的generate function,帶入我們收到的資訊,來產出正確的
CheckMacValue
$calculatedCheckMacValue = CheckMacValue::generate($parameters, env('HASHKEY'), env('HASHIV'), EncryptType::ENC_SHA256);
|
- 最後,比較兩者的值是否一樣?如果一樣,表示這則訊息的確來自歐付寶,如果不同,那此資訊沒有可信度
if($receivedCheckMacValue == $calculatedCheckMacValue) return true; return false;
|
驗證之後呢?
- 確認資訊來源正確之後,我們就可以依據收到的資訊下去做事了!
- 比方說,付款了做些什麼,付款失敗又做些什麼
- 本篇範例為收到付款之後,將資料庫內訂單標記付款,並發email通知買家,如下:
if (PaymentServiceOrders::checkIfCheckMacValueCorrect($request) && PaymentServiceOrders::checkIfPaymentPaid($request->RtnCode)) { $paymentServiceOrder = (new PaymentServiceOrders)->where('MerchantTradeNo', $request->MerchantTradeNo)->first(); $paymentServiceOrder->update(['status' => 1, 'expiry_time' => null]);
$orderRelations = $paymentServiceOrder->where('MerchantTradeNo', $request->MerchantTradeNo)->first()->orderRelations; Order::updateStatus($orderRelations);
$payerEmail = $paymentServiceOrder->user->email;
if ($payerEmail !== null) Mail::to($payerEmail)->send(new PaymentReceived($paymentServiceOrder, $orderRelations));
return '1|OK'; }
|
- 最後記得別忘記return ‘1|OK’, 通知歐付寶我們已經收到付款囉!
一些沒說到的事
- 歐付寶測試帳號中,除了信用卡之外,也支援多種其他方式付款哦!可於登入買家測試帳號之後使用。
- 超商或轉帳付款方式,需特別登入歐付寶提供的後台測試帳號,方可達到模擬付款!
退款
退款範例如下:
public static function refund($order, $paymentServiceInstance, $orderRelation) { try { $obj = new AllInOne();
$obj->ServiceURL = "https://payment-stage.opay.tw/Cashier/AioChargeback"; // 服務位置 $obj->HashKey = env('HASHKEY'); // 測試用Hashkey,請自行帶入AllPay提供的HashKey $obj->HashIV = env('HASHIV'); // 測試用HashIV,請自行帶入AllPay提供的HashIV $obj->MerchantID = env('MERCHANTID'); // 測試用MerchantID,請自行帶入AllPay提供的MerchantID $obj->EncryptType = EncryptType::ENC_SHA256; // CheckMacValue加密類型,請固定填入1,使用SHA256加密
$obj->ChargeBack['MerchantTradeNo'] = $paymentServiceInstance->MerchantTradeNo; // 當初訂單成立時,提供的訂單號碼
$obj->ChargeBack['TradeNo'] = $paymentServiceInstance->TradeNo; // AllPay提供的訂單號碼 $obj->ChargeBack['ChargeBackTotalAmount'] = $order->total_amount; // 退款金額 $obj->AioChargeback();
} catch (Exception $e) { // 若錯誤,return return Helpers::result(true, 'Something wrong happened', 200); // Debug模式,印出錯誤 echo $e->getMessage(); }
}
|
帶入參數可參考官方文件,選項12,會員通知退款。
留言