PHP網站串綠界金流-教學筆記

本文章最後更新日期:2020/01/21

最近手邊的案子需要建置網站並串金流,花了不少時間搞懂綠界的串金流方式,想試著稍微筆記一下,不然之後可能又會忘記了。

1. 說明:

此篇文章主要以PHP網站及後台如何串接綠界科技的PHP版本之API為主,且只有著重信用卡一次付清的付費模式的設定說明為主。
使用的php框架為最簡易的 CodeIgniter 為主。php版本為7。
另外,因為綠界的刷卡回傳資訊等都要求以https頁面為主,所以建議還是在租賃主機上測試會比較方便快速。

2. 導入綠界API:

綠界網站 有提供不少的文件說明,也有連結導向github函式庫,雖說看起來容易,但那份文件其實還蠻長且複雜的,其中還有一些小地雷,筆者也是摸了很久才慢慢了解。

簡單說明,在串接上,先前往 ECPayAIO_PHP的github 取得原始碼函式庫,核心就是 AioSDK/sdk/ECPay.Payment.Integration.php 這隻檔案,而如何使用這它來串接綠界的所有範例檔,都在AioSDK/example/ 資料夾裡,裡頭有各種付費模式的範例檔,理論上來說,只要摸透其中一個範例檔,其他也差不多都通了,其他的付費模式就都只是傳遞參數和設定的不同而已。

以CI架構來串接的話,可以將ECPay.Payment.Integration.php檔案先以library的方式來導入,後面要用時再建立物件即可。

//先建立一個library來導入函式庫
//檔案位置及名稱: libraries/Ecpay_library.php
class Ecpay_library {
  public function __construct(){
    log_message('Debug', 'ECpay class is loaded.');
  }
  public function load(){
    require_once APPPATH.'third_party/AioSDK/sdk/ECPay.Payment.Integration.php';
    $obj = new ECPay_AllInOne();
    return $obj;
  }
}

說明:在libraries中建立一個檔案,用來讀入ECPay.Payment.Integration.php函式庫,然後建立ECPay_AllInOne物件並回傳該物件,就這樣。
之後使用時,只需要讓controller讀入這個libraries檔(或是你也可以寫進autoload設定檔裡),再使用其load方法就能拿到物件了,例如:

$this->load->library('ecpay_library');  //讀入該library檔,建議可以寫在controller的 __construct()中
...
...
...
$obj = $this->ecpay_library->load(); //在controller中任何地方使用該library檔的load方法,取得ECPay_AllInOne物件

補充:第一行讀入這個library 時,名稱用小寫,似乎跟ci架構設定有關,用大寫會抓不到 (至少在筆者這邊的環境上用大寫會抓不到該檔案,可能有其他設定可以調整)

這樣就算是完成ECPay.Payment.Integration.php函式庫的導入了。而後面的實際使用範例,其實把 github 中的 ECPayAIO_PHP/AioSDK/example/sample_Credit_CreateOrder.php 這支程式中的所有程式碼原封不動複製貼上後,就能跑測試了 (記得把 include(‘ECPay.Payment.Integration.php’); 及 $obj = new ECPay_AllInOne(); 這兩行拿掉,因為前面已經用 CI 框架讀入並建立物件了)。

3. 流程:

首先假設你有自己的商店網站,然後假設要販賣烤餅乾,那你一定會有烤餅乾的名稱、數量、價格等資訊,如果你會將這些資訊傳遞給php後台去處理或是存入資料庫中,那就一定可以把這些資料丟給綠界,讓綠界幫忙產生刷卡的介面。與綠界串接的處理流程可以簡單整理成下圖:

綠界處理流程 (點擊可看大圖)

說明:頁面A的表單送出資料給後端,後端取得$_POST資訊後,開始建立ECPay_AllInOne物件,同時設定各項參數資訊,將客戶導向綠界的刷卡頁面,客戶刷卡完後,綠界會將刷卡結果資料傳給頁面B及頁面C,其中頁面C會是使用者刷卡完成後看到的畫面 (若是沒有設定 Send[‘OrderResultURL’] 參數,客戶刷卡完後會看到綠界提供的刷卡結果畫面,就不會自動導回商店網站。)

4. 程式碼簡易說明:

簡單用程式碼來說明一下:
(1)建立綠界物件: (所有內容幾乎都來自綠界提供的sample_Credit_CreateOrder.php範例檔中的程式碼,照抄就差不多能RUN了)

try {
   $obj = $this->ecpay_library->load(); //用前述建立的library來取得ECPay_AllInOne物件
   //服務參數,最重要的是前四項,正式刷卡與測試刷卡時使用的服務位置會不同,後續三個參數是當你已經向綠界申請好會員後,可以在會員管理後台找到的自己的商店代號
   $obj->ServiceURL  = "https://payment-stage.ecpay.com.tw/Cashier/AioCheckOut/V5";   //服務位置
   $obj->HashKey     = "5294y06JbISpM5x9"; //測試用Hashkey,請自行帶入ECPay提供的HashKey
   $obj->HashIV      = "v77hoKGq4kWxNNIS"; //測試用HashIV,請自行帶入ECPay提供的HashIV
   $obj->MerchantID  = "2000132";   //測試用MerchantID,請自行帶入ECPay提供的MerchantID
   $obj->EncryptType = "1";    //CheckMacValue加密類型,請固定填入1,使用SHA256加密

   //基本參數(請依系統規劃自行調整)
   $MerchantTradeNo = "TNO".time() ; //付款的訂單編號,這邊用時間戳記來當作編號,避免重複
   $obj->Send['ReturnURL']         = "https://mysite.com.tw/pageB";    //付款完成通知回傳的網址(頁面B)
   $obj->Send['ClientBackURL']     = "https://mysite.com.tw";    //提供一個可以連回網站頁面的按鈕(頁面D)
   $obj->Send['MerchantTradeNo']   = $MerchantTradeNo;      //訂單編號
   $obj->Send['MerchantTradeDate'] = date('Y/m/d H:i:s');   //交易時間
   $obj->Send['TotalAmount']       = 400;    //交易金額,刷卡時需付款的實際金額數值
   $obj->Send['TradeDesc']         = "這是一筆測試交易" ;                          //交易描述
                    
   switch($pay_way){ //你可以用一個參數來決定客戶是用什麼方式付款
       case "Credit":
           $obj->Send['ChoosePayment'] = ECPay_PaymentMethod::Credit ; //付款方式:Credit
           $obj->Send['OrderResultURL']= "https://mysite.com.tw/pageC"; //付款完成通知回傳的網址,客戶會被導回此頁面(頁面C)
           break;
       case "ATM": 
           $obj->Send['ChoosePayment'] = ECPay_PaymentMethod::ATM ; //付款方式:ATM
           $obj->Send['ExpireDate']    = 3; //用ATM付款的話,可以設定要求客戶要在幾天內完成付款
           //$obj->Send['ClientBackURL'] = "https://mysite.com.tw/pageC"; //付款完成通知回傳的網址,客戶會被導回此頁面(頁面C)
           break;
   }
                                 
            $obj->Send['IgnorePayment']     = ECPay_PaymentMethod::GooglePay ; //不使用付款方式:GooglePay
            //可以傳遞一些自己的資訊給綠界,這些資訊會被記錄在刷卡紀錄中,最多可以傳遞4個自定義參數,但參數的key值不能自訂,算是有點不方便
            $obj->Send['CustomField1']      = "AAAAAAAAAAAAA";  
            $obj->Send['CustomField2']      = "BBBBBBBBBBBBB";  
                    
     //訂單的商品資料,資料來源就是頁面A的表單傳遞給後端的POST資料,把這些資料再傳給綠界,就會在刷卡頁面中呈現給客戶看
     //其實這邊的資料主要只有訊息呈現,以及產生發票時會用到,對於刷卡金額及流程都不會有任何影響
     array_push($obj->Send['Items'], 
         array('Name' => "烤餅乾1", 'Price' => (int)"100", 'Currency' => "元", 'Quantity' => (int) "2", 'URL' => "dedwed"), 
         array('Name' => "烤餅乾2", 'Price' => (int)"200", 'Currency' => "元", 'Quantity' => (int) "1", 'URL' => "dedwed")
     );
 
     //Credit信用卡分期付款延伸參數(可依系統需求選擇是否代入)
     //以下參數不可以跟信用卡定期定額參數一起設定
     $obj->SendExtend['CreditInstallment'] = '' ;    //分期期數,預設0(不分期),信用卡分期可用參數為:3,6,12,18,24
     $obj->SendExtend['InstallmentAmount'] = 0 ;    //使用刷卡分期的付款金額,預設0(不分期)
     $obj->SendExtend['Redeem'] = false ;           //是否使用紅利折抵,預設false
     $obj->SendExtend['UnionPay'] = false;          //是否為聯營卡,預設false;
     //Credit信用卡定期定額付款延伸參數(可依系統需求選擇是否代入)
     //以下參數不可以跟信用卡分期付款參數一起設定
     // $obj->SendExtend['PeriodAmount'] = '' ;    //每次授權金額,預設空字串
     // $obj->SendExtend['PeriodType']   = '' ;    //週期種類,預設空字串
     // $obj->SendExtend['Frequency']    = '' ;    //執行頻率,預設空字串
     // $obj->SendExtend['ExecTimes']    = '' ;    //執行次數,預設空字串
        
     # 電子發票參數
     /*
     $obj->Send['InvoiceMark'] = ECPay_InvoiceState::Yes;
     $obj->SendExtend['RelateNumber'] = "Test".time();
     $obj->SendExtend['CustomerEmail'] = 'test@ecpay.com.tw';
     $obj->SendExtend['CustomerPhone'] = '0911222333';
     $obj->SendExtend['TaxType'] = ECPay_TaxType::Dutiable;
     $obj->SendExtend['CustomerAddr'] = '台北市南港區三重路19-2號5樓D棟';
     $obj->SendExtend['InvoiceItems'] = array();
     // 將商品加入電子發票商品列表陣列
     foreach ($obj->Send['Items'] as $info)
     {
        array_push($obj->SendExtend['InvoiceItems'],array('Name' => $info['Name'],'Count' =>
          $info['Quantity'],'Word' => '個','Price' => $info['Price'],'TaxType' => ECPay_TaxType::Dutiable));
     }
        $obj->SendExtend['InvoiceRemark'] = '測試發票備註';
        $obj->SendExtend['DelayDay'] = '0';
        $obj->SendExtend['InvType'] = ECPay_InvType::General;
     */
     //產生訂單(auto submit至ECPay),此步驟會將前述設定的所有參數都一併傳給綠界,並將客戶導到綠界的刷卡頁面
     $obj->CheckOut();
    
} catch (Exception $e) {
   echo $e->getMessage();
} 

其實這樣就算是完成串接了,綠界的刷卡動作完成後,會自動導回網站 ( 如果有設定Send[‘OrderResultURL’]參數的話 )。 而後續的步驟則是綠界提供的範例檔中幾乎沒有提及的部分,就是刷卡完成後的刷卡結果。

要如何從綠界接收這些資訊呢? 答案其實就是前述提到多次的頁面B及頁面C,當刷卡完成後,綠界會將刷卡結果資訊傳給頁面B及頁面C,這兩個頁面必須是https的頁面,只要在這兩個頁面接收綠界傳來的$_POST資訊來處理即可。

(2)頁面B的程式碼如下:

$obj = $this->ecpay_library->load(); //因為後面會進行檢查碼的檢驗,仍需要建立ECPay_AllInOne物件來取用其方法
            
//可以試著先印出接受到的POST中所有的資訊來查看
$strr = print_r($_POST, true);
file_put_contents(dirname(__FILE__)."/readme.txt" ,$strr, FILE_APPEND);
            
$arParameters = $_POST;
$ECPay_MerchantID = "2000132";
$ECPay_HashKey = "5294y06JbISpM5x9";
$ECPay_HashIV = "v77hoKGq4kWxNNIS";

//進行驗證碼檢查,將POST陣列, HashKey, HashIV, 以及當初設定的加密方式($obj->EncryptType = "1";)作為參數,
//傳給 ECPay_CheckMacValue::generate 來產生驗證碼
$CheckMacValue = ECPay_CheckMacValue::generate( $arParameters, $ECPay_HashKey, $ECPay_HashIV, 1);
//$_POST['RtnCode'] == 1表示刷卡成功
//比對傳來的POST中的驗證碼與這邊剛計算出來的驗證碼是否相同,相同才進行後續處理,若不同,則表示這份POST可能是偽造的,或是錯誤的交易紀錄
if ( $_POST['RtnCode'] =='1' && $CheckMacValue == $_POST['CheckMacValue'] ){ 
    
    ...
    自己的處理邏輯、連資料庫等等動作
    ...
    
    //最後一定要回傳這一行,告知綠界說:「我的商店網站確實有收到綠界的通知了!」才算完成。
    echo '1|OK';
}

頁面C的程式其實也是類似的程式碼,只是綠界除了傳資料給頁面C以外,也會同時把使用者自動導回頁面C,所以中間的處理訂單、連資料庫等部分,可以換成呈現提示資訊給使用者查看之類的。

(3)綠界回傳的POST的可能內容如下:

Array
(
    [CustomField1] => AAAAAAAAAA
    [CustomField2] => BBBBBBBBBB
    [CustomField3] => 
    [CustomField4] => 
    [MerchantID] => 2000132
    [MerchantTradeNo] => TNO1576830000
    [PaymentDate] => 2019/12/20 16:20:43
    [PaymentType] => Credit_CreditCard
    [PaymentTypeChargeFee] => 154
    [RtnCode] => 1
    [RtnMsg] => 交易成功
    [SimulatePaid] => 0
    [StoreID] => 
    [TradeAmt] => 7700
    [TradeDate] => 2019/12/20 16:20:00
    [TradeNo] => 1912201620000523
    [CheckMacValue] => EF31FB1BD4E367E9E95CD188B64DE38D1EC3E0A4A81880D9E5A87BB1AB0D8AAF
)

整理過後發現串綠界的方式其實不算難,所有工具都包在他們提供的api裡,只要呼叫物件,把參數加到物件上,把物件傳給綠界,綠界就會幫忙產生刷卡頁面了。
等客戶刷卡完成,就會將刷卡結果傳給商店網站的頁面,或是將客戶再導回自己的商店網站。
搞懂流程後,剩下的就是參數設定的問題,希望達到什麼功能或是設定,就可以查看綠界提供的技術說明文件,找出要查詢的項目,看有什麼參數可以設定。

在使用測試版時,綠界有提供測試用的信用卡:
卡號: 4311-9522-2222-2222 背後安全三碼: 222
有效日期: 大於當前年月即可
測試後的刷卡紀錄,就可以到綠界的開發者測試用後台查看,有資料進去就表示刷卡有成功。 (因為這個後台是所有有使用綠界測試的測試人員都能查看的後台,你可以在這邊看到各種其他工程師的測試紀錄,所以自己在測試時,也要多注意個人資料的保護)

其他遇到的小地雷: 當引導到綠界的刷卡畫面時,傳遞的 Send[‘MerchantTradeNo’] 參數必須是唯一的,筆者發現, 假設客戶到了綠界刷卡頁面,此時的訂單編號設為123456789,然後客戶不刷卡,直接關掉這個綠界的頁面時, 綠界商店的後台管理不會有這筆訂單編號的紀錄,也不會有任何刷卡紀錄(畢竟客戶沒刷卡就關掉頁面了,合理)

但下次再以同樣的訂單編號123456789想重現一樣的刷卡頁面時,綠界會告知此訂單編號已被用過,而無法產生刷卡頁面。 筆者的案子中就有這樣的情況:客戶下單,到刷卡畫面後,不小心關掉網頁,這時就必須想辦法重現刷卡畫面讓客戶付款, 因此自己的商店網站有提供「前往付款」的按鈕給尚未付款的客戶去使用,就發現同樣的訂單編號會被綠界檔下來, 不得已,只好改以時戳的方式產生決不會重複的訂單編號,而自己商店真正的訂單編號則只能寫在CustomField裡,算是有點不方便的地方。 或許有其他設定是我沒找到的,可以解決這個問題也說不定……

其他參考文章:Hoyo 教你串金流 – 綠界

筆者也有參考蠻多這篇文章的內容才搞懂,不過裡面有個小地方要注意,如果是直接引用綠界的程式範例檔的話,會發現它有一句

$obj->EncryptType = "1";

這是綠界刷卡驗證的加密方式代碼,送出綠界資訊時有加這一行的話,後面在驗證綠界回傳的$_POST是否正確時,要將

$CheckMacValue = ECPay_CheckMacValue::generate( $arParameters, $ECPay_HashKey, $ECPay_HashIV );

改成

$CheckMacValue = ECPay_CheckMacValue::generate( $arParameters, $ECPay_HashKey, $ECPay_HashIV, 1);

最後面要多一個參數 1 ,才會正確。

這次大概就先記到這邊,下次有機會再來接觸看看其他金流系統或API

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

在 WordPress.com 建立自己的網站
立即開始使用
%d 位部落客按了讚: