电商工具集 - ectools

ectools简介

ectools是集支付,货币,数据精度和地区包为一体的电子商务通用功能。可以灵活配置支付功能和数据精度管理,方便其他应用使用。ectools作为ecos的一个基础app一般不单独存在,是其他app操作流程的载体。ecstore是基于ecos的开源商城系统,亦依赖ectools。

此手册目标人群

开发者:开发基于ecos的新应用或扩展已有支付方式的扩展都会用到此应用。

ectools提供了下列功能:

  • 支付方式管理
  • 支付和退款单管理
  • 货币管理
  • 数据精度配置
  • 地区包管理

支付方式管理与配置

ecstore是基于ecos的开源商城系统需要用到默认支付方式就是由此应用来管理,我们可以管理已有的支付方式,也可以新建支付方式。

现以支付宝支付方式为例:

  • 支付方式接口ectools_interface_payment_app
  • 支付方式父类ectools_payment_app

1. 接口方法介绍:

admin_intro:显示支付接口后台的信息

setting:设置后台的显示项目(表单项目)

intro:前台在线支付列表相应项目的说明

dopay:支付表单的提交方式

is_fields_valiad:验证提交表单数据的正确性

callback:支付后返回后处理的事件的动作

gen_form:生成支付表单 - 自动提交(点击链接提交的那种方式,通常用于支付方式列表)

2. 具体介绍下setting里面的数据结构(以aplipay的参数配置为准)

'pay_name'=>array(
	'title'=>app::get('ectools')->_('支付方式名称'),
	'type'=>'string',
	'validate_type' => 'required',
),
'mer_id'=>array(
	'title'=>app::get('ectools')->_('合作者身份(parterID)'),
	'type'=>'string',
	'validate_type' => 'required',
),
'mer_key'=>array(
	'title'=>app::get('ectools')->_('交易安全校验码(key)'),
	'type'=>'string',
	'validate_type' => 'required',
),
'support_cur'=>array(
	'title'=>app::get('ectools')->_('支持币种'),
	'type'=>'text hidden',
	'options'=>array('1'=>app::get('ectools')->_('人民币'),'2'=>app::get('ectools')->_('其他'),'3'=>app::get('ectools')->_('商店默认货币'))
),
'real_method'=>array(
	'title'=>app::get('ectools')->_('选择接口类型'),
	'type'=>'select',
	'options'=>array('0'=>app::get('ectools')->_('使用标准双接口'),'2'=>app::get('ectools')->_('使用担保交易接口'),'1'=>app::get('ectools')->_('使用即时到帐交易接口'))
),
'pay_fee'=>array(
	'title'=>app::get('ectools')->_('交易费率'),
	'type'=>'pecentage',
	'validate_type' => 'number',
),
'pay_desc'=>array(
	'title'=>app::get('ectools')->_('描述'),
	'type'=>'html',
	'includeBase' => true,
),
'pay_type'=>array(
	 'title'=>app::get('ectools')->_('支付类型(是否在线支付)'),
	 'type'=>'hidden',
	 'name' => 'pay_type',
),
'status'=>array(
	'title'=>app::get('ectools')->_('是否开启此支付方式'),
	'type'=>'radio',
	'options'=>array('false'=>app::get('ectools')->_('否'),'true'=>app::get('ectools')->_('是')),
	'name' => 'status',
),

title:参数的标题

type:参数的标题(string,text hidden,select,pecentage,html和radio)

options:只有type=radio,type=checkbox和type=select才需要填写

validate_type:需要验证的方式(类型可以参考desktop下面的validate.js)

3. 支付方式父类方法介绍:

add_field:设置提交表单的元素属性

getConf:得到支付方式配置参数

get_html:支付方式提交表单的html内容

4. 本类的属性说明

public $name = '支付宝支付';
public $app_name = '支付宝支付接口';
public $app_key = 'alipay';
public $display_name = '支付宝';
public $curname = 'CNY';
public $ver = '1.0';

name:支付接口名称,一般不被前台或者后台显示使用,仅仅只是说明(目前)

app_name:支付接口应用名称,一般不被前台或者后台显示使用,仅仅只是说明(目前)

app_key:支付接口的唯一标示,用于数据对应取值,一般用英文表示

display_name:用于前后台显示在页面上的名称

5. 下面我来实现一个例子(新建一个支付方式):

  • 首先拷贝一个支付方式的文件ectools/lib/payment/plugin/yunchoubao.php

(1). 首先配置service.xml,因为支付方式本来就是一种服务

	<service id="ectools_payment.ectools_mdl_payment_cfgs" optname="支付插件列表">
		<class>ectools_payment_plugin_alipay</class>
		<class>ectools_payment_plugin_99bill</class>
		<class>ectools_payment_plugin_paypal</class>
		<class>ectools_payment_plugin_tenpay</class>
		<class>ectools_payment_plugin_offline</class>
		<class>ectools_payment_plugin_deposit</class>
		<class>ectools_payment_plugin_yunchoubao</class>
	</service>

增加了一行<class>ectools_payment_plugin_yunchoubao</class>

然后安装开发者中心,然后在“应用中心”维护下即可,如果不安装开发者中心的话,也可以选择重新安装。

(2). 首先打开这个文件

(2). 修改类名,将原来的类名修改为ectools_payment_plugin_yunchoubao

(3). 修改构造方法中的“$this->callback_url”变量的值,将ectools_payment_plugin_alipay=>ectools_payment_plugin_yunchoubao

(4). 修改setting方法中的表单元素,修改成你自己支付网关中需要用户配置的参数,具体配置的方法详见第二节(setting里面的数据结构)

(5). 然后修改支付网关中的dopay()和callback(),按照网关规定的接口来修改。

  • 这样一个网关支付方式也就修改完毕了。
  • 然后进行相应的配置

如图:

  • 对相应的内容进行配置即可。

ectools的其他功能简介

1. 支付与退款单管理

如图:

支付单

退款单

支付与退款单据的finder上面与其他的有所不同,需要顶一个关联搜索的字段(rel_id - 对象外键),下面已支付单为例:

  • 先打开代码文件app/ectools/model/payments.php,增加此外键在搜索下拉框中的显示。
/**
 * 重写搜索的下拉选项方法
 * @param null
 * @return null
 */
public function searchOptions(){
	$columns = array();
	foreach($this->_columns() as $k=>$v){
		if(isset($v['searchtype']) && $v['searchtype']){
			$columns[$k] = $v['label'];
		}
	}

	// 添加额外的
	$ext_columns = array('rel_id'=>$this->app->_('对象ID'));

	return array_merge($columns, $ext_columns);
}
  • 修改finder搜索相关数据支持的方法的重写
/**
 * 重写去除所要查询的列表的数据数量
 * @param null
 * @return null
 */
public function count($filter=null)
{
	if (!array_key_exists('rel_id', $filter))
		$row = $this->db->select('SELECT count(*) as _count FROM '.$this->table_name(1).' WHERE '.$this->_filter($filter));
	else
	{
		$row = $this->db->select('SELECT count(payments.payment_id) as _count
					FROM '.$this->table_name(1).' AS payments
					INNER JOIN ' . kernel::database()->prefix.$this->app->app_id . '_order_bills AS bills ON bills.bill_id=payments.payment_id
					WHERE bills.rel_id=' . $filter['rel_id']);
	}

	return intval($row[0]['_count']);
}

/**
 * 重载getList方法
 * @params string - 特殊的列名
 * @params array - 限制条件
 * @params 偏移量起始值
 * @params 偏移位移值
 * @params 排序条件
 */
public function getList($cols='*', $filter=array('disabled' => 'false'), $offset=0, $limit=-1, $orderby=null)
{
	if ($filter)
	{
		if (!array_key_exists('rel_id', $filter))
			return parent::getList($cols, $filter, $offset, $limit, $orderby);
		else
		{
			$arr_cols = explode(',', $cols);
			if ($arr_cols)
			{
				foreach ($arr_cols as $key=>&$str_cols)
				{
					if ($key == 0) continue;

					$str_cols = $this->table_name(1) . '.' . $str_cols;
				}

				$cols = implode(',', $arr_cols);
			}

			$rows = $this->db->selectLimit('SELECT '.$cols.'
							FROM '. $this->table_name(1) .'
							INNER JOIN ' . kernel::database()->prefix.$this->app->app_id . '_order_bills ON ' . kernel::database()->prefix.$this->app->app_id . '_order_bills.bill_id=' . $this->table_name(1) . '.payment_id
							WHERE ' . kernel::database()->prefix.$this->app->app_id . '_order_bills.rel_id=' . $filter['rel_id'],$limit,$offset);
			$this->tidy_data($rows, $cols);
			return $rows;
		}
	}
	else
		return parent::getList($cols, array('disabled' => 'false'), $offset, $limit, $orderby);
}

2. 货币管理

  • 列表页面:


  • 新建:


3. 数据精度配置

系统数据计算和显示设定,可用系统应用其他数据的计算和现实。


3. 地区包管理

可以自由管理地区,可以导入,或者自己定义与新建


ectools 报表,统计图调用

因为在finder中有top_extra_view这个属性,所以在finder的头部可以自定义添加,而下面的报表样式则根据

finder提供的属性写的。

$finder['params']['top_extra_view'] = $this->_extra_view;

在ectools中提供了报表,统计图的接口,只要按照规定重写几个方法就出现如下的一个报表



调用流程

首先需要在调用方法中根据需要选择其中的一个写法去调用一个继承ectools方法的一个类

<?php
//写法1
public function index(){
        
$html kernel::single('b2c_analysis_index')->set_service('b2c_analysis_shopsale')->set_extra_view(array('ectools'=>'analysis/index_view.html'))->set_params($_POST)->fetch();
        
$this->pagedata['_PAGE_CONTENT'] = $html;
        
$this->page();
    }

//写法2
public function index(){
        
$html kernel::single('b2c_analysis_index')->set_extra_view(array('ectools'=>'analysis/index_view.html'))->set_params($_POST)->fetch();
        
$this->pagedata['_PAGE_CONTENT'] = $html;
        
$this->page();
   }

//写法3
public function sale(){
        
kernel::single('b2c_analysis_index')->set_params($_POST)->display();
    }

在lib下创建 b2c_analysis_index

<?php
class b2c_analysis_index extends ectools_analysis_abstract implements ectools_analysis_interface{

}
所有要实现的功能就是在这个类中实现

1区 标题

  • 如果没定义过finder 则定义标题需要在添加
    protected $_title = '经营概况';
    
  • 如果定义finder则标题为finder的标题名

2区

这个在ectools中实现了,在ectools提供的报表统计图样式都依赖了这个数据,在一般情况下只要调用ectools的

报表并不需要改变,

如果需要替换掉需劫持ectools/view/analysis/time_header.html进行修改

3区

此区域的数据都是存放在$detail变量中的

<?php
class b2c_analysis_index extends ectools_analysis_abstract implements ectools_analysis_interface{
    
//可以配置显示的项,值默认是0
    
public $logs_options = array(
        
'1' => array(
                        
'name' => '订单量',
                        
'flag' => array(),
                    ),
        
'2' => array(
                        
'name' => '订单额',
                        
'flag' => array(),
                    ),
    );

    
/*
        $logs_options 完整的写法
        public $logs_options = array(
            '1' => array(
                'name' => '订单成交量',
                'flag' => array(
                    '0' => '全部',
                    '1' => '已发货',
                    '2' => '已付款',
                ),
                'memo' => '已发货订单数量',//鼠标移上去显示的说明
                'icon' => 'application_add.gif',
        ),

      */

    //定义3区的项,和显示的数据
    
public function ext_detail(&$detail){
        
$detail = array(); //把前面定义的清空,不再这个区域显示,使上面定义的只作为4区域的提供配置

        //中间的数据来源代码省掉了
        
$detail[app::get('b2c')->_('收入')]['value']= $earn;
        
$detail[app::get('b2c')->_('收入')]['memo']= app::get('b2c')->_('“收款额”减去“退款额”');
        
$detail[app::get('b2c')->_('收入')]['icon'] = 'coins.gif';

        
$detail[app::get('b2c')->_('新增订单')]['value']= $orderAll;
        
$detail[app::get('b2c')->_('新增订单')]['memo']= app::get('b2c')->_('新增加的订单数量');
        
$detail[app::get('b2c')->_('新增订单')]['icon'] = 'application_add.gif';
        
$detail[app::get('b2c')->_('付款订单')]['value']= $orderPay;
        
$detail[app::get('b2c')->_('付款订单')]['memo']= app::get('b2c')->_('付款的订单数量');
        
$detail[app::get('b2c')->_('付款订单')]['icon'] = 'application_key.gif';
        
$detail[app::get('b2c')->_('发货订单')]['value']= $orderShip;
        
$detail[app::get('b2c')->_('发货订单')]['memo']= app::get('b2c')->_('发货的订单数量');
        
$detail[app::get('b2c')->_('发货订单')]['icon'] = 'application_go.gif';

        
$detail[app::get('b2c')->_('新增会员')]['value']= $memberNewadd;
        
$detail[app::get('b2c')->_('新增会员')]['memo']= app::get('b2c')->_('新增加的会员数量');
        
$detail[app::get('b2c')->_('新增会员')]['icon'] = 'folder_user.gif';
        
$detail[app::get('b2c')->_('会员总数')]['value']= $memberNum;
        
$detail[app::get('b2c')->_('会员总数')]['memo']= app::get('b2c')->_('网店会员总数');
        
$detail[app::get('b2c')->_('会员总数')]['icon'] = 'group_add.gif';
    }

}

如果不要显示这个区域,则可以进行如下配置

public $detail_options = array(
        'hidden'=>true,
        );

配置统计图的高度

public $graph_options = array(
        'iframe_height' => 300,
    );

4区

这个统计图是ectools内置的,销售统计这个标题也是固定的,不可改变。

  • 如果不需要显示此区域则进行如下配置
    public $graph_options = array(
            'hidden' => true,
        );
    
    

    值得注意的是如果是在调用的时候使用如下的调用方法则不能对这个区域进行隐藏

    set_extra_view(array('ectools'=>'analysis/index_view.html'))
    

  • 统计表数据来源配置
    public function get_logs($time){
           $ilter = array(
                'time_from' => $time,
                'time_to' => $time+86400,
            );
            $filterShip = array(
                'time_from' => $time,
                'time_to' => $time+86400,
                'ship_status' => 1,
            );
            $filterPay = array(
                'time_from' => $time,
                'time_to' => $time+86400,
                'pay_status' => 1,
            );
            $shopsaleObj = $this->app->model('analysis_shopsale');
            $order = $shopsaleObj->get_order($filter); //全部
            $saleTimes = $order['saleTimes']; //订单量
            $salePrice = $order['salePrice']; //订单额
    
            $orderShip = $shopsaleObj->get_order($filterShip); //已发货
            $shipTimes = $orderShip['saleTimes']; //订单量
            $shipPrice = $orderShip['salePrice']; //订单额
    
            $orderPay = $shopsaleObj->get_order($filterPay); //已支付
            $payTimes = $orderPay['saleTimes']; //订单量
            $payPrice = $orderPay['salePrice']; //订单额
    
            $reship_num = $shopsaleObj->get_reship_num($filter); //商品退换量
            $sale_num = $shopsaleObj->get_sale_num($filter); //商品销售量
            $refund_ratio = isset($sale_num)?number_format($reship_num/$sale_num,2):0; //商品退换率
    
            $result[] = array('type'=>0, 'target'=>1, 'flag'=>0, 'value'=>$saleTimes); //target是$logs_options中的键,
            $result[] = array('type'=>0, 'target'=>1, 'flag'=>1, 'value'=>$shipTimes); //flag是$logs_options中的flag的键
            $result[] = array('type'=>0, 'target'=>1, 'flag'=>2, 'value'=>$payTimes);
            $result[] = array('type'=>0, 'target'=>2, 'flag'=>0, 'value'=>$salePrice);
            $result[] = array('type'=>0, 'target'=>2, 'flag'=>1, 'value'=>$shipPrice);
            $result[] = array('type'=>0, 'target'=>2, 'flag'=>2, 'value'=>$payPrice);
            $result[] = array('type'=>0, 'target'=>3, 'flag'=>0, 'value'=>$reship_num);
            $result[] = array('type'=>0, 'target'=>4, 'flag'=>0, 'value'=>$refund_ratio);
    
            return $result;
        }
    

    注意:

    如果用function get_logs($time){}这个方法保存统计图的数据则需要对这个类进行services注册
    注册的时候会在在sdb_ectools_analysis中,注册的作用是每天定时运行这个方法,并且把数据存放在
    sdb_ectools_analysis_logs这个数据表中,统计图调用的数据则是这个表的数据
    
    <service id="ectools_analyse_day">
        <class>b2c_analysis_index</class>
    </service>
    

5区

这个区域和4区是一样的,只是这个是自定义的,而4区是ectools内置的,

要把显示这个区域,则首先需要在定义如下方法

public function rank(){
        $filter = $this->_params;
        $filter['time_from'] = isset($filter['time_from'])?$filter['time_from']:'';
        $filter['time_to'] = isset($filter['time_to'])?$filter['time_to']:'';

        $render = kernel::single('base_render');

        $productObj = $this->app->model('analysis_productsale');
        $numProducts = $productObj->getlist('*', $filter, $offset=0, 5, 'saleTimes desc');
        $priceProducts = $productObj->getlist('*', $filter, $offset=0, 5, 'salePrice desc');

        $render->pagedata['numProducts'] = $numProducts;
        $render->pagedata['priceProducts'] = $priceProducts;
        $imageDefault = app::get('image')->getConf('image.set');
        $render->pagedata['defaultImage'] = $imageDefault['S']['default_image'];
        $html = $render->fetch('admin/analysis/productlist.html','b2c');

        $this->_render->pagedata['rank_html'] = $html;
    }
统计图需要自定义 admin/analysis/productlist.html

6区

如果finder区域不需要显示则一定要写如果下配置,不然会报错

public $finder_options = array(
        'hidden' => true,
    );

finder区域,和一般的[finder]写法是差不多的

public function finder(){
        return array(
            'model' => 'b2c_mdl_analysis_shopsale',
            'params' => array(
                'actions'=>array(
                    array(
                        'label'=>app::get('b2c')->_('生成报表'),
                        'class'=>'export',
                        'icon'=>'add.gif',
                        'href'=>'index.php?app=b2c&ctl=admin_analysis&act=shopsale&action=export',
                        'target'=>'{width:400,height:170,title:\''.app::get('b2c')->_('生成报表').'\'}'),
                ),
                'title'=>app::get('b2c')->_('店铺销售概况'),
                'use_buildin_recycle'=>false,
                'use_buildin_selectrow'=>false,
            ),
        );
    }
在生成报表列表的时候,和一般的finder是不同的,可能需要同时用到几个表,则需要在model重构几个方法,

function count, function getList, function _filter ,function get_schema 重构可以参照[url]

需要注意的是_filter的写法

public function _filter($filter,$tableAlias=null,$baseWhere=null){
        $where = array(1);
        if(isset($filter['time_from']) && $filter['time_from']){
            $where[] = ' createtime >='.$filter['time_from'];
        }
        if(isset($filter['time_to']) && $filter['time_to']){
            $where[] = ' createtime <'.$filter['time_to'];
        }
        if(isset($filter['ship_status']) && $filter['ship_status']){
            $where[] = ' ship_status =\''.$filter['ship_status'].'\'';
        }
        if(isset($filter['pay_status']) && $filter['pay_status']){
            $where[] = ' pay_status =\''.$filter['pay_status'].'\'';
        }

        return implode($where,' AND ');
    }