api.so-manager-dev.com/app/Http/Controllers/Api/RefundController.php
Your Name f139a3f608
All checks were successful
Deploy api / deploy (push) Successful in 22s
支払いAPI実装
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 20:02:25 +09:00

187 lines
7.0 KiB
PHP
Raw 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
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\RefundRequest;
use App\Models\PaymentTransaction;
use App\Services\Wellnet\WellnetCreditService;
use Illuminate\Http\JsonResponse;
use RuntimeException;
/**
* 返金コントローラAPI 6
*/
class RefundController extends Controller
{
private const REFUND_STATUS_REFUNDED = 'REFUNDED';
private const REFUND_STATUS_PENDING = 'REFUND_PENDING';
/**
* 返金処理
*
* POST /api/newwipe/refund
*/
public function refund(
RefundRequest $request,
WellnetCreditService $creditService
): JsonResponse {
$validated = $request->validated();
// トランザクション検索
$transaction = PaymentTransaction::where('syuno_recv_num', $validated['SyunoRecvNum'])->first();
if (!$transaction) {
return response()->json([
'error' => [
'code' => 'E17',
'message' => '対象取引が未入金または存在しません。',
]
], 400);
}
// 入金済みチェック
if ($transaction->status !== '入金済み') {
return response()->json([
'error' => [
'code' => 'E17',
'message' => '未入金のため返金できません。',
]
], 400);
}
// クレジット決済のみ返金可能
if ($transaction->payment_type !== 'credit') {
return response()->json([
'error' => [
'code' => 'INVALID_REQUEST',
'message' => 'コンビニ決済の返金はこのAPIでは対応していません。',
]
], 400);
}
// 既に返金済みチェック
if ($transaction->refund_status === 'REFUNDED') {
return response()->json([
'error' => [
'code' => 'D01',
'message' => '既に返金処理済みです。',
]
], 400);
}
// 決済番号チェックCAFIS決済番号が必要
if (empty($transaction->payment_number)) {
return response()->json([
'error' => [
'code' => 'E20',
'message' => 'クレジット決済番号paymentNumberが未取得のため返金できません。',
]
], 400);
}
// 返金額計算
$refundAmount = $validated['refundAmount'] ?? $transaction->amount;
$afterRefundPayAmount = $transaction->amount - $refundAmount;
if ($afterRefundPayAmount < 0) {
return response()->json([
'error' => [
'code' => 'INVALID_REQUEST',
'message' => '返金額が決済金額を超えています。',
]
], 400);
}
$creditResponse = null;
try {
// 全額かつ与信取消可能な場合
if ($afterRefundPayAmount === 0) {
$creditResponse = $creditService->cancelAuthorize($transaction->payment_number);
} else {
// 部分返金(差額返金)
$creditResponse = $creditService->refund($transaction->payment_number, $afterRefundPayAmount);
}
} catch (RuntimeException $e) {
$mapped = $this->mapCreditExceptionToApiError($e);
// 与信取消失敗時はrefundで再試行
if ($afterRefundPayAmount === 0 && str_contains($e->getMessage(), 'INVALID_OPERATION')) {
try {
$creditResponse = $creditService->refund($transaction->payment_number, 0);
} catch (RuntimeException $retryException) {
$retryMapped = $this->mapCreditExceptionToApiError($retryException);
return response()->json([
'error' => [
'code' => $retryMapped['code'],
'message' => $retryMapped['message'],
]
], $retryMapped['code'] === 'E99' ? 500 : 400);
}
} else {
return response()->json([
'error' => [
'code' => $mapped['code'],
'message' => $mapped['message'],
]
], $mapped['code'] === 'E99' ? 500 : 400);
}
}
// トランザクション更新
$refundId = 'REF-' . date('YmdHis') . '-' . substr(uniqid(), -6);
$refundStatus = $creditResponse['refundStatus'] ?? self::REFUND_STATUS_REFUNDED;
$transaction->update([
'refund_amount' => $refundAmount,
'refund_status' => $refundStatus,
'refund_id' => $refundId,
]);
$response = [
'SyunoRecvNum' => $transaction->syuno_recv_num,
'refundStatus' => $refundStatus,
'refundAmount' => $refundAmount,
'originalAmount' => $transaction->amount,
'refundId' => $refundId,
];
if ($transaction->payment_number) {
$response['transactionId'] = $transaction->payment_number;
}
return response()->json($response);
}
/**
* Wellnet/CAFISエラーをapi.pdfの返金APIエラーコードへ寄せる
*/
private function mapCreditExceptionToApiError(RuntimeException $e): array
{
$message = $e->getMessage();
if (preg_match('/\\bE29\\b/u', $message) || str_contains($message, '有効期限が過ぎ')) {
return ['code' => 'E29', 'message' => '期限経過: ' . $message];
}
if (str_contains($message, 'PAYMENT_NUMBER_DOES_NOT_EXIST')) {
return ['code' => 'E20', 'message' => '取引不明: ' . $message];
}
if (str_contains($message, 'INVALID_OPERATION_REFUNDED')) {
return ['code' => 'D01', 'message' => '返金不可: ' . $message];
}
if (str_contains($message, 'INVALID_OPERATION_CANCELED')) {
return ['code' => 'D01', 'message' => '返金不可: ' . $message];
}
if (preg_match('/\\bD0[2-8]\\b/u', $message) || str_contains($message, 'D81')) {
// D02: 売上前 / D03: 差額なし(取消推奨) / D04: 返金後金額不正 / D05: 対象なし
// D06-08: 二重等 / D81: 差額返金済の為取消不可
return ['code' => 'E30', 'message' => '処理不可: ' . $message];
}
if (str_contains($message, 'HTTP 423')) {
return ['code' => 'E30', 'message' => '処理不可: ' . $message];
}
if (str_contains($message, 'USER_ACTION_IN_PROGRESS')) {
return ['code' => 'E30', 'message' => '処理不可: ' . $message];
}
return ['code' => 'E99', 'message' => '返金処理に失敗しました: ' . $message];
}
}