CaiWuXiTong/app/Http/Controllers/Business/SupplierSummaryCardCallback.php
苏木 ee871dafc1 feat(供应商对账): 钉钉卡片一键确认回传财务端 (#1175)
厂家可在钉钉 AI 卡片内直接确认月度货款,确认结果签名回传并在财务端实时展示。
Co-authored-by: 苏木 <594326199@qq.com>
Co-committed-by: 苏木 <594326199@qq.com>
2026-11-15 10:01:31 +08:00

82 lines
3.3 KiB
PHP

<?php
namespace App\Http\Controllers\Business;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\EcommerceFinance\Services\SupplierSummaryAICard;
use Modules\EcommerceFinance\Services\SupplierSummaryConfirmation;
use Symfony\Component\HttpFoundation\Response;
use Throwable;
/**
* 钉钉 AI 卡片 HTTP 回传入口。DingTalk 的签名头用于校验请求来源。
*/
class SupplierSummaryCardCallback extends Controller
{
public function __construct(
private readonly SupplierSummaryAICard $summaryAICard,
private readonly SupplierSummaryConfirmation $confirmations,
) {
}
public function __invoke(Request $request): JsonResponse
{
$this->summaryAICard->assertValidCallbackSignature($request);
if (trim((string) $request->input('type', '')) !== 'actionCallback') {
return response()->json(['success' => false, 'message' => '不支持的钉钉回调类型。'], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$outTrackId = trim((string) $request->input('outTrackId', ''));
$companyId = $this->confirmations->companyIdFromOutTrackId($outTrackId);
if ($companyId === null) {
return response()->json(['success' => false, 'message' => '确认卡片标识无效。'], Response::HTTP_NOT_FOUND);
}
$dingTalkUserId = trim((string) $request->input('userId', ''));
if ($dingTalkUserId === '') {
return response()->json(['success' => false, 'message' => '钉钉确认人缺失。'], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$content = $request->input('content', []);
if (is_string($content)) {
$decoded = json_decode($content, true);
$content = is_array($decoded) ? $decoded : [];
}
$params = is_array($content) ? data_get($content, 'cardPrivateData.params', []) : [];
if (is_string($params)) {
$decoded = json_decode($params, true);
$params = is_array($decoded) ? $decoded : [];
}
if (! is_array($params) || trim((string) ($params['action'] ?? '')) !== 'confirm') {
return response()->json(['success' => false, 'message' => '不支持的卡片操作。'], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$record = $this->confirmations->find($outTrackId, $companyId);
if ($record === null) {
return response()->json(['success' => false, 'message' => '确认记录不存在。'], Response::HTTP_NOT_FOUND);
}
$result = $this->confirmations->confirm(
$outTrackId,
$companyId,
$dingTalkUserId,
trim((string) $request->input('corpId', '')),
);
// 重复回调也使用同一个 outUpdateId 重试卡片状态,避免首次回写卡片
// 短暂失败后按钮一直停留在“厂家确认”,财务确认记录本身保持幂等。
try {
$this->summaryAICard->updateConfirmed($result['record']);
} catch (Throwable $exception) {
report($exception);
}
return response()->json([
'success' => true,
'confirmed' => true,
'newly_confirmed' => $result['newly_confirmed'],
'confirmed_at' => (string) ($result['record']['confirmed_at'] ?? ''),
]);
}
}