PHP + Laravel 雞排聯盟API實作: 串接歐付寶金流API

by Benmr

前言

在前面的幾篇文章中
我們陸續完成了雞排聯盟API的雛型

那麼
如果我們要新增訂單並以第三方金流結帳的話
要怎麼處理呢?

以下就以歐付寶的API為範例
介紹如何串接歐付寶的信用卡支付API
達成結帳的效果

流程簡介

根據歐付寶官網介紹 https://developers.opay.tw/AioCreditCard/CreateOrder
信用卡金流服務流程如下
file

而我的文章的大略流程如下:

  1. clone 歐付寶SDK (什麼SDK? 參考文章)
    並複製到專案資料夾底下
  2. 建立 Controller 並複製 SDK 內的 example 以串接歐付寶API
  3. 建立 Orders table 紀錄交易資料
  4. 建立前台畫面輸入訂單
  5. 實際建立訂單以確認流程ok

實作

1. clone 歐付寶SDK,並複製到專案資料夾底下

step 1


clone 歐付寶 SDK URL 如下
https://github.com/o-pay/Payment_PHP

step 2


複製到專案資料夾底下
總共有2個
file

2. 建立 Controller 並複製 SDK 內的 example 以串接歐付寶API

step 1


在 terminal 內下指令建立 controller

php artisan make:controller OpayPaymentsController

step 2


複製 example 到我們的 controller 裡
首先是付款

<?php
class OpayPaymentController extends Controller
{
/**
*
*/
    public function pay(Request $request) {

    //載入SDK(路徑可依系統規劃自行調整)
    include('Opay.Payment.Integration.php');
    try {

        $obj = new OpayAllInOne();

        //服務參數
        $obj->ServiceURL  = "https://payment-stage.opay.tw/Cashier/AioCheckOut/V5";         //服務位置
        $obj->HashKey     = '5294y06JbISpM5x9' ;                                            //測試用Hashkey,請自行帶入OPay提供的HashKey
        $obj->HashIV      = 'v77hoKGq4kWxNNIS' ;                                            //測試用HashIV,請自行帶入OPay提供的HashIV
        $obj->MerchantID  = '2000132';                                                      //測試用MerchantID,請自行帶入OPay提供的MerchantID
        $obj->EncryptType = OpayEncryptType::ENC_SHA256;                                    //CheckMacValue加密類型,請固定填入1,使用SHA256加密

        //基本參數(請依系統規劃自行調整)
        $MerchantTradeNo = "Test".time();

        $obj->Send['ReturnURL']         = 'http://localhost/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']     = OpayPaymentMethod::ALL;                                 //付款方式:全功能

        //訂單的商品資料
        array_push($obj->Send['Items'], array('Name' => "歐付寶黑芝麻豆漿", 'Price' => (int)"2000",
                   'Currency' => "元", 'Quantity' => (int) "1", 'URL' => "dedwed"));

        //產生訂單(auto submit至OPay)
        $obj->CheckOut();

    } catch (Exception $e) {
        echo $e->getMessage();
      }
    }
}
?>

接著是接收歐付寶 response 的部份

  public function receive(Request $request) {

    //載入SDK(路徑可依系統規劃自行調整)
    include('Opay.Payment.Integration.php');
    try {

            $obj = new OpayAllInOne();

            /* 服務參數 */
            $obj->HashKey     = '5294y06JbISpM5x9' ;
            $obj->HashIV      =  'v77hoKGq4kWxNNIS' ;
            $obj->MerchantID  = '2000132';
            $obj->EncryptType = OpayEncryptType::ENC_SHA256;

            /* 取得回傳參數 */
            $arFeedback = $obj->CheckOutFeedback();

            // 參數寫入檔案
            if(true)
            {
                $sLog_Path  = __DIR__.'/sample_payment_return.log' ; // LOG路徑
                $sLog = '+++++++++++++++++++++++++++++++++++++++ 接收回傳參數 ' . date('Y-m-d H:i:s') . ' ++++++++++++++++++++++++++++++++++++++++++++' . "\n";
                $fp=fopen($sLog_Path, "a+");
                fputs($fp, $sLog);
                fclose($fp);

                $sLog_File =  print_r($arFeedback, true). "\n";
                $fp=fopen($sLog_Path, "a+");
                fputs($fp, $sLog_File);
                fclose($fp);
            }

            echo '1|OK' ;

    } catch (Exception $e) {
        if(true)
        {
            $sLog_Path  = __DIR__.'/sample_payment_return.log' ; // LOG路徑
            $sLog = '+++++++++++++++++++++++++++++++++++++++ 接收回傳參數(ERROR) ' . date('Y-m-d H:i:s') . ' ++++++++++++++++++++++++++++++++++++++++++++' . "\n";
            $fp=fopen($sLog_Path, "a+");
            fputs($fp, $sLog);
            fclose($fp);

            $sLog_File =  $e->getMessage(). "\n";
            $fp=fopen($sLog_Path, "a+");
            fputs($fp, $sLog_File);
            fclose($fp);
        }
     }
    }

step 3


置換掉include

因為後面會一直include 歐付寶的 SDK 進來
所以這邊一次性置換成 use

首先是刪掉 example 原本的 use

//載入SDK(路徑可依系統規劃自行調整)
include('Opay.Payment.Integration.php');

接著新增AllPay.Payment.Integration.php到composer.json file

"autoload-dev": {
    "psr-4": {
        "Tests\\": "tests/"
    },
    "files": [
        "app/AllPay.Payment.Integration.php" // 新增這個files
    ]
},

ps.
有關PSR-4 Autoloader 機制
可以參考以下文章
http://blog.tonycube.com/2016/09/php-psr-4-autoloader.html
大概就是說我們直接拿composer的autoload來自動載入對應的檔案

所以我們接著在 terminal 下 autoload 指令

composer dump-autoload

最後我們在 OpayPayment controller 裡 use 會使用到的 class

<?php
namespace App\Http\Controllers;

use OpayAllInOne;
use OpayEncryptType;
use Exception;
use OpayPaymentMethod;

3. 建立 orders table & Model 紀錄交易資料

這邊我們要用最簡單的方法生成訂單相關的 db table 及 model
以簡單達到兩個目的

1. 能成立訂單

2. 能紀錄訂單是否已付款

step 1


生成 orders table
我們將使用 migration
在 terminal 內下指令

php artisan make:model Order -mc
  • m: 預產生migration
  • c: 預產生controller

step 2


完成 migration 內容
首先到剛剛用指令生成的 migration 裡新增我們要的欄位

class CreateOrdersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->integer('user_id');
            $table->string('shop_name');
            $table->string('item_name');
            $table->string('item_description');
            $table->integer('unit_price');
            $table->integer('quantity');
            $table->integer('total_amount');
            $table->string('merchant_trade_no'); // 預計拿這個訂單編來當作是PK
            $table->boolean('status')->default(false);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('orders');
    }
}

step 3


執行 migration

php artisan migrate

這樣子我們的 orders table 就完成了
file

step 4


完成 Order model
簡單寫兩個方法在裡面
分別是

1. 成立訂單
2. 在付款完成後將status改為1(付款完)

code 如下

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */

    public static function createOrder($item) {
        $order = new Order();
        $order->name = $item['order_name'];
        $order->user_id = $item['user_id'];
        $order->shop_name = $item['shop_name'];
        $order->item_name = $item['product_name'];
        $order->item_description = isset($item['description']) ? $item['description'] : '';
        $order->unit_price = $item['price'];
        $order->quantity = $item['qty'];
        $order->total_amount = $item['price'] * $item['qty'];
        $order->merchant_trade_no = $item['merchant_trade_no'];
        $order->save();
        return $order;
    }

    public static function updateOrderStatus($merchant_trade_no) {
        $order = Order::where('merchant_trade_no', $merchant_trade_no)->update(['status' => 1]);
        return $order;
    }
}

4. 建立前台畫面輸入訂單

在前面的文章中我們已經做好註冊/登入功能
所以這邊我們就直接把登入後的id 給存到 session 裡
讓後續建訂單時可以存到 db

step 1


在 laravel 預設的 welcome.blade.php 新增一個連結到下訂單處
同時把 user_id 給存入 session

<div class="links">
    <a href="{{ url('/OpayPayment/') }}">雞排下單連結</a>
    <a href="https://github.com/BenmrChen/" TARGET="_blank">GitHub</a>
    <?php
        if (isset(\Illuminate\Support\Facades\Auth::user()->id)) {
            session()->put('user_id', \Illuminate\Support\Facades\Auth::user()->id);
        }
    ?>
</div>

file

step 2


新增 route
web.php

Route::get('OpayPayment', function () {
    return view('OpayPayment');
});

api.php

Route::post('pay', 'OpayPaymentController@pay');
Route::post('receive', 'OpayPaymentController@receive');

step 3


簡單生成一個下單頁面

<!DOCTYPE html>
<html>
<head>
    <title>OpayPayment Exercise</title>
    <meta charset="UTF-8">
</head>
<body>
<form method="POST" action="/api/pay">
    @csrf
    <p>店舖名:
        <select name="shop_name" id="">
            <option value="TW_No_1">台灣第一家雞排</option>
            <option value="World_No_1">世界第一家雞排</option>
        </select>
    </p>
    <p>雞排種類:
        <select name="product_name" id="">
            <option name="chrispy" value="脆皮雞排">脆皮雞排</option>
            <option name="traditional" value="傳統雞排">傳統雞排</option>
        </select>
    </p>
    <p>數量:
        <select name="qty" id="">
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
            <option value="4">4</option>
            <option value="5">5</option>
        </select>
    </p>
    <input type="hidden" value="http://547776ad.ngrok.io" name="ClintBackURL">
    <input type="hidden" value="<?php echo 

step 4


修改複製過到 controller 的 example
好讓 sdk 可以收到前台傳來的資料做結帳
下面要特別注意的點是
ReturnURL 是設定要讓歐付寶傳值回來的 URL
由於我是用 valet 當作 local 開發環境
所以可以直接在 command line 下

valet share

就會產生 https://b30097bf.ngrok.io 這個url 讓外部ip連
所以請不要跟我用一樣的 url 喔
更多 share 相關可以看官網說明
https://laravel.com/docs/5.8/valet#sharing-sites

 public function pay(Request $request) {
        $MerchantTradeNo = "Test".time();
        // 存入訂單資料
        $item['user_id'] = $request->input('user_id');
        $item['shop_name'] = $request->input('shop_name');
        $item['product_name'] = $request->input('product_name');
        $item['qty']  = $request->input('qty');
        $item['price'] = 65;
        $item['order_name'] = 'test_order';
        $item['merchant_trade_no'] = $MerchantTradeNo;
        $order = Order::createOrder($item);

        $price = 65;
        $qty   = $request->input('qty');
        $name  = $request->input('product_name');

        try {

            $obj = new OpayAllInOne();

            //服務參數
            $obj->ServiceURL  = "https://payment-stage.opay.tw/Cashier/AioCheckOut/V5";         //服務位置
            $obj->HashKey     = '5294y06JbISpM5x9' ;                                            //測試用Hashkey,請自行帶入OPay提供的HashKey
            $obj->HashIV      = 'v77hoKGq4kWxNNIS' ;                                            //測試用HashIV,請自行帶入OPay提供的HashIV
            $obj->MerchantID  = '2000132';                                                      //測試用MerchantID,請自行帶入OPay提供的MerchantID
            $obj->EncryptType = OpayEncryptType::ENC_SHA256;                                    //CheckMacValue加密類型,請固定填入1,使用SHA256加密

            $obj->Send['ReturnURL']         = 'https://b30097bf.ngrok.io/api/receive'; //付款完成通知回傳的網址
            $obj->Send['MerchantTradeNo']   = $MerchantTradeNo;                                       //訂單編號
            $obj->Send['MerchantTradeDate'] = date('Y/m/d H:i:s');                                    //交易時間
            $obj->Send['TotalAmount']       = $price * $qty;                                                   //交易金額
            $obj->Send['TradeDesc']         = "Hen好吃,史勾以!";                                        //交易描述
            $obj->Send['ChoosePayment']     = OpayPaymentMethod::ALL;                                 //付款方式:全功能

            //訂單的商品資料
            array_push($obj->Send['Items'], array('Name' => $name, 'Price' => (int)$price,
                'Currency' => "元", 'Quantity' => (int) $qty, 'URL' => ""));

            //產生訂單(auto submit至OPay)
            $obj->CheckOut();

        } catch (Exception $e) {
            echo $e->getMessage();
        }
    }

同時也修改 receive
在確認到歐付寶傳回來的 response
確認裡頭的 CheckMacValue 和我們這邊 server 生成的一致之後
再去把 order status 改成 1

public function receive(Request $request) {

        try {

            $obj = new OpayAllInOne();

            /* 服務參數 */
            $obj->HashKey     = '5294y06JbISpM5x9' ;
            $obj->HashIV      =  'v77hoKGq4kWxNNIS' ;
            $obj->MerchantID  = '2000132';
            $obj->EncryptType = OpayEncryptType::ENC_SHA256;

            /* 取得回傳參數 */
           $arFeedback = $obj->CheckOutFeedback();
            // 參數寫入檔案
           if(true)
           {
               $sLog_Path  = __DIR__.'/sample_payment_return.log' ; // LOG路徑
               $sLog = '+++++++++++++++++++++++++++++++++++++++ 接收回傳參數 ' . date('Y-m-d H:i:s') . ' ++++++++++++++++++++++++++++++++++++++++++++' . "\n";
               $fp=fopen($sLog_Path, "a+");
               fputs($fp, $sLog);
               fclose($fp);
               Log::info($sLog);

               $sLog_File =  print_r($arFeedback, true). "\n";
               $fp=fopen($sLog_Path, "a+");
               fputs($fp, $sLog_File);
               fclose($fp);
           }
            echo '1|OK' ;
            $MerchantTradeNo = $request['MerchantTradeNo']; // 取得MerchantTradeNo 當作是PK去更更新 order status
            $status = Order::updateOrderStatus($MerchantTradeNo);

        } catch (Exception $e) {
            if(true)
            {
                $sLog_Path  = __DIR__.'/sample_payment_return.log' ; // LOG路徑
                $sLog = '+++++++++++++++++++++++++++++++++++++++ 接收回傳參數(ERROR) ' . date('Y-m-d H:i:s') . ' ++++++++++++++++++++++++++++++++++++++++++++' . "\n";
                $fp=fopen($sLog_Path, "a+");
                fputs($fp, $sLog);
                fclose($fp);
                Log::info($sLog);

                $sLog_File =  $e->getMessage(). "\n";
                $fp=fopen($sLog_Path, "a+");
                fputs($fp, $sLog_File);
                fclose($fp);
            }
        }
    }

5. 實際建立訂單以確認流程ok

step 1


進入首頁 並點右上右登入 點選進入下單頁面
file

step 2


進入下單面後確認訂單 submit

step 3


此時會導入歐付寶的頁面 裡面會有剛剛點選的雞排訂單
file

同時check 一下 db
裡頭有剛剛新建的訂單
可以發現

1. status = 0 ,代表還未付款
2. merchant_trade_no 是剛剛生成的 當作是PK 
(其實應該是要建關聯db 但這邊就當測試就好 直接用)

file

step 4


登入並輸入信用卡
可以參考官網的介紹
https://developers.opay.tw/AioCreditCard/CreateOrder

step 5


完成訂單!
我們會收到以下畫面
file

同時再check一下db
hen 好! status = 1 ,代表已完成信用卡支付
file

讚! 完成!

系列文章

最後附上系列文章

  1. PHP + Laravel 雞排聯盟API實作: 前言
  2. PHP + Laravel 雞排聯盟API實作: 產出 table 及可供操作的物件
  3. PHP + Laravel 雞排聯盟API實作: CRUD 增刪改查
  4. PHP + Laravel 雞排聯盟API實作: 追加會員身份驗證
  5. PHP + Laravel 雞排聯盟API實作: 串接歐付寶金流API

以及初學時看的書 推薦!

還有gibhub連結

You may also like

1 comment

AffiliateLabz 2020-02-16 - 1:07 下午

Great content! Super high-quality! Keep it up! 🙂

Reply

Leave a Comment