143 lines
4.5 KiB
PHP
Executable File
143 lines
4.5 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\adminapi\controller\order;
|
|
|
|
use app\adminapi\logic\order\OrderLogic;
|
|
use app\common\model\Order;
|
|
use think\response\Xml;
|
|
|
|
/**
|
|
* 微信支付回调控制器
|
|
* 支持:1) 预创建订单的支付 2) 员工直接在企业微信发起对外收款(自动创建订单)
|
|
* Class WechatNotifyController
|
|
* @package app\adminapi\controller\order
|
|
*/
|
|
class WechatNotifyController
|
|
{
|
|
/**
|
|
* @notes 微信异步通知
|
|
* @return Xml
|
|
*/
|
|
public function notify()
|
|
{
|
|
try {
|
|
// 获取微信配置
|
|
$wechatConfig = config('pay.wechat');
|
|
|
|
// 获取XML数据
|
|
$xmlData = file_get_contents('php://input');
|
|
$data = $this->xmlToArray($xmlData);
|
|
|
|
// 验证签名
|
|
if (!$this->verifyWechatSign($data, $wechatConfig['api_key'])) {
|
|
return $this->xmlResponse('FAIL', '签名验证失败');
|
|
}
|
|
|
|
$outTradeNo = $data['out_trade_no'] ?? '';
|
|
$totalFee = (int)($data['total_fee'] ?? 0);
|
|
$amount = $totalFee / 100;
|
|
$transactionId = $data['transaction_id'] ?? '';
|
|
|
|
$order = Order::where('order_no', $outTradeNo)->find();
|
|
|
|
if (!$order) {
|
|
// 订单不存在:员工直接在企业微信发起收款,自动创建订单并标记已支付
|
|
if ($data['result_code'] == 'SUCCESS' && $totalFee > 0) {
|
|
$newOrder = OrderLogic::createFromCallback($outTradeNo, $amount, $transactionId);
|
|
if ($newOrder) {
|
|
return $this->xmlResponse('SUCCESS', '支付成功');
|
|
}
|
|
}
|
|
return $this->xmlResponse('FAIL', '订单不存在');
|
|
}
|
|
|
|
if ($totalFee != (int)($order->amount * 100)) {
|
|
return $this->xmlResponse('FAIL', '金额不匹配');
|
|
}
|
|
|
|
// 验证交易状态
|
|
if ($data['result_code'] == 'SUCCESS') {
|
|
// 更新订单状态(支持普通微信支付及企业微信对外收款)
|
|
if ($order->status == 1) { // 只有待支付的订单才能更新
|
|
$order->status = 2; // 已支付
|
|
$order->payment_method = 'wechat';
|
|
$order->payment_time = date('Y-m-d H:i:s');
|
|
$order->trade_no = $transactionId;
|
|
$order->save();
|
|
}
|
|
}
|
|
|
|
return $this->xmlResponse('SUCCESS', '支付成功');
|
|
} catch (\Exception $e) {
|
|
return $this->xmlResponse('FAIL', $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @notes 验证微信签名
|
|
* @param array $data
|
|
* @param string $apiKey
|
|
* @return bool
|
|
*/
|
|
private function verifyWechatSign(array $data, string $apiKey): bool
|
|
{
|
|
try {
|
|
// 获取签名
|
|
$sign = $data['sign'] ?? '';
|
|
unset($data['sign']);
|
|
|
|
// 按键排序
|
|
ksort($data);
|
|
|
|
// 构建签名字符串
|
|
$signStr = '';
|
|
foreach ($data as $key => $value) {
|
|
if ($value !== '' && $value !== null) {
|
|
$signStr .= $key . '=' . $value . '&';
|
|
}
|
|
}
|
|
$signStr .= 'key=' . $apiKey;
|
|
|
|
// MD5签名
|
|
$computedSign = strtoupper(md5($signStr));
|
|
|
|
return $computedSign === $sign;
|
|
} catch (\Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @notes XML转数组
|
|
* @param string $xml
|
|
* @return array
|
|
*/
|
|
private function xmlToArray(string $xml): array
|
|
{
|
|
try {
|
|
$data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
|
|
return is_array($data) ? $data : [];
|
|
} catch (\Exception $e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @notes 返回XML响应
|
|
* @param string $returnCode
|
|
* @param string $returnMsg
|
|
* @return Xml
|
|
*/
|
|
private function xmlResponse(string $returnCode, string $returnMsg): Xml
|
|
{
|
|
$xml = '<xml>';
|
|
$xml .= '<return_code><![CDATA[' . $returnCode . ']]></return_code>';
|
|
$xml .= '<return_msg><![CDATA[' . $returnMsg . ']]></return_msg>';
|
|
$xml .= '</xml>';
|
|
|
|
return response($xml, 200, ['Content-Type' => 'application/xml']);
|
|
}
|
|
}
|