<?php
|
|
namespace app\common\library\fbpay;
|
|
use app\api\service\order\paysuccess\type\PayTypeSuccessFactory;
|
use app\common\enum\order\OrderTypeEnum;
|
use app\common\enum\order\OrderPayTypeEnum;
|
use app\common\exception\BaseException;
|
use app\common\model\app\AppWx as AppWxModel;
|
use think\helper\Str;
|
use EasyWeChat\Factory;
|
use app\common\model\user\UserAuth as UserAuthModel;
|
use app\shop\model\shop\FbCashApply;
|
use app\shop\service\FbCashApply as FbCashApplyService;
|
use app\common\model\app\App as AppModel;
|
use app\common\model\app\AppPay as AppPayModel;
|
use app\api\model\settings\Setting as SettingModel;
|
use app\common\model\supplier\Supplier as SupplierModel;
|
|
/**
|
* 付呗支付
|
*/
|
class FbPay
|
{
|
// 付呗支付配置
|
private $app;
|
|
private $gateway = 'https://shq-api.51fubei.com/gateway/agent';
|
|
/**
|
* 构造函数
|
*/
|
public function __construct($app)
|
{
|
$this->app = $app;
|
}
|
|
/**
|
* 统一下单API
|
*/
|
public function unifiedorder($order_no, $openid, $totalFee, $orderType = OrderTypeEnum::MASTER, $pay_source, $multiple)
|
{
|
//判断是否使用付呗插件,兼容其他商户的付呗支付 by yj 2023.12.16
|
if(!empty($this->app['config']["is_use_plus"])){
|
$sub_appid = $this->app['config']["cashier_app_id"];
|
}else{
|
$sub_appid = $this->app['config']["app_id"];
|
}
|
|
$requestConfigs = array(
|
'merchant_id' => $this->app['config']["merchant_id"],
|
'store_id' => $this->app['config']["store_id"],
|
//'sub_appid' => $this->app['config']["app_id"],
|
'sub_appid'=> $sub_appid,
|
'merchant_order_sn' => $order_no,
|
'total_amount' => $totalFee,
|
'user_id' => $openid,
|
'pay_type' => "wxpay",
|
'body' =>$order_no,
|
'attach' => ['order_type' => $orderType, 'pay_source' => $pay_source, 'multiple' => $multiple],
|
'notify_url' => base_url() . 'index.php/job/notify/fbpay',
|
);
|
//使用插件才能传该字段 by yj 2023.12.22
|
if(!empty($this->app['config']["is_use_plus"])){
|
$requestConfigs["pay_scene"] = "OPENAPI_PAYMENT_APPLET_PLUGIN";
|
$requestConfigs["timeout_express"] = date('YmdHis',time() + 60 * 15);
|
}
|
$commonConfigs = array(
|
//公共参数
|
'method' => 'fbpay.order.create',
|
'format' => 'json',
|
'sign_method' => 'md5',
|
'nonce' => Str::random(32),
|
'version' => '2.0',
|
'biz_content' => json_encode($requestConfigs),
|
);
|
if($this->app['config']["vendor_sn"]) {
|
$commonConfigs['vendor_sn'] = $this->app['config']["vendor_sn"];
|
} else {
|
$commonConfigs['app_id'] = $this->app['config']["fbapp_id"];
|
}
|
$commonConfigs["sign"] = $this->makeSign($commonConfigs);
|
$result = $this->curlPostContents($this->gateway,$commonConfigs);
|
$result = json_decode($result);
|
|
if($result->result_code != 200){
|
throw new BaseException(['msg' => '付呗支付api: ' . $result->result_message]);
|
}
|
$sign_package = (array)($result->data->sign_package);
|
// log_write($result);
|
//如果是微信小程序
|
$time = time();
|
if($pay_source == 'wx') {
|
// 二次签名的参数必须与下面相同
|
$params = [
|
'appId' => $sign_package['appId'],//有所修改
|
'timeStamp' => $time,
|
'nonceStr' => $sign_package['nonceStr'],
|
'package' => $sign_package['package'],
|
'signType' => 'MD5',
|
];
|
//$sign_package['paySign'] = $this->makeSign($params);
|
//$sign_package['paySign'] = $sign_package['paySign'];
|
$sign_package['prepay_id'] = str_replace('prepay_id=', '', $sign_package['package']);
|
$result_data =[
|
'prepay_id' => $sign_package['prepay_id'],
|
'nonceStr' => $sign_package['nonceStr'],
|
'timeStamp' => $sign_package['timeStamp'],
|
'paySign' => $sign_package['paySign'],
|
'signType' => $sign_package['signType'],
|
// 'orderSn'=>$result->data->order_sn,// 付呗新增参数 by yj 2023.12.16
|
'apiType'=>1,//返回付呗支付标识 0微信原生 1付呗支付 by yj 2023.12.16
|
];
|
if(!empty($this->app['config']["is_use_plus"])){
|
$result_data["orderSn"] = $result->data->order_sn;
|
}
|
return $result_data;
|
}
|
return $sign_package;
|
}
|
|
/**
|
* 支付成功异步通知
|
*/
|
public function notify()
|
{
|
log_write('付呗回调');
|
$data = json_decode($_POST['data'], true);
|
$attach = json_decode($data['attach'], true);
|
// 实例化订单模型
|
$PaySuccess = PayTypeSuccessFactory::getFactory($data['merchant_order_sn'], $attach);
|
$app_id = $PaySuccess->isExist();
|
$app_id == 0 && $this->returnCode(false, '订单不存在1');
|
$shop_supplier_id = $PaySuccess->isExistSupplier();
|
// $shop_supplier_id == 0 && $this->returnCode(false, '订单商户不存在2'); // 暂时屏蔽 by lyzflash 20251029
|
// 支付配置信息
|
$this->app = $this->getFbPayApp($app_id,$shop_supplier_id);
|
// 保存微信服务器返回的签名sign
|
$dataSign = $_POST['sign'];
|
// sign不参与签名算法
|
unset($_POST['sign']);
|
// 生成签名
|
$sign = $this->makeSign($_POST);
|
// 判断签名是否正确 判断支付状态
|
if (
|
($sign !== $dataSign)
|
|| ($data['order_status'] !== 'SUCCESS')
|
) {
|
$this->returnCode(false, '签名失败');
|
}
|
// 订单支付成功业务处理
|
$data['transaction_id'] = $data['channel_order_sn'];
|
$status = $PaySuccess->onPaySuccess(OrderPayTypeEnum::WECHAT, $data);
|
if ($status == false) {
|
echo 'error';
|
exit();
|
}
|
// 返回状态
|
echo 'success';
|
exit();
|
}
|
|
/**
|
* 申请退款API
|
*/
|
public function refund($transaction_id, $order_no, $refund_fee)
|
{
|
//请求参数
|
$requestConfigs = array(
|
'merchant_order_sn' => $order_no,
|
'merchant_refund_sn' => $transaction_id,
|
'refund_amount' => $refund_fee,
|
);
|
if($this->app['config']["vendor_sn"]) {
|
$requestConfigs['merchant_id'] = $this->app['config']["merchant_id"];
|
}
|
$commonConfigs = array(
|
//公共参数
|
'method' => 'fbpay.order.refund',
|
'format' => 'json',
|
'sign_method' => 'md5',
|
'nonce' => Str::random(32),
|
'version' => '2.0',
|
'biz_content' => json_encode($requestConfigs),
|
);
|
if($this->app['config']["vendor_sn"]) {
|
$commonConfigs['vendor_sn'] = $this->app['config']["vendor_sn"];
|
} else {
|
$commonConfigs['app_id'] = $this->app['config']["fbapp_id"];
|
}
|
$commonConfigs["sign"] = $this->makeSign($commonConfigs);
|
$result = $this->curlPostContents($this->gateway,$commonConfigs);
|
$result = json_decode($result);
|
if($result->result_code && $result->result_code == 200){
|
return true;
|
}else{
|
throw new BaseException(['msg' => 'return_msg: ' . $result->result_message]);
|
}
|
}
|
|
/**
|
* 企业付款到零钱API
|
*/
|
public function transfers($order_no, $openid, $amount, $desc)
|
{
|
$result = $this->app->transfer->toBalance([
|
'partner_trade_no' => $order_no, // 商户订单号,需保持唯一性(只能是字母或者数字,不能包含有符号)
|
'openid' => $openid,
|
'check_name' => 'NO_CHECK', // NO_CHECK:不校验真实姓名, FORCE_CHECK:强校验真实姓名
|
'amount' => $amount * 100, // 企业付款金额,单位为分
|
'desc' => $desc, // 企业付款操作说明信息。必填
|
]);
|
// 请求失败
|
if (empty($result)) {
|
throw new BaseException(['msg' => '微信提现到零钱api请求失败']);
|
}
|
// 请求失败
|
if ($result['return_code'] === 'FAIL') {
|
throw new BaseException(['msg' => 'return_msg: ' . $result['return_msg']]);
|
}
|
if ($result['result_code'] === 'FAIL') {
|
throw new BaseException(['msg' => 'err_code_des: ' . $result['err_code_des']]);
|
}
|
return true;
|
}
|
|
/**
|
* 上传加密图片
|
*/
|
public function imgupload($file_data, $bus_type='bankCard')
|
{
|
//请求参数
|
$requestConfigs = array(
|
'bus_type' => $bus_type,
|
'file_data' => $file_data,
|
);
|
$commonConfigs = array(
|
//公共参数
|
'method' => 'openapi.agent.base.imgupload.security',
|
'format' => 'json',
|
'sign_method' => 'md5',
|
'nonce' => Str::random(32),
|
'version' => '2.0',
|
'biz_content' => json_encode($requestConfigs),
|
);
|
if($this->app['config']["vendor_sn"]) {
|
$commonConfigs['vendor_sn'] = $this->app['config']["vendor_sn"];
|
} else {
|
$commonConfigs['app_id'] = $this->app['config']["fbapp_id"];
|
}
|
$commonConfigs["sign"] = $this->makeSign($commonConfigs);
|
$result = $this->curlPostContents($this->gateway,$commonConfigs);
|
$result = json_decode($result);
|
if($result->result_code && $result->result_code == 200){
|
$resource_id = (array)($result->data->resource_id);
|
return $resource_id;
|
}else{
|
throw new BaseException(['msg' => 'return_msg: ' . $result->result_message]);
|
}
|
}
|
|
/**
|
* 分账接收方入驻
|
*/
|
public function subaccount($data)
|
{
|
//请求参数
|
$requestConfigs = $data;
|
$commonConfigs = array(
|
//公共参数
|
'method' => 'openapi.agent.account.subaccount.income',
|
'format' => 'json',
|
'sign_method' => 'md5',
|
'nonce' => Str::random(32),
|
'version' => '2.0',
|
'biz_content' => json_encode($requestConfigs),
|
);
|
if($this->app['config']["vendor_sn"]) {
|
$commonConfigs['vendor_sn'] = $this->app['config']["vendor_sn"];
|
} else {
|
$commonConfigs['app_id'] = $this->app['config']["fbapp_id"];
|
}
|
$commonConfigs["sign"] = $this->makeSign($commonConfigs);
|
$result = $this->curlPostContents($this->gateway,$commonConfigs);
|
$result = json_decode($result);
|
// print_r($result);exit;
|
if($result->result_code && $result->result_code == 200){
|
$resutl_data = (array)($result->data);
|
return $resutl_data["account_id"];
|
}else{
|
throw new BaseException(['msg' => '请把信息填写完整再提交: ' . $result->result_message]);
|
}
|
}
|
|
/**
|
* 分账接收方入驻回调
|
*/
|
public function subaccountResult()
|
{
|
log_write('入驻回调');
|
$data = json_decode($_POST['data'], true);
|
|
$where["merchant_order_sn"]=$data["merchant_order_sn"];
|
$model = UserAuthModel::detailById($where);
|
if(empty($model)){
|
$this->returnCode(false, '数据不存在');
|
}
|
|
// 数据处理
|
$save_data['account_id'] = $data['account_id'];
|
$save_data['auth_status'] = $data['status'];
|
$save_data['fail_message'] = $data['fail_message'];
|
$status = $model->save($save_data);
|
if ($status == false) {
|
echo 'error';
|
exit();
|
}
|
// 返回状态
|
echo 'success';
|
exit();
|
}
|
|
/**
|
* 分账提现API
|
*/
|
public function withdraw($data)
|
{
|
//请求参数
|
$requestConfigs = array(
|
'merchant_order_sn' => $data["merchant_order_sn"],
|
'account_id' => $data["account_id"],
|
'amount' => $data["amount"],
|
'type' => 1,//提现方式 1慢 2快(每日十次)
|
'notify_url' => base_url() . 'index.php/job/notify/withdraw_result'
|
);
|
if($this->app['config']["vendor_sn"]) {
|
$requestConfigs['merchant_id'] = $this->app['config']["merchant_id"];
|
}
|
$commonConfigs = array(
|
//公共参数
|
'method' => 'openapi.agent.account.withdraw',
|
'format' => 'json',
|
'sign_method' => 'md5',
|
'nonce' => Str::random(32),
|
'version' => '2.0',
|
'biz_content' => json_encode($requestConfigs),
|
);
|
if($this->app['config']["vendor_sn"]) {
|
$commonConfigs['vendor_sn'] = $this->app['config']["vendor_sn"];
|
} else {
|
$commonConfigs['app_id'] = $this->app['config']["fbapp_id"];
|
}
|
$commonConfigs["sign"] = $this->makeSign($commonConfigs);
|
$result = $this->curlPostContents($this->gateway,$commonConfigs);
|
$result = json_decode($result);
|
if($result->result_code && $result->result_code == 200){
|
$resutl_data = (array)($result->data);
|
$save_data = [
|
"bank_name"=>$resutl_data["bank_name"],//银行名称
|
"bank_card_no"=>$resutl_data["bank_card_no"],//银行卡号
|
"create_time"=>$resutl_data["create_time"],//申请付呗打款的时间
|
"finish_time"=>$resutl_data["finish_time"],//预计到账的时间
|
"status"=>$resutl_data["status"],//提现中
|
"amount"=>$resutl_data["amount"],//提现金额
|
];
|
FbCashApply::update($save_data, ['merchant_order_sn' => $resutl_data["merchant_order_sn"]]);
|
return true;
|
}else{
|
throw new BaseException(['msg' => 'return_msg: ' . $result->result_message]);
|
}
|
}
|
|
/**
|
* 分账结果回调
|
*/
|
public function withdrawResult()
|
{
|
log_write('分账结果回调');
|
$data = json_decode($_POST['data_list'], true);
|
$status = FbCashApplyService::applySuccess($_POST['merchant_order_sn'],$data);
|
if ($status == false) {
|
echo 'error';
|
exit();
|
}
|
// 返回状态
|
echo 'success';
|
exit();
|
}
|
|
/**
|
* 微信参数配置
|
*/
|
public function wxconfig()
|
{
|
//请求参数
|
$requestConfigs = array(
|
'store_id' => $this->app['config']["store_id"],
|
'merchant_id' => $this->app['config']["merchant_id"],
|
'sub_appid' => $this->app['config']['is_embed'] ? $this->app['config']["embed_app_id"] : $this->app['config']["app_id"],
|
'account_type' => '01',
|
'jsapi_path' => base_url() . 'h5/pages/order/',
|
);
|
$commonConfigs = array(
|
//公共参数
|
'method' => 'fbpay.order.wxconfig',
|
'format' => 'json',
|
'sign_method' => 'md5',
|
'nonce' => Str::random(32),
|
'version' => '2.0',
|
'biz_content' => json_encode($requestConfigs),
|
);
|
if($this->app['config']["vendor_sn"]) {
|
$commonConfigs['vendor_sn'] = $this->app['config']["vendor_sn"];
|
} else {
|
$commonConfigs['app_id'] = $this->app['config']["fbapp_id"];
|
}
|
$commonConfigs["sign"] = $this->makeSign($commonConfigs);
|
$result = $this->curlPostContents($this->gateway,$commonConfigs);
|
$result = json_decode($result);
|
/* if($result->result_code && $result->result_code == 200){
|
return true;
|
}else{
|
throw new BaseException(['msg' => '付呗微信配置返回: ' . $result->result_message]);
|
}*/
|
if($result->result_code && $result->result_code == 200 && ($result->data->sub_appid_code == 1 || $result->data->jsapi_code == 1)){
|
return true;
|
}else{
|
if ($result->data->sub_appid_code != 1) {
|
throw new BaseException(['msg' => '付呗微信配置返回: ' . $result->data->sub_appid_msg]);
|
return false;
|
}
|
if ($result->data->jsapi_code != 1) {
|
throw new BaseException(['msg' => '付呗微信配置返回: ' . $result->data->jsapi_msg]);
|
return false;
|
}
|
throw new BaseException(['msg' => '付呗微信配置返回: ' . $result->result_message]);
|
}
|
}
|
|
/**
|
* 返回状态给微信服务器
|
*/
|
private function returnCode($returnCode = true, $msg = null)
|
{
|
// 返回状态
|
$return = [
|
'return_code' => $returnCode ? 'SUCCESS' : 'FAIL',
|
'return_msg' => $msg ?: 'OK',
|
];
|
// 记录日志
|
log_write([
|
'describe' => '返回微信支付状态',
|
'data' => $return
|
]);
|
die($this->toXml($return));
|
}
|
|
/**
|
* 输出xml字符
|
* @param $values
|
* @return bool|string
|
*/
|
private function toXml($values)
|
{
|
if (!is_array($values)
|
|| count($values) <= 0
|
) {
|
return false;
|
}
|
|
$xml = "<xml>";
|
foreach ($values as $key => $val) {
|
if (is_numeric($val)) {
|
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
|
} else {
|
$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
|
}
|
}
|
$xml .= "</xml>";
|
return $xml;
|
}
|
|
/**
|
* 生成签名
|
*/
|
private function makeSign($values)
|
{
|
//签名步骤一:按字典序排序参数
|
ksort($values);
|
$string = $this->toUrlParams($values);
|
//签名步骤二:在string后加入KEY
|
$string = trim($string, '&') . $this->app['config']['secret'];
|
//签名步骤三:MD5加密
|
$string = md5($string);
|
//签名步骤四:所有字符转为大写
|
$result = strtoupper($string);
|
return $result;
|
}
|
|
/**
|
* 格式化参数格式化成url参数
|
*/
|
private function toUrlParams($values)
|
{
|
$buff = '';
|
foreach ($values as $k => $v) {
|
if ($k != 'sign' && $v != '' && !is_array($v)) {
|
$buff .= $k . '=' . $v . '&';
|
}
|
}
|
return trim($buff, '&');
|
}
|
|
public static function getFbPayApp($app_id,$shop_supplier_id =0){
|
// 获取当前小程序信息
|
$wxConfig = AppWxModel::getAppWxCache($app_id);
|
//获取后台是否设置了商户独立收款 by yj 2024.3.12
|
//$is_independent = SettingModel::getIndependentOpen();
|
$is_independent = SupplierModel::getIndependentOpen($shop_supplier_id);
|
if(!empty($is_independent)){
|
//分每个商户有单独的付呗支付信息
|
$app = AppPayModel::detail($shop_supplier_id);
|
}else{
|
$app = AppModel::detail($app_id);
|
}
|
// 验证fbpay_vendor_sn或fbpay_app_id是否填写,不能全为空
|
if (empty($app['fbpay_vendor_sn']) && empty($app['fbpay_app_id'])) {
|
throw new BaseException(['msg' => '请到 [后台-应用-小程序设置] 服务商开放平台vendor-sn和商户开放平台app_id不能全为空']);
|
}
|
if (empty($app['fbpay_secret']) || empty($app['fbpay_merchant_id']) || empty($app['fbpay_store_id'])) {
|
throw new BaseException(['msg' => '请到 [后台-应用-小程序设置] 填写相关参数']);
|
}
|
|
$config = [
|
'app_id' => $wxConfig['wxapp_id'],
|
'vendor_sn' => $app['fbpay_vendor_sn'],
|
'secret' => $app['fbpay_secret'],
|
'merchant_id' => $app['fbpay_merchant_id'],
|
'store_id' => $app['fbpay_store_id'],
|
'fbapp_id' => $app['fbpay_app_id'],
|
'is_use_plus' => $app['is_use_plus'],// 用于判断是否用付呗收银台插件
|
'cashier_app_id'=> $app['fbpay_cashier_app_id'],// 付呗收银台插件使用
|
'is_embed' => $app['is_embed'], // 是否开启半屏小程序支付
|
'embed_app_id'=> $app['embed_app_id'], // 半屏小程序的appId
|
'embed_key' => $app['embed_key'], // 半屏小程序安全密钥
|
'embed_path' => $app['embed_path'], // 半屏小程序支付路径
|
];
|
return Factory::payment($config);
|
}
|
/**
|
* 提交提交结果
|
* @param $url 网关地址
|
* @param array $data 请求参数
|
* @param int $timeout 超时时间
|
* @return bool|string
|
* @throws Exception
|
*/
|
public static function curlPostContents($url, $data = array(), $timeout=10){
|
$data_string = json_encode($data);
|
$ch = curl_init();
|
curl_setopt($ch, CURLOPT_URL,$url);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
|
curl_setopt($ch, CURLOPT_POSTFIELDS,$data_string);
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
|
curl_setopt($ch, CURLOPT_TIMEOUT,$timeout);
|
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
|
'Content-Type: application/json',
|
'Content-Length: ' . strlen($data_string))
|
);
|
$result = curl_exec($ch);
|
if ($no = curl_errno($ch)) {
|
$error = curl_error($ch);
|
curl_close($ch);
|
if(in_array(intval($no), [7, 28], true)) {
|
throw new BaseException(['msg' => '连接或请求超时' . $error]);
|
}
|
}
|
curl_close($ch);
|
return $result;
|
}
|
}
|