api.so-manager-dev.com/app/Http/Controllers/Api/RemPaymentController.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

196 lines
6.8 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\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);
}
}