### 一、后台添加接口
1、登录管理员后台 → 接口通道 → 添加接口
![](https://img.kancloud.cn/99/a4/99a441e777708f83951964c0910851d4_1667x802.png)
2、接口英文别名就是接口实现类的文件名(必须是唯一的,不要和其他接口重名)
![](https://img.kancloud.cn/66/c2/66c249fa95caedb888d32d2621ba3b3e_1411x1329.png)
### 二、编写接口代码
1、pay/application/common/目录里创建接口类,extends继承自Pay.php,例如元宝支付Yuanbaopay.php,参考代码如下:
![](https://img.kancloud.cn/40/9e/409ef8f55ef5e3c666257cf4772836b8_1104x1370.png)
`<?php
namespace app\common;
use think\Request;
use app\common\model\Order as OrderModel;
use app\common\model\WayAccount;
abstract class Yuanbaopay extends Pay{
public function startPay($params){
//验证订单
if(!isset($params["debug"])){
$order = OrderModel::field("id")->where('number',$params["pay_id"])->find();
empty($order) && outputError('订单不存在,请重新提交');
}
$alias = $this->way['alias'];
$params["notify_url"] = $this->notify_url($alias);//异步通知地址
$params["return_url"] = $this->return_url($alias);//前台地址
$params['paytype']= $this->get_paytype();//接口支付方式
//创建支付对象
$payobj= new \pay\Yuanbaopay($this->account['params']);
//冻结接口额度(子类调用父类方法)
parent::freezeApiAmount($params,$this->account);
echo $payobj->submit($params);
}
public function query($order_no){
$falg=false;
//查询支付订单状态
try{
//创建支付对象
$payobj= new \pay\Yuanbaopay($this->account['params']);
$result=$payobj->orderquery($order_no);
if($result){
$falg=true;
}else {
$falg=false;
}
} catch (\Exception $e) {
}
return $falg;
}
/**
* 页面回调
*/
public function page_callback($params){
//查询订单
$order= OrderModel::where('number',$params["pay_id"])->find();
if(!$order){
exit('不存在编号['.$params["pay_id"].']的订单!');
}
//渠道账户
$accountid = $order["way_account_id"];
$account = CacheData("account-".$accountid,function() use($accountid){
return WayAccount::where('id',$accountid)->find();
},300);
//创建支付对象
$payobj= new \pay\Yuanbaopay(json_decode($account['params'],true));
//调用异步通知验证
$verifyresult=$payobj->notify($params);
if ($verifyresult) {
//整理需要的数据(统一格式)
$postdata=array(
"pay_id" => empty($order['pay_id'])?$order['number']:$order['pay_id'],//需要充值的ID 或订单号 或用户名
"pay_no" => $order['number'], //付款后生成的唯一流水号
"pay_money" => $order['money'],//实际付款金额
"pay_time" => (int)$order['pay_time'], //付款的时间戳
"pay_type" => (int)$order['pay_type'], //支付方式 1:支付宝 2:微信支付 3:QQ钱包
);
}else{
$postdata=array(
"pay_id" => '',
"pay_no" => '',
"pay_money" => '0',
"pay_time" => (int)time(),
"pay_type" => 0
);
}
//跳转到页面回调地址
$this->jumpPageCallback($order,$postdata);
}
/**
* 异步通知回调
*/
public function notify_callback($params){
//查询数据库订单状态
$order = OrderModel::where('number', $params['pay_id'])->find();
if(!$order){
exit('不存在编号['.$params['pay_id'].']的订单!');
}
//防止恶意刷新加钱
if ($order['status'] == 1) {
//直接返回给上游OK
exit('success');
}
//渠道账户
$account = WayAccount::where('id',$order["way_account_id"])->find();
//创建支付对象
$payobj= new \pay\Yuanbaopay(json_decode($account['params'],true));
//调用异步通知验证
$verifyresult=$payobj->notify($params);
if ($verifyresult) {
//实际付款金额
$money = $params['pay_money'];
//完成订单
$falg=parent::completeOrder($order,$money);
exit($falg ? 'success' : 'fail');
}else{
exit('fail'); //返回失败 继续补单
}
}
//获取支付方式
abstract public function get_paytype();
//异步通知地址
abstract public function notify_url($alias);
//同步通知地址
abstract public function return_url($alias);
}
?>`
3、pay/application/common/payment目录下,新建接口实现类(类名和后台添加的接口通道名称一样),如下:
![](https://img.kancloud.cn/90/b1/90b17931ad838c0b00b760b9f1ca1e16_1146x455.png)
`<?php
namespace app\common\payment;
use think\Request;
use app\common\Yuanbaopay;
class YuanbaopayAlipay extends Yuanbaopay{
public function get_paytype()
{
return "1";
}
public function return_url($alias)
{
return Request::instance()->domain().'/callback/page/'.$alias;
}
public function notify_url($alias)
{
return Request::instance()->domain().'/callback/notify/'.$alias;
}
public function page_callback($params)
{
return parent::page_callback($params);
}
public function notify_callback($params)
{
return parent::notify_callback($params);
}
}
?>`
4、pay/extend/pay目录下添加接口交互类(目标平台的SDK),参考代码如下:
![](https://img.kancloud.cn/ad/6d/ad6d18f1e6e0d7b15b9aacd30ed24b9d_1039x483.png)
`<?php
namespace pay;
use \think\Db;
use think\Request;
class Yuanbaopay {
//网关地址
private $gateway;
//查询订单地址
private $order_url;
//商户号
private $appid;
//请求单号
private $sn;
//支付秘钥
private $appkeyappkey;
//字符集
private $charset = 'UTF-8';
//超时时间
private $timeOut = 120;
//http状态码
private $responseCode = 0;
//异常信息
private $error;
//构造方法
public function __construct($account){
$this->appid = $account['appid'];
$this->appkey = $account['appkey'];
if(isset($account['url']) && !empty($account['url'])){
$this->gateway = $account['url'];
}
if(isset($account['order_url']) && !empty($account['order_url'])){
$this->order_url = $account['order_url'];
}
}
/**
*
* 提交订单
* $params 传输的数据
*/
public function submit($params){
//商品名称
$subject=isset($params['subject']) && !empty($params['subject']) ? $params['subject'] : "购买商品";
//组合生成签名参数
$postdata=array(
"appid" => $this->appid,//商户ID
"type" => $params['paytype'],//支付方式,支付宝1 微信支付2
"payid" => $params['pay_id'],//商户订单号
"notify_url" => $params['notify_url'],//异步通知地址
"return_url" => $params['return_url'],//同步通知地址
"subject" => $subject,//商品名称
"money" => $params['money'], //订单金额(元)
);
//生成签名
$postdata["sign"] = $this->generateSign($postdata,$this->appkey);
//发起请求
if(isset($params['native']) && $params['native'] ){
//返回原生支付结果
$pay_url = $this->gateway."?".http_build_query($postdata);
returnNativeJson($params['pay_id'],$pay_url);
}else{
// 跳转到页面
return $this->buildRequestForm($this->gateway,$postdata);
}
}
/**
* 支付通知
* @param $params 通知的数据
*/
public function notify($params){
$falg=false;
//验证签名
$rst = $this->rsaCheck($params);
if(!$rst){
//file_put_contents(LOG_PATH .'yuanbaopay.log', '验签失败\r\n' , FILE_APPEND );
return false;
}
//查询支付订单状态
try{
$rst = $this->orderquery($params['pay_no'],true);
} catch (\Exception $e) {
}
if($rst){
return true;
}else {
//file_put_contents(LOG_PATH .'yuanbaopay.log', '查询订单状态错误\r\n', FILE_APPEND);
return false;
}
}
/**
*
* 支付查询接口
* @param order_no 订单编号
*/
public function orderquery($order_no,$pay_no=false){
$param = $pay_no ? "pay_no" : "pay_id";
$data=array(
"appid" => $this->appid,
$param => $order_no//商户订单号
);
//生成签名
$data["sign"] = $this->generateSign($data,$this->appkey);
$responseData = $this->curl_post($this->order_url,$data);
if(empty($responseData)){
return false;
}
if($responseData['code']==1){
return true;
}else {
return false;
}
}
protected function generateSign($params,$appkey) {
//ksort()对数组按照键名进行升序排序
ksort($params);
//reset()内部指针指向数组中的第一个元素
reset($params);
//$sign = http_build_query($params, '', '&');
$sign = ''; //初始化
foreach ($params AS $key => $val) { //遍历POST参数
if ($val == '' || $key == 'sign' || $key == 'signArray'|| $key == '__token__'||$key=='encrypt'||$key=='charset') continue; //跳过这些不签名
if ($sign) $sign.= '&'; //第一个字符串签名不加& 其他加&连接起来参数
$sign.= "$key=$val"; //拼接为url参数形式
}
//拼接商户密钥,然后md5加密
$stringTemp = md5($sign.$appkey);
//转为小写
return strtolower($stringTemp);
}
/**
* 转换字符集编码
* @param $data
* @param $targetCharset
* @return string
*/
protected function characet($data, $targetCharset) {
if (!empty($data)) {
$fileType = $this->charset;
if (strcasecmp($fileType, $targetCharset) != 0) {
$data = mb_convert_encoding($data, $targetCharset, $fileType);
//$data = iconv($fileType, $targetCharset.'//IGNORE', $data);
}
}
return $data;
}
/**
*
* 校验$value是否非空
*/
protected function checkEmpty($value) {
if (!isset($value))
return true;
if ($value === null)
return true;
if (trim($value) === "")
return true;
return false;
}
/**
*
* 验签函数
*/
protected function rsaCheck($params){
$sign1 = $params['sign'];
//不参与签名
unset($params['sign']);
unset($params['alias']);
$sign2= $this->generateSign($params,$this->appkey);
if ($sign1 != $sign2) {
return false;
}else{
return true;
}
}
/**
* curl发送post请求
* @param url 请求地址
* @param postData 要传递的post数据
*/
private function curl_post($url, $xmldata) {
//启动一个CURL会话
$ch = curl_init();
// 设置curl允许执行的最长秒数
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeOut);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
// 获取的信息以文件流的形式返回,而不是直接输出。
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
//发送一个常规的POST请求。
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_URL, $url);
//要传送的所有数据
curl_setopt($ch, CURLOPT_POSTFIELDS, $xmldata);
// 执行操作
$res = curl_exec($ch);
$this->responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($res == NULL) {
$this->error = "call http err :" . curl_errno($ch) . " - " . curl_error($ch) ;
curl_close($ch);
return null;
} else if($this->responseCode != "200") {
$this->error = "call http err httpcode=" . $this->responseCode ;
curl_close($ch);
return null;
}
curl_close($ch);
return $res;
}
/**
* 建立请求,以表单HTML形式构造(默认)
* @param $url 请求地址
* @param $params 请求参数数组
* @return 提交表单HTML文本
*/
protected function buildRequestForm($url,$params) {
$sHtml = "正在跳转至支付页面...<form id='baofupaysubmit' name='baofupaysubmit' action='".$url."?charset=".$this->charset."' method='GET'>";
foreach($params as $key=>$val){
if (false === $this->checkEmpty($val)) {
$val = str_replace("'","'",$val);
$sHtml.= "<input type='hidden' name='".$key."' value='".$val."'/>";
}
}
//submit按钮控件请不要含有name属性
$sHtml = $sHtml."<input type='submit' value='ok' style='display:none;''></form>";
$sHtml = $sHtml."<script>document.forms['baofupaysubmit'].submit();</script>";
return $sHtml;
}
}
?>`
在接口类或者接口实现类里调用,调用代码如下:
`<?php
//创建支付对象
$payobj= new \pay\Yuanbaopay($this->account['params']);
//补充说明:$this->account['params']是传递过去的通道账户
?>`