Files
anmo/server/app/common/service/WeChatPayService.php
2025-08-19 14:16:51 +08:00

357 lines
13 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | likeshop开源商城系统
// +----------------------------------------------------------------------
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
// | gitee下载https://gitee.com/likeshop_gitee
// | github下载https://github.com/likeshop-github
// | 访问官网https://www.likeshop.cn
// | 访问社区https://home.likeshop.cn
// | 访问手册http://doc.likeshop.cn
// | 微信公众号likeshop技术社区
// | likeshop系列产品在gitee、github等公开渠道开源版本可免费商用未经许可不能去除前后端官方版权标识
// | likeshop系列产品收费版本务必购买商业授权购买去版权授权后方可去除前后端官方版权标识
// | 禁止对系统程序代码以任何目的,任何形式的再发布
// | likeshop团队版权所有并拥有最终解释权
// +----------------------------------------------------------------------
// | author: likeshop.cn.team
// +----------------------------------------------------------------------
namespace app\common\service;
use app\common\enum\OrderEnum;
use app\common\enum\PayEnum;
use app\common\enum\user\UserTerminalEnum;
use app\common\logic\PayNotifyLogic;
use app\common\logic\RefundLogic;
use app\common\model\deposit\DepositOrder;
use app\common\model\order\Order;
use app\common\model\order\OrderAppend;
use app\common\model\order\OrderGap;
use app\common\model\RechargeOrder;
use app\common\model\user\UserAuth;
use EasyWeChat\Factory;
use EasyWeChat\Payment\Application;
use think\facade\Log;
class WeChatPayService extends BasePayService
{
/**
* 授权信息
*/
protected $auth;
/**
* 微信配置
*/
protected $config;
/**
* easyWeChat实例
*/
protected $pay;
/**
* 当前使用客户端
*/
protected $terminal;
/**
* 初始化微信配置
* @param $terminal //用户终端
* @param null $userId //用户id(获取授权openid)
*/
public function __construct($terminal, $userId = null)
{
$this->terminal = $terminal;
$this->config = WeChatConfigService::getWechatConfigByTerminal($terminal);
$this->pay = Factory::payment($this->config);
if ($userId !== null) {
$this->auth = UserAuth::where(['user_id' => $userId, 'terminal' => $terminal])->findOrEmpty();
}
}
/**
* @notes 退款
* @param $data
* @return array|\EasyWeChat\Kernel\Support\Collection|false|object|\Psr\Http\Message\ResponseInterface|string
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
* @author ljj
* @date 2022/2/15 4:58 下午
*/
public function refund($data)
{
if (!empty($data["transaction_id"])) {
return $this->pay->refund->byTransactionId(
$data['transaction_id'],
$data['refund_sn'],
$data['total_fee'],
$data['refund_fee']
);
} else {
return false;
}
}
/**
* @notes 发起微信支付统一下单
* @param $from
* @param $order
* @return array|false|string
* @author 段誉
* @date 2021/8/4 15:05
*/
public function pay($from, $order)
{
try {
switch ($this->terminal) {
case UserTerminalEnum::WECHAT_MMP:
case UserTerminalEnum::WECHAT_OA:
$result = $this->jsapiPay($from, $order);
break;
case UserTerminalEnum::H5:
$result = $this->mwebPay($from, $order);
break;
default:
throw new \Exception('支付方式错误');
}
return [
'config' => $result,
'pay_way' => PayEnum::WECHAT_PAY
];
} catch (\Exception $e) {
$this->setError($e->getMessage());
return false;
}
}
/**
* @notes jsapi支付(小程序,公众号)
* @param $from
* @param $order
* @return array|string
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
* @author 段誉
* @date 2021/8/4 15:05
*/
public function jsapiPay($from, $order)
{
$check_source = [UserTerminalEnum::WECHAT_MMP, UserTerminalEnum::WECHAT_OA];
if ($this->auth->isEmpty() && in_array($this->terminal, $check_source)) {
throw new \Exception('获取授权信息失败');
}
$result = $this->pay->order->unify($this->getAttributes($from, $order));
$this->checkResultFail($result);
return $this->pay->jssdk->bridgeConfig($result['prepay_id'], false);
}
/**
* @notes h5支付 (非微信环境下h5)
* @param $from
* @param $order
* @return string
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
* @throws \GuzzleHttp\Exception\GuzzleException
* @author 段誉
* @date 2021/8/4 15:07
*/
public function mwebPay($from, $order)
{
$result = $this->pay->order->unify($this->getAttributes($from, $order));
$this->checkResultFail($result);
$redirect_url = request()->domain() . '/mobile/bundle/pages/h5_pay_query/h5_pay_query?pay_way='.PayEnum::WECHAT_PAY;
if ($from == 'deposit') {
switch ($order['type']){
case 1:
$redirect_url = request()->domain() . '/coach/bundle/pages/h5_pay_query/h5_pay_query?pay_way='.PayEnum::WECHAT_PAY;
break;
case 2:
$redirect_url = request()->domain() . '/shop/bundle/pages/h5_pay_query/h5_pay_query?pay_way='.PayEnum::WECHAT_PAY;
break;
}
}
$redirect_url = urlencode($redirect_url);
return $result['mweb_url'] . '&redirect_url=' . $redirect_url;
}
/**
* @notes 验证微信返回数据
* @param $result
* @throws \Exception
* @author 段誉
* @date 2021/8/4 14:56
*/
public function checkResultFail($result)
{
if ($result['return_code'] != 'SUCCESS' || $result['result_code'] != 'SUCCESS') {
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('未知原因');
}
}
/**
* @notes 支付请求参数
* @param $from
* @param $order
* @return array
* @author 段誉
* @date 2021/8/4 15:07
*/
public function getAttributes($from, $order)
{
switch ($from) {
case 'order':
$attributes = [
'trade_type' => 'JSAPI',
'body' => '预约服务',
'total_fee' => $order['order_amount'] * 100, // 单位:分
'openid' => $this->auth['openid'],
'attach' => 'order',
];
break;
case 'recharge':
$attributes = [
'trade_type' => 'JSAPI',
'body' => '充值',
'total_fee' => $order['order_amount'] * 100, // 单位:分
'openid' => $this->auth['openid'],
'attach' => 'recharge',
];
break;
case 'orderGap':
$attributes = [
'trade_type' => 'JSAPI',
'body' => '补差价',
'total_fee' => $order['order_amount'] * 100, // 单位:分
'openid' => $this->auth['openid'],
'attach' => 'orderGap',
];
break;
case 'orderAppend':
$attributes = [
'trade_type' => 'JSAPI',
'body' => '加时',
'total_fee' => $order['order_amount'] * 100, // 单位:分
'openid' => $this->auth['openid'],
'attach' => 'orderAppend',
];
break;
case 'deposit':
$attributes = [
'trade_type' => 'JSAPI',
'body' => '充值保证金',
'total_fee' => $order['order_amount'] * 100, // 单位:分
'openid' => $order['openid'],
'attach' => 'deposit',
];
break;
}
//h5支付类型
if ($this->terminal == UserTerminalEnum::H5) {
$attributes['trade_type'] = 'MWEB';
}
//修改微信统一下单,订单编号 -> 支付回调时截取前面的单号 18个
//修改原因:回调时使用了不同的回调地址,导致跨客户端支付时(例如小程序,公众号)可能出现201,商户订单号重复错误
$suffix = mb_substr(time(), -4);
$attributes['out_trade_no'] = $order['sn'] . $attributes['trade_type'] . $this->terminal . $suffix;
return $attributes;
}
/**
* @notes 支付回调
* @return \Symfony\Component\HttpFoundation\Response
* @throws \EasyWeChat\Kernel\Exceptions\Exception
* @author 段誉
* @date 2021/8/13 14:19
*/
public function notify()
{
$app = new Application($this->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 = Order::where(['sn' => $message['out_trade_no']])->findOrEmpty();
if ($order->isEmpty() || $order['pay_status'] >= PayEnum::ISPAID) {
return true;
}
//特殊情况:用户在前端支付成功的情况下,调用回调接口之前,订单被关闭
if ($order['order_status'] == OrderEnum::ORDER_STATUS_CLOSE) {
//原路退款
$order['transaction_id'] = $extra['transaction_id'];
(new RefundLogic())->refund($order,$order['order_amount'],0,1,1);
return true;
}
PayNotifyLogic::handle('order', $message['out_trade_no'], $extra);
break;
case 'orderGap':
$orderGap = OrderGap::where(['sn' => $message['out_trade_no']])->findOrEmpty();
if ($orderGap->isEmpty() || $orderGap['pay_status'] >= PayEnum::ISPAID) {
return true;
}
PayNotifyLogic::handle('orderGap', $message['out_trade_no'], $extra);
break;
case 'orderAppend':
$orderAppend = OrderAppend::where(['sn' => $message['out_trade_no']])->findOrEmpty();
if ($orderAppend->isEmpty() || $orderAppend['pay_status'] >= PayEnum::ISPAID) {
return true;
}
PayNotifyLogic::handle('orderAppend', $message['out_trade_no'], $extra);
break;
case 'recharge':
$order = RechargeOrder::where(['sn' => $message['out_trade_no']])->findOrEmpty();
if($order->isEmpty() || $order->pay_status == PayEnum::ISPAID) {
return true;
}
PayNotifyLogic::handle('recharge', $message['out_trade_no'], $extra);
break;
case 'deposit':
$order = DepositOrder::where(['sn' => $message['out_trade_no']])->findOrEmpty();
if($order->isEmpty() || $order->pay_status == PayEnum::ISPAID) {
return true;
}
PayNotifyLogic::handle('deposit', $message['out_trade_no'], $extra);
break;
}
} elseif ($message['result_code'] === 'FAIL') {
// 用户支付失败
}
return true; // 返回处理完成
});
return $response->send();
}
}