LakalaAccountService.php 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  1. <?php
  2. namespace bizGhs\lakala\services;
  3. use bizGhs\lakala\classes\LakalaAccountClass;
  4. use bizGhs\lakala\classes\LakalaApplicationClass;
  5. use bizGhs\lakala\classes\LakalaApplicationVersionClass;
  6. use bizGhs\lakala\classes\LakalaOperationClass;
  7. use bizGhs\shop\classes\ShopClass;
  8. use common\components\imgUtil;
  9. use common\components\lakala\LakalaOnboardingClient;
  10. use common\components\oss;
  11. use common\components\stringUtil;
  12. use common\components\util;
  13. use Yii;
  14. class LakalaAccountService
  15. {
  16. const RATE = '0.33';
  17. const MCC = '5992';
  18. const ACTIVITY_ID = '208'; // WECHAT_PAY -- 专业化扫码
  19. public static function list($mainId, $shopId)
  20. {
  21. $applications = LakalaApplicationClass::getAllByCondition(
  22. ['mainId' => $mainId, 'shopId' => $shopId],
  23. ['id' => SORT_DESC],
  24. '*'
  25. );
  26. $accounts = LakalaAccountClass::getAllByCondition(
  27. ['mainId' => $mainId, 'shopId' => $shopId],
  28. ['isCurrent' => SORT_DESC, 'id' => SORT_DESC],
  29. '*', null, true
  30. );
  31. foreach ($applications as &$application) {
  32. $application['formData'] = self::decode($application['formData'] ?? '');
  33. }
  34. $refreshAccountIds = [];
  35. foreach ($accounts as $account) {
  36. if ($account->wxAuthorizeState == 1 && $account->zfbAuthorizeState == 1) {
  37. continue;
  38. }
  39. $refreshAccountIds[] = $account->id;
  40. }
  41. LakalaAccountClass::dispatchAuthorizeStatesRefresh($refreshAccountIds);
  42. $accountList = [];
  43. foreach ($accounts as $account) {
  44. $accountList[] = $account->attributes;
  45. }
  46. return ['applications' => $applications, 'accounts' => $accountList];
  47. }
  48. public static function detail($id, $mainId, $shopId)
  49. {
  50. $application = self::application($id, $mainId, $shopId);
  51. $application['formData'] = self::formatFormImages(self::decode($application['formData'] ?? ''));
  52. return $application;
  53. }
  54. public static function create($mainId, $shopId, $shop, $type = LakalaApplicationClass::TYPE_ONBOARDING, $accountId = 0)
  55. {
  56. $now = date('Y-m-d H:i:s');
  57. $form = self::defaultForm($shop);
  58. if ($type === LakalaApplicationClass::TYPE_SETTLEMENT_CHANGE && $accountId > 0) {
  59. $account = LakalaAccountClass::getByCondition(['id' => $accountId, 'mainId' => $mainId, 'shopId' => $shopId]);
  60. if (empty($account)) {
  61. util::fail('没有找到收款账户');
  62. }
  63. $form['mode'] = 'settlement-change';
  64. $form['settlement']['accountName'] = $account['accountName'] ?? '';
  65. $form['settlement']['accountNo'] = $account['accountNo'] ?? '';
  66. $form['settlement']['bankName'] = $account['bankName'] ?? '';
  67. }
  68. $application = LakalaApplicationClass::add([
  69. 'mainId' => $mainId,
  70. 'shopId' => $shopId,
  71. 'accountId' => $accountId,
  72. 'type' => $type,
  73. 'status' => LakalaApplicationClass::STATUS_DRAFT,
  74. 'currentStep' => $type === LakalaApplicationClass::TYPE_SETTLEMENT_CHANGE ? 3 : 1,
  75. 'formData' => self::encode($form),
  76. 'createTime' => $now,
  77. 'updateTime' => $now,
  78. ], true);
  79. return self::detail($application->id, $mainId, $shopId);
  80. }
  81. public static function saveDraft($id, $mainId, $shopId, array $form, $step)
  82. {
  83. $application = self::application($id, $mainId, $shopId, true);
  84. if (!in_array($application->status, [LakalaApplicationClass::STATUS_DRAFT, LakalaApplicationClass::STATUS_REJECT, LakalaApplicationClass::STATUS_CONTRACT_PENDING], true)) {
  85. util::fail('当前申请不能修改');
  86. }
  87. $application->formData = self::encode($form);
  88. $application->currentStep = max(1, min(5, (int)$step));
  89. $application->updateTime = date('Y-m-d H:i:s');
  90. $application->save();
  91. return self::detail($id, $mainId, $shopId);
  92. }
  93. public static function deleteDraft($id, $mainId, $shopId)
  94. {
  95. $application = self::application($id, $mainId, $shopId);
  96. if ($application['status'] !== LakalaApplicationClass::STATUS_DRAFT) {
  97. util::fail('仅草稿可以删除');
  98. }
  99. LakalaApplicationClass::deleteById($id);
  100. }
  101. public static function upload($id, $mainId, $shopId, $base64, $imgType, $ocr)
  102. {
  103. self::application($id, $mainId, $shopId);
  104. if (!preg_match('/^data:image\/[a-zA-Z0-9.+-]+;base64,/', $base64)) {
  105. util::fail('图片格式错误');
  106. }
  107. $content = base64_decode(substr($base64, strpos($base64, ',') + 1), true);
  108. if ($content === false || strlen($content) > 5 * 1024 * 1024) {
  109. util::fail('图片内容无效或超过5MB');
  110. }
  111. $info = getimagesizefromstring($content);
  112. if ($info === false) {
  113. util::fail('图片内容无效');
  114. }
  115. $allowMime = [
  116. 'image/jpeg',
  117. 'image/png',
  118. 'image/gif',
  119. 'image/webp',
  120. ];
  121. if (!in_array($info['mime'], $allowMime, true)) {
  122. Yii::error('不允许上传:' . $info['mime']);
  123. util::fail('图片出错,不允许上传');
  124. }
  125. if (strlen($content) > 5 * 1024 * 1024) {
  126. util::fail('图片超过5MB');
  127. }
  128. $extension = self::imageExtension($info['mime']);
  129. $path = 'lakala-onboarding/' . $mainId . '/' . date('Ymd') . '/' . $id . '/' . stringUtil::uniqueFileName() . '.' . $extension;
  130. oss::uploadContent($path, $content);
  131. $response = (new LakalaOnboardingClient())->upload($base64, $imgType, (bool)$ocr);
  132. $lakalaUrl = $response['data']['url'] ?? '';
  133. return [
  134. 'ossPath' => self::formatOssPath($path),
  135. 'lakalaUrl' => $lakalaUrl,
  136. 'imgType' => $imgType,
  137. 'batchNo' => $response['data']['batch_no'] ?? '',
  138. 'ocrStatus' => $response['data']['status'] ?? '',
  139. 'ocrResult' => $response['data']['result'] ?? null,
  140. 'url' => $lakalaUrl,
  141. 'status' => $response['data']['status'] ?? '',
  142. 'ocr' => $response['data']['result'] ?? null,
  143. ];
  144. }
  145. public static function ocr($id, $mainId, $shopId, $batchNo, $imgType)
  146. {
  147. self::application($id, $mainId, $shopId);
  148. $response = (new LakalaOnboardingClient())->ocr($batchNo, $imgType);
  149. return $response['data'];
  150. }
  151. public static function option($type, $parentCode = '', $keyword = '')
  152. {
  153. $client = new LakalaOnboardingClient();
  154. if ($type === 'region') {
  155. return $client->query('/api/v3/tkbs/organization_parent_code', ['parent_code' => $parentCode ?: '1'])['data'];
  156. }
  157. if ($type === 'bank-region') {
  158. return $client->query('/api/v3/tkbs/organization_bank_parent_code', ['parent_code' => $parentCode ?: '1'])['data'];
  159. }
  160. if ($type === 'bank') {
  161. return $client->query('/api/v3/tkbs/bank', ['area_code' => $parentCode, 'bank_name' => $keyword])['data'];
  162. }
  163. util::fail('不支持的选项类型');
  164. }
  165. public static function applyContract($id, $mainId, $shopId)
  166. {
  167. $application = self::application($id, $mainId, $shopId, true);
  168. $form = self::decode($application->formData);
  169. self::validateContract($form);
  170. $client = new LakalaOnboardingClient();
  171. $orderNo = date('YmdHis') . substr(str_shuffle('0123456789'), 0, 8);
  172. $request = self::contractRequest($form, $orderNo, $client->orgCode());
  173. $response = $client->call('/api/v3/mms/open_api/ec/apply', $request);
  174. $application->contractOrderNo = $orderNo;
  175. $application->contractApplyId = $response['data']['ec_apply_id'] ?? '';
  176. $application->contractStatus = 'UNDONE';
  177. $application->status = LakalaApplicationClass::STATUS_CONTRACT_PENDING;
  178. $application->updateTime = date('Y-m-d H:i:s');
  179. $application->save();
  180. self::operation($application, 0, 'contract', '', $request, $response, 'UNDONE');
  181. return ['resultUrl' => $response['data']['result_url'] ?? '', 'status' => 'UNDONE'];
  182. }
  183. public static function contractStatus($id, $mainId, $shopId)
  184. {
  185. $application = self::application($id, $mainId, $shopId, true);
  186. if (empty($application->contractApplyId) || empty($application->contractOrderNo)) {
  187. util::fail('请先生成电子合同');
  188. }
  189. $client = new LakalaOnboardingClient();
  190. $response = $client->query('/api/v3/mms/open_api/ec/q_status', [
  191. 'ec_apply_id' => $application->contractApplyId,
  192. 'order_no' => $application->contractOrderNo,
  193. ]);
  194. $application->contractStatus = $response['data']['ec_status'] ?? '';
  195. $application->contractNo = $response['data']['ec_no'] ?? '';
  196. $application->updateTime = date('Y-m-d H:i:s');
  197. $application->save();
  198. return ['status' => $application->contractStatus, 'contractNo' => $application->contractNo];
  199. }
  200. public static function submit($id, $mainId, $shopId, array $form)
  201. {
  202. $application = self::application($id, $mainId, $shopId, true);
  203. if ($application->type === LakalaApplicationClass::TYPE_SETTLEMENT_CHANGE) {
  204. return self::submitSettlementChange($application, $form);
  205. }
  206. self::validateOnboarding($form);
  207. if ($application->contractStatus !== 'COMPLETED' || empty($application->contractNo)) {
  208. util::fail('请先完成电子合同签约并刷新签约状态');
  209. }
  210. $client = new LakalaOnboardingClient();
  211. $request = self::merchantRequest($form, $application->contractNo, $client);
  212. $response = $client->call('/api/v3/tkbs/merchant_encry', $request, 'both');
  213. $application->formData = self::encode($form);
  214. $application->merchantNo = $response['data']['merchant_no'] ?? '';
  215. $application->customerNo = $response['data']['merchant_no'] ?? '';
  216. $application->status = LakalaApplicationClass::STATUS_WAIT_AUDIT;
  217. $application->rejectReason = '';
  218. $application->versionNo = ((int)$application->versionNo) + 1;
  219. $application->updateTime = date('Y-m-d H:i:s');
  220. $application->save();
  221. self::version($application, $request, $response);
  222. self::operation($application, 0, 'onboarding', '', $request, $response, $application->status);
  223. return self::detail($application->id, $mainId, $shopId);
  224. }
  225. public static function submitForPlatformAudit($id, $mainId, $shopId, array $form)
  226. {
  227. $application = self::application($id, $mainId, $shopId, true);
  228. if ($application->type === LakalaApplicationClass::TYPE_SETTLEMENT_CHANGE) {
  229. return self::submitSettlementChange($application, $form);
  230. }
  231. self::validateOnboarding($form);
  232. if ($application->contractStatus !== 'COMPLETED' || empty($application->contractNo)) {
  233. util::fail('请先完成电子合同签约并刷新签约状态');
  234. }
  235. $application->formData = self::encode($form);
  236. $application->status = LakalaApplicationClass::STATUS_PLATFORM_AUDIT;
  237. $application->rejectReason = '';
  238. $application->currentStep = 5;
  239. $application->updateTime = date('Y-m-d H:i:s');
  240. $application->save();
  241. return self::detail($application->id, $mainId, $shopId);
  242. }
  243. public static function platformList($status = '', $keyword = '')
  244. {
  245. $where = [];
  246. if ($status !== '' && $status !== '-1') {
  247. $where['status'] = $status;
  248. }
  249. if ($keyword !== '') {
  250. if (is_numeric($keyword)) {
  251. $where['id'] = (int)$keyword;
  252. } else {
  253. $where['formData'] = ['like', $keyword];
  254. }
  255. }
  256. $result = LakalaApplicationClass::getList('*', $where, ['id' => SORT_DESC]);
  257. foreach ($result['list'] as &$item) {
  258. $form = self::formatFormImages(self::decode($item['formData'] ?? ''));
  259. $item['formData'] = $form;
  260. $item['merchantName'] = $form['merchant']['businessName'] ?? ($form['license']['licenseName'] ?? '');
  261. $item['legalName'] = $form['license']['legalName'] ?? '';
  262. $item['accountName'] = $form['settlement']['accountName'] ?? '';
  263. }
  264. return $result;
  265. }
  266. public static function platformDetail($id)
  267. {
  268. $application = LakalaApplicationClass::getById($id);
  269. if (empty($application)) {
  270. util::fail('没有找到进件申请');
  271. }
  272. $application['formData'] = self::formatFormImages(self::decode($application['formData'] ?? ''));
  273. return $application;
  274. }
  275. public static function platformSave($id, array $form, $step = 5)
  276. {
  277. $application = LakalaApplicationClass::getById($id, true);
  278. if (empty($application)) {
  279. util::fail('没有找到进件申请');
  280. }
  281. if (!in_array($application->status, [
  282. LakalaApplicationClass::STATUS_DRAFT,
  283. LakalaApplicationClass::STATUS_PLATFORM_AUDIT,
  284. LakalaApplicationClass::STATUS_REJECT,
  285. LakalaApplicationClass::STATUS_CONTRACT_PENDING,
  286. ], true)) {
  287. util::fail('当前申请不能修改');
  288. }
  289. $application->formData = self::encode($form);
  290. $application->currentStep = max(1, min(5, (int)$step));
  291. $application->updateTime = date('Y-m-d H:i:s');
  292. $application->save();
  293. return self::platformDetail($id);
  294. }
  295. public static function platformUpload($id, $base64, $imgType, $ocr = 0)
  296. {
  297. $application = LakalaApplicationClass::getById($id);
  298. if (empty($application)) {
  299. util::fail('没有找到进件申请');
  300. }
  301. return self::upload($id, $application['mainId'], $application['shopId'], $base64, $imgType, (int)$ocr);
  302. }
  303. public static function platformReject($id, $reason)
  304. {
  305. $application = LakalaApplicationClass::getById($id, true);
  306. if (empty($application)) {
  307. util::fail('没有找到进件申请');
  308. }
  309. if ($application->status !== LakalaApplicationClass::STATUS_PLATFORM_AUDIT) {
  310. util::fail('仅后台审核中的申请可以驳回');
  311. }
  312. $application->status = LakalaApplicationClass::STATUS_REJECT;
  313. $application->rejectReason = mb_substr($reason ?: '后台审核驳回', 0, 500);
  314. $application->updateTime = date('Y-m-d H:i:s');
  315. $application->save();
  316. }
  317. public static function platformApprove($id)
  318. {
  319. $application = LakalaApplicationClass::getById($id, true);
  320. if (empty($application)) {
  321. util::fail('没有找到进件申请');
  322. }
  323. if ($application->status !== LakalaApplicationClass::STATUS_PLATFORM_AUDIT) {
  324. util::fail('仅后台审核中的申请可以提交拉卡拉');
  325. }
  326. return self::submit(
  327. $application->id,
  328. $application->mainId,
  329. $application->shopId,
  330. self::decode($application->formData)
  331. );
  332. }
  333. public static function refresh($id, $mainId, $shopId)
  334. {
  335. $application = self::application($id, $mainId, $shopId, true);
  336. if ($application->type === LakalaApplicationClass::TYPE_SETTLEMENT_CHANGE) {
  337. return self::refreshChange($application);
  338. }
  339. if (empty($application->customerNo) && empty($application->merchantNo)) {
  340. util::fail('申请尚未提交(流程未走完)');
  341. }
  342. $client = new LakalaOnboardingClient();
  343. $response = $client->query('/api/v3/tkbs/open_merchant_info', [
  344. 'customer_no' => $application->customerNo,
  345. ], 'both');
  346. $customer = $response['data']['customer'];
  347. $status = $customer['customer_status'];
  348. if (in_array($status, ['REJECT', 'REVIEW_FAIL'], true)) {
  349. $application->status = LakalaApplicationClass::STATUS_REJECT;
  350. $application->rejectReason = $customer['audit_remark'] ?? '拉卡拉审核驳回';
  351. } elseif (in_array($status, ['OPEN', 'ACTIVITY'], true)) {
  352. $application->merchantNo = $customer['merchant_no'] ?? $application->merchantNo;
  353. $application->customerNo = $customer['customer_no'] ?? $application->customerNo;
  354. $shopInfo = $response['data']['shop_info_list'][0] ?? [];
  355. $application->shopNo = $shopInfo['shop_id'] ?? $application->shopNo;
  356. $account = self::upsertAccount($application, $response['data']);
  357. self::ensureTermOperations($application, $account, $client);
  358. self::syncTerms($application, $account, $client);
  359. if ($account->scanTermNo != '' && $account->b2bTermNo != '') {
  360. $application->status = LakalaApplicationClass::STATUS_APPROVED;
  361. }
  362. } else {
  363. $application->status = LakalaApplicationClass::STATUS_WAIT_AUDIT;
  364. }
  365. $application->updateTime = date('Y-m-d H:i:s');
  366. $application->save();
  367. return self::detail($application->id, $mainId, $shopId);
  368. }
  369. public static function setCurrent($accountId, $mainId, $shopId)
  370. {
  371. $account = LakalaAccountClass::getByCondition(['id' => $accountId, 'mainId' => $mainId, 'shopId' => $shopId], true);
  372. if (empty($account)) {
  373. util::fail('没有找到收款账户');
  374. }
  375. if (empty($account->scanTermNo) || empty($account->b2bTermNo)) {
  376. util::fail('账户业务尚未全部开通');
  377. }
  378. $transaction = Yii::$app->db->beginTransaction();
  379. try {
  380. LakalaAccountClass::updateByCondition(['shopId' => $shopId], ['isCurrent' => 0, 'updateTime' => date('Y-m-d H:i:s')]);
  381. $account->isCurrent = 1;
  382. $account->updateTime = date('Y-m-d H:i:s');
  383. $account->save();
  384. ShopClass::updateById($shopId, [
  385. 'lklSjNo' => $account->merchantNo,
  386. 'lklScanTermNo' => $account->scanTermNo,
  387. 'lklB2BTermNo' => $account->b2bTermNo,
  388. ]);
  389. $transaction->commit();
  390. } catch (\Throwable $e) {
  391. $transaction->rollBack();
  392. throw $e;
  393. }
  394. }
  395. private static function submitSettlementChange($application, array $form)
  396. {
  397. self::validateSettlement($form['settlement'] ?? []);
  398. $account = LakalaAccountClass::getByCondition(['id' => $application->accountId, 'mainId' => $application->mainId], true);
  399. if (empty($account)) {
  400. util::fail('没有找到收款账户');
  401. }
  402. $client = new LakalaOnboardingClient();
  403. $request = [
  404. 'org_code' => $client->orgCode(),
  405. 'customer_no' => $account->customerNo,
  406. 'customer_settle_update_dto' => self::settlementDto($form['settlement']),
  407. ];
  408. $response = $client->call('/api/v3/tkbs/open_merchant_update_settle_encry', $request, 'both');
  409. $application->formData = self::encode($form);
  410. $application->reviewRelatedId = $response['data']['review_related_id'] ?? '';
  411. $application->status = LakalaApplicationClass::STATUS_WAIT_AUDIT;
  412. $application->versionNo = ((int)$application->versionNo) + 1;
  413. $application->updateTime = date('Y-m-d H:i:s');
  414. $application->save();
  415. self::version($application, $request, $response);
  416. self::operation($application, $account->id, 'settlement_change', '', $request, $response, 'PASSING');
  417. return self::detail($application->id, $application->mainId, $application->shopId);
  418. }
  419. private static function refreshChange($application)
  420. {
  421. if (empty($application->reviewRelatedId)) {
  422. util::fail('变更申请尚未提交');
  423. }
  424. $client = new LakalaOnboardingClient();
  425. $response = $client->query('/api/v3/tkbs/customer_update_review', ['review_related_id' => $application->reviewRelatedId]);
  426. $review = $response['data']['review_pass'] ?? '';
  427. if ($review === 'PASS') {
  428. $application->status = LakalaApplicationClass::STATUS_APPROVED;
  429. $account = LakalaAccountClass::getById($application->accountId, true);
  430. if (!empty($account)) {
  431. $settlement = self::decode($application->formData)['settlement'] ?? [];
  432. $account->accountName = $settlement['accountName'] ?? $account->accountName;
  433. $account->accountNo = $settlement['accountNo'] ?? $account->accountNo;
  434. $account->bankName = $settlement['bankName'] ?? $account->bankName;
  435. $account->accountType = $settlement['accountType'] ?? $account->accountType;
  436. $account->updateTime = date('Y-m-d H:i:s');
  437. $account->save();
  438. }
  439. } elseif ($review === 'UNPASS') {
  440. $application->status = LakalaApplicationClass::STATUS_REJECT;
  441. $application->rejectReason = $response['data']['review_result'] ?? '变更审核驳回';
  442. } else {
  443. $application->status = LakalaApplicationClass::STATUS_WAIT_AUDIT;
  444. }
  445. $application->updateTime = date('Y-m-d H:i:s');
  446. $application->save();
  447. return self::detail($application->id, $application->mainId, $application->shopId);
  448. }
  449. private static function ensureTermOperations($application, $account, $client)
  450. {
  451. foreach (['B2B_SYT'] as $bizType) { // 'WECHAT_PAY' -- 专业化扫码已经有了,不需要额外创建
  452. $operation = LakalaOperationClass::getByCondition([
  453. 'applicationId' => $application->id,
  454. 'operationType' => 'add_term',
  455. 'bizType' => $bizType,
  456. 'status!=' => 'FAILED',
  457. ], true);
  458. if (!empty($operation)) {
  459. continue;
  460. }
  461. $request = [
  462. 'merchant_no' => $account->merchantNo,
  463. 'bz_pos' => $bizType,
  464. 'term_num' => 1,
  465. 'shop_id' => $account->shopNo,
  466. 'org_code' => $client->orgCode(),
  467. 'fees' => self::termFees(),
  468. 'attachments' => [],
  469. ];
  470. try {
  471. $response = $client->call('/api/v3/tkbs/open_merchant_addTerm', $request, 'both');
  472. self::operation($application, $account->id, 'add_term', $bizType, $request, $response, 'PASSING');
  473. } catch (\Throwable $e) {
  474. self::operation($application, $account->id, 'add_term', $bizType, $request, [], 'FAILED', $e->getMessage());
  475. }
  476. }
  477. }
  478. private static function syncTerms($application, $account, $client)
  479. {
  480. $operations = LakalaOperationClass::getAllByCondition(
  481. ['applicationId' => $application->id, 'operationType' => 'add_term'],
  482. null,
  483. '*',
  484. null,
  485. true
  486. );
  487. foreach ($operations as $operation) {
  488. if (!empty($operation->reviewRelatedId) && !in_array($operation->status, ['PASS', 'FAILED'], true)) {
  489. $review = $client->query('/api/v3/tkbs/customer_update_review', ['review_related_id' => $operation->reviewRelatedId]);
  490. $operation->status = $review['data']['review_pass'] ?? $operation->status;
  491. $operation->responseData = self::encode($review);
  492. $operation->errorMessage = $review['data']['review_result'] ?? '';
  493. $operation->updateTime = date('Y-m-d H:i:s');
  494. $operation->save();
  495. }
  496. }
  497. try {
  498. $res = $client->query('/api/v3/tkbs/open_merchant_info', [
  499. 'customer_no' => $account->customerNo ?: $application->customerNo,
  500. 'merchant_no' => $account->merchantNo ?: $application->merchantNo,
  501. ], 'both');
  502. $merchantInfo = $res['data'];
  503. $customer = $merchantInfo['customer'];
  504. $terms = self::merchantInfoTermMap($merchantInfo);
  505. $merchantNo = $customer['merchant_no'] ?? '';
  506. if ($merchantNo !== '') {
  507. $application->merchantNo = $merchantNo;
  508. $account->merchantNo = $merchantNo;
  509. }
  510. $account->scanTermNo = $terms['scanTermNo'][0] ?? $account->scanTermNo;
  511. $account->b2bTermNo = $terms['b2BTermNo'][0] ?? $account->b2bTermNo;
  512. $account->updateTime = date('Y-m-d H:i:s');
  513. $account->save();
  514. $application->save();
  515. LakalaAccountClass::dispatchAuthorizeStatesRefresh([$account->id]);
  516. } catch (\Throwable $e) {
  517. Yii::warning('Unable to sync Lakala merchant info terms: ' . $e->getMessage());
  518. }
  519. }
  520. private static function upsertAccount($application, array $remote)
  521. {
  522. $account = LakalaAccountClass::getByCondition(['mainId' => $application->mainId, 'merchantNo' => $application->merchantNo], true);
  523. $form = self::decode($application->formData);
  524. $settlement = $form['settlement'] ?? [];
  525. $license = $form['license'] ?? [];
  526. $shopInfo = $remote['shop_info_list'][0] ?? [];
  527. $data = [
  528. 'mainId' => $application->mainId,
  529. 'shopId' => $application->shopId,
  530. 'applicationId' => $application->id,
  531. 'merchantNo' => $application->merchantNo,
  532. 'customerNo' => $application->customerNo,
  533. 'shopNo' => $shopInfo['shop_id'] ?? $application->shopNo,
  534. 'accountName' => $settlement['accountName'] ?? '',
  535. 'accountNo' => $settlement['accountNo'] ?? '',
  536. 'bankName' => $settlement['bankName'] ?? '',
  537. 'accountType' => $settlement['accountType'] ?? '58',
  538. 'licenseName' => $license['licenseName'] ?? ($license['legalName'] ?? ''),
  539. 'identityExpire' => $license['legalIdEnd'] ?? '',
  540. 'wxAuthorizeState' => $account->wxAuthorizeState ?? 0,
  541. 'zfbAuthorizeState' => $account->zfbAuthorizeState ?? 0,
  542. 'status' => 'ACTIVE',
  543. 'updateTime' => date('Y-m-d H:i:s'),
  544. ];
  545. if (empty($account)) {
  546. $data['createTime'] = date('Y-m-d H:i:s');
  547. return LakalaAccountClass::add($data, true);
  548. }
  549. foreach ($data as $key => $value) {
  550. $account->$key = $value;
  551. }
  552. $account->save();
  553. return $account;
  554. }
  555. private static function defaultForm($shop)
  556. {
  557. $merchantName = $shop->merchantName ?? ($shop->shopName ?? '');
  558. $mobile = $shop->mobile ?? ($shop->telephone ?? '');
  559. return [
  560. 'mode' => 'create',
  561. 'license' => [
  562. 'merchantType' => 'TP_MERCHANT', 'licenseName' => '', 'licenseNo' => '',
  563. 'licenseStart' => '', 'licenseEnd' => '9999-12-31', 'registeredAddress' => '',
  564. 'businessContent' => '', 'legalName' => '', 'legalIdNo' => '',
  565. 'legalIdStart' => '', 'legalIdEnd' => '9999-12-31', 'attachments' => [],
  566. ],
  567. 'merchant' => [
  568. 'businessName' => ($shop->city ?? '') . $merchantName,
  569. 'provinceCode' => '', 'cityCode' => '', 'countyCode' => '',
  570. 'provinceName' => $shop->province ?? '', 'cityName' => $shop->city ?? '',
  571. 'countyName' => $shop->dist ?? '', 'address' => $shop->address ?? ($shop->fullAddress ?? ''),
  572. 'contactName' => '石少华', 'contactMobile' => '15280215347', 'email' => '479439056@qq.com',
  573. ],
  574. 'settlement' => [
  575. 'accountType' => '58', 'isLegalPerson' => true, 'accountName' => '',
  576. 'accountNo' => '', 'bankName' => '', 'bankNo' => '', 'clearingBankNo' => '',
  577. 'provinceCode' => '', 'provinceName' => '', 'cityCode' => '', 'cityName' => '',
  578. 'accountIdNo' => '', 'accountIdStart' => '', 'accountIdEnd' => '', 'attachments' => [],
  579. ],
  580. 'shop' => [
  581. 'name' => $merchantName, 'provinceCode' => '', 'cityCode' => '', 'countyCode' => '',
  582. 'provinceName' => $shop->province ?? '', 'cityName' => $shop->city ?? '',
  583. 'countyName' => $shop->dist ?? '', 'address' => $shop->address ?? ($shop->fullAddress ?? ''),
  584. 'contactName' => '', 'contactMobile' => $mobile, 'longitude' => $shop->long ?? ($shop->shopLong ?? ''),
  585. 'latitude' => $shop->lat ?? ($shop->shopLat ?? ''), 'attachments' => [],
  586. ],
  587. 'business' => ['signMobile' => $mobile ?: '15280215347', 'rate' => self::RATE, 'settleType' => 'D1', 'settlementType' => 'AUTOMATIC'],
  588. ];
  589. }
  590. /**
  591. * 商户请求数据构建
  592. * 请参照拉卡拉"新增商户进件"接口文档进行核实参数
  593. * @param array $form
  594. * @param string $contractNo
  595. * @param LakalaOnboardingClient $client
  596. * @return array
  597. */
  598. private static function merchantRequest(array $form, $contractNo, $client)
  599. {
  600. $l = $form['license']; //营业执照信息
  601. $m = $form['merchant']; //商家信息
  602. $s = $form['settlement']; //收款银行卡信息
  603. $shop = $form['shop']; //门店信息(带有门店图片信息)
  604. $fees = self::merchantFees();
  605. return [
  606. 'org_code' => $client->orgCode(),
  607. 'user_no' => $client->userNo(),
  608. 'email' => $m['email'],
  609. 'busi_code' => 'WECHAT_PAY',
  610. 'mer_reg_name' => $m['businessName'],
  611. 'mer_type' => $l['merchantType'],
  612. 'mer_name' => $m['businessName'],
  613. 'mer_addr' => $m['address'],
  614. 'province_code' => $m['provinceCode'],
  615. 'city_code' => $m['cityCode'],
  616. 'county_code' => $m['countyCode'],
  617. 'license_name' => $l['licenseName'],
  618. 'license_no' => $l['licenseNo'],
  619. 'license_dt_start' => $l['licenseStart'],
  620. 'license_dt_end' => $l['licenseEnd'],
  621. 'latitude' => $shop['longitude'],
  622. 'longtude' => $shop['latitude'],
  623. 'source' => 'APP',
  624. 'business_content' => $l['businessContent'],
  625. 'is_legal_person' => (bool)$s['isLegalPerson'],
  626. 'lar_name' => $l['legalName'], 'lar_id_type' => '01', 'lar_id_card' => $l['legalIdNo'],
  627. 'lar_id_card_start' => $l['legalIdStart'], 'lar_id_card_end' => $l['legalIdEnd'],
  628. 'contact_mobile' => $shop['contactMobile'] != '' ? $shop['contactMobile'] : $m['contactMobile'],
  629. 'contact_name' => $l['legalName'] != '' ? $l['legalName'] : ($shop['contactName'] ?? $m['contactName']),
  630. 'openning_bank_code' => $s['bankNo'],
  631. 'openning_bank_name' => $s['bankName'],
  632. 'clearing_bank_code' => $s['clearingBankNo'], 'settle_province_code' => $s['provinceCode'],
  633. 'settle_province_name' => $s['provinceName'], 'settle_city_code' => $s['cityCode'],
  634. 'settle_city_name' => $s['cityName'], 'account_no' => $s['accountNo'], 'account_name' => $s['accountName'],
  635. 'account_type' => $s['accountType'], 'account_id_card' => $s['accountIdNo'] ?: $l['legalIdNo'],
  636. 'account_id_dt_start' => $s['accountIdStart'] ?: $l['legalIdStart'],
  637. 'account_id_dt_end' => $s['accountIdEnd'] ?: $l['legalIdEnd'],
  638. 'settle_type' => 'D1', 'settlement_type' => 'AUTOMATIC', 'contract_no' => $contractNo,
  639. 'biz_content' => ['term_num' => '1', 'mcc' => self::MCC, 'activity_id' => self::activityId(), 'fees' => $fees], //业务拓展信息 -- 会创建终端(此处创建:专业化扫码)
  640. 'attchments' => self::attachments($form),
  641. ];
  642. }
  643. private static function contractRequest(array $form, $orderNo, $orgCode)
  644. {
  645. $l = $form['license'];
  646. $m = $form['merchant'];
  647. $s = $form['settlement'];
  648. $shop = $form['shop'];
  649. $b = $form['business'];
  650. $content = [];
  651. foreach (['A' => 130, 'B' => 58, 'C' => 56, 'D' => 17, 'E' => 79] as $prefix => $max) {
  652. for ($i = 1; $i <= $max; $i++) {
  653. $content[$prefix . $i] = '/';
  654. }
  655. }
  656. $content = array_merge($content, [
  657. 'A1' => $l['licenseName'] ?: $l['legalName'], 'A2' => '是', 'A6' => '是',
  658. 'A7' => '0.50', 'A8' => '20', 'A9' => '是', 'A10' => '0.60',
  659. 'A11' => '是', 'A12' => self::RATE, 'A13' => '是', 'A14' => self::RATE,
  660. 'A15' => '0.60', 'A34' => self::RATE, 'A35' => self::RATE,
  661. 'A63' => '是', 'A64' => self::RATE, 'A65' => '是', 'A66' => self::RATE,
  662. 'A116' => '自动结算',
  663. 'A117' => '是',
  664. 'A121' => ($shop['provinceName'] ?? '') . ($shop['cityName'] ?? ''),
  665. 'A124' => date('Y'), 'A125' => date('m'), 'A126' => date('d'), // 甲方签约 年/月/日
  666. 'A127' => date('Y'), 'A128' => date('m'), 'A129' => date('d'), // 乙方签约 年/月/日
  667. 'A130' => $l['legalName'] ?: $m['contactName'], // 甲方电子签名/签字/盖章
  668. // ===== B 系列:商户注册登记 / 基本信息 / 结算账户 / 法人联系人 / 终端 / 拓展 =====
  669. 'B1' => date('Y'), 'B2' => date('m'), // 商户注册登记表 年/月
  670. 'B3' => '是',
  671. 'B8' => $l['licenseName'], // 基本信息:工商注册名称(中英文)
  672. 'B10' => $m['businessName'], // 基本信息:对外经营名称(中英文)
  673. 'B13' => ($m['provinceName'] ?? '') . ($m['cityName'] ?? ''), 'B14' => $l['licenseNo'] ?: '/',
  674. 'B18' => $l['legalName'],
  675. 'B19' => $s['bankName'],
  676. 'B20' => $s['accountNo'],
  677. 'B24' => $s['accountName'],
  678. 'B25' => $l['legalIdNo'], 'B26' => $b['signMobile'],
  679. 'B27' => $l['legalName'],
  680. 'B28' => $m['email'],
  681. 'B29' => $l['legalIdNo'],// 联系人证件号码
  682. 'B30' => $b['signMobile'],// 联系人手机号
  683. 'B31' => $l['licenseName'] ?: $l['legalName'],
  684. 'B32' => $l['legalName'],// 网点联系人姓名
  685. 'B33' => $m['address'],
  686. 'B34' => $m['contactMobile'],
  687. // ===== D 系列:各类授权书 =====
  688. 'D1' => $s['bankName'],// 授权书(银行):结算账户所属银行
  689. 'D2' => $l['legalName'] ?: $m['contactName'],// 授权书(银行):授权承诺方签字
  690. 'D3' => date('Y/m/d'),// 授权书(银行):签章/签字日期
  691. 'D4' => $l['legalName'] ?: $m['contactName'],// 授权书(开通微信、支付宝收单):承诺授权方签名
  692. 'D5' => date('Y/m/d'),// 授权书(开通微信、支付宝收单):盖章/签字日期
  693. // ===== E 系列:分银行费率明细(费率一致则不填,统一占位 "/")=====
  694. 'E78' => $l['legalName'] ?: $m['contactName'],// 电子签约服务授权委托书:承诺授权方签名
  695. 'E79' => $l['legalName'] ?: $m['contactName'],// 电子签约服务授权委托书:承诺签名或盖章
  696. ]);
  697. return [
  698. 'order_no' => $orderNo, 'org_id' => (int)$orgCode, 'ec_type_code' => 'EC015',
  699. 'cert_type' => 'RESIDENT_ID', 'cert_name' => $l['legalName'], 'cert_no' => $l['legalIdNo'],
  700. 'mobile' => $b['signMobile'], 'business_license_no' => $l['licenseNo'],
  701. 'business_license_name' => $l['licenseName'], 'openning_bank_code' => $s['bankNo'],
  702. 'openning_bank_name' => $s['bankName'], 'acct_type_code' => $s['accountType'],
  703. 'acct_no' => $s['accountNo'], 'acct_name' => $s['accountName'],
  704. 'ec_content_parameters' => self::encode($content), 'remark' => '销花宝商户入网',
  705. ];
  706. }
  707. private static function settlementDto(array $s)
  708. {
  709. return [
  710. 'account_kind' => $s['accountType'], 'is_legal_person' => (bool)$s['isLegalPerson'],
  711. 'account_name' => $s['accountName'], 'identity_no' => $s['accountIdNo'],
  712. 'account_no' => $s['accountNo'], 'bank_no' => $s['bankNo'], 'bank_name' => $s['bankName'],
  713. 'clearing_bank_no' => $s['clearingBankNo'], 'settle_province_code' => $s['provinceCode'],
  714. 'settle_province_name' => $s['provinceName'], 'settle_city_code' => $s['cityCode'],
  715. 'settle_city_name' => $s['cityName'], 'attachments' => self::attachmentDtos($s['attachments'] ?? []),
  716. ];
  717. }
  718. private static function merchantFees()
  719. {
  720. return [
  721. ['fee_code' => 'WECHAT', 'fee_value' => self::RATE], //微信
  722. ['fee_code' => 'ALIPAY', 'fee_value' => self::RATE], //支付宝
  723. //['fee_code' => 'SCAN_PAY', 'fee_value' => self::RATE], //未知项
  724. ['fee_code' => 'DEBIT_CARD', 'fee_value' => '0.50', 'top_fee' => '20'], //借记卡费率
  725. ['fee_code' => 'CREDIT_CARD', 'fee_value' => '0.60'], //贷记卡费率
  726. ['fee_code' => 'UNIONPAY_WALLET_DEBIT_FEE', 'fee_value' => self::RATE, 'top_fee' => '20'], //银联二维码借记卡
  727. ['fee_code' => 'UNIONPAY_WALLET_CREDIT_FEE', 'fee_value' => '0.60'], //银联二维码贷记卡
  728. ];
  729. }
  730. private static function activityId()
  731. {
  732. return (string)(Yii::$app->params['lakalaOnboarding']['activity_id'] ?? getenv('LAKALA_ONBOARDING_ACTIVITY_ID') ?: self::ACTIVITY_ID);
  733. }
  734. private static function termFees()
  735. {
  736. return [
  737. ['fee' => 0.33, 'fee_type' => 'WECHAT'],
  738. ['fee' => 0.33, 'fee_type' => 'ALIPAY'],
  739. ['fee' => 0.5, 'top_fee' => 20, 'fee_type' => 'DEBIT_CARD'],
  740. ['fee' => 0.6, 'fee_type' => 'CREDIT_CARD'],
  741. ];
  742. }
  743. private static function attachments(array $form)
  744. {
  745. $all = [];
  746. foreach (['license', 'settlement', 'shop'] as $section) {
  747. $all = array_merge($all, self::attachmentDtos($form[$section]['attachments'] ?? [], true));
  748. }
  749. return $all;
  750. }
  751. private static function attachmentDtos(array $attachments, $merchantShape = false)
  752. {
  753. $result = [];
  754. foreach ($attachments as $attachment) {
  755. $lakalaUrl = self::attachmentLakalaUrl($attachment);
  756. if ($lakalaUrl === '' || empty($attachment['imgType'])) {
  757. continue;
  758. }
  759. $result[] = $merchantShape
  760. ? ['id' => $lakalaUrl, 'type' => $attachment['imgType']]
  761. : ['img_path' => $lakalaUrl, 'img_type' => $attachment['imgType']];
  762. }
  763. return $result;
  764. }
  765. private static function attachmentLakalaUrl(array $attachment)
  766. {
  767. return trim((string)($attachment['lakalaUrl'] ?? ($attachment['url'] ?? '')));
  768. }
  769. private static function validateContract(array $form)
  770. {
  771. self::validateOnboarding($form, false);
  772. if (empty($form['business']['signMobile'])) {
  773. util::fail('请填写签约手机号');
  774. }
  775. }
  776. private static function validateOnboarding(array $form, $includeShop = true)
  777. {
  778. $l = $form['license'] ?? []; $m = $form['merchant'] ?? []; $s = $form['settlement'] ?? []; $shop = $form['shop'] ?? [];
  779. $items = ['merchantType', 'legalName', 'legalIdNo', 'legalIdStart', 'legalIdEnd', 'businessContent'];
  780. if ($l['merchantType'] === 'TP_PERSONAL') {
  781. unset($items[5]);
  782. }
  783. foreach ($items as $key) {
  784. if (empty($l[$key])) util::fail('执照信息未填写完整');
  785. }
  786. self::requireAttachments($l['attachments'] ?? [], ['ID_CARD_FRONT', 'ID_CARD_BEHIND'], '请上传法人身份证正反面');
  787. if (($l['merchantType'] ?? '') === 'TP_MERCHANT' && (empty($l['licenseName']) || empty($l['licenseNo']))) {
  788. util::fail('企业商户请填写营业执照信息');
  789. }
  790. if (($l['merchantType'] ?? '') === 'TP_MERCHANT') {
  791. self::requireAttachments($l['attachments'] ?? [], ['BUSINESS_LICENCE'], '请上传营业执照');
  792. }
  793. foreach (['businessName', 'provinceCode', 'cityCode', 'countyCode', 'address'] as $key) {
  794. if (empty($m[$key])) util::fail('商户信息未填写完整');
  795. }
  796. self::validateSettlement($s);
  797. if ($includeShop) {
  798. foreach (['name', 'address', 'contactName', 'contactMobile', 'longitude', 'latitude'] as $key) {
  799. if (empty($shop[$key])) util::fail('门店信息未填写完整');
  800. }
  801. self::requireAttachments($shop['attachments'] ?? [], ['SHOP_OUTSIDE_IMG', 'SHOP_INSIDE_IMG', 'CHECKSTAND_IMG'], '请上传完整门店照片');
  802. }
  803. }
  804. private static function validateSettlement(array $s)
  805. {
  806. foreach (['accountType', 'accountName', 'accountNo', 'bankName', 'bankNo', 'clearingBankNo', 'provinceCode', 'cityCode'] as $key) {
  807. if (empty($s[$key])) util::fail('结算信息未填写完整');
  808. }
  809. if (($s['accountType'] ?? '58') === '57') {
  810. self::requireAttachments($s['attachments'] ?? [], ['OPENING_PERMIT'], '请上传开户许可证');
  811. } else {
  812. self::requireAttachments($s['attachments'] ?? [], ['BANK_CARD'], '请上传银行卡照片');
  813. if (empty($s['isLegalPerson'])) {
  814. self::requireAttachments($s['attachments'] ?? [], ['ID_CARD_FRONT', 'ID_CARD_BEHIND', 'LETTER_OF_AUTHORIZATION'], '请上传结算人证件与法人授权函');
  815. }
  816. }
  817. }
  818. private static function requireAttachments(array $attachments, array $types, $message)
  819. {
  820. $present = array_column($attachments, 'imgType');
  821. foreach ($types as $type) {
  822. if (!in_array($type, $present, true)) {
  823. util::fail($message);
  824. }
  825. $matched = array_values(array_filter($attachments, function ($attachment) use ($type) {
  826. return ($attachment['imgType'] ?? '') === $type && self::attachmentLakalaUrl($attachment) !== '';
  827. }));
  828. if (empty($matched)) {
  829. util::fail($message);
  830. }
  831. }
  832. }
  833. private static function version($application, array $request, array $response)
  834. {
  835. LakalaApplicationVersionClass::add([
  836. 'applicationId' => $application->id, 'mainId' => $application->mainId,
  837. 'versionNo' => $application->versionNo, 'type' => $application->type,
  838. 'formData' => $application->formData, 'requestData' => self::encode($request),
  839. 'responseData' => self::encode($response), 'createTime' => date('Y-m-d H:i:s'),
  840. ]);
  841. }
  842. private static function operation($application, $accountId, $type, $bizType, array $request, array $response, $status, $error = '')
  843. {
  844. return LakalaOperationClass::add([
  845. 'applicationId' => $application->id,
  846. 'accountId' => $accountId,
  847. 'mainId' => $application->mainId,
  848. 'operationType' => $type,
  849. 'bizType' => $bizType,
  850. 'status' => $status,
  851. 'reviewRelatedId' => $response['data']['review_related_id'] ?? '',
  852. 'requestData' => self::encode($request),
  853. 'responseData' => self::encode($response),
  854. 'errorMessage' => mb_substr($error, 0, 500),
  855. 'createTime' => date('Y-m-d H:i:s'),
  856. 'updateTime' => date('Y-m-d H:i:s'),
  857. ], true);
  858. }
  859. private static function application($id, $mainId, $shopId, $object = false)
  860. {
  861. $application = LakalaApplicationClass::getByCondition(['id' => $id, 'mainId' => $mainId, 'shopId' => $shopId], $object);
  862. if (empty($application)) {
  863. util::fail('没有找到进件申请');
  864. }
  865. return $application;
  866. }
  867. private static function merchantInfoTermMap($value)
  868. {
  869. $result = [];
  870. $terminalInfos = $value['terminal_info'] ?? [];
  871. foreach ($terminalInfos as $term) {
  872. if (($term['term_type_name'] ?? '') == '专业化扫码') {
  873. $result['scanTermNo'][] = $term['active_no_vo_list'][0]['term_no'] ?? '';
  874. }
  875. if (($term['term_type_name'] ?? '') == '聚合收银台') {
  876. $list = $term['active_no_vo_list'] ?? [];
  877. foreach ($list as $item) {
  878. if (($item['busi_type_code'] ?? '') == 'QR_CODE_CARD') {
  879. $result['b2BTermNo'][] = $item['term_no'] ?? '';
  880. }
  881. }
  882. }
  883. }
  884. return $result;
  885. }
  886. private static function imageExtension($mime)
  887. {
  888. $map = [
  889. 'image/jpeg' => 'jpg',
  890. 'image/png' => 'png',
  891. 'image/gif' => 'gif',
  892. 'image/webp' => 'webp',
  893. ];
  894. return $map[$mime] ?? 'png';
  895. }
  896. private static function encode($value)
  897. {
  898. return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
  899. }
  900. private static function decode($value)
  901. {
  902. if (is_array($value)) return $value;
  903. $decoded = json_decode((string)$value, true);
  904. return is_array($decoded) ? $decoded : [];
  905. }
  906. private static function formatFormImages(array $form)
  907. {
  908. return self::formatOssPathFields($form);
  909. }
  910. private static function formatOssPathFields(array $data)
  911. {
  912. foreach ($data as $key => $value) {
  913. if (is_array($value)) {
  914. $data[$key] = self::formatOssPathFields($value);
  915. continue;
  916. }
  917. if ($key === 'ossPath' && is_string($value) && $value !== '') {
  918. $data[$key] = self::formatOssPath($value);
  919. }
  920. }
  921. return $data;
  922. }
  923. private static function formatOssPath($path)
  924. {
  925. if (strpos($path, 'http://') === 0 || strpos($path, 'https://') === 0) {
  926. return $path;
  927. }
  928. return imgUtil::groupImg($path);
  929. }
  930. }