上一篇文章主要說明充值的執行邏輯和控制層的設計,這篇文章主要討論充值業務層的具體實現。
正如上一篇文章所說到的,生成訂單需要如下幾個步驟:
(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爲例,進一步說明支付系統,同時關注支付安全的問題。