diff --git a/backend/application/api/controller/Auth.php b/backend/application/api/controller/Auth.php index 57f64bc..ede5eb4 100644 --- a/backend/application/api/controller/Auth.php +++ b/backend/application/api/controller/Auth.php @@ -7,7 +7,6 @@ use app\common\model\User; use app\common\service\UserService; use think\exception\HttpResponseException; use think\Validate; -use EasyWeChat\Factory; /** * 登录接口 diff --git a/backend/application/api/controller/Order.php b/backend/application/api/controller/Order.php index 5192c1f..6e34b3a 100644 --- a/backend/application/api/controller/Order.php +++ b/backend/application/api/controller/Order.php @@ -243,25 +243,26 @@ class Order extends Api } /** - * 支付回调 + * 支付回调(微信支付 v3: 返回 JSON,失败时微信将按频率重试) * @return void * @throws \Exception */ public function payNotify() { - header('Content-Type: text/xml; charset=utf-8'); + header('Content-Type: application/json; charset=utf-8'); try { - $xml = OrderService::payNotify(); - echo $xml; + // PSR-7 响应(SDK 已完成验签/解密,业务失败返回 500 由微信重试) + $response = OrderService::payNotify(); + http_response_code($response->getStatusCode()); + foreach ($response->getHeaders() as $name => $values) { + header($name . ': ' . implode(', ', $values)); + } + echo $response->getBody()->getContents(); exit; } catch (\Exception $e) { - $res = [ - 'return_code' => 'FAIL', - 'return_msg' => $e->getMessage() - ]; - $xml = ToolService::ToXml($res); - Log::info('微信回调信息信息_result_error:' . json_encode(['res' => $res, 'xml' => $xml], JSON_UNESCAPED_UNICODE)); - echo $xml; + Log::info('微信回调信息信息_result_error:' . json_encode(['msg' => $e->getMessage()], JSON_UNESCAPED_UNICODE)); + http_response_code(500); + echo json_encode(['code' => 'FAIL', 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE); exit; } } diff --git a/backend/application/common/service/AccountWithdrawService.php b/backend/application/common/service/AccountWithdrawService.php index 660731a..8c715dc 100644 --- a/backend/application/common/service/AccountWithdrawService.php +++ b/backend/application/common/service/AccountWithdrawService.php @@ -105,12 +105,13 @@ class AccountWithdrawService AccountService::updateData($userId, 0, -$amount, 0,0, $log_res->id); if($type == AccountWithdrawService::TYPE_WECHAT){ - //发起微信付款到用户微信余额 + //发起微信商家转账到用户零钱(v3,原 v2 企业付款已停用) $payRes = (new EasyWeChatService())->mchPay($withdraw_record->apply_sn, $user->openid, $user->username, $amount); - if (!(isset($payRes['result_code']) && in_array($payRes['result_code'], ['SUCCESS']))) { - throw new \Exception(($payRes['return_msg'] ?? '企业付款给用户失败') . ':' . ($payRes['err_code_des'] ?? '')); + if (empty($payRes['batch_id'])) { + throw new \Exception($payRes['message'] ?? '商家转账发起失败'); } - $withdraw_record->result_status = $payRes['result_code']; + //批次受理成功(资金到账以微信侧为准,可通过 queryBalance 查询批次状态) + $withdraw_record->result_status = 'SUCCESS'; $withdraw_record->save(); } } else { diff --git a/backend/application/common/service/EasyWeChatService.php b/backend/application/common/service/EasyWeChatService.php index 5d4f90b..708db14 100644 --- a/backend/application/common/service/EasyWeChatService.php +++ b/backend/application/common/service/EasyWeChatService.php @@ -2,88 +2,116 @@ namespace app\common\service; -namespace app\common\service; - -use EasyWeChat\Factory; - +use EasyWeChat\MiniApp\Application as MiniAppApplication; +use EasyWeChat\Pay\Application as PayApplication; +use EasyWeChat\Pay\Message as PayMessage; use think\Log; +/** + * 微信服务(EasyWeChat 6.x) + * 文档: https://easywechat.com/6.x/ + * - 小程序: MiniApp\Application + * - 支付(含回调验签): Pay\Application (微信支付 v3) + */ class EasyWeChatService { public $config; + + /** @var MiniAppApplication 小程序实例 */ public $app; + + /** @var PayApplication 微信支付 v3 实例 */ public $payApp; public function __construct() { $this->config = config('common.wechat'); - $this->app = Factory::miniProgram($this->config); - $this->payApp = Factory::payment($this->config); + + // 小程序 + $this->app = new MiniAppApplication([ + 'app_id' => $this->config['app_id'], + 'secret' => $this->config['secret'], + 'http' => ['throw' => false, 'timeout' => 10.0], + ]); + + // 微信支付 v3 + $this->payApp = new PayApplication([ + 'mch_id' => $this->config['mch_id'], + 'secret_key' => $this->config['secret_key'], // APIv3 密钥 + 'v2_secret_key' => $this->config['v2_secret_key'], // 旧 v2 密钥(兼容 v2 模式回调) + 'private_key' => $this->config['private_key'], // 商户证书私钥 + 'certificate' => $this->config['certificate'], // 商户API证书 + 'platform_certs' => $this->config['platform_certs'], // 微信支付公钥/平台证书 + 'http' => ['throw' => false, 'timeout' => 10.0], + ]); } + /** + * 小程序登录(code2session) + * @param string $code wx.login 获取的 code + * @return array {openid, session_key, unionid?} + */ public function login($code) { - $res = $this->app->auth->session($code); - Log::info('login-res' . json_encode([$res])); + $res = $this->app->getUtils()->codeToSession($code); + Log::info('login-res' . json_encode([$res], JSON_UNESCAPED_UNICODE)); return $res; } - /** - * @param $session + * 解密开放数据(手机号等) + * @param $sessionKey code2session 返回的 session_key * @param $iv * @param $encryptedData * @return array */ - public function decryData($session, $iv, $encryptedData) + public function decryData($sessionKey, $iv, $encryptedData) { - $decryptedData = $this->app->encryptor->decryptData($session, $iv, $encryptedData); - - return $decryptedData; + return $this->app->getUtils()->decryptSession($sessionKey, $iv, $encryptedData); } /** - * 统一下单接口 - * @param $body - * @param $out_trade_no - * @param $total_fee float 单位元 - * @param $openid - * @param string $notify_url - * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * 统一下单(小程序 JSAPI 支付, v3) + * @param string $body 商品描述 + * @param string $out_trade_no 商户订单号 + * @param float $total_fee 金额(单位:元) + * @param string $openid 支付用户 openid + * @param string $notify_url 支付结果通知地址,留空使用配置默认 + * @return array 含 prepay_id;失败时含 message 等错误信息 */ public function unify($body, $out_trade_no, $total_fee, $openid, $notify_url = '') { $params = [ - 'body' => $body, + 'appid' => $this->config['app_id'], + 'mchid' => (string)$this->payApp->getMerchant()->getMerchantId(), + 'description' => $body, 'out_trade_no' => $out_trade_no, - 'total_fee' => $total_fee * 100, //记得转为分 - 'spbill_create_ip' => '', // 可选,如不传该参数,SDK 将会自动获取相应 IP 地址 - 'notify_url' => $notify_url ?: null, // 支付结果通知网址,如果不设置则会使用配置里的默认地址 - 'trade_type' => 'JSAPI', // 请对应换成你的支付方式对应的值类型 - 'openid' => $openid, + 'notify_url' => $notify_url ?: $this->config['notify_url'], + 'amount' => ['total' => (int)round($total_fee * 100), 'currency' => 'CNY'], // 转为分 + 'payer' => ['openid' => $openid], ]; - Log::info('unify-params' . json_encode($params)); - $result = $this->payApp->order->unify($params); - Log::info('unify-res' . json_encode($result)); + Log::info('unify-params' . json_encode($params, JSON_UNESCAPED_UNICODE)); + $response = $this->payApp->getClient()->postJson('v3/pay/transactions/jsapi', $params); + $result = $response->toArray(false); + Log::info('unify-res' . json_encode($result, JSON_UNESCAPED_UNICODE)); return $result; } /** - * @param $prepayId - * @return array|string + * 小程序调起支付参数(wx.requestPayment) + * @param string $prepayId + * @return array {timeStamp, nonceStr, package, signType, paySign} */ public function sdkConfig($prepayId) { - $jssdk = $this->payApp->jssdk; - $config = $jssdk->bridgeConfig($prepayId, false); // 返回数组 - Log::info('bridgeConfig-res' . json_encode($config)); + $config = $this->payApp->getUtils()->buildMiniAppConfig($prepayId, $this->config['app_id']); + Log::info('buildMiniAppConfig-res' . json_encode($config, JSON_UNESCAPED_UNICODE)); return $config; } /** - * 发送数据消息 - * @param $order_id + * 发送小程序订阅消息 * @param $user_id * @param $template_id * @param array $msgData @@ -92,76 +120,127 @@ class EasyWeChatService { $data = SubscribeMessageService::packMsg($user_id, $template_id, $msgData); - $res = $this->app->subscribe_message->send($data); + $response = $this->app->getClient()->postJson('/cgi-bin/message/subscribe/send', $data); + $res = $response->toArray(false); Log::info('发送订阅消息-res' . json_encode(['data' => $data, 'res' => $res], JSON_UNESCAPED_UNICODE)); } /** - * @param $order_sn - * @param $openid - * @param $name - * @param $amount - * @param $desc - * @return array|mixed - * @throws \Exception + * 商家转账到零钱(v3, 原 v2 企业付款已停用) + * 需在商户平台开通「商家转账」产品权限 + * @param string $order_sn 提现单号(字母或数字) + * @param string $openid + * @param string $name 收款用户姓名(商家转账不校验时可留空) + * @param float $amount 金额(单位:元) + * @param string $desc + * @return array {out_batch_no, batch_id, create_time};失败时含 message */ public function mchPay($order_sn, $openid, $name, $amount, $desc = '用户提现') { - $data = [ - 'partner_trade_no' => $order_sn, // 商户订单号,需保持唯一性(只能是字母或者数字,不能包含有符号) - 'openid' => $openid, - 'check_name' => 'FORCE_CHECK', // NO_CHECK:不校验真实姓名, FORCE_CHECK:强校验真实姓名 - 're_user_name' => $name, // 如果 check_name 设置为FORCE_CHECK,则必填用户真实姓名 - 'amount' => $amount * 100, // 企业付款金额,单位为分 - 'desc' => $desc, // 企业付款操作说明信息。必填 + $params = [ + 'appid' => $this->config['app_id'], + 'out_batch_no' => $order_sn, + 'batch_name' => '用户提现', + 'batch_remark' => $desc, + 'total_amount' => (int)round($amount * 100), // 转为分 + 'total_num' => 1, + 'transfer_detail_list' => [ + [ + 'out_detail_no' => $order_sn . 'D1', // 明细单号(字母数字,保持唯一) + 'transfer_amount' => (int)round($amount * 100), + 'transfer_remark' => $desc, + 'openid' => $openid, + ], + ], ]; - $res = $this->payApp->transfer->toBalance($data); - Log::info('企业付款-res' . json_encode(['data' => $data, 'res' => $res], JSON_UNESCAPED_UNICODE)); + Log::info('商家转账-params' . json_encode($params, JSON_UNESCAPED_UNICODE)); + $response = $this->payApp->getClient()->postJson('v3/transfer/batches', $params); + $res = $response->toArray(false); + Log::info('商家转账-res' . json_encode($res, JSON_UNESCAPED_UNICODE)); + return $res; } /** - * @param $apply_sn - * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * 查询商家转账批次状态 + * @param string $apply_sn 商家转账单号(out_batch_no) + * @return array 转账批次状态及明细 */ public function queryBalance($apply_sn) { - $res = $this->payApp->transfer->queryBalanceOrder($apply_sn); - Log::info('企业付款查询-res' . json_encode(['apply_sn' => $apply_sn, 'res' => $res])); + $response = $this->payApp->getClient()->get("v3/transfer/batches/out-batch-no/{$apply_sn}", [ + 'query' => [ + 'need_query_detail' => 'true', + 'offset' => 0, + 'limit' => 20, + 'detail_status' => 'ALL', + ], + ]); + $res = $response->toArray(false); + Log::info('商家转账查询-res' . json_encode(['apply_sn' => $apply_sn, 'res' => $res], JSON_UNESCAPED_UNICODE)); return $res; } /** - * @param $order_sn - * @param $refund_no - * @param $actual_amount - * @param $refund_amount - * @param $desc - * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * 微信支付退款(v3) + * @param string $order_sn 商户订单号 + * @param string $refund_no 商户退款单号 + * @param float $actual_amount 原订单金额(单位:元) + * @param float $refund_amount 退款金额(单位:元) + * @param string $desc 退款原因 + * @return array */ public function refund($order_sn, $refund_no, $actual_amount, $refund_amount, $desc = '出票失败退款') { - //商户订单号、商户退款单号、订单金额、退款金额、其他参数 - $res = $this->payApp->refund->byOutTradeNumber($order_sn, $refund_no, bcmul($actual_amount, 100), bcmul($refund_amount, 100), [ - // 可在此处传入其他参数,详细参数见微信支付文档 - 'refund_desc' => $desc, - ]); - Log::info('微信订单退款-res' . json_encode(['param' => func_get_args(), 'res' => $res, [bcmul($actual_amount, 100), bcmul($refund_amount, 100)]])); + $params = [ + 'out_trade_no' => $order_sn, + 'out_refund_no' => $refund_no, + 'reason' => $desc, + 'amount' => [ + 'refund' => (int)bcmul($refund_amount, 100), // 转为分 + 'total' => (int)bcmul($actual_amount, 100), + 'currency' => 'CNY', + ], + ]; + Log::info('微信订单退款-params' . json_encode($params, JSON_UNESCAPED_UNICODE)); + $response = $this->payApp->getClient()->postJson('v3/refund/domestic/refunds', $params); + $res = $response->toArray(false); + Log::info('微信订单退款-res' . json_encode(['param' => func_get_args(), 'res' => $res], JSON_UNESCAPED_UNICODE)); return $res; } /** - * @param $refund_no - * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * 查询退款(v3) + * @param string $refund_no 商户退款单号 + * @return array */ public function refundQuery($refund_no) { - //商户订单号、商户退款单号、订单金额、退款金额、其他参数 - $res = $this->payApp->refund->queryByOutRefundNumber($refund_no); - Log::info('微信订单退款查询-res' . json_encode(['param' => func_get_args(), 'res' => $res])); + $response = $this->payApp->getClient()->get("v3/refund/domestic/refunds/{$refund_no}"); + $res = $response->toArray(false); + Log::info('微信订单退款查询-res' . json_encode(['param' => func_get_args(), 'res' => $res], JSON_UNESCAPED_UNICODE)); return $res; } + + /** + * 处理微信支付 v3 回调(验签+解密),返回 PSR-7 响应 + * @param \Closure $paidHandler function(PayMessage $message){ 业务处理;异常时抛出则返回失败 } + * @return \Psr\Http\Message\ResponseInterface + */ + public function handlePayNotify(\Closure $paidHandler) + { + $server = $this->payApp->getServer(); + $server->handlePaid(function (PayMessage $message, \Closure $next) use ($paidHandler) { + // 回调签名验证(官方推荐,验签失败抛异常 -> serve 返回失败,微信将重试) + $this->payApp->getValidator()->validate($this->payApp->getRequest()); + $paidHandler($message); + + return $next($message); + }); + + return $server->serve(); + } } diff --git a/backend/application/common/service/OrderService.php b/backend/application/common/service/OrderService.php index 2e7d3b1..62d83b9 100644 --- a/backend/application/common/service/OrderService.php +++ b/backend/application/common/service/OrderService.php @@ -385,77 +385,55 @@ class OrderService /** - * 微信支付回调 - * @param - * @return string + * 微信支付回调(EasyWeChat 6.x / 支付 v3: 自动验签+解密) + * @return \Psr\Http\Message\ResponseInterface * @throws \Exception */ public static function payNotify() { - $xmlData = file_get_contents('php://input'); - $data = ToolService::FromXml($xmlData); - Log::info('微信回调信息_request_xml:' . json_encode(['data' => $data, 'xml' => $xmlData])); - $order_no = $data['out_trade_no'] ?? ''; - //更新订单状态 - $orderInfo = OrderService::getOne(['order_no' => $order_no]); - self::checkPaySuccess($data, $orderInfo); - $res = [ - 'return_code' => 'SUCCESS', - 'return_msg' => 'OK' - ]; - $xml = ToolService::ToXml($res); - Log::info('微信回调信息信息_result:' . json_encode(['res' => $res, 'xml' => $xml])); - //如果为待支付,才可更新为已支付。防止重复更新状态,导致重复发货。 - if ($orderInfo->order_status == self::ORDER_STATUS_WAIT_PAY) { - $paymentData = [ - 'order_no' => $order_no, - 'pay_status' => $data['result_code'], - 'amount' => $data['total_fee'] / 100, - 'currency' => $data['fee_type'], - 'pay_type' => '1', // 1微信 2支付宝 - 'pay_way' => $data['trade_type'], - 'trace_no' => $data['transaction_id'], - 'pay_time' => time(), - 'pay_success_time' => strtotime($data['time_end']), - ]; - Db::transaction(function () use ($order_no, $paymentData, $orderInfo) { - self::where('order_no', $order_no)->update(['order_status' => self::ORDER_STATUS_WAIT_SHIP]); - OrderPaymentService::create($paymentData); - if ($orderInfo->order_type == 1) { - DemandService::customerContractFinishedDeal($orderInfo->parent_order_no); - } elseif ($orderInfo->order_type == 2) { - DemandService::customerWorkAcceptFinishedDeal($orderInfo->parent_order_no); - } - }); - } + $service = new EasyWeChatService(); - return $xml; - } - - /** - * @param $data - * @param $info - * @return void - * @throws \Exception - */ - public static function checkPaySuccess($data, $info) - { - if ($info) { - $checkSign = ToolService::CheckSign($data, $data['sign']); - $checkMoney = sprintf("%.1f", $data['total_fee'] / 100) == sprintf("%.1f", $info->payment_amount); //分 - $checkResult = $data['result_code'] == 'SUCCESS'; - if (!$checkSign) { - throw new \Exception('签名错误'); + return $service->handlePayNotify(function (PayMessage $message) { + Log::info('微信回调信息_request:' . json_encode($message->getOriginalAttributes() ?? [], JSON_UNESCAPED_UNICODE)); + $order_no = $message->out_trade_no ?? ''; + $orderInfo = OrderService::getOne(['order_no' => $order_no]); + if (!$orderInfo) { + throw new \Exception('获取订单数据失败'); } + //金额校验(v3 通知金额单位为分) + $total = $message->amount['total'] ?? 0; + $checkMoney = sprintf('%.1f', $total / 100) == sprintf('%.1f', $orderInfo->payment_amount); if (!$checkMoney) { - throw new \Exception('金额错误:' . sprintf("%.2f", $data['total_fee'] / 100) . '----' . sprintf("%.2f", $info->payment_amount)); + throw new \Exception('金额错误:' . sprintf('%.2f', $total / 100) . '----' . sprintf('%.2f', $orderInfo->payment_amount)); } - if (!$checkResult) { - throw new \Exception('支付失败'); + if (($message->trade_state ?? '') != 'SUCCESS') { + throw new \Exception('支付失败:' . ($message->trade_state ?? '')); } - } else { - throw new \Exception('获取订单数据失败'); - } + Log::info('微信回调信息_result:' . json_encode(['order_no' => $order_no, 'trade_state' => $message->trade_state], JSON_UNESCAPED_UNICODE)); + //如果为待支付,才可更新为已支付。防止重复更新状态,导致重复发货。 + if ($orderInfo->order_status == self::ORDER_STATUS_WAIT_PAY) { + $paymentData = [ + 'order_no' => $order_no, + 'pay_status' => 'SUCCESS', + 'amount' => $total / 100, + 'currency' => $message->amount['currency'] ?? 'CNY', + 'pay_type' => '1', // 1微信 2支付宝 + 'pay_way' => $message->trade_type ?? 'JSAPI', + 'trace_no' => $message->transaction_id ?? '', + 'pay_time' => time(), + 'pay_success_time' => strtotime((string)($message->success_time ?? '')) ?: time(), + ]; + Db::transaction(function () use ($order_no, $paymentData, $orderInfo) { + self::where('order_no', $order_no)->update(['order_status' => self::ORDER_STATUS_WAIT_SHIP]); + OrderPaymentService::create($paymentData); + if ($orderInfo->order_type == 1) { + DemandService::customerContractFinishedDeal($orderInfo->parent_order_no); + } elseif ($orderInfo->order_type == 2) { + DemandService::customerWorkAcceptFinishedDeal($orderInfo->parent_order_no); + } + }); + } + }); } /** @@ -487,8 +465,8 @@ class OrderService $EasyWeChatService = new EasyWeChatService(); $payRes = $EasyWeChatService->unify($body, $order_sn, $total_fee, $openid); - if (!($payRes && $payRes['return_code'] == 'SUCCESS' && $payRes['return_msg'] == 'OK')) { - throw new \Exception('统一下单接口调用失败'); + if (!($payRes && !empty($payRes['prepay_id']))) { + throw new \Exception('统一下单接口调用失败:' . ($payRes['message'] ?? ($payRes['errmsg'] ?? '未知错误'))); } $payData = $EasyWeChatService->sdkConfig($payRes['prepay_id']); diff --git a/backend/application/common/service/ToolService.php b/backend/application/common/service/ToolService.php index 9e13410..b996dff 100644 --- a/backend/application/common/service/ToolService.php +++ b/backend/application/common/service/ToolService.php @@ -130,8 +130,10 @@ class ToolService throw new \Exception("xml数据异常!"); } //将XML转为array - //禁止引用外部xml实体 - libxml_disable_entity_loader(true); + //禁止引用外部xml实体(libxml 2.9+已默认禁用,PHP8起该函数已废弃) + if (PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader(true); + } $values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return $values; } diff --git a/backend/application/common/service/UserService.php b/backend/application/common/service/UserService.php index a4e315d..43e97a2 100644 --- a/backend/application/common/service/UserService.php +++ b/backend/application/common/service/UserService.php @@ -4,7 +4,6 @@ namespace app\common\service; use app\common\model\Accounts; use app\common\model\User; -use EasyWeChat\Factory; use app\common\traits\ServiceTrait; class UserService @@ -23,12 +22,10 @@ class UserService */ public static function wxLogin($code) { - // 微信配置 - $wechatConfig = config('common.wechat'); - $app = Factory::miniProgram($wechatConfig); - $result = $app->auth->session($code); - if (isset($result['errcode'])) { - throw new \Exception('微信登录失败:' . $result['errmsg']); + // 微信登录(EasyWeChat 6.x: code2session) + $result = (new EasyWeChatService())->login($code); + if (empty($result['openid'])) { + throw new \Exception('微信登录失败:' . ($result['errmsg'] ?? '未知错误')); } $openid = $result['openid']; diff --git a/backend/application/database.php b/backend/application/database.php index cd976ff..09e25e8 100644 --- a/backend/application/database.php +++ b/backend/application/database.php @@ -18,13 +18,13 @@ return [ // 服务器地址 'hostname' => Env::get('database.hostname', '127.0.0.1'), // 数据库名 - 'database' => Env::get('database.database', 'softmt'), + 'database' => Env::get('database.database', ''), // 用户名 - 'username' => Env::get('database.username', 'root'), + 'username' => Env::get('database.username', ''), // 密码 - 'password' => Env::get('database.password', '123456'), + 'password' => Env::get('database.password', ''), // 端口 - 'hostport' => Env::get('database.hostport', ''), + 'hostport' => Env::get('database.hostport', '3306'), // 连接dsn 'dsn' => '', // 数据库连接参数 diff --git a/backend/application/extra/common.php b/backend/application/extra/common.php index 57d8477..63de2da 100644 --- a/backend/application/extra/common.php +++ b/backend/application/extra/common.php @@ -9,18 +9,22 @@ return [ 'domain' => 'https://' . Env::get('oss.BUCKET') . '.oss-cn-shenzhen.aliyuncs.com/', ], 'wechat' => [ - 'app_id' => Env::get('wechat.app_id'), - 'secret' => Env::get('wechat.app_secret'), - 'mch_id' => Env::get('wechat.mch_id') , - 'key' => Env::get('wechat.api_key'), // API 密钥 - // 如需使用敏感接口(如退款、发送红包等)需要配置 API 证书路径(登录商户平台下载 API 证书) - 'cert_path' => APP_PATH. '/extra/cert/apiclient_cert.pem', // XXX: 绝对路径!!!! - 'key_path' => APP_PATH . '/extra/cert/apiclient_key.pem', // XXX: 绝对路径!!!! - 'notify_url' => Env::get('app.url') . '/api/order/payNotify', // 你也可以在下单时单独设置来想覆盖它 - 'response_type' => 'array', - 'log' => [ - 'level' => 'debug', - 'file' => LOG_PATH . '/wechat.log', - ], + // 小程序 + 'app_id' => Env::get('wechat.app_id'), + 'secret' => Env::get('wechat.app_secret'), + // 微信支付 v3 (EasyWeChat 6.x) + 'mch_id' => Env::get('wechat.mch_id'), // 商户号 + 'secret_key' => Env::get('wechat.mch_secret_key'), // APIv3 密钥(商户平台-账户中心-API安全) + 'v2_secret_key' => Env::get('wechat.api_key'), // 旧 v2 API 密钥(兼容 v2 模式回调解密) + // 商户 API 证书(商户平台-账户中心-API安全 下载, v2/v3 共用同一套) + 'private_key' => APP_PATH . 'extra/cert/apiclient_key.pem', // 商户证书私钥 绝对路径 + 'certificate' => APP_PATH . 'extra/cert/apiclient_cert.pem', // 商户API证书 绝对路径 + // 微信支付公钥模式(2024Q3+ 推荐): 公钥ID以 PUB_KEY_ID_ 开头, 公钥文件存放于 extra/cert/wechatpay_pub.pem + // 两者都配置后无需再下载平台证书 + 'platform_certs' => (Env::get('wechat.pub_key_id') && is_file(APP_PATH . 'extra/cert/wechatpay_pub.pem')) + ? [Env::get('wechat.pub_key_id') => APP_PATH . 'extra/cert/wechatpay_pub.pem'] + : [], + // 支付结果通知地址(也可在下单时单独覆盖) + 'notify_url' => Env::get('app.url') . '/api/order/payNotify', ], ]; \ No newline at end of file diff --git a/backend/composer.json b/backend/composer.json index 89d38a8..b96a872 100644 --- a/backend/composer.json +++ b/backend/composer.json @@ -24,7 +24,7 @@ "karsonzhang/fastadmin-addons": "~1.4.0", "overtrue/pinyin": "^3.0", "phpoffice/phpspreadsheet": "^1.29.1", - "overtrue/wechat": "~4.0", + "w7corp/easywechat": "^6.17", "ext-json": "*", "ext-curl": "*", "ext-pdo": "*", @@ -35,8 +35,7 @@ "config": { "preferred-install": "dist", "allow-plugins": { - "topthink/think-installer": true, - "easywechat-composer/easywechat-composer": true + "topthink/think-installer": true } }, "autoload": { diff --git a/backend/extend/fast/Form.php b/backend/extend/fast/Form.php index e1041a1..5b43dc6 100644 --- a/backend/extend/fast/Form.php +++ b/backend/extend/fast/Form.php @@ -761,12 +761,30 @@ EOD; $months = []; foreach (range(1, 12) as $month) { - $months[$month] = strftime($format, mktime(0, 0, 0, $month, 1)); + $months[$month] = date(self::strftimeToDate($format), mktime(0, 0, 0, $month, 1)); } return $this->select($name, $months, $selected, $options); } + /** + * 将 strftime 格式符转换为 date() 格式符 + * (strftime 自 PHP 8.1 起废弃) + * @param string $format strftime 格式串 + * @return string + */ + protected static function strftimeToDate($format) + { + $map = [ + '%d' => 'd', '%e' => 'j', '%j' => 'z', '%m' => 'm', + '%H' => 'H', '%I' => 'h', '%M' => 'i', '%S' => 's', + '%y' => 'y', '%Y' => 'Y', '%%' => '%', + '%a' => 'D', '%A' => 'l', '%b' => 'M', '%B' => 'F', '%h' => 'M', + '%p' => 'A', '%T' => 'H:i:s', '%R' => 'H:i', + ]; + return strtr($format, $map); + } + /** * 根据传递的值生成option *