1. 程式人生 > >充值系列——充值系統業務邏輯層實現(三)

充值系列——充值系統業務邏輯層實現(三)

上一篇文章主要說明充值的執行邏輯和控制層的設計,這篇文章主要討論充值業務層的具體實現。

正如上一篇文章所說到的,生成訂單需要如下幾個步驟:

(1)例項化操作人 (操作人)

(2)例項化產品模型 (獲取產品的詳細資訊)

(3)例項化訂單模型 (生成一筆狀態為“交易中”的訂單)

(4)例項化支付平臺模型 (把生成的訂單返回給客戶端,並引導用於去充值平臺)

一個完整的充值系統需要完善以上4個基本的模型類。詳細的UML圖如下:

支付平臺依賴操作人類,訂單類,產品類,助手類。

支付平臺工廠類

class Model_Payment
{
    // 對應關係為:'渠道ID' => '渠道類名'
    private static $_channels = array(
        '10' => 'alipay',
        '20' => 'applestore'
    );
 
    // 預設的充值渠道
    const DEFAULT_CHANNEL_ID = 40;
 
    public function factory($channelId) 
    {
        if isset(self::$_channels[$channelId]) : throws('渠道不合法');
 
        $className = 'Model_Payment_Channel_' . ucfirst(self::$_chanels[$channelId]);
 
        return new $className;
    }
}

支付平臺類

如上一篇文章所談到的,支付平臺類必須提供三個方法:生成訂單,非同步響應,同步響應。 對應不同的支付平臺,非同步響應,同步響應的處理方式不同,下一篇部落格將詳細討論,這裡主要講解生成訂單函式。

function createOrder(Model_User $user, Model_Payment_Product $product, array $extraData = array())
    {
        if (! $product instanceof Model_Payment_ProductNull) {
            if ($product['channel_id'] != $this->_channelId) {
                throw new Model_Payment_Exception_Common(_('購買的產品和當前充值渠道不匹配'));
            }
        }
 
        // 執行新增訂單
        $orderResult = $this->_insertOrder($user, $product, $extraData);
 
        // 組裝生成訂單後的返回結果
        return $this->_buildCreateReturn($orderResult, $product);
    }

執行新增訂單的函式的時候,必須返回新增函式的訂單號。

protected function _insertOrder(Model_User $user, Model_Payment_Product $product, array $extraData = array())
    {
 
        $setArr = array(
            'status'        => Model_Payment_Order::STATUS_UNPAID, // 未支付
            'create_time'   => $GLOBALS['_DATE'],
            'uid'           => $user['uid'],
            'product_name'  => $product['name'],
             // 此處省略其他訂單資訊
            'channel_id'    => $this->_channelId,       // 充值渠道
        );
 
        if (! $orderSn = Model_Payment_Order::create($setArr)) {
            throw new Model_Payment_Exception_Common(_('儲存訂單失敗'));
        }
 
        return array(
            'order_sn'    => $orderSn,  // 必須返回訂單詳細
    }

我方組裝生成訂單後的返回結果
情況1:以JSON格式輸出響應給客戶端(預設)
情況2:以HTML表單格式自動提交GET/POST請求
情況3:以URL訊號的方式重新跳轉

 protected function _buildCreateReturn(array $orderResult, Model_Payment_Product $product)
    {
        return json_encode(array(
            'status'         => 'success',
            'productId'      => $product['id'],                  // 我方產品Id
            'thirdProductId' => $product['third_product_id'],    // 對應第三方平臺的產品Id
            'totalPrice'     => $orderResult['total_price'],     // 訂單總價
            'orderSn'        => $orderResult['order_sn'],        // 內部訂單流水號
            'subject'        => $product['name'],                // 訂單名稱
            'description'    => _('購買') . $product['name'],    // 訂單備註
        ));
    }

訂單類

主要需要實現的方法有:插入訂單,更改訂單資訊,獲取訂單詳細,驗證非同步響應回來的訂單資訊中,包含的產品價格是否和當前訂單中的一致,這一步特別主要。

public function create($setArr)
    {
        // 生成新的訂單流水號 (這個方法在這個系列(一)中有說明)
        $orderSn = self::createSn();
        $setArr['order_sn'] = $orderSn;
 
        // 下單時間
        if (! isset($setArr['create_time'])) {
            $setArr['create_time'] = $GLOBALS['_DATE'];
        }
 
        if (! Dao('Order_Orders')->insert($setArr)) {
            return false;
        }
 
        return $orderSn;
    }

獲取訂單資訊

function __construct($orderSn)
    {
        if (! $orderSn) {
            throw new Model_Payment_Exception_Common('Invalid OrderSn');
        }
 
        if (! $this->_prop = Dao('Order_Orders')->get($orderSn)) {
            throw new Model_Payment_Exception_Common(_('訂單資訊不存在。') . 'OrderSn:' . $orderSn);
        }
    }

還有沒有實現的方法請腦補。

產品類

主要需要實現的方法有:根據渠道獲取產品列表,獲取產品的資訊,判斷產品是否處於促銷中等

function __construct($productId = 0)
    {
        if ($productId < 1) {
            throw new Model_Payment_Exception_Common('Invalid ProductId');
        }
 
        if (! $this->_prop = Dao('Order_Product')->get($productId)) {
            throw new Model_Payment_Exception_Common(_('購買的產品不存在') . 'ProductId:' . $productId);
        }
 
        // 拼接產品名稱(因為多語言版本)
        $this->_prop['name'] = _('手機大航海') . $this->_prop['gold_value'] . _('金塊');
    }

獲取產品列表

function getListByChannel($channelId)
    {
        return Dao('Order_Product')->getListByChannel($channelId);
    }

助手類
主要實現的方法有:載入配置檔案,組裝跳轉的URL,以表單HTML形式構造請求

function buildRequestForm($action, array $params, $method, $btnName = null)
    {
        $html = "<form id='payformsubmit' action='" . $action . "' method='" . $method . "'>";
 
        if ($params) {
            foreach ($params as $key => $value) {
                $html .= "<input type='hidden' name='" . $key . "' value='" . $value ."' />";
            }
        }
 
        $btnName = $btnName ?: _('訂單確認');
 
        $html .= "<input type='submit' value='" . $btnName . "' />";
        $html .= "</form>";
        $html .= "<script>document.getElementById('payformsubmit').submit();</script>";
 
        return $html;
    }

下一篇文章將以alipay為例,進一步說明支付系統,同時關注支付安全的問題。