All checks were successful
Deploy api / deploy (push) Successful in 22s
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
196 lines
6.8 KiB
PHP
196 lines
6.8 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Api;
|
||
|
||
use App\Exceptions\WellnetSoapException;
|
||
use App\Http\Controllers\Controller;
|
||
use App\Http\Requests\Api\RemLinkRequest;
|
||
use App\Models\PaymentTransaction;
|
||
use App\Services\Wellnet\PaymentLinkBuilder;
|
||
use App\Services\Wellnet\WellnetSoapService;
|
||
use Illuminate\Http\JsonResponse;
|
||
use RuntimeException;
|
||
|
||
/**
|
||
* REM支払案内コントローラ(API 2)
|
||
*/
|
||
class RemPaymentController extends Controller
|
||
{
|
||
/**
|
||
* REM支払案内リンク生成
|
||
*
|
||
* POST /api/newwipe/rem/link
|
||
*/
|
||
public function createLink(
|
||
RemLinkRequest $request,
|
||
WellnetSoapService $soapService,
|
||
PaymentLinkBuilder $linkBuilder
|
||
): JsonResponse {
|
||
$validated = $request->validated();
|
||
|
||
// 受付番号の重複チェック
|
||
$existing = PaymentTransaction::where('syuno_recv_num', $validated['SyunoRecvNum'])->first();
|
||
if ($existing) {
|
||
// 期限切れ(DB未更新)を補正
|
||
if ($existing->status === '入金待ち' && $existing->pay_limit && $existing->pay_limit->lessThan(now())) {
|
||
$existing->update(['status' => '支払期限切れ']);
|
||
}
|
||
|
||
if ($existing->status === '入金済み') {
|
||
return response()->json([
|
||
'error' => ['code' => 'E17', 'message' => '入金処理完了済みの受付番号です。']
|
||
], 400);
|
||
}
|
||
if ($existing->status === '支払期限切れ') {
|
||
return response()->json([
|
||
'error' => ['code' => 'E23', 'message' => '支払期限切れの受付番号です。']
|
||
], 400);
|
||
}
|
||
if ($existing->status === '取消済み') {
|
||
return response()->json([
|
||
'error' => ['code' => 'E24', 'message' => '取消済みの受付番号です。']
|
||
], 400);
|
||
}
|
||
|
||
// 冪等対応:入金待ちの既存データがある場合は同一リンクを返す
|
||
if (!empty($existing->kessai_no) && $existing->status === '入金待ち') {
|
||
$paymentGuideUrl = $linkBuilder->buildRemUrl($existing->kessai_no);
|
||
return response()->json([
|
||
'result' => 'OK',
|
||
'paymentGuideUrl' => $paymentGuideUrl,
|
||
'SyunoRecvNum' => $existing->syuno_recv_num,
|
||
]);
|
||
}
|
||
|
||
return response()->json([
|
||
'error' => ['code' => 'INVALID_REQUEST', 'message' => '既に登録済みの受付番号です。']
|
||
], 400);
|
||
}
|
||
|
||
// SyunoFreeArray構築
|
||
$freeArray = $this->buildFreeArray($validated);
|
||
|
||
// SOAP送信データ構築
|
||
$inData = [
|
||
'SyunoRecvNum' => $validated['SyunoRecvNum'],
|
||
'SyunoTel' => $validated['SyunoTel'],
|
||
'SyunoNameKanji' => $validated['SyunoNameKanji'],
|
||
'SyunoPayLimit' => $validated['SyunoPayLimit'],
|
||
'SyunoPayAmount' => (string) $validated['SyunoPayAmount'],
|
||
'SyunoFreeArray' => $freeArray,
|
||
];
|
||
|
||
if (!empty($validated['SyunoNameKana'])) {
|
||
$inData['SyunoNameKana'] = $validated['SyunoNameKana'];
|
||
}
|
||
if (!empty($validated['SyunoReserveNum'])) {
|
||
$inData['SyunoReserveNum'] = $validated['SyunoReserveNum'];
|
||
}
|
||
if (!empty($validated['SyunoMemberNum'])) {
|
||
$inData['SyunoMemberNum'] = $validated['SyunoMemberNum'];
|
||
}
|
||
|
||
try {
|
||
// Wellnet受付登録
|
||
$result = $soapService->register($inData);
|
||
} catch (WellnetSoapException $e) {
|
||
$code = $e->getWellnetCode();
|
||
$errorCode = $this->normalizeWellnetErrorCode($code);
|
||
return response()->json([
|
||
'error' => [
|
||
'code' => $errorCode,
|
||
'message' => 'Wellnet受付登録に失敗しました: ' . $e->getMessage(),
|
||
]
|
||
], 400);
|
||
} catch (RuntimeException $e) {
|
||
return response()->json([
|
||
'error' => [
|
||
'code' => 'E99',
|
||
'message' => 'Wellnet通信エラー: ' . $e->getMessage(),
|
||
]
|
||
], 500);
|
||
}
|
||
|
||
$kessaiNo = $result['KKessaiNo'];
|
||
|
||
// 支払案内リンク生成
|
||
$paymentGuideUrl = $linkBuilder->buildRemUrl($kessaiNo);
|
||
|
||
// トランザクション保存
|
||
PaymentTransaction::create([
|
||
'syuno_recv_num' => $validated['SyunoRecvNum'],
|
||
'payment_type' => 'rem',
|
||
'status' => '入金待ち',
|
||
'amount' => $validated['SyunoPayAmount'],
|
||
'pay_limit' => $this->parsePayLimit($validated['SyunoPayLimit']),
|
||
'kessai_no' => $kessaiNo,
|
||
'name_kanji' => $validated['SyunoNameKanji'],
|
||
'name_kana' => $validated['SyunoNameKana'] ?? null,
|
||
'tel' => $validated['SyunoTel'],
|
||
'subscription_flg' => 0,
|
||
'free_area' => $this->buildFreeAreaJson($validated),
|
||
'wellnet_response' => json_encode($result, JSON_UNESCAPED_UNICODE),
|
||
]);
|
||
|
||
return response()->json([
|
||
'result' => 'OK',
|
||
'paymentGuideUrl' => $paymentGuideUrl,
|
||
'SyunoRecvNum' => $validated['SyunoRecvNum'],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* SyunoFreeArray構築(Index付き配列)
|
||
*/
|
||
private function buildFreeArray(array $validated): array
|
||
{
|
||
$freeArray = [];
|
||
for ($i = 1; $i <= 23; $i++) {
|
||
$key = 'SyunoFree' . $i;
|
||
if (isset($validated[$key]) && $validated[$key] !== '') {
|
||
$freeArray[$i] = $validated[$key];
|
||
}
|
||
}
|
||
return $freeArray;
|
||
}
|
||
|
||
/**
|
||
* Free AreaをJSON保存用に構築
|
||
*/
|
||
private function buildFreeAreaJson(array $validated): ?array
|
||
{
|
||
$freeArea = [];
|
||
for ($i = 1; $i <= 23; $i++) {
|
||
$key = 'SyunoFree' . $i;
|
||
if (isset($validated[$key]) && $validated[$key] !== '') {
|
||
$freeArea[$key] = $validated[$key];
|
||
}
|
||
}
|
||
return !empty($freeArea) ? $freeArea : null;
|
||
}
|
||
|
||
/**
|
||
* 支払期限文字列(YYYYMMDDhhmm)をdatetime形式に変換
|
||
*/
|
||
private function parsePayLimit(string $payLimit): string
|
||
{
|
||
return substr($payLimit, 0, 4) . '-'
|
||
. substr($payLimit, 4, 2) . '-'
|
||
. substr($payLimit, 6, 2) . ' '
|
||
. substr($payLimit, 8, 2) . ':'
|
||
. substr($payLimit, 10, 2) . ':00';
|
||
}
|
||
|
||
/**
|
||
* WellnetエラーコードをAPIエラーコード形式へ正規化
|
||
*/
|
||
private function normalizeWellnetErrorCode(string $code): string
|
||
{
|
||
if (!ctype_digit($code)) {
|
||
return $code;
|
||
}
|
||
$trimmed = ltrim($code, '0');
|
||
return 'R' . ($trimmed === '' ? '0' : $trimmed);
|
||
}
|
||
}
|