一、支付接口基本概念
- 支付接口的定义:
支付接口是指通过特定的协议和规范,将商户的支付请求提交给第三方支付平台,由第三方支付平台完成支付的过程。通过支付接口,商户可以方便地接受来自用户的支付请求,而用户也可以更加便捷地完成支付。 - 支付接口的流程:
支付接口的流程一般包括以下几个步骤:
(1)商户向支付平台发送支付请求;
(2)支付平台对支付请求进行验证和处理;
(3)支付平台向银行发送支付指令;
(4)银行处理支付指令,完成支付交易;
(5)支付平台向商户发送支付结果。
注:在这个过程中,如果任何一个环节出现问题,都有可能导致支付失败或出现错误,因此需要对每个环节都进行严格的处理和验证。 - 支付接口的安全性:
支付接口涉及到用户的资金交易,因此需要具备非常高的安全性。常用的支付接口安全技术包括SSL加密、支付密码、手机验证码等。
二、在PHP中处理支付接口
在PHP中处理支付接口,需要使用到一些常用的技术和工具。下面我们将逐一介绍这些技术和工具。
- 网络请求工具——cURL
cURL是一款用于访问网站、发送和接收网络数据的开源工具库。在PHP中,我们常常使用cURL来对支付接口进行访问和请求。通过使用cURL,我们可以方便地向支付平台发送请求,并且得到支付结果的返回值。同时,cURL还提供了对HTTPS协议的支持,可以确保支付请求的安全性。除了cURL外,还有一些常用的网络请求工具,如fsockopen、stream_socket_client等,可以根据实际情况进行选择和使用。 - 支付接口的SDK
支付接口的SDK是针对特定支付平台所开发的API接口,提供了一些常用的接口函数和参数,方便开发者快速开发和集成支付功能。常见的支付平台SDK包括支付宝接口SDK、微信支付SDK等。通过使用SDK,我们可以更加方便地调用支付接口,而不必关心具体的接口实现和技术细节。 - 接口签名和加密
为了确保支付接口的安全性,需要使用一些加密算法来对支付请求进行签名和加密。支付接口的签名算法一般包括MD5算法、SHA1算法等,通过对支付请求进行签名,可以确保请求的完整性和真实性,防止支付请求被篡改或伪造。支付接口的加密通常涉及到AES、RSA等加密算法,通过对支付数据进行加密,可以确保支付信息的安全性和保密性。 - 回调通知机制
在实际的支付场景中,支付平台会将支付结果通知给商户,以确保商户可以及时掌握支付状态和完成相应的业务处理。回调通知机制是指支付平台将异步通知消息发送到商户指定的URL地址,让商户可以获得及时的支付结果信息。商户需要对接收到的支付结果进行验证和处理,包括验证签名、验证订单状态等。在处理回调通知时,需要考虑一些支付平台的特殊性质,如微信支付的异步通知规则,支付宝接口的RSA签名等。
三、常用的支付接口处理方案
在PHP中处理支付接口,需要根据实际情况选择不同的处理方案。下面介绍几种常用的支付接口处理方案。
-
集成支付平台官方SDK
这是最常见的一种方案,即通过集成支付平台官方提供的SDK,直接调用其API接口实现支付功能。这种方案需要开发者了解支付平台的相关API接口和技术细节,可以通过阅读官方的文档和开发指导进行学习和理解。 -
使用第三方支付接口集成工具
针对不同的支付平台,也有一些第三方支付接口集成工具,如支付宝开放平台、微信支付SDK等,可以方便地实现支付接口的集成和使用。这种方案需要开发者了解具体的支付平台和第三方集成工具的技术细节和使用方法,以确保支付功能的稳定和安全。 -
自行编写支付接口处理类
对于一些特殊的支付场景,开发者也可以自行编写支付接口处理类,实现对支付请求的处理和支付结果的回调。这种方案需要开发者了解具体的支付接口技术和编程技术,具有较高的技术难度和风险,需要进行充分的测试和验证。
综上所述,处理支付接口是网站开发中的一个非常关键的环节,需要开发者具备深入的技术和扎实的实践经验。在选择支付接口处理方案和技术工具时,需要根据实际情况进行选择和权衡,确保支付功能的稳定、安全和可用性。
以上内容转至php中文网:原文链接
四、支付接口
- 准备工作:
(1)下载OpenSSL(官网链接):OpenSSL是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。这个包广泛被应用在互联网的网页服务器上。OpenSSL整个软件包大概可以分成三个主要的功能部分:SSL协议库、应用程序以及密码算法库。OpenSSL的目录结构自然也是围绕这三个功能部分进行规划的。作为一个基于密码学的安全开发包,OpenSSL提供的功能相当强大和全面,囊括了主要的密码算法、常用的密钥和证书封装管理功能以及SSL协议,并提供了丰富的应用程序供测试或其它目的使用。
(2) 生成公、私秘钥(安装和环境变量配置略)
- 检测OpenSSL是否安装成功: 打开命令窗口,快捷键【win+r】,输入CMD回车: 输入: openssl
- 打开命令窗口来操作OpenSSL来生成密钥:先切换到要生成密钥的目录文件下:运行如下命令生成密钥:生成一个1024位的名为privatekey密钥的pem文件;
openssl genrsa -out privatekey.pem 1024
- 根据刚刚生成的私钥 (privatekey.pem) 来生成公钥 (publickkey.pem) :
openssl rsa -in privatekey.pem -pubout -out publickkey.pem
2. TP编写配置文件:项目目录/config/pay.php (项目目录项的config文件下创建pay.php配置文件)
<?php
return [
'bestPay'=>[
// 私钥(刚刚生成的)
'pri_key'=>'',
// 公钥(刚刚生成的)
'pub_key'=>'',
// 平台编号(随机唯一数字)
'merchantNo'=>'',
// 小程序ID
'appId'=>'wx.........................'
]
];
- 预支付接口逻辑
/**
* 小程序支付
*/
public function pay()
{
// 小程序中的用户ID
if (!$this->input['openId']) {
$this->ApiError("openId数据错误");
}
// 获取订单信息
$order = HotelOrder::where('id', '=', $this->input['id'])->find();
// 支付流水号
$order->out_trade_no = date('His') . $order['order_number'];
// 保存订单流水号
$order->save();
// 支付对象工厂模式 创建bestPay翼支付对象
$payment = PayFactory::createPayMent('bestPay');
// extend 支付扩展参数
// 支付类型:订单支付
$extend['payType'] = 'payOrder';
// 支付主题
$extend['subject'] = '门店下单';
// 商品信息
$extend['goodsInfo'] = '梅菜扣肉、红烧肉';
//$extend['notifyUrl'] = \think\facade\Config::get('app.app_host').url('/api/Notify/pay');
// 微信支付完成后回调通知地址
$extend['notifyUrl'] = (string)Route::buildUrl('/api/Notify/pay')->domain(true);
// 支付超时时间(秒)
$extend['timeOut'] = 180;
// 交易场景
$extend['tradeScene'] = 'ONLINE';
// 支付场景
$extend['payScene'] = 'APP';
// 交易类型
$extend['tradeType'] = 'TENPAY';
// 小程序ID
$extend['appId'] = Config::get('pay.bestPay.appId');
//$extend['userCode'] = 'oh1xg5KJxeZ3pSDBYQwFYMxEIsD0';
// 小程序用户openID
$extend['userCode'] = $this->input['openId'];
// 请求时间
$extend['requestDate'] = date("Y-m-d H:i:s");
// fk 风险
// 服务身份识别
$fk['service_identify'] = '10000001';
$fk['subject'] = $extend['subject'];
$fk['product_type'] = 1;
$fk['goods_count'] = 1;
$fk = array($fk);
// 风险控制信息
$extend['riskControlInfo'] = json_encode($fk);
// 组装支付数据,并接入翼支付接口
$res = $payment->pay($order['deal_price'] * 100, $order->out_trade_no, array_merge($this->getBestPayParams(), $extend));
//返回微信小程序支付所需数据
if ($res['success']) {
//更新outTradeNo
$this->ApiSuccess('执行成功', $res['data']['appMap']);
} else {
$this->ApiError($res['error']);
}
}
- $payment->pay:支付接口逻辑
class BestPay implements IPay
{
private $config;
private const API = ""; // 翼支付接口(第三方提供 )
private const APIURL=[
'_payOrder' => self::API.'r', // 支付接口(第三方提供 )
'_refund' => self::API.'', // 退款接口(第三方提供 )
];
/** 构造方法获取支付的参数配置 */
public function __construct()
{
$this->config = Config::get('pay.bestPay'); // 公钥:pub_key 、私钥:pri_key、平台编码:merchantNo、微信小程序ID:appId
}
/**
* 魔术方法,监视该类生成对象所调用的方法,如果对象调用的方法不存在,则执行__call()方法
* @param $name 支付名称 _payOrder
* @param $arguments 订单详细数据
* @return mixed|string|null
* @throws Exception
*/
public function __call($name, $arguments)
{
if(array_key_exists($name,self::APIURL)){
$data = $arguments[0];
// 声明翼支付对象(该对象属性值包含:验证签名、加密方式等)
$util = new PayUtil('bestPay');
// 数组key按照abc...排序
ksort($data);
// 通过RSA非对称加密手段,获取签名后的数据(保证数据传输的安全性)
$data['sign'] = $util->Sign($data);
ksort($data);
$data = json_encode($data);
// 返回请求支付接口返回的数据
return $util->request(self::APIURL[$name],$data,'post');
}else{
throw new \Exception("there is no method {$name} for payment".self::class);
}
}
/**
* @param int $amount 金额(分)
* @param string $orderNo 支付流水号(也可以是订单号,保持唯一性即可)
* @param array $extend merchantNo:平台编码,operator:支付人的微信昵称,notifyUrl:支付结果回调地址
* ,goodsInfo:商品信息,subject:主题
* ,timeOut:超时时间,mediumNo:,requestDate:请求时间
* @return mixed
*/
public function pay($amount, $orderNo, $extend = [])
{
$type = 'payOrder';
if(isset($extend['payType'])){
$type = $extend['payType'];
unset($extend['payType']);
}
// 支付金额
$request['tradeAmt'] = $amount;
// 支付流水号
$request['outTradeNo'] = $orderNo;
$request = array_merge($request,$extend);
// call_user_func_array: 自定义回调函数回调__call魔术方法,并把一个数组参数作为回调函数的参数。
$res = call_user_func_array(array($this, '_'.$type), [$request]);
// 回调函数错误信息
$error = $res['errorMsg']??$res['result']['tradeResultDesc'];
// 回调函数成功
$success = $res['success'];
$data = $res['result'];
$tradeNo = '';
// compact 创建一个包含变量名和它们的值的数组
return compact('success','data','error','tradeNo');
}
}
- 展示接口返回数据
- 微信小程序端
// 发起支付
submit: function() {
let that = this;
wx.showLoading({
title: '发起支付中',
})
let newData = {};
newData = {
id: that.data.order_id, //订单id
pay_type: this.data.infoObj.pay_type, //支付方式
openId: wx.getStorageSync('openid')
}
newData = JSON.stringify(newData)
util.send('/api/pay/pay', 'post', newData, function(payinfo) { // 先访问预支付接口,得到payinfo数据->微信统一下单接口
wx.requestPayment({
'timeStamp': payinfo.timeStamp,
'nonceStr': payinfo.nonceStr,
'package': payinfo.package,
'signType': payinfo.signType,
'paySign': payinfo.paySign,
success: (res) => {
// 支付成功跳转订单详情页
},
fail: function(res) {
// 支付失败
},
})
}, function(err) {
console.log(err)
wx.showToast({
title: err.rtnMsg,
icon: 'none'
})
})
},
附:统一下单接口:wx.requestPayment (下图:官方解释字段)