# 接口编写
`LoveKKVIP`支持自定义支付接口,站长可根据自身需求扩展自己所需要的在线支付接口。
## 接口文件
接口文件为一个独立的类文件,按照PHP类的编写方式进行编写。
接口文件名称必须首字母大写,其余小写,如:Base.php、Fateqq.php。
接口文件必须存放在`LoveKKVIP/Pay/`目录下。
## 接口描述
接口编写前必须对接口进行描述,接口描述使用PHP注释代码进行编写。
接口描述编写格式:
```php
/**
* 介绍文本
*
* @title 唯一标识
* @name 显示名称
* @regurl 注册地址
* @support 支持列表
* @config 变量列表
*/
```
**介绍文本**:接口的文本说明,将在[接口设置](option/pay.md)界面对接口信息进行描述,支持Html代码。
**唯一标识**:用以区分支付接口的标识名称,避免接口调用错误,请保证此标识与其余支付接口不重复,如:alipay。
**显示名称**:显示给站长查看的名称,仅作为显示,并不涉及其他方面,方便站长对所用支付接口进行区分。
**注册地址**:支付接口的注册地址,方便站长在使用接口的时候直接点击到达注册界面。
**支持列表**:支付接口所支持的支付类型列表,此项重要,将关系到接口的使用问题。
支持列表配置方式为:`类型标识:显示名称`,类型标识与显示名称中间使用英文冒号(:)分隔,多项类型以英文逗号(,)进行分隔,如:`alipay:支付宝,wxpay:微信`。
配置中的类型标识有固定格式,目前支持的格式为:`alipay:支付宝`、`wxpay:微信`、`qqpay:QQ钱包`、`tenpay:财付通`、`paypal:贝宝`、`bank:网银`,设置了支持列表后,在用户充值时将会自动调用所支持的支付类型,并显示相应图标提供选择。
如码支付设置`alipay:支付宝,wxpay:微信,qqpay:QQ钱包`,则充值时将显示`支付宝`、`微信`、`QQ钱包`三种充值接口。
易支付设置`wxpay:微信,alipay:支付宝,qqpay:QQ钱包,tenpay:财付通`,则充值时将显示`支付宝`、`微信`、`QQ钱包`、`财付通`四中充值接口。
**变量列表**:支付接口使用所需的配置变量,此项极为重要,将关系到接口是否能够正常使用问题。
变量列表配置方式为:`变量名称:显示名称`,变量名称与显示名称中间使用英文冒号(:)分隔,多个变量以英文逗号(,)进行分隔,如:`id:码支付ID,key:通信密钥`。
变量名称为接口开发中调用变量时的变量名称,显示名称为[接口设置](/option/pay)界面显示给站长看的文本。
## 接口类命名
所有支付接口命名以`LoveKKVIP_Pay_接口名称`方式命名,接口名称为首字母大写,其余小写格式,且必须与接口文件名对应。
如支付宝接口类名为:`LoveKKVIP_Pay_Alipay`,对应文件为:`LoveKKVIP/Pay/Alipay.php`;码支付接口类名为:`LoveKKVIP_Pay_Fateqq`,对应文件为:`LoveKKVIP/Pay/Fateqq.php`。
## 接口继承
所有支付接口必须继承`LoveKKVIP_Pay`类。
```php
class LoveKKVIP_Pay_Fateqq extends LoveKKVIP_Pay {
}
```
## 基类支持
接口继承`LoveKKVIP_Pay`类后,将获得接口开发所需基础支持变量。
**$options变量**:Helper::options()对象变量,可获取当前网站各项配置。
**$request变量**:Typecho_Request对象变量,可获取请求参数。
**$response变量**:Typecho_Response对象变量,可进行输出操作。
**$user变量**:Widget_User对象,可对用户进行操作。
**$config变量**:变量数组,为接口描述中编写的变量保存数组,如接口中编写了变量列表为`id:码支付ID,key:通信密钥`,则使用`$this->config['id']`可获取id的配置信息,使用`$this->config['key']`可获取key的配置信息。
**$notify_url变量**:异步通知调用地址,此为自动配置变量,无特殊需求无需更改。
**$return_url变量**:支付跳转地址,此为自动配置变量,无特殊需求无需更改。
!> 接口必须编写三个外部调用方法提供给插件调用,其余接口实现方法则由自己编写。
## getParam方法
getParam方法为支付接口请求参数生成方法,在此方法中对接口请求所需进行操作,并返回给插件使用。
**接收参数**:方法将传入一个$order数组,此数组保存用户充值订单数据。
**返回数据**:数据返回可为数组或字符串格式,字符串格式返回后插件将不做处理直接输出,数组格式将解析为一个表单并自动提交。
数组中总计包含五项内容,格式如下:
```php
$form = [
'name' => 'alipay',
'action' => $this->api,
'method' => 'POST',
'hidden' => $param,
'submit' => 'ok'
];
```
**name**:表单名称。
**action**:提交地址。
**method**:提交方式。
**hidden**:隐藏字段数组,为`$key => $val`格式。
**submit**:可选内容,设定提交按钮`value`。
当插件接收到返回的数组后,将按照数组进行表单创建,创建过程如下:
```php
// 如果返回结果是字符串
if ( is_string($param) ) echo $param; // 直接输出
else { // 数组则生成表单
// 生成表单Html
$strHtml = "<form id='lovekkvip_submit' name='" . $param['name'] . "' action='" . $param['action'] . "' method='" . $param['method'] . "'>";
// 循环隐藏数据
foreach ( $param['hidden'] as $key => $val )
$strHtml .= "<input type='hidden' name='" . $key . "' value='" . $val . "'>"; // 加入隐藏数据
// 循环其他数据
foreach ( $param as $key => $val ) {
// 如果是表单信息或隐藏信息则跳过
if ( 'name' == $key || 'action' == $key || 'method' == $key || 'hidden' == $key || 'submit' == $key ) continue;
// 加入表单数据
$strHtml .= "<input name='" . $key . "' type='" . $val['type'] . "' value='" . $val['value'] . "'>";
}
// 如果设置了提交按钮的值
if ( isset($param['submit']) )
$strHtml .= "<input type='submit' value='" . $param['submit'] . "' style='display:none'></form><script>document.forms['lovekkvip_submit'].submit();</script>";
else
$strHtml .= "<input type='submit' value='正在验证订单并跳转, 请耐心等候...'></form><script>document.forms['lovekkvip_submit'].submit();</script>";
// 输出表单
echo $strHtml;
}
```
## notifyUrl方法
notifyUrl方法为异步通知请求验证方法,此方法仅提供验证支持,验证成功后返回订单数据,插件根据订单数据对订单进行操作,验证失败则返回false。
返回的订单数据格式为:
```php
[
'trade' => '', // 本地订单号
'trade_out' => '', // 远程订单号
'pay_type' => '', // 支付方式
'price' => '', // 订单金额
'money' => '' // 实付金额
];
```
## returnUrl方法
returnUrl方法为支付跳转验证方法,此方法仅提供验证支持,验证成功后返回true,不成功返回false,插件接收返回信息后进行页面跳转。
## 完整支付接口代码
以易支付接口为例,完整开发代码如下:
```php
<?php
if ( !defined('__TYPECHO_ROOT_DIR__') ) exit;
/**
* 易支付接口,第三方支付接口,<font style="color:red">本程序仅提供接口支持,不承担第三方跑路风险</font>
*
* @title hackwl
* @name 易支付
* @regurl http://pay.hackwl.cn/user/reg.php
* @support wxpay:微信,alipay:支付宝,qqpay:QQ钱包,tenpay:财付通
* @config id:商户ID,key:商户密钥
*/
class LoveKKVIP_Pay_Hackwl extends LoveKKVIP_Pay {
// API提交地址
private $api = 'http://pay.hackwl.cn/submit.php?';
/**
* 支付提交方法
*
* @access public
* @param array $order 订单数据
* @return array
*/
public function getParam($order) {
// 设置提交参数
$param = [
'pid' => $this->config['id'], // 商户ID
'type' => $order['pay_type'], // 支付方式
'notify_url' => $this->notify_url, // 通知地址
'return_url' => $this->return_url, // 回调地址
'out_trade_no' => $order['trade'], // 订单编号
'name' => '充值 ' . $order['price'] . '元', // 商品名称
'money' => $order['price'], // 充值金额
'sitename' => $this->options->title // 站点名称
];
// 处理请求参数
$param = $this->buildParam($param);
// 设置表单数据
$form = [
'name' => 'alipaysubmit', // 表单名称
'action' => $this->api . 'submit.php?_input_charset=utf-8', // 提交地址
'method' => 'POST', // 提交方式
'hidden' => $param // 提交参数
];
return $form;
}
/**
* 支付通知地址方法
*
* @access public
* @return void
*/
public function notifyUrl() {
// 获取参数
$param = [
'pid' => $this->request->filter('int')->pid, // 商户ID
'trade_no' => $this->request->filter('strip_tags', 'trim', 'xss')->trade_no, // 易支付订单编号
'out_trade_no' => $this->request->filter('strip_tags', 'trim', 'xss')->out_trade_no, // 本地订单编号
'type' => $this->request->filter('strip_tags', 'trim', 'xss')->type, // 支付方式
'name' => $this->request->filter('strip_tags', 'trim', 'xss')->name, // 商品名称
'money' => $this->request->filter('strip_tags', 'trim', 'xss')->money, // 金额
'trade_status' => $this->request->filter('strip_tags', 'trim', 'xss')->trade_status, // 支付状态
'sign' => $this->request->filter('strip_tags', 'trim', 'xss')->sign, // 签名字符串
'sign_type' => $this->request->filter('strip_tags', 'trim', 'xss')->sign_type // 签名类型
];
// 获取签名
$sign = $this->getSign($param);
// 验证通过
if ( $sign == $param['sign'] ) {
// 如果支付成功
if ( 'TRADE_FINISHED' == $param['trade_status'] || 'TRADE_SUCCESS' == $param['trade_status'] )
return [
'trade' => $param['out_trade_no'], // 本地订单号
'trade_out' => $param['trade_no'], // 远程订单号
'pay_type' => $param['type'], // 支付方式
'price' => $param['money'], // 订单金额
'money' => $param['money'] // 实付金额
];
}
return false;
}
/**
* 支付回调地址方法
*
* @access public
* @return void
*/
public function returnUrl() {
return $this->notifyUrl();
}
/**
* 获取签名
*
* @access private
* @param array $param 请求参数
* @return string
*/
private function getSign($param) {
// 过滤参数
$param = $this->paramFilter($param);
// 参数排序
$param = $this->argSort($param);
$sign = $this->makeSign($param);
return $sign;
}
/**
* 生成提交参数数组
*
* @access private
* @param array $param 要提交的参数数组
* @return array
*/
private function buildParam($param) {
// 获取签名
$sign = $this->getSign($param);
// 加入签名
$param['sign'] = $sign;
// 加密方式为md5
$param['sign_type'] = 'MD5';
return $param;
}
/**
* 过滤空值及签名参数
*
* @access private
* @param array $param 提交参数
* @return array
*/
private function paramFilter($param) {
// 过滤空值
$param = array_filter($param);
// 删除sign
unset($param['sign']);
// 删除sign_type
unset($param['sign_type']);
return $param;
}
/**
* 数组排序
*
* @access private
* @param array $param 提交参数
* @return array
*/
private function argSort($param) {
// 排序
ksort($param);
// 充值游标
reset($param);
return $param;
}
/**
* 请求签名
*
* @access private
* @param array $param 提交请求
* @return string
*/
private function makeSign($param) {
// 拼接字符串
$str = get_magic_quotes_gpc() ? stripslashes(http_build_query($param)) : http_build_query($param);
// 不编译url
$str = urldecode($str);
// 生成签名
$sign = md5($str . $this->config['key']);
return $sign;
}
}
```