領収証発行サービス
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

330 行
8.6KB

  1. <?php
  2. namespace App\Logic\SMS;
  3. use App\Codes\SMSProviderName;
  4. use App\Codes\SMSSendPurpose;
  5. use App\Models\ReceiptIssuingOrder;
  6. use App\Models\SMSProvider;
  7. use App\Models\SMSProviderFourSMessageCommunication;
  8. use App\Models\SMSSendOrder;
  9. use App\Util\DateUtil;
  10. use Exception;
  11. use Illuminate\Http\Client\Response;
  12. use Illuminate\Support\Collection;
  13. use Illuminate\Support\Facades\Http;
  14. use LogicException;
  15. class FourSMessageManager implements SMSManager
  16. {
  17. private const SEND_URL = ['POST', "https://4sm.jp/api/sms_send"];
  18. private const CANCEL_URL = ['POST', "https://4sm.jp/api/sms_cancel"];
  19. private const RESULT_URL = ['POST', "https://4sm.jp/api/get_send_result"];
  20. private static function getUserId(): string
  21. {
  22. $ret = config("logic.sms.FourSMessage.userId");
  23. if ($ret === null) {
  24. throw new LogicException("SMS FourSMessage UserID 未定義");
  25. }
  26. return $ret;
  27. }
  28. private static function getPassword(): string
  29. {
  30. $ret = config("logic.sms.FourSMessage.password");
  31. if ($ret === null) {
  32. throw new LogicException("SMS FourSMessage Password 未定義");
  33. }
  34. return $ret;
  35. }
  36. public static function makeSMSSendOrder(ReceiptIssuingOrder $receiptIssuingOrder, SMSSendPurpose $purpose, string $contents): SMSSendOrder
  37. {
  38. $order = new SMSSendOrder();
  39. $order->receipt_issuing_order_id = $receiptIssuingOrder->id;
  40. $order->contract_id = $receiptIssuingOrder->contract_id;
  41. $order->phone_number = $receiptIssuingOrder->sms_phone_number;
  42. $order->sumamry_key1 = $receiptIssuingOrder->sumamry_key1;
  43. $order->sumamry_key2 = $receiptIssuingOrder->sumamry_key2;
  44. $order->purpose = $purpose;
  45. $order->content = $contents;
  46. return $$order;
  47. }
  48. public function __construct(
  49. private SMSSendOrder $order,
  50. ) {
  51. }
  52. public function loadOrder(string $id): static
  53. {
  54. $this->order = SMSSendOrder::findOrFail($id);
  55. return $this;
  56. }
  57. public function setOrder(SMSSendOrder $order): static
  58. {
  59. $this->order = $order;
  60. return $this;
  61. }
  62. public function getOrder()
  63. {
  64. return $this->order;
  65. }
  66. public function send(): bool
  67. {
  68. $sendContents = [
  69. 'cp_userid' => static::getUserId(),
  70. 'cp_password' => static::getPassword(),
  71. 'carrier_id' => '99',
  72. 'message' => $this->order->content,
  73. 'address' => $this->order->phone_number,
  74. 'urlshorterflg' => '1',
  75. 'send_date' => '202305120000',
  76. ];
  77. $this->order->sms_provider_id = CommunicationData::getMst()->id;
  78. $res = $this->communication(self::SEND_URL, $sendContents);
  79. if ($res->isSuccess()) {
  80. // 送信依頼を作成
  81. $this->order->save();
  82. $comm = new SMSProviderFourSMessageCommunication();
  83. $comm->sms_send_order_id = $this->order->id;
  84. $comm->contract_id = $this->order->contract_id;
  85. $comm->request_id = $res->requestId;
  86. $comm->request_date = $res->requestDate;
  87. $comm->save();
  88. return true;
  89. } else {
  90. return false;
  91. }
  92. }
  93. public function poll(): bool
  94. {
  95. $requestId = SMSProviderFourSMessageCommunication::whereSmsSendOrderId($this->order->id)
  96. ->firstOrFail()
  97. ->request_id;
  98. $sendContents = [
  99. 'request_id' => $requestId,
  100. 'user_id' => static::getUserId(),
  101. 'password' => base64_encode(static::getPassword()),
  102. ];
  103. $res = $this->communication(self::RESULT_URL, $sendContents);
  104. if ($res->isSuccess()) {
  105. $res->save();
  106. }
  107. return $res->isSuccess();
  108. }
  109. public function cancel(): bool
  110. {
  111. $requestId = SMSProviderFourSMessageCommunication::whereSmsSendOrderId($this->order->id)
  112. ->firstOrFail()
  113. ->request_id;
  114. $sendContents = [
  115. 'request_id' => $requestId,
  116. 'cp_userid' => static::getUserId(),
  117. 'cp_password' => static::getPassword(),
  118. ];
  119. $res = $this->communication(self::CANCEL_URL, $sendContents);
  120. return $res->isSuccess();
  121. }
  122. private function communication(array $urlDef, array $sendData): CommunicationData
  123. {
  124. $method = $urlDef[0];
  125. $form = Http::withHeaders([
  126. 'Content-Type' => 'application/x-www-form-urlencoded',
  127. ])
  128. ->asForm();
  129. if ($method === 'GET') {
  130. $query = "";
  131. foreach ($sendData as $key => $value) {
  132. if ($query !== "") {
  133. $query .= '&';
  134. }
  135. $query .= $key . "=" . $value;
  136. }
  137. $url = $urlDef[1] . '?' . $query;
  138. $res = $form->get($url);
  139. } else {
  140. $url = $urlDef[1];
  141. $res = $form->post($url, $sendData);
  142. }
  143. logger(['SMS-SendContet' => $sendData, 'URL' => $url]);
  144. if (!$res->ok()) {
  145. $res->throw();
  146. }
  147. logger(['SMS-ReveiveContet' => $res->json()]);
  148. $ret = new CommunicationData($res, $this->order);
  149. return $ret;
  150. }
  151. }
  152. class CommunicationData
  153. {
  154. public string|null $result;
  155. public string|null $requestId;
  156. public string|null $requestDate;
  157. public string|null $errorCode;
  158. public string|null $errorMessage;
  159. /**
  160. * @var Collection<SMSProviderFourSMessageCommunication>
  161. */
  162. public Collection $data;
  163. private SMSSendOrder $order;
  164. private const RESULT_SUCCESS = 'SUCCESS';
  165. private const DATE_FORMAT = 'YmdHi';
  166. public function __construct(Response $res, SMSSendOrder $order)
  167. {
  168. $this->order = $order;
  169. $data = $res->json();
  170. $response = data_get($data, 'response');
  171. if ($response !== null) {
  172. $data = $response;
  173. }
  174. $this->result = data_get($data, 'result');
  175. $this->requestId = data_get($data, 'request_id');
  176. $this->requestDate = data_get($data, 'request_date');
  177. $this->errorCode = data_get($data, 'error_code');
  178. $this->errorMessage = data_get($data, 'error_message');
  179. $this->data = collect();
  180. $records = data_get($data, 'records');
  181. if (is_array($records)) {
  182. foreach ($records as $record) {
  183. $requestId = data_get($record, 'request_id');
  184. // 空文字の場合があるので、数値にキャストする
  185. $record['success_count'] = intVal($record['success_count']);
  186. $ele = SMSProviderFourSMessageCommunication::whereRequestId($requestId)
  187. ->firstOrFail();
  188. $ele->fill($record);
  189. $ele->sms_send_order_id = $order->id;
  190. $this->data->push($ele);
  191. }
  192. }
  193. }
  194. public function isSuccess(): bool
  195. {
  196. return $this->result === self::RESULT_SUCCESS;
  197. }
  198. public function save()
  199. {
  200. foreach ($this->data as $data) {
  201. $data->save();
  202. if ($this->isFixRecord($data)) {
  203. $this->order->done = true;
  204. if ($data->deliv_rcpt_date !== null) {
  205. $this->order->send_datetime = DateUtil::parse($data->deliv_rcpt_date, self::DATE_FORMAT);
  206. }
  207. if ($data->success_count > 0) {
  208. $this->order->cost = $this->calcCost($data);
  209. }
  210. }
  211. }
  212. }
  213. public function isFixRecord(SMSProviderFourSMessageCommunication $comm): bool
  214. {
  215. // 以下のコードになれば「状態確定」のタイミングであると判断できます。
  216. //  ・受付状態 : 90受付取消、または99受付失敗
  217. //  ・送信エラー: 31~35送信エラー、または40送達済
  218. if (in_array($comm->request_status, ['90', '99'])) {
  219. return true;
  220. }
  221. if (in_array($comm->sending_status, [
  222. '31',
  223. '32',
  224. '33',
  225. '34',
  226. '35',
  227. '40',
  228. ])) {
  229. return true;
  230. }
  231. return false;
  232. }
  233. private function calcCost(SMSProviderFourSMessageCommunication $comm): int
  234. {
  235. $provider = self::getMst();
  236. return $comm->success_count * $provider->cost;
  237. }
  238. private static function getMst(): SMSProvider
  239. {
  240. static $provider = null;
  241. if ($provider === null) {
  242. $provider = SMSProvider::whereProviderName(SMSProviderName::FOUR_S_MESSAGE->value)
  243. ->firstOrFail();
  244. }
  245. return $provider;
  246. }
  247. }