iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 28
1
Modern Web

寫給朋友的 PHP 從 0 到 100 實戰教程系列 第 28

Day 28. PHP教學: 實作商務邏輯層

昨天我們做完畫面跟設定資料庫
開啟首頁後我們設計的方法是請使用者登入臉書就可以免費下載商用圖片。
我們做過臉書登入,回來 fb-callback 若是新帳號就幫他註冊,完成後就導向加購頁

controller/fb-callback.php

<?php
  $helper = $fb->getRedirectLoginHelper();
  try {
    $accessToken = $helper->getAccessToken();
  } catch(Facebook\Exceptions\FacebookResponseException $e) {
    // When Graph returns an error
    echo 'Graph returned an error: ' . $e->getMessage();
    exit;
  } catch(Facebook\Exceptions\FacebookSDKException $e) {
    // When validation fails or other local issues
    echo 'Facebook SDK returned an error: ' . $e->getMessage();
    exit;
  }
  
  if (! isset($accessToken)) {
    if ($helper->getError()) {
      header('HTTP/1.0 401 Unauthorized');
      echo "Error: " . $helper->getError() . "\n";
      echo "Error Code: " . $helper->getErrorCode() . "\n";
      echo "Error Reason: " . $helper->getErrorReason() . "\n";
      echo "Error Description: " . $helper->getErrorDescription() . "\n";
    } else {
      header('HTTP/1.0 400 Bad Request');
      echo 'Bad request';
    }
    exit;
  }
  
  // The OAuth 2.0 client handler helps us manage access tokens
  $oAuth2Client = $fb->getOAuth2Client();
  
  // Get the access token metadata from /debug_token
  $tokenMetadata = $oAuth2Client->debugToken($accessToken);
  
  // Validation (these will throw FacebookSDKException's when they fail)
  $tokenMetadata->validateAppId(Config::FB_APP_ID); // Replace {app-id} with your app id
  // If you know the user ID this access token belongs to, you can validate it here
  //$tokenMetadata->validateUserId('123');
  $tokenMetadata->validateExpiration();
  
  if (! $accessToken->isLongLived()) {
    // Exchanges a short-lived access token for a long-lived one
    try {
      $accessToken = $oAuth2Client->getLongLivedAccessToken($accessToken);
    } catch (Facebook\Exceptions\FacebookSDKException $e) {
      echo "<p>Error getting long-lived access token: " . $helper->getMessage() . "</p>\n\n";
      exit;
    }
  }
  
  $_SESSION['fb_access_token'] = (string) $accessToken;

  $fb->setDefaultAccessToken($accessToken);
  $response = $fb->get('/me?locale=en_US&fields=id,name,email');
  $userNode = $response->getGraphUser();
  $email = $userNode->getField('email');
  $fbUserId = $userNode->getField('id');

  $userVeridator = new UserVeridator();
  $userVeridator->isEmailDuplicate($email);
  $error = $userVeridator->getErrorArray();
  
  if(count($error) == 0)
  {
    // 沒有註冊

    $passwordObject = new Password();

    $table = 'members';
    $data_array = array(
      'username' => $fbUserId,
      'password' => $passwordObject->password_hash(date().rand(), PASSWORD_BCRYPT),
      'email' => $email,
      'active' => 'Yes',
      'fbUserId' => $fbUserId
    );
    Database::get()->insert($table, $data_array);
    
    $_SESSION['memberID'] = Database::get()->getLastId();
    $_SESSION['username'] = $fbUserId;

  }else{
    // 有註冊了
    if($userVeridator->fbLoginVerification($email, $fbUserId)){
      $_SESSION['memberID'] = $userVeridator->getMemberIdByFb($fbUserId);
      $_SESSION['username'] = $fbUserId;
    }
  }
  header('Location: '.Config::BASE_URL."upsell");
  exit;

來看加購頁的邏輯,因為登入成功了,所以我們要把答應給人的商品加入購物車

那先來實作購物車物件吧,基本上我設計的邏輯是透過三個陣列, array key 用來記住 productID, 對應的值是 產品名稱、數量或價格

libraries/Cart.php

<?php
class Cart {

    private $cartQty;
    private $cartPrice;
    private $cartName;

    function __construct($cartQty, $cartPrice, $cartName) {
        if(is_array($cartQty) AND is_array($cartPrice) AND is_array($cartName)){
            $this->cartQty   = $cartQty;
            $this->cartPrice = $cartPrice;
            $this->cartName  = $cartName;
        }else{
            $this->cartQty   = array();
            $this->cartPrice = array();
            $this->cartName  = array();
        }
    }

    function getCartQty(){
        return $this->cartQty;
    }

    function getCartPrice(){
        return $this->cartPrice;
    }

    function getCartName(){
        return $this->cartName;
    }

    function toString(){
        $temp = '';
        foreach($this->getAllProductID() as $productID){
            $temp .= $this->getProductNameInCart($productID)." x ".$this->getProductQtyInCart($productID). " $".$this->getProductPriceInCart($productID).", ";
        }
        return substr_replace($temp, "", -2);
    }

    function getTotalQty(){
        $count = 0;
        foreach($this->cartQty as $qty){
            $count += $qty;
        }
        return $count;
    }

    function getCartTotalAmount(){
        $amount = 0;
        foreach($this->cartQty as $productID => $qty){
            $amount += $this->cartPrice[$productID] * $qty;
        }
        return $amount;
    }

    function addToCart($productID, $name, $price, $qty)
    {
        if(is_numeric($productID) AND is_numeric($qty) AND !empty($productID) AND !empty($name) AND !empty($qty)){
            if(array_key_exists($productID, $this->cartQty)) {
                $this->cartQty[$productID] += $qty;
            }else{
                $this->cartQty[$productID] = $qty;
                $this->cartName[$productID] = $name;
                $this->cartPrice[$productID] = $price;
            } 
            return true;
        }
        return false;
    }

    function addToCartIfNotExist($productID, $name, $price, $qty)
    {
        if(is_numeric($productID) AND is_numeric($qty) AND !empty($productID)){
            if(array_key_exists($productID, $this->cartQty)) {
                return false;
            }else{
                $this->cartQty[$productID] = $qty;
                $this->cartName[$productID] = $name;
                $this->cartPrice[$productID] = $price;
                return true;
            }
        }
        return false;
    }

    function removeOneFromCart($productID)
    {
        if(is_numeric($productID) AND !empty($productID)){
            if(array_key_exists($productID, $this->cartQty)) {
                if($this->cartQty[$productID] >= 1){
                    $this->cartQty[$productID]--;
                }
                if($this->cartQty[$productID] <= 0){
                    unset($this->cartQty[$productID]);
                    unset($this->cartName[$productID]);
                    unset($this->cartPrice[$productID]);
                }
                return true;
            }
        }
        return false;
    }

    function removeProductFromCart($productID){
        if(is_numeric($productID)){
            if(array_key_exists($productID, $this->cartQty)) {
                unset($this->cartQty[$productID]);
                unset($this->cartName[$productID]);
                unset($this->cartPrice[$productID]);
                return true;
            }
        }
        return false;
    }

    function getProductPriceInCart($productID){
        if(is_numeric($productID)){
            if(array_key_exists($productID, $this->cartQty)) {
                return $this->cartPrice[$productID];
            }
        }
    }

    function getProductNameInCart($productID){
        if(is_numeric($productID)){
            if(array_key_exists($productID, $this->cartQty)) {
                return $this->cartName[$productID];
            }
        }
    }

    function getProductQtyInCart($productID){
        if(is_numeric($productID)){
            if(array_key_exists($productID, $this->cartQty)) {
                return $this->cartQty[$productID];
            }
        }
    }

    function getAllProductID(){
        return array_keys($this->cartQty);
    }
}

這樣就可以直接用了,處理完畢後記得送到 Session 把購物車內容記起來

controller/upsell.php

<?php
  // 先檢查是否登入,沒登入就踹回首頁
  if(!UserVeridator::isLogin(isset($_SESSION['username'])?$_SESSION['username']:'')){
    header('Location: '.Config::BASE_URL);
    exit;
  }

  // 登入成功,將產品加入加入購物車
  $cart = new Cart($_SESSION['cartQty'], $_SESSION['cartPrice'], $_SESSION['cartName']);
  $cart->removeProductFromCart(2);
  $cart->addToCartIfNotExist(1, "免費圖庫", 0, 1);

  $_SESSION['cartQty'] = $cart->getCartQty();
  $_SESSION['cartPrice'] = $cart->getCartPrice();
  $_SESSION['cartName'] = $cart->getCartName();

  include('view/header/default.php'); // 載入共用的頁首
  include('view/body/upsell.php');    
  include('view/footer/default.php'); // 載入共用的頁尾

在這頁面中按下『我要一個』的按鈕時 我們送到 do_upsell 頁面
所以要開這個檔案來處理

controller/do_upsell.php

<?php
  if(!UserVeridator::isLogin(isset($_SESSION['memberID'])?$_SESSION['memberID']:'')){
    header('Location: '.Config::BASE_URL);
    exit;
  }

  // 加付費產品到購物車中
  $cart = new Cart($_SESSION['cartQty'], $_SESSION['cartPrice'], $_SESSION['cartName']);
  $cart->addToCartIfNotExist(2, "折扣圖庫", 150, 1);

  $_SESSION['cartQty'] = $cart->getCartQty();
  $_SESSION['cartPrice'] = $cart->getCartPrice();
  $_SESSION['cartName'] = $cart->getCartName();

  header('Location: '.Config::BASE_URL."do_checkout");
  exit;

若選謝謝不用了,或是已完成 do_upsell 就會自動導向 do_checkout
這頁要做的事情很簡單就是『結帳』 若是零元訂單就把訂單存到資料庫,若要付費的話,送到綠界金流
先設計一個 Order 物件來處理這塊邏輯吧

libraries/Order.php

<?php
class Order {

    private $orderID;
    public $memberID;
    public $description;
    public $serviceFee = 0;
    public $shippingFee = 0;
    public $cartTotal = 0;
    public $orderTotal = 0;
    public $status = 0;

    function __construct() {
        if (session_status() == PHP_SESSION_NONE) {
            session_start();
        }
        $this->orderID = Config::COUNTRY_CODE.Config::SHOP_CODE.microtime(true)*10000;
        $this->memberID = $_SESSION['memberID'];
        $cart = new Cart($_SESSION['cartQty'], $_SESSION['cartPrice'], $_SESSION['cartName']);
        $this->description = $cart->toString();
        $this->serviceFee = 0;
        $this->shippingFee = 0;
        $this->cartTotal = $cart->getCartTotalAmount();
        $this->orderTotal = $cart->getCartTotalAmount();
    }

    function setOrderComplete(){
        $this->status = 1;
    }

    function setOrderFail(){
        $this->status = -1;
    }

    function getOrderID(){
        return $this->orderID;
    }

    function save(){
        if(!empty($this->orderID)){
            if(OrderVeridator::isOrderIDExist($this->orderID)){
                $table = 'orders';
                $data_array = array(
                    'memberID' => $this->memberID,
                    'description' => $this->description,
                    'serviceFee' => $this->serviceFee,
                    'shippingFee' => $this->shippingFee,
                    'cartTotal' => $this->cartTotal,
                    'orderTotal' => $this->orderTotal,
                    'status' => $this->status
                );
                Database::get()->update($table, $data_array, "orderID", $this->orderID);
            }else{
                $table = 'orders';
                $data_array = array(
                    'orderID' => $this->orderID,
                    'memberID' => $this->memberID,
                    'description' => $this->description,
                    'serviceFee' => $this->serviceFee,
                    'shippingFee' => $this->shippingFee,
                    'cartTotal' => $this->cartTotal,
                    'orderTotal' => $this->orderTotal,
                    'status' => $this->status
                );
                Database::get()->insert($table, $data_array);
            }
        }
    }
}

再來就可以實作 do_checkout 的邏輯,若是零元訂單就直接送感謝頁吧

controller/do_checkout.php

<?php
  if(!UserVeridator::isLogin(isset($_SESSION['memberID'])?$_SESSION['memberID']:'')){
    header('Location: '.Config::BASE_URL);
    exit;
  }

  $order = new Order();
  $cart = new Cart($_SESSION['cartQty'], $_SESSION['cartPrice'], $_SESSION['cartName']);

  if($order->orderTotal == 0){

    $order->setOrderComplete();
    $order->save();
    $_SESSION['order'] = base64_encode(serialize($order));
    header('Location: '.Config::BASE_URL."thankyou");
    exit;

  }else{

    $_SESSION['order'] = base64_encode(serialize($order));
    
    try {
      $obj = new ECPay_AllInOne();
      $obj->ServiceURL  = Config::ECPAY_API_URL;
      $obj->HashKey     = Config::ECPAY_HASH_KEY;
      $obj->HashIV      = Config::ECPAY_HASH_IV;
      $obj->MerchantID  = Config::ECPAY_MERCHANT_ID;
      $obj->Send['ReturnURL'] = Config::ECPAY_CALLBACK_URL;       //付款完成通知回傳的網址
      $obj->Send['MerchantTradeNo']   = $order->getOrderID();
      $obj->Send['MerchantTradeDate'] = date('Y/m/d H:i:s');      //交易時間
      $obj->Send['TotalAmount']       = (int)$order->orderTotal;  //交易金額
      $obj->Send['TradeDesc']         = $cart->toString();
      $obj->Send['NeedExtraPaidInfo'] = 'Y'; //額外的付款資訊(消費者信用卡末四碼)
      $obj->Send['OrderResultURL']    = Config::ECPAY_CALLBACK_URL; //付款完成導回平台的網址
      $obj->Send['ChoosePayment'] = ECPay_PaymentMethod::Credit;
      $obj->Send['EncryptType'] = 1;
  
      //訂單的商品資料
      $productIdArray = $cart->getAllProductID();
      foreach($productIdArray as $productID) {
        array_push($obj->Send['Items'], array(
          'Name'  => $cart->getProductNameInCart($productID),
          'Price'  => (int)$cart->getProductPriceInCart($productID),
          'Currency'  => "元",
          'Quantity'  => (int) $cart->getProductQtyInCart($productID),
          'URL'  => ""));
      }
  
      //產生訂單(auto submit至ECPay)
      $obj->CheckOut();
  
    } catch (Exception $e) {
        echo $e->getMessage();
    }
    exit;

  }

下一篇來講解 從綠界回來該怎麼處理


上一篇
Day 27. PHP教程: 實作簡單電商網站 (準備篇)
下一篇
Day 29. PHP教學: 刷完信用卡導回網站的處理
系列文
寫給朋友的 PHP 從 0 到 100 實戰教程30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言