dggzichahu/application/common/server/WeChatPayServer.php

327 lines
10 KiB
PHP
Executable File

<?php
// +----------------------------------------------------------------------
// | 宏驰云科技开发团队 版权所有 拥有最终解释权
// +----------------------------------------------------------------------
// | Author: HcyShop-kiki
// +----------------------------------------------------------------------
namespace app\common\server;
use app\common\logic\PaymentLogic;
use app\common\logic\PayNotifyLogic;
use app\common\model\Client_;
use app\common\model\Pay;
use EasyWeChat\Factory;
use EasyWeChat\Payment\Application;
use Endroid\QrCode\QrCode;
use http\Client;
use think\Db;
use think\Exception;
class WeChatPayServer
{
protected static $error = '未知错误';
protected static $return_code = 0;
/**
* Notes: 错误信息
* @return string
* @author kiki
*/
public static function getError()
{
return self::$error;
}
/**
* Notes: 返回状态码
* @return int
* @author kiki
*/
public static function getReturnCode()
{
return self::$return_code;
}
/**
* Notes: 微信统一下单
* @param $from
* @param $order
* @param $order_source
* @return array|bool|string
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
* @author kiki
*/
public static function unifiedOrder($from, $order, $order_source)
{
try {
$wechat_config = self::getWeChatConfig($order, $order_source);
$auth = $wechat_config['auth'];
$config = $wechat_config['config'];
$notify_url = $wechat_config['notify_url'];
//jsapi需要验证openID
$check_source = [Client_::mnp, Client_::oa];
if (!$auth && in_array($order_source, $check_source)) {
throw new Exception('授权信息失效');
}
$app = Factory::payment($config);
$attributes = self::getAttributes($from, $order, $order_source, $auth, $notify_url);
$result = $app->order->unify($attributes);
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
//小程序,公众号
if (in_array($order_source, [Client_::mnp, Client_::oa])) {
$data = $app->jssdk->bridgeConfig($result['prepay_id'], false);
}
//app客户端
if (in_array($order_source, [Client_::ios, Client_::android])) {
$data = $app->jssdk->appConfig($result['prepay_id'], false);
}
//pc端
if ($order_source == Client_::pc) {
$data = self::getNativeCode($result, $order);
}
//h5(非微信环境)
if ($order_source == Client_::h5) {
$redirect_url = request()->domain().'/mobile/pages/user_order/user_order';
$redirect_url = urlencode($redirect_url);
$data = $result['mweb_url'].'&redirect_url='.$redirect_url;
}
return $data;
} else {
if (isset($result['return_code']) && $result['return_code'] == 'FAIL') {
throw new Exception($result['return_msg']);
}
if (isset($result['err_code_des'])) {
throw new Exception($result['err_code_des']);
}
throw new Exception('未知原因');
}
} catch (Exception $e) {
self::$error = '支付失败:' . $e->getMessage();
return false;
}
}
/**
* Notes: NATIVE 支付二维码
* @param $result
* @param $order
* @author kiki
* @return string
* @throws \Endroid\QrCode\Exception\InvalidWriterException
*/
public static function getNativeCode($result, $order)
{
$save_dir = 'uploads/pay_code/';
$qr_src = md5($order['order_sn'].mt_rand(10000, 99999)) . '.png';
$code_url = ROOT_PATH.'/'.$save_dir . $qr_src;
$qrCode = new QrCode();
$qrCode->setText($result['code_url']);
$qrCode->setSize(200);
$qrCode->setWriterByName('png');
!file_exists($save_dir) && mkdir($save_dir, 777, true);
$qrCode->writeFile($code_url);
//生成base64临时图片
if ($fp = fopen($code_url, "rb", 0)) {
$gambar = fread($fp, filesize($code_url));
fclose($fp);
$base64 = chunk_split(base64_encode($gambar));
$base64 = 'data:image/png;base64,' . $base64;
}
//删除文件
if (strstr($code_url, $save_dir)) {
unlink($code_url);
}
return $base64;
}
/**
* Notes: 支付参数
* @param $from
* @param $order
* @param $order_source
* @param $auth
* @param $notify_url
* @author kiki
* @return array
*/
public static function getAttributes($from, $order, $order_source, $auth, $notify_url)
{
switch ($from) {
case 'order':
$attributes = [
'trade_type' => 'JSAPI',
'body' => '商品',
// 'out_trade_no' => $order['order_sn'],
'total_fee' => $order['order_amount'] * 100, // 单位:分
'notify_url' => $notify_url,
'openid' => $auth['openid'],
'attach' => 'order'
];
break;
case 'recharge':
$attributes = [
'trade_type' => 'JSAPI',
'body' => '充值',
// 'out_trade_no' => $order['order_sn'],
'total_fee' => $order['order_amount'] * 100, // 单位:分
'notify_url' => $notify_url,
'openid' => $auth['openid'],
'attach' => 'recharge'
];
break;
}
//app支付类型
if ($order_source == Client_::android || $order_source == Client_::ios) {
$attributes['trade_type'] = 'APP';
}
//NATIVE模式设置
if ($order_source == Client_::pc) {
$attributes['trade_type'] = 'NATIVE';
$attributes['product_id'] = $order['order_sn'];
}
//h5支付类型
if ($order_source == Client_::h5) {
$attributes['trade_type'] = 'MWEB';
}
//在白名单内,一分钱
if (PaymentLogic::isPayWhiteList($order['user_id'])) {
$attributes['total_fee'] = 1;
}
//修改微信统一下单,订单编号 -> 支付回调时截取前面的单号 18个
//修改原因:回调时使用了不同的回调地址,导致跨客户端支付时(例如小程序,公众号)可能出现201,商户订单号重复错误
$attributes['out_trade_no'] = $order['order_sn'].$attributes['trade_type'].$order_source;
return $attributes;
}
/**
* Notes: 获取微信配置
* @param $order
* @param $order_source
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
* @author kiki
*/
public static function getWeChatConfig($order, $order_source)
{
$pay_config = WeChatServer::getPayConfigBySource($order_source);
$where[] = ['user_id', '=', $order['user_id']];
$where[] = ['client', '=', $order_source];
$auth = Db::name('user_auth')->where($where)->find();
$data = [
'auth' => $auth,
'config' => $pay_config['config'],
'notify_url' => $pay_config['notify_url'],
'order_source' => $order_source,
];
return $data;
}
/**
* Notes: 支付回调
* @param $config
* @author kiki
* @throws \EasyWeChat\Kernel\Exceptions\Exception
*/
public static function notify($config)
{
$app = new Application($config);
$response = $app->handlePaidNotify(function ($message, $fail) {
if ($message['return_code'] !== 'SUCCESS') {
return $fail('通信失败');
}
// 用户是否支付成功
if ($message['result_code'] === 'SUCCESS') {
$extra['transaction_id'] = $message['transaction_id'];
$attach = $message['attach'];
$message['out_trade_no'] = mb_substr($message['out_trade_no'], 0, 18);
switch ($attach) {
case 'order':
$order = Db::name('order')->where(['order_sn' => $message['out_trade_no']])->find();
if (!$order || $order['pay_status'] >= Pay::ISPAID) {
return true;
}
PayNotifyLogic::handle('order', $message['out_trade_no'], $extra);
break;
case 'recharge':
$order = Db::name('recharge_order')->where(['order_sn' => $message['out_trade_no']])->find();
if (!$order || $order['pay_status'] >= Pay::ISPAID) {
return true;
}
PayNotifyLogic::handle('recharge', $message['out_trade_no'], $extra);
break;
}
} elseif ($message['result_code'] === 'FAIL') {
// 用户支付失败
}
return true; // 返回处理完成
});
$response->send();
}
/**
* Notes: 退款
* @param $config
* @param $data //微信订单号、商户退款单号、订单金额、退款金额、其他参数
* @return array|bool|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
* @author kiki
*/
public static function refund($config, $data)
{
if (!empty($data["transaction_id"])) {
$app = Factory::payment($config);
$result = $app->refund->byTransactionId(
$data['transaction_id'],
$data['refund_sn'],
$data['total_fee'],
$data['refund_fee']
);
return $result;
} else {
return false;
}
}
}