diff --git a/app/Console/Commands/SMBCBankAccountRegisterPoll.php b/app/Console/Commands/SMBCBankAccountRegisterPoll.php index 041a567..15fb875 100644 --- a/app/Console/Commands/SMBCBankAccountRegisterPoll.php +++ b/app/Console/Commands/SMBCBankAccountRegisterPoll.php @@ -16,7 +16,6 @@ use App\Util\DBUtil; use Exception; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\DB; class SMBCBankAccountRegisterPoll extends BaseCommand { @@ -116,7 +115,7 @@ class SMBCBankAccountRegisterPoll extends BaseCommand private function getFromTo() { - $status = SmbcPollStatus::all()->first(); + $status = SmbcPollStatus::whereType(self::class)->first(); $now = DateUtil::now(); if ($status === null) { $this->outputInfo("検索範囲初期化"); @@ -183,9 +182,8 @@ class SMBCBankAccountRegisterPoll extends BaseCommand private function saveFromTo(Carbon $from, Carbon $to) { - DB::table(SmbcPollStatus::getTableName())->delete(); - - $status = new SmbcPollStatus(); + $status = SmbcPollStatus::whereType(self::class)->firstOrNew(); + $status->type = self::class; $status->condition_datetime_to = $to; $status->save(); } diff --git a/app/Console/Commands/SMBCPaymentPoll.php b/app/Console/Commands/SMBCPaymentPoll.php new file mode 100644 index 0000000..5b8da26 --- /dev/null +++ b/app/Console/Commands/SMBCPaymentPoll.php @@ -0,0 +1,194 @@ + + */ + private Collection $results; + + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->applications = collect(); + $this->results = collect(); + } + + /** + * Execute the console command. + * + * @return int + */ + public function service(): int + { + try { + $db = DBUtil::instance(); + $db->beginTransaction(); + + // 検索範囲の取得 + [$from, $to] = $this->getFromTo(); + $this->outputInfo(sprintf("検索範囲 %s-%s", $from->format('Y/m/d H:i:s'), $to->format('Y/m/d H:i:s'))); + + // 検索実行 + $result = SMBC::poll($from, $to); + + // 検索結果の確認 + if (!$result->ok()) { + $this->outputError($result->getMessage()); + return self::RESULTCODE_FAILED; + } + $this->outputInfo(sprintf("取得対象 %d件", $result->getCount())); + + // データハンドリング + foreach ($result->getRecord() as $data) { + $this->handleData($data); + } + + // 検索実績の登録 + $this->saveFromTo($from, $to); + + // キントーンへ登録 + foreach ($this->results as $result) { + $result->save(); + } + $this->outputInfo(sprintf("登録件数:%d件", $this->results->count())); + + $db->commit(); + } catch (Exception $e) { + $db->rollBack(); + throw $e; + } + + return self::RESULTCODE_SUCCESS; + } + + /** + * @return Carbon[] + */ + private function getFromTo() + { + + $status = SmbcPollStatus::whereType(self::class)->first(); + $now = DateUtil::now(); + if ($status === null) { + $this->outputInfo("検索範囲初期化"); + return [ + $now->clone()->addDays(-5), + $now->clone(), + ]; + } + + return [ + $status->condition_datetime_to->clone()->addSecond(), + $now->clone(), + ]; + } + + private function handleData(PollResultRecord $data) + { + try { + $payment = SmbcPayment::getAccess()->first( + SmbcPayment::getQuery() + ->where(SmbcPayment::FIELD_ORDER_NO, $data->orderNo) + ); + } catch (ModelNotFoundException $e) { + $this->outputWarn(sprintf("存在しない支払予定 [%s]", $data->orderNo)); + throw $e; + } + + + // 完了ケース + if ($data->paymentStatus === PaymentStatus::C0200_決済完了) { + $payment->status = SmbcPaymentStatus::S003_決済結果反映済み; + + $payment->paymentAmount = $data->paymentAmount; + $payment->paymentDate = $data->paymentDate; + } + + // 未完了ケース + else if (in_array( + $data->paymentStatus, + [ + PaymentStatus::C0011_請求取消, + PaymentStatus::C0111_依頼取消, + PaymentStatus::C0700_不明入金, + PaymentStatus::C0800_不明通知, + PaymentStatus::C0900_決済申込NG, + ], + true + )) { + $payment->status = SmbcPaymentStatus::S003_決済結果反映済み; + } + + // 処理中ケース + else { + $payment->status = SmbcPaymentStatus::S002_決済結果待ち; + } + + // 共通ケース + + $payment->acceptNo = $data->acceptNo; + $payment->paymentStatus = $data->paymentStatusName; + $payment->paymentStatusUpdateDatetime = $data->paymentStatusUpdateDatetime; + $payment->paymentWarningStatus = $data->paymentWarningStatusName; + $payment->paymentWarningStatusUpdateDatetime = $data->paymentWarningStatusUpdateDatetime; + $payment->allResponse = $data->all; + + + $this->results->push($payment); + } + + private function saveFromTo(Carbon $from, Carbon $to) + { + $status = SmbcPollStatus::whereType(self::class)->firstOrNew(); + $status->type = self::class; + $status->condition_datetime_to = $to; + $status->save(); + } +} diff --git a/app/Console/Schedules/SMBCBankAccountRegisterPoll.php b/app/Console/Schedules/SMBCBankAccountRegisterPoll.php index eb37c11..72518c9 100644 --- a/app/Console/Schedules/SMBCBankAccountRegisterPoll.php +++ b/app/Console/Schedules/SMBCBankAccountRegisterPoll.php @@ -11,7 +11,7 @@ class SMBCBankAccountRegisterPoll extends BaseSchedule static public function register(Schedule $schedule) { $schedule->command(Command::class) - ->everyThreeMinutes() + ->everyOddHour() ->unlessBetween('1:00', '6:00') ->description("SMBC口座振替申請結果取得"); } diff --git a/app/Console/Schedules/SMBCPaymentPoll.php b/app/Console/Schedules/SMBCPaymentPoll.php new file mode 100644 index 0000000..01ec020 --- /dev/null +++ b/app/Console/Schedules/SMBCPaymentPoll.php @@ -0,0 +1,18 @@ +command(Command::class) + ->everyTwoHours() + ->unlessBetween('1:00', '6:00') + ->description("SMBC支払結果取得"); + } +} diff --git a/app/Http/API/SMBC/BankAccountRegister/SMBC.php b/app/Http/API/SMBC/BankAccountRegister/SMBC.php index ee6cff5..0ec5898 100644 --- a/app/Http/API/SMBC/BankAccountRegister/SMBC.php +++ b/app/Http/API/SMBC/BankAccountRegister/SMBC.php @@ -14,14 +14,14 @@ class SMBC public static function poll(Carbon $from, Carbon $to) { - $url = config('smbc.searchUrl'); + $url = config('smbc.bankAccountRegister.searchUrl'); if (!$url) { - throw new ConfigException("smbc.searchUrl", $url); + throw new ConfigException("smbc.bankAccountRegister.searchUrl", $url); } - $password = config('smbc.searchPassword'); + $password = config('smbc.bankAccountRegister.searchPassword'); if (!$password) { - throw new ConfigException("smbc.searchPassword", $password); + throw new ConfigException("smbc.bankAccountRegister.searchPassword", $password); } $sendData = [ @@ -60,14 +60,14 @@ class SMBC public static function getRegisterStartParam(Customer $customer) { - $password = config('smbc.systemPassword'); + $password = config('smbc.bankAccountRegister.systemPassword'); if (!$password) { - throw new ConfigException('smbc.systemPassword', $password); + throw new ConfigException('smbc.bankAccountRegister.systemPassword', $password); } - $url = config('smbc.registerUrl'); + $url = config('smbc.bankAccountRegister.registerUrl'); if (!$url) { - throw new ConfigException('smbc.registerUrl', $url); + throw new ConfigException('smbc.bankAccountRegister.registerUrl', $url); } $param = [ diff --git a/app/Http/API/SMBC/Payment/PaymentStatus.php b/app/Http/API/SMBC/Payment/PaymentStatus.php new file mode 100644 index 0000000..70d289c --- /dev/null +++ b/app/Http/API/SMBC/Payment/PaymentStatus.php @@ -0,0 +1,19 @@ +> + */ + private Collection $lines; + + /** + * @var Collection + */ + private Collection $body; + + private bool $success = false; + private string $message = ""; + private int $count = 0; + + public function __construct(string $data) + { + $this->lines = collect(); + $lines = Str::of($data)->replace('"', '')->explode("\r\n"); + foreach ($lines as $lineStr) { + if ($lineStr) { + $lineStr = EncodingUtil::toUtf8FromSjis($lineStr); + $this->lines->push(Str::of($lineStr)->explode(',')); + } + } + + if (!$this->parseHeader()) { + return; + } + if (!$this->parseBody()) { + return; + } + if (!$this->parseFooter()) { + return; + } + + $this->success = true; + } + + public function ok(): bool + { + return $this->success; + } + public function getMessage(): string + { + return $this->message; + } + + public function getRecord() + { + return $this->body; + } + + public function getCount(): int + { + return $this->count; + } + + private function parseHeader(): bool + { + $header = $this->lines->first(); + if (!$header) { + $this->success = false; + $this->message = "ヘッダーなし"; + return false; + } + + $resultCode = $header->get(self::IDX_HEADER_RESULT); + if (!in_array($resultCode, self::RESULT_CODE_SUCCESS)) { + $this->success = false; + $this->message = sprintf("結果コードNG %s", $resultCode); + $readMessage = EncodingUtil::toUtf8FromSjis($header->get(self::IDX_HEADER_MESSAGE)); + return false; + } + return true; + } + + private function parseBody(): bool + { + $this->body = collect(); + + try { + foreach ($this->lines as $line) { + if ($line[self::IDX_RECORD_CODE] === self::RECORD_CODE_BODY) { + $this->body->push(new PollResultRecord($line)); + } + } + } catch (Exception $e) { + $this->success = false; + $this->message = sprintf("Bodyパース失敗 %s", $e->getMessage()); + return false; + } + + return true; + } + + private function parseFooter(): bool + { + $footer = $this->lines->last(); + + if (!$footer) { + $this->success = false; + $this->message = "フッターなし"; + return false; + } + + $count = intval($footer->get(self::IDX_FOOTER_COUNT)); + + if ($count !== $this->body->count()) { + $this->success = false; + $this->message = sprintf("読込件数に差異あり %d : %d", $count, $this->body->count()); + return false; + } + $this->count = $count; + + return true; + } +} diff --git a/app/Http/API/SMBC/Payment/PollResultRecord.php b/app/Http/API/SMBC/Payment/PollResultRecord.php new file mode 100644 index 0000000..bca16c3 --- /dev/null +++ b/app/Http/API/SMBC/Payment/PollResultRecord.php @@ -0,0 +1,55 @@ + $data + */ + public function __construct(Collection $data) + { + $this->orderNo = $data[self::IDX_ORDER_NO]; + $this->acceptNo = $data[self::IDX_ACCEPT_NO]; + $this->paymentStatus = PaymentStatus::from($data[self::IDX_PAYMENT_STATUS]); + $this->paymentWarningStatus = $data[self::IDX_PAYMENT_WARNING_STATUS]; + $this->paymentStatusName = $data[self::IDX_PAYMENT_STATUS_NAME]; + $this->paymentWarningStatusName = $data[self::IDX_PAYMENT_WARNING_STATUS_NAME]; + $this->paymentStatusUpdateDatetime = DateUtil::parse($data[self::IDX_PAYMENT_STATUS_UPDATE_DATETIME]); + $this->paymentWarningStatusUpdateDatetime = DateUtil::parse($data[self::IDX_PAYMENT_WARNING_STATUS_UPDATE_DATETIME]); + $this->paymentAmount = intval($data[self::IDX_PAYMENT_AMOUNT]); + $this->paymentDate = DateUtil::parse($data[self::IDX_PAYMENT_DATE]); + + $this->all = $data->implode(","); + } +} diff --git a/app/Http/API/SMBC/Payment/SMBC.php b/app/Http/API/SMBC/Payment/SMBC.php new file mode 100644 index 0000000..26db488 --- /dev/null +++ b/app/Http/API/SMBC/Payment/SMBC.php @@ -0,0 +1,100 @@ + "213", + 'shori_kbn' => "0500", + 'shop_cd' => "7068054", + 'syuno_co_cd' => "58800", + 'shop_pwd' => $password, + + // 更新日時のFROM-TO検索条件 + 'jotai_date_from' => $from->format('Ymd'), + 'jotai_time_from' => $from->format('Hi'), + 'jotai_date_to' => $to->format('Ymd'), + 'jotai_time_to' => $to->format('Hi'), + + // ソート指定 + 'sort_list' => "13", // 処理日時 + 'sort_jun' => "1" // 昇順 + ]; + + $res = Http::withHeaders([ + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Encoding' => 'Shift_JIS' + ])->asForm()->post( + $url, + $sendData + ); + + if ($res->failed()) { + throw $res->toException(); + } + + return new PollResult($res->body()); + } + + public static function getRegisterStartParam(Customer $customer) + { + + $password = config('smbc.bankAccountRegister.systemPassword'); + if (!$password) { + throw new ConfigException('smbc.bankAccountRegister.systemPassword', $password); + } + + $url = config('smbc.bankAccountRegister.registerUrl'); + if (!$url) { + throw new ConfigException('smbc.bankAccountRegister.registerUrl', $url); + } + + $param = [ + 'bill_no' => sprintf("%012d", $customer->customerCode), + 'bill_name' => $customer->customerName, + 'bill_kana' => mb_convert_kana($customer->customerNameKana, "ks"), + + 'version' => "130", + 'bill_method' => "01", + 'shop_cd' => "7694156", + 'syuno_co_cd' => "58763", + 'shop_pwd' => $password, + 'shoporder_no' => "", + 'koushin_kbn' => "1", + 'shop_phon_hyoji_kbn' => "1", + 'shop_mail_hyoji_kbn' => "1", + 'kessai_id' => "0101", + + 'bill_adr_5' => self::CONDITION_ADDR5_FROM_MY_PAGE, + ]; + $param['fs'] = hash('sha256', $param['shop_cd'] . $param['syuno_co_cd'] . $param['bill_no'] . $param['shoporder_no'] . $param['shop_pwd']); + + $data = [ + 'url' => $url, + 'param' => $param, + ]; + + return $data; + } +} diff --git a/app/Kintone/Models/DropDown/SmbcPayment/SmbcPaymentStatus.php b/app/Kintone/Models/DropDown/SmbcPayment/SmbcPaymentStatus.php new file mode 100644 index 0000000..53e0f05 --- /dev/null +++ b/app/Kintone/Models/DropDown/SmbcPayment/SmbcPaymentStatus.php @@ -0,0 +1,12 @@ + FieldType::DROP_DOWN, + self::FIELD_ORDER_NO => FieldType::SINGLE_LINE_TEXT, + self::FIELD_CUSTOMER_CODE => FieldType::NUMBER, + self::FIELD_CLAIM_AMOUNT => FieldType::NUMBER, + self::FIELD_ACCEPT_NO => FieldType::SINGLE_LINE_TEXT, + self::FIELD_PAYMENT_STATUS => FieldType::DROP_DOWN, + self::FIELD_PAYMENT_STATUS_UPDATE_DATETIME => FieldType::DATETIME, + self::FIELD_PAYMENT_WARNING_STATUS => FieldType::SINGLE_LINE_TEXT, + self::FIELD_PAYMENT_WARNING_STATUS_UPDATE_DATETIME => FieldType::DATETIME, + self::FIELD_PAYMENT_AMOUNT => FieldType::NUMBER, + self::FIELD_PAYMENT_DATE => FieldType::DATE, + self::FIELD_ALL_RESPONSE => FieldType::SINGLE_LINE_TEXT, + ]; + + protected const FIELD_NAMES = [ + ...parent::FIELD_NAMES, + ]; +} diff --git a/app/Models/SmbcPollStatus.php b/app/Models/SmbcPollStatus.php index c3a8def..bbbed95 100644 --- a/app/Models/SmbcPollStatus.php +++ b/app/Models/SmbcPollStatus.php @@ -17,6 +17,6 @@ class SmbcPollStatus extends AppModel public function getModelName(): string { - return "SMBC口座振替登録依頼確認ステータス"; + return "SMBC確認ステータス"; } } diff --git a/app/Util/DateUtil.php b/app/Util/DateUtil.php index 8d2a74e..b37cc28 100644 --- a/app/Util/DateUtil.php +++ b/app/Util/DateUtil.php @@ -26,7 +26,7 @@ class DateUtil public static function parse(?string $source): Carbon|null { - if ($source === null) { + if (!$source) { return null; } $date = Carbon::parse($source); diff --git a/config/kintone.php b/config/kintone.php index f987fd7..2e7d72c 100644 --- a/config/kintone.php +++ b/config/kintone.php @@ -49,6 +49,7 @@ return [ ...App\Kintone\Models\FAQ::setConfig(), ...App\Kintone\Models\Ask::setConfig(), ...App\Kintone\Models\Receipt::setConfig(), + ...App\Kintone\Models\SmbcPayment::setConfig(), ], ]; diff --git a/config/smbc.php b/config/smbc.php index 845e2c1..fafb01a 100644 --- a/config/smbc.php +++ b/config/smbc.php @@ -10,11 +10,18 @@ return [ | | キントーンAPIのホストを定義 */ + 'bankAccountRegister' => [ + 'systemPassword' => env("SMBC_SYSTEM_PASSWORD"), + 'searchPassword' => env("SMBC_SEARCH_PASSWORD"), - 'systemPassword' => env("SMBC_SYSTEM_PASSWORD"), - 'searchPassword' => env("SMBC_SEARCH_PASSWORD"), + 'registerUrl' => env("SMBC_URL_REGISTER"), + 'searchUrl' => env("SMBC_URL_SEARCH"), + ], - 'registerUrl' => env("SMBC_URL_REGISTER"), - 'searchUrl' => env("SMBC_URL_SEARCH"), + 'payment' => [ + 'searchPassword' => env("SMBC_SEARCH_PASSWORD"), + + 'searchUrl' => env("SMBC_URL_SEARCH"), + ], ]; diff --git a/database/migrations/2023_11_06_163700_add_column_smbc_type.php b/database/migrations/2023_11_06_163700_add_column_smbc_type.php new file mode 100644 index 0000000..44138d5 --- /dev/null +++ b/database/migrations/2023_11_06_163700_add_column_smbc_type.php @@ -0,0 +1,29 @@ +string("type")->comment('タイプ')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('smbc_poll_statuses', function (Blueprint $table) { + $table->dropColumn("type"); + }); + } +};