上一篇文章主要说明充值的执行逻辑和控制层的设计,这篇文章主要讨论充值业务层的具体实现。
正如上一篇文章所说到的,生成订单需要如下几个步骤:
(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为例,进一步说明支付系统,同时关注支付安全的问题。