|
|
@@ -0,0 +1,768 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace bizGhs\lakala\services;
|
|
|
+
|
|
|
+use bizGhs\lakala\classes\LakalaAccountClass;
|
|
|
+use bizGhs\lakala\classes\LakalaApplicationClass;
|
|
|
+use bizGhs\lakala\classes\LakalaApplicationVersionClass;
|
|
|
+use bizGhs\lakala\classes\LakalaOperationClass;
|
|
|
+use bizGhs\shop\classes\ShopClass;
|
|
|
+use common\components\lakala\LakalaOnboardingClient;
|
|
|
+use common\components\oss;
|
|
|
+use common\components\stringUtil;
|
|
|
+use common\components\util;
|
|
|
+use Yii;
|
|
|
+
|
|
|
+class LakalaAccountService
|
|
|
+{
|
|
|
+ const RATE = '0.33';
|
|
|
+ const MCC = '5992';
|
|
|
+ const ACTIVITY_ID = '37';
|
|
|
+
|
|
|
+ public static function list($mainId, $shopId)
|
|
|
+ {
|
|
|
+ $applications = LakalaApplicationClass::getAllByCondition(
|
|
|
+ ['mainId' => $mainId, 'shopId' => $shopId],
|
|
|
+ ['id' => SORT_DESC],
|
|
|
+ '*'
|
|
|
+ );
|
|
|
+ $accounts = LakalaAccountClass::getAllByCondition(
|
|
|
+ ['mainId' => $mainId, 'shopId' => $shopId],
|
|
|
+ ['isCurrent' => SORT_DESC, 'id' => SORT_DESC],
|
|
|
+ '*'
|
|
|
+ );
|
|
|
+ foreach ($applications as &$application) {
|
|
|
+ $application['formData'] = self::decode($application['formData'] ?? '');
|
|
|
+ }
|
|
|
+ return ['applications' => $applications, 'accounts' => $accounts];
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function detail($id, $mainId, $shopId)
|
|
|
+ {
|
|
|
+ $application = self::application($id, $mainId, $shopId);
|
|
|
+ $application['formData'] = self::decode($application['formData'] ?? '');
|
|
|
+ return $application;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function create($mainId, $shopId, $shop, $type = LakalaApplicationClass::TYPE_ONBOARDING, $accountId = 0)
|
|
|
+ {
|
|
|
+ $now = date('Y-m-d H:i:s');
|
|
|
+ $form = self::defaultForm($shop);
|
|
|
+ if ($type === LakalaApplicationClass::TYPE_SETTLEMENT_CHANGE && $accountId > 0) {
|
|
|
+ $account = LakalaAccountClass::getByCondition(['id' => $accountId, 'mainId' => $mainId, 'shopId' => $shopId]);
|
|
|
+ if (empty($account)) {
|
|
|
+ util::fail('没有找到收款账户');
|
|
|
+ }
|
|
|
+ $form['mode'] = 'settlement-change';
|
|
|
+ $form['settlement']['accountName'] = $account['accountName'] ?? '';
|
|
|
+ $form['settlement']['accountNo'] = $account['accountNo'] ?? '';
|
|
|
+ $form['settlement']['bankName'] = $account['bankName'] ?? '';
|
|
|
+ }
|
|
|
+ $application = LakalaApplicationClass::add([
|
|
|
+ 'mainId' => $mainId,
|
|
|
+ 'shopId' => $shopId,
|
|
|
+ 'accountId' => $accountId,
|
|
|
+ 'type' => $type,
|
|
|
+ 'status' => LakalaApplicationClass::STATUS_DRAFT,
|
|
|
+ 'currentStep' => $type === LakalaApplicationClass::TYPE_SETTLEMENT_CHANGE ? 3 : 1,
|
|
|
+ 'formData' => self::encode($form),
|
|
|
+ 'createTime' => $now,
|
|
|
+ 'updateTime' => $now,
|
|
|
+ ], true);
|
|
|
+ return self::detail($application->id, $mainId, $shopId);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function saveDraft($id, $mainId, $shopId, array $form, $step)
|
|
|
+ {
|
|
|
+ $application = self::application($id, $mainId, $shopId, true);
|
|
|
+ if (!in_array($application->status, [LakalaApplicationClass::STATUS_DRAFT, LakalaApplicationClass::STATUS_REJECT], true)) {
|
|
|
+ util::fail('当前申请不能修改');
|
|
|
+ }
|
|
|
+ $application->formData = self::encode($form);
|
|
|
+ $application->currentStep = max(1, min(5, (int)$step));
|
|
|
+ $application->updateTime = date('Y-m-d H:i:s');
|
|
|
+ $application->save();
|
|
|
+ return self::detail($id, $mainId, $shopId);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function deleteDraft($id, $mainId, $shopId)
|
|
|
+ {
|
|
|
+ $application = self::application($id, $mainId, $shopId);
|
|
|
+ if ($application['status'] !== LakalaApplicationClass::STATUS_DRAFT) {
|
|
|
+ util::fail('仅草稿可以删除');
|
|
|
+ }
|
|
|
+ LakalaApplicationClass::deleteById($id);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function upload($id, $mainId, $shopId, $base64, $imgType, $ocr)
|
|
|
+ {
|
|
|
+ self::application($id, $mainId, $shopId);
|
|
|
+ if (!preg_match('/^data:image\/[a-zA-Z0-9.+-]+;base64,/', $base64)) {
|
|
|
+ util::fail('图片格式错误');
|
|
|
+ }
|
|
|
+ $content = base64_decode(substr($base64, strpos($base64, ',') + 1), true);
|
|
|
+ if ($content === false || strlen($content) > 10 * 1024 * 1024) {
|
|
|
+ util::fail('图片内容无效或超过10MB');
|
|
|
+ }
|
|
|
+ $object = 'lakala-onboarding/' . $mainId . '/' . date('Ymd') . '/' . $id . '/' . stringUtil::uniqueFileName() . '.jpg';
|
|
|
+ oss::uploadContent($object, $content);
|
|
|
+ $response = (new LakalaOnboardingClient())->upload($base64, $imgType, (bool)$ocr);
|
|
|
+ return [
|
|
|
+ 'ossPath' => $object,
|
|
|
+ 'imgType' => $imgType,
|
|
|
+ 'batchNo' => $response['data']['batch_no'] ?? '',
|
|
|
+ 'url' => $response['data']['url'] ?? '',
|
|
|
+ 'status' => $response['data']['status'] ?? '',
|
|
|
+ 'ocr' => $response['data']['result'] ?? null,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function ocr($id, $mainId, $shopId, $batchNo, $imgType)
|
|
|
+ {
|
|
|
+ self::application($id, $mainId, $shopId);
|
|
|
+ $response = (new LakalaOnboardingClient())->ocr($batchNo, $imgType);
|
|
|
+ return $response['data'];
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function option($type, $parentCode = '', $keyword = '')
|
|
|
+ {
|
|
|
+ $client = new LakalaOnboardingClient();
|
|
|
+ if ($type === 'region') {
|
|
|
+ return $client->query('/api/v3/tkbs/organization_parent_code', ['parent_code' => $parentCode ?: '1'])['data'];
|
|
|
+ }
|
|
|
+ if ($type === 'bank-region') {
|
|
|
+ return $client->query('/api/v3/tkbs/organization_bank_parent_code', ['parent_code' => $parentCode ?: '1'])['data'];
|
|
|
+ }
|
|
|
+ if ($type === 'bank') {
|
|
|
+ return $client->query('/api/v3/tkbs/bank', ['area_code' => $parentCode, 'bank_name' => $keyword])['data'];
|
|
|
+ }
|
|
|
+ util::fail('不支持的选项类型');
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function applyContract($id, $mainId, $shopId)
|
|
|
+ {
|
|
|
+ $application = self::application($id, $mainId, $shopId, true);
|
|
|
+ $form = self::decode($application->formData);
|
|
|
+ self::validateContract($form);
|
|
|
+ $client = new LakalaOnboardingClient();
|
|
|
+ $orderNo = date('YmdHis') . substr(str_shuffle('0123456789'), 0, 8);
|
|
|
+ $request = self::contractRequest($form, $orderNo, $client->orgCode());
|
|
|
+ $response = $client->call('/api/v3/mms/open_api/ec/apply', $request);
|
|
|
+ $application->contractOrderNo = $orderNo;
|
|
|
+ $application->contractApplyId = $response['data']['ec_apply_id'] ?? '';
|
|
|
+ $application->contractStatus = 'UNDONE';
|
|
|
+ $application->status = LakalaApplicationClass::STATUS_CONTRACT_PENDING;
|
|
|
+ $application->updateTime = date('Y-m-d H:i:s');
|
|
|
+ $application->save();
|
|
|
+ self::operation($application, 0, 'contract', '', $request, $response, 'UNDONE');
|
|
|
+ return ['resultUrl' => $response['data']['result_url'] ?? '', 'status' => 'UNDONE'];
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function contractStatus($id, $mainId, $shopId)
|
|
|
+ {
|
|
|
+ $application = self::application($id, $mainId, $shopId, true);
|
|
|
+ if (empty($application->contractApplyId) || empty($application->contractOrderNo)) {
|
|
|
+ util::fail('请先生成电子合同');
|
|
|
+ }
|
|
|
+ $client = new LakalaOnboardingClient();
|
|
|
+ $response = $client->query('/api/v3/mms/open_api/ec/q_status', [
|
|
|
+ 'ec_apply_id' => $application->contractApplyId,
|
|
|
+ 'order_no' => $application->contractOrderNo,
|
|
|
+ ]);
|
|
|
+ $application->contractStatus = $response['data']['ec_status'] ?? '';
|
|
|
+ $application->contractNo = $response['data']['ec_no'] ?? '';
|
|
|
+ $application->updateTime = date('Y-m-d H:i:s');
|
|
|
+ $application->save();
|
|
|
+ return ['status' => $application->contractStatus, 'contractNo' => $application->contractNo];
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function submit($id, $mainId, $shopId, array $form)
|
|
|
+ {
|
|
|
+ $application = self::application($id, $mainId, $shopId, true);
|
|
|
+ if ($application->type === LakalaApplicationClass::TYPE_SETTLEMENT_CHANGE) {
|
|
|
+ return self::submitSettlementChange($application, $form);
|
|
|
+ }
|
|
|
+ self::validateOnboarding($form);
|
|
|
+ if ($application->contractStatus !== 'COMPLETED' || empty($application->contractNo)) {
|
|
|
+ util::fail('请先完成电子合同签约并刷新签约状态');
|
|
|
+ }
|
|
|
+ $client = new LakalaOnboardingClient();
|
|
|
+ $request = self::merchantRequest($form, $application->contractNo, $client);
|
|
|
+ $response = $client->call('/api/v3/tkbs/merchant_encry', $request, 'both');
|
|
|
+ $application->formData = self::encode($form);
|
|
|
+ $application->merchantNo = $response['data']['merchant_no'] ?? '';
|
|
|
+ $application->customerNo = $response['data']['merchant_no'] ?? '';
|
|
|
+ $application->status = LakalaApplicationClass::STATUS_WAIT_AUDIT;
|
|
|
+ $application->rejectReason = '';
|
|
|
+ $application->versionNo = ((int)$application->versionNo) + 1;
|
|
|
+ $application->updateTime = date('Y-m-d H:i:s');
|
|
|
+ $application->save();
|
|
|
+ self::version($application, $request, $response);
|
|
|
+ self::operation($application, 0, 'onboarding', '', $request, $response, $application->status);
|
|
|
+ return self::detail($application->id, $mainId, $shopId);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function refresh($id, $mainId, $shopId)
|
|
|
+ {
|
|
|
+ $application = self::application($id, $mainId, $shopId, true);
|
|
|
+ if ($application->type === LakalaApplicationClass::TYPE_SETTLEMENT_CHANGE) {
|
|
|
+ return self::refreshChange($application);
|
|
|
+ }
|
|
|
+ if (empty($application->customerNo) && empty($application->merchantNo)) {
|
|
|
+ util::fail('申请尚未提交');
|
|
|
+ }
|
|
|
+ $client = new LakalaOnboardingClient();
|
|
|
+ $response = $client->query('/api/v3/tkbs/open_merchant_info', [
|
|
|
+ 'customer_no' => $application->customerNo,
|
|
|
+ ], 'both');
|
|
|
+ $customer = $response['data']['customer'];
|
|
|
+ $status = $customer['customer_status'];
|
|
|
+ if (in_array($status, ['REJECT', 'REVIEW_FAIL'], true)) {
|
|
|
+ $application->status = LakalaApplicationClass::STATUS_REJECT;
|
|
|
+ $application->rejectReason = $customer['audit_remark'] ?? '拉卡拉审核驳回';
|
|
|
+ } elseif (in_array($status, ['OPEN', 'ACTIVITY'], true)) {
|
|
|
+ $application->status = LakalaApplicationClass::STATUS_APPROVED;
|
|
|
+ $application->merchantNo = $customer['merchant_no'] ?? $application->merchantNo;
|
|
|
+ $application->customerNo = $customer['customer_no'] ?? $application->customerNo;
|
|
|
+ $shopInfo = $response['data']['shop_info_list'][0] ?? [];
|
|
|
+ $application->shopNo = $shopInfo['shop_id'] ?? $application->shopNo;
|
|
|
+ $account = self::upsertAccount($application, $response['data']);
|
|
|
+ self::ensureTermOperations($application, $account, $client);
|
|
|
+ self::syncTerms($application, $account, $client);
|
|
|
+ } else {
|
|
|
+ $application->status = LakalaApplicationClass::STATUS_WAIT_AUDIT;
|
|
|
+ }
|
|
|
+ $application->updateTime = date('Y-m-d H:i:s');
|
|
|
+ $application->save();
|
|
|
+ return self::detail($application->id, $mainId, $shopId);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function setCurrent($accountId, $mainId, $shopId)
|
|
|
+ {
|
|
|
+ $account = LakalaAccountClass::getByCondition(['id' => $accountId, 'mainId' => $mainId, 'shopId' => $shopId], true);
|
|
|
+ if (empty($account)) {
|
|
|
+ util::fail('没有找到收款账户');
|
|
|
+ }
|
|
|
+ if (empty($account->scanTermNo) || empty($account->b2bTermNo)) {
|
|
|
+ util::fail('账户业务尚未全部开通');
|
|
|
+ }
|
|
|
+ $transaction = Yii::$app->db->beginTransaction();
|
|
|
+ try {
|
|
|
+ LakalaAccountClass::updateByCondition(['shopId' => $shopId], ['isCurrent' => 0, 'updateTime' => date('Y-m-d H:i:s')]);
|
|
|
+ $account->isCurrent = 1;
|
|
|
+ $account->updateTime = date('Y-m-d H:i:s');
|
|
|
+ $account->save();
|
|
|
+ ShopClass::updateById($shopId, [
|
|
|
+ 'lklSjNo' => $account->merchantNo,
|
|
|
+ 'lklScanTermNo' => $account->scanTermNo,
|
|
|
+ 'lklB2BTermNo' => $account->b2bTermNo,
|
|
|
+ ]);
|
|
|
+ $transaction->commit();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $transaction->rollBack();
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function submitSettlementChange($application, array $form)
|
|
|
+ {
|
|
|
+ self::validateSettlement($form['settlement'] ?? []);
|
|
|
+ $account = LakalaAccountClass::getByCondition(['id' => $application->accountId, 'mainId' => $application->mainId], true);
|
|
|
+ if (empty($account)) {
|
|
|
+ util::fail('没有找到收款账户');
|
|
|
+ }
|
|
|
+ $client = new LakalaOnboardingClient();
|
|
|
+ $request = [
|
|
|
+ 'org_code' => $client->orgCode(),
|
|
|
+ 'customer_no' => $account->customerNo,
|
|
|
+ 'customer_settle_update_dto' => self::settlementDto($form['settlement']),
|
|
|
+ ];
|
|
|
+ $response = $client->call('/api/v3/tkbs/open_merchant_update_settle_encry', $request, 'both');
|
|
|
+ $application->formData = self::encode($form);
|
|
|
+ $application->reviewRelatedId = $response['data']['review_related_id'] ?? '';
|
|
|
+ $application->status = LakalaApplicationClass::STATUS_WAIT_AUDIT;
|
|
|
+ $application->versionNo = ((int)$application->versionNo) + 1;
|
|
|
+ $application->updateTime = date('Y-m-d H:i:s');
|
|
|
+ $application->save();
|
|
|
+ self::version($application, $request, $response);
|
|
|
+ self::operation($application, $account->id, 'settlement_change', '', $request, $response, 'PASSING');
|
|
|
+ return self::detail($application->id, $application->mainId, $application->shopId);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function refreshChange($application)
|
|
|
+ {
|
|
|
+ if (empty($application->reviewRelatedId)) {
|
|
|
+ util::fail('变更申请尚未提交');
|
|
|
+ }
|
|
|
+ $client = new LakalaOnboardingClient();
|
|
|
+ $response = $client->query('/api/v3/tkbs/customer_update_review', ['review_related_id' => $application->reviewRelatedId]);
|
|
|
+ $review = $response['data']['review_pass'] ?? '';
|
|
|
+ if ($review === 'PASS') {
|
|
|
+ $application->status = LakalaApplicationClass::STATUS_APPROVED;
|
|
|
+ $account = LakalaAccountClass::getById($application->accountId, true);
|
|
|
+ if (!empty($account)) {
|
|
|
+ $settlement = self::decode($application->formData)['settlement'] ?? [];
|
|
|
+ $account->accountName = $settlement['accountName'] ?? $account->accountName;
|
|
|
+ $account->accountNo = $settlement['accountNo'] ?? $account->accountNo;
|
|
|
+ $account->bankName = $settlement['bankName'] ?? $account->bankName;
|
|
|
+ $account->accountType = $settlement['accountType'] ?? $account->accountType;
|
|
|
+ $account->updateTime = date('Y-m-d H:i:s');
|
|
|
+ $account->save();
|
|
|
+ }
|
|
|
+ } elseif ($review === 'UNPASS') {
|
|
|
+ $application->status = LakalaApplicationClass::STATUS_REJECT;
|
|
|
+ $application->rejectReason = $response['data']['review_result'] ?? '变更审核驳回';
|
|
|
+ } else {
|
|
|
+ $application->status = LakalaApplicationClass::STATUS_WAIT_AUDIT;
|
|
|
+ }
|
|
|
+ $application->updateTime = date('Y-m-d H:i:s');
|
|
|
+ $application->save();
|
|
|
+ return self::detail($application->id, $application->mainId, $application->shopId);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function ensureTermOperations($application, $account, $client)
|
|
|
+ {
|
|
|
+ foreach (['WECHAT_PAY', 'B2B_SYT'] as $bizType) { // 'B2B_SYT' -- 聚合收银台已经有了,不需要额外创建
|
|
|
+ $operation = LakalaOperationClass::getByCondition([
|
|
|
+ 'applicationId' => $application->id,
|
|
|
+ 'operationType' => 'add_term',
|
|
|
+ 'bizType' => $bizType,
|
|
|
+ 'status!=' => 'FAILED',
|
|
|
+ ], true);
|
|
|
+ if (!empty($operation)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $request = [
|
|
|
+ 'merchant_no' => $account->merchantNo,
|
|
|
+ 'bz_pos' => $bizType,
|
|
|
+ 'term_num' => 1,
|
|
|
+ 'shop_id' => $account->shopNo,
|
|
|
+ 'org_code' => $client->orgCode(),
|
|
|
+ 'fees' => self::termFees(),
|
|
|
+ 'attachments' => [],
|
|
|
+ ];
|
|
|
+ try {
|
|
|
+ $response = $client->call('/api/v3/tkbs/open_merchant_addTerm', $request, 'both');
|
|
|
+ self::operation($application, $account->id, 'add_term', $bizType, $request, $response, 'PASSING');
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ self::operation($application, $account->id, 'add_term', $bizType, $request, [], 'FAILED', $e->getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function syncTerms($application, $account, $client)
|
|
|
+ {
|
|
|
+ $operations = LakalaOperationClass::getAllByCondition(
|
|
|
+ ['applicationId' => $application->id, 'operationType' => 'add_term'],
|
|
|
+ null,
|
|
|
+ '*',
|
|
|
+ null,
|
|
|
+ true
|
|
|
+ );
|
|
|
+ foreach ($operations as $operation) {
|
|
|
+ if (!empty($operation->reviewRelatedId) && !in_array($operation->status, ['PASS', 'FAILED'], true)) {
|
|
|
+ $review = $client->query('/api/v3/tkbs/customer_update_review', ['review_related_id' => $operation->reviewRelatedId]);
|
|
|
+ $operation->status = $review['data']['review_pass'] ?? $operation->status;
|
|
|
+ $operation->responseData = self::encode($review);
|
|
|
+ $operation->errorMessage = $review['data']['review_result'] ?? '';
|
|
|
+ $operation->updateTime = date('Y-m-d H:i:s');
|
|
|
+ $operation->save();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ $res = $client->query('/api/v3/tkbs/open_merchant_info', [
|
|
|
+ 'customer_no' => $account->customerNo ?: $application->customerNo,
|
|
|
+ 'merchant_no' => $account->merchantNo ?: $application->merchantNo,
|
|
|
+ ], 'both');
|
|
|
+ $merchantInfo = $res['data'];
|
|
|
+ $customer = $merchantInfo['customer'];
|
|
|
+ $terms = self::merchantInfoTermMap($merchantInfo);
|
|
|
+ $merchantNo = $customer['merchant_no'] ?? '';
|
|
|
+ if ($merchantNo !== '') {
|
|
|
+ $application->merchantNo = $merchantNo;
|
|
|
+ $account->merchantNo = $merchantNo;
|
|
|
+ }
|
|
|
+ $account->scanTermNo = $terms['scanTermNo'][0];
|
|
|
+ $account->b2bTermNo = $terms['b2BTermNo'][0];
|
|
|
+ $account->updateTime = date('Y-m-d H:i:s');
|
|
|
+ $account->save();
|
|
|
+ $application->save();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Yii::warning('Unable to sync Lakala merchant info terms: ' . $e->getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function upsertAccount($application, array $remote)
|
|
|
+ {
|
|
|
+ $account = LakalaAccountClass::getByCondition(['mainId' => $application->mainId, 'merchantNo' => $application->merchantNo], true);
|
|
|
+ $form = self::decode($application->formData);
|
|
|
+ $settlement = $form['settlement'] ?? [];
|
|
|
+ $license = $form['license'] ?? [];
|
|
|
+ $shopInfo = $remote['shop_info_list'][0] ?? [];
|
|
|
+ $data = [
|
|
|
+ 'mainId' => $application->mainId,
|
|
|
+ 'shopId' => $application->shopId,
|
|
|
+ 'applicationId' => $application->id,
|
|
|
+ 'merchantNo' => $application->merchantNo,
|
|
|
+ 'customerNo' => $application->customerNo,
|
|
|
+ 'shopNo' => $shopInfo['shop_id'] ?? $application->shopNo,
|
|
|
+ 'accountName' => $settlement['accountName'] ?? '',
|
|
|
+ 'accountNo' => $settlement['accountNo'] ?? '',
|
|
|
+ 'bankName' => $settlement['bankName'] ?? '',
|
|
|
+ 'accountType' => $settlement['accountType'] ?? '58',
|
|
|
+ 'licenseName' => $license['licenseName'] ?? ($license['legalName'] ?? ''),
|
|
|
+ 'identityExpire' => $license['legalIdEnd'] ?? '',
|
|
|
+ 'status' => 'ACTIVE',
|
|
|
+ 'updateTime' => date('Y-m-d H:i:s'),
|
|
|
+ ];
|
|
|
+ if (empty($account)) {
|
|
|
+ $data['createTime'] = date('Y-m-d H:i:s');
|
|
|
+ return LakalaAccountClass::add($data, true);
|
|
|
+ }
|
|
|
+ foreach ($data as $key => $value) {
|
|
|
+ $account->$key = $value;
|
|
|
+ }
|
|
|
+ $account->save();
|
|
|
+ return $account;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function defaultForm($shop)
|
|
|
+ {
|
|
|
+ $merchantName = $shop->merchantName ?? ($shop->shopName ?? '');
|
|
|
+ $mobile = $shop->mobile ?? ($shop->telephone ?? '');
|
|
|
+ return [
|
|
|
+ 'mode' => 'create',
|
|
|
+ 'license' => [
|
|
|
+ 'merchantType' => 'TP_PERSONAL', 'licenseName' => '', 'licenseNo' => '',
|
|
|
+ 'licenseStart' => '', 'licenseEnd' => '9999-12-31', 'registeredAddress' => '',
|
|
|
+ 'businessContent' => '', 'legalName' => '', 'legalIdNo' => '',
|
|
|
+ 'legalIdStart' => '', 'legalIdEnd' => '9999-12-31', 'attachments' => [],
|
|
|
+ ],
|
|
|
+ 'merchant' => [
|
|
|
+ 'businessName' => ($shop->city ?? '') . $merchantName,
|
|
|
+ 'provinceCode' => '', 'cityCode' => '', 'countyCode' => '',
|
|
|
+ 'provinceName' => $shop->province ?? '', 'cityName' => $shop->city ?? '',
|
|
|
+ 'countyName' => $shop->dist ?? '', 'address' => $shop->address ?? ($shop->fullAddress ?? ''),
|
|
|
+ 'contactName' => '石少华', 'contactMobile' => '15280215347', 'email' => '479439056@qq.com',
|
|
|
+ ],
|
|
|
+ 'settlement' => [
|
|
|
+ 'accountType' => '58', 'isLegalPerson' => true, 'accountName' => '',
|
|
|
+ 'accountNo' => '', 'bankName' => '', 'bankNo' => '', 'clearingBankNo' => '',
|
|
|
+ 'provinceCode' => '', 'provinceName' => '', 'cityCode' => '', 'cityName' => '',
|
|
|
+ 'accountIdNo' => '', 'accountIdStart' => '', 'accountIdEnd' => '', 'attachments' => [],
|
|
|
+ ],
|
|
|
+ 'shop' => [
|
|
|
+ 'name' => $merchantName, 'provinceCode' => '', 'cityCode' => '', 'countyCode' => '',
|
|
|
+ 'provinceName' => $shop->province ?? '', 'cityName' => $shop->city ?? '',
|
|
|
+ 'countyName' => $shop->dist ?? '', 'address' => $shop->address ?? ($shop->fullAddress ?? ''),
|
|
|
+ 'contactName' => '', 'contactMobile' => $mobile, 'longitude' => $shop->long ?? ($shop->shopLong ?? ''),
|
|
|
+ 'latitude' => $shop->lat ?? ($shop->shopLat ?? ''), 'attachments' => [],
|
|
|
+ ],
|
|
|
+ 'business' => ['signMobile' => $mobile ?: '15280215347', 'rate' => self::RATE, 'settleType' => 'D1', 'settlementType' => 'AUTOMATIC'],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function merchantRequest(array $form, $contractNo, $client)
|
|
|
+ {
|
|
|
+ $l = $form['license'];
|
|
|
+ $m = $form['merchant'];
|
|
|
+ $s = $form['settlement'];
|
|
|
+ $shop = $form['shop'];
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'org_code' => $client->orgCode(),
|
|
|
+ 'user_no' => $client->userNo(),
|
|
|
+ 'email' => $m['email'],
|
|
|
+ 'busi_code' => 'WECHAT_PAY',
|
|
|
+ 'mer_reg_name' => $m['businessName'],
|
|
|
+ 'mer_type' => $l['merchantType'],
|
|
|
+ 'mer_name' => $m['businessName'],
|
|
|
+ 'mer_addr' => $m['address'],
|
|
|
+ 'province_code' => $m['provinceCode'],
|
|
|
+ 'city_code' => $m['cityCode'],
|
|
|
+ 'county_code' => $m['countyCode'],
|
|
|
+ 'license_name' => $l['licenseName'],
|
|
|
+ 'license_no' => $l['licenseNo'],
|
|
|
+ 'license_dt_start' => $l['licenseStart'],
|
|
|
+ 'license_dt_end' => $l['licenseEnd'],
|
|
|
+ 'latitude' => $shop['longitude'],
|
|
|
+ 'longtude' => $shop['latitude'],
|
|
|
+ 'source' => 'APP',
|
|
|
+ 'business_content' => $l['businessContent'],
|
|
|
+ 'is_legal_person' => (bool)$s['isLegalPerson'],
|
|
|
+ 'lar_name' => $l['legalName'], 'lar_id_type' => '01', 'lar_id_card' => $l['legalIdNo'],
|
|
|
+ 'lar_id_card_start' => $l['legalIdStart'], 'lar_id_card_end' => $l['legalIdEnd'],
|
|
|
+ 'contact_mobile' => $m['contactMobile'],
|
|
|
+ 'contact_name' => $m['contactName'],
|
|
|
+ 'openning_bank_code' => $s['bankNo'],
|
|
|
+ 'openning_bank_name' => $s['bankName'],
|
|
|
+ 'clearing_bank_code' => $s['clearingBankNo'], 'settle_province_code' => $s['provinceCode'],
|
|
|
+ 'settle_province_name' => $s['provinceName'], 'settle_city_code' => $s['cityCode'],
|
|
|
+ 'settle_city_name' => $s['cityName'], 'account_no' => $s['accountNo'], 'account_name' => $s['accountName'],
|
|
|
+ 'account_type' => $s['accountType'], 'account_id_card' => $s['accountIdNo'] ?: $l['legalIdNo'],
|
|
|
+ 'account_id_dt_start' => $s['accountIdStart'] ?: $l['legalIdStart'],
|
|
|
+ 'account_id_dt_end' => $s['accountIdEnd'] ?: $l['legalIdEnd'],
|
|
|
+ 'settle_type' => 'D1', 'settlement_type' => 'AUTOMATIC', 'contract_no' => $contractNo,
|
|
|
+ 'biz_content' => ['term_num' => '1', 'mcc' => self::MCC, 'activity_id' => self::activityId(), 'fees' => self::merchantFees()],
|
|
|
+ 'attchments' => self::attachments($form),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function contractRequest(array $form, $orderNo, $orgCode)
|
|
|
+ {
|
|
|
+ $l = $form['license'];
|
|
|
+ $m = $form['merchant'];
|
|
|
+ $s = $form['settlement'];
|
|
|
+ $shop = $form['shop'];
|
|
|
+ $b = $form['business'];
|
|
|
+ $content = [];
|
|
|
+
|
|
|
+ foreach (['A' => 130, 'B' => 58, 'C' => 56, 'D' => 17, 'E' => 79] as $prefix => $max) {
|
|
|
+ for ($i = 1; $i <= $max; $i++) {
|
|
|
+ $content[$prefix . $i] = '/';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $content = array_merge($content, [
|
|
|
+ 'A1' => $l['licenseName'] ?: $l['legalName'], 'A2' => '是', 'A6' => '是',
|
|
|
+ 'A7' => '0.50', 'A8' => '20', 'A9' => '是', 'A10' => '0.60',
|
|
|
+ 'A11' => '是', 'A12' => self::RATE, 'A13' => '是', 'A14' => self::RATE,
|
|
|
+ 'A15' => '0.60', 'A34' => self::RATE, 'A35' => self::RATE,
|
|
|
+ 'A63' => '是', 'A64' => self::RATE, 'A65' => '是', 'A66' => self::RATE,
|
|
|
+ 'A116' => '自动结算',
|
|
|
+ 'A117' => '是',
|
|
|
+ 'A121' => ($shop['provinceName'] ?? '') . ($shop['cityName'] ?? ''),
|
|
|
+ 'A124' => date('Y'), 'A125' => date('m'), 'A126' => date('d'), // 甲方签约 年/月/日
|
|
|
+ 'A127' => date('Y'), 'A128' => date('m'), 'A129' => date('d'), // 乙方签约 年/月/日
|
|
|
+ 'A130' => $l['legalName'] ?: $m['contactName'], // 甲方电子签名/签字/盖章
|
|
|
+ // ===== B 系列:商户注册登记 / 基本信息 / 结算账户 / 法人联系人 / 终端 / 拓展 =====
|
|
|
+ 'B1' => date('Y'), 'B2' => date('m'), // 商户注册登记表 年/月
|
|
|
+ 'B3' => '是',
|
|
|
+ 'B8' => $l['licenseName'], // 基本信息:工商注册名称(中英文)
|
|
|
+ 'B10' => $m['businessName'], // 基本信息:对外经营名称(中英文)
|
|
|
+ 'B13' => ($m['provinceName'] ?? '') . ($m['cityName'] ?? ''), 'B14' => $l['licenseNo'] ?: '/',
|
|
|
+ 'B18' => $l['legalName'],
|
|
|
+ 'B19' => $s['bankName'],
|
|
|
+ 'B20' => $s['accountNo'],
|
|
|
+ 'B24' => $s['accountName'],
|
|
|
+ 'B25' => $l['legalIdNo'], 'B26' => $b['signMobile'],
|
|
|
+ 'B27' => $l['legalName'],
|
|
|
+ 'B28' => $m['email'],
|
|
|
+ 'B29' => $l['legalIdNo'],// 联系人证件号码
|
|
|
+ 'B30' => $b['signMobile'],// 联系人手机号
|
|
|
+ 'B31' => $l['licenseName'] ?: $l['legalName'],
|
|
|
+ 'B32' => $l['legalName'],// 网点联系人姓名
|
|
|
+ 'B33' => $m['address'],
|
|
|
+ 'B34' => $m['contactMobile'],
|
|
|
+ // ===== D 系列:各类授权书 =====
|
|
|
+ 'D1' => $s['bankName'],// 授权书(银行):结算账户所属银行
|
|
|
+ 'D2' => $l['legalName'] ?: $m['contactName'],// 授权书(银行):授权承诺方签字
|
|
|
+ 'D3' => date('Y/m/d'),// 授权书(银行):签章/签字日期
|
|
|
+ 'D4' => $l['legalName'] ?: $m['contactName'],// 授权书(开通微信、支付宝收单):承诺授权方签名
|
|
|
+ 'D5' => date('Y/m/d'),// 授权书(开通微信、支付宝收单):盖章/签字日期
|
|
|
+ // ===== E 系列:分银行费率明细(费率一致则不填,统一占位 "/")=====
|
|
|
+ 'E78' => $l['legalName'] ?: $m['contactName'],// 电子签约服务授权委托书:承诺授权方签名
|
|
|
+ 'E79' => $l['legalName'] ?: $m['contactName'],// 电子签约服务授权委托书:承诺签名或盖章
|
|
|
+ ]);
|
|
|
+ return [
|
|
|
+ 'order_no' => $orderNo, 'org_id' => (int)$orgCode, 'ec_type_code' => 'EC015',
|
|
|
+ 'cert_type' => 'RESIDENT_ID', 'cert_name' => $l['legalName'], 'cert_no' => $l['legalIdNo'],
|
|
|
+ 'mobile' => $b['signMobile'], 'business_license_no' => $l['licenseNo'],
|
|
|
+ 'business_license_name' => $l['licenseName'], 'openning_bank_code' => $s['bankNo'],
|
|
|
+ 'openning_bank_name' => $s['bankName'], 'acct_type_code' => $s['accountType'],
|
|
|
+ 'acct_no' => $s['accountNo'], 'acct_name' => $s['accountName'],
|
|
|
+ 'ec_content_parameters' => self::encode($content), 'remark' => '销花宝商户入网',
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function settlementDto(array $s)
|
|
|
+ {
|
|
|
+ return [
|
|
|
+ 'account_kind' => $s['accountType'], 'is_legal_person' => (bool)$s['isLegalPerson'],
|
|
|
+ 'account_name' => $s['accountName'], 'identity_no' => $s['accountIdNo'],
|
|
|
+ 'account_no' => $s['accountNo'], 'bank_no' => $s['bankNo'], 'bank_name' => $s['bankName'],
|
|
|
+ 'clearing_bank_no' => $s['clearingBankNo'], 'settle_province_code' => $s['provinceCode'],
|
|
|
+ 'settle_province_name' => $s['provinceName'], 'settle_city_code' => $s['cityCode'],
|
|
|
+ 'settle_city_name' => $s['cityName'], 'attachments' => self::attachmentDtos($s['attachments'] ?? []),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function merchantFees()
|
|
|
+ {
|
|
|
+ return [
|
|
|
+ ['fee_code' => 'WECHAT', 'fee_value' => self::RATE],
|
|
|
+ ['fee_code' => 'ALIPAY', 'fee_value' => self::RATE],
|
|
|
+ ['fee_code' => 'SCAN_PAY', 'fee_value' => self::RATE],
|
|
|
+ ['fee_code' => 'DEBIT_CARD', 'fee_value' => self::RATE, 'top_fee' => '20'],
|
|
|
+ ['fee_code' => 'CREDIT_CARD', 'fee_value' => '0.60'],
|
|
|
+ ['fee_code' => 'UNIONPAY_WALLET_DEBIT_FEE', 'fee_value' => self::RATE, 'top_fee' => '20'],
|
|
|
+ ['fee_code' => 'UNIONPAY_WALLET_CREDIT_FEE', 'fee_value' => self::RATE],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function activityId()
|
|
|
+ {
|
|
|
+ return (string)(Yii::$app->params['lakalaOnboarding']['activity_id'] ?? getenv('LAKALA_ONBOARDING_ACTIVITY_ID') ?: self::ACTIVITY_ID);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function termFees()
|
|
|
+ {
|
|
|
+ return [
|
|
|
+ ['fee' => (float)self::RATE, 'top_fee' => 20, 'fee_type' => 'DEBIT_CARD'],
|
|
|
+ ['fee' => (float)self::RATE, 'fee_type' => 'CREDIT_CARD'],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function attachments(array $form)
|
|
|
+ {
|
|
|
+ $all = [];
|
|
|
+ foreach (['license', 'settlement', 'shop'] as $section) {
|
|
|
+ $all = array_merge($all, self::attachmentDtos($form[$section]['attachments'] ?? [], true));
|
|
|
+ }
|
|
|
+ return $all;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function attachmentDtos(array $attachments, $merchantShape = false)
|
|
|
+ {
|
|
|
+ $result = [];
|
|
|
+ foreach ($attachments as $attachment) {
|
|
|
+ if (empty($attachment['url']) || empty($attachment['imgType'])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $result[] = $merchantShape
|
|
|
+ ? ['id' => $attachment['url'], 'type' => $attachment['imgType']]
|
|
|
+ : ['img_path' => $attachment['url'], 'img_type' => $attachment['imgType']];
|
|
|
+ }
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function validateContract(array $form)
|
|
|
+ {
|
|
|
+ self::validateOnboarding($form, false);
|
|
|
+ if (empty($form['business']['signMobile'])) {
|
|
|
+ util::fail('请填写签约手机号');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function validateOnboarding(array $form, $includeShop = true)
|
|
|
+ {
|
|
|
+ $l = $form['license'] ?? []; $m = $form['merchant'] ?? []; $s = $form['settlement'] ?? []; $shop = $form['shop'] ?? [];
|
|
|
+ $items = ['merchantType', 'legalName', 'legalIdNo', 'legalIdStart', 'legalIdEnd', 'businessContent'];
|
|
|
+ if ($l['merchantType'] === 'TP_PERSONAL') {
|
|
|
+ unset($items[5]);
|
|
|
+ }
|
|
|
+ foreach ($items as $key) {
|
|
|
+ if (empty($l[$key])) util::fail('执照信息未填写完整');
|
|
|
+ }
|
|
|
+ self::requireAttachments($l['attachments'] ?? [], ['ID_CARD_FRONT', 'ID_CARD_BEHIND'], '请上传法人身份证正反面');
|
|
|
+ if (($l['merchantType'] ?? '') === 'TP_MERCHANT' && (empty($l['licenseName']) || empty($l['licenseNo']))) {
|
|
|
+ util::fail('企业商户请填写营业执照信息');
|
|
|
+ }
|
|
|
+ if (($l['merchantType'] ?? '') === 'TP_MERCHANT') {
|
|
|
+ self::requireAttachments($l['attachments'] ?? [], ['BUSINESS_LICENCE'], '请上传营业执照');
|
|
|
+ }
|
|
|
+ foreach (['businessName', 'provinceCode', 'cityCode', 'countyCode', 'address'] as $key) {
|
|
|
+ if (empty($m[$key])) util::fail('商户信息未填写完整');
|
|
|
+ }
|
|
|
+ self::validateSettlement($s);
|
|
|
+ if ($includeShop) {
|
|
|
+ foreach (['name', 'address', 'contactName', 'contactMobile', 'longitude', 'latitude'] as $key) {
|
|
|
+ if (empty($shop[$key])) util::fail('门店信息未填写完整');
|
|
|
+ }
|
|
|
+ self::requireAttachments($shop['attachments'] ?? [], ['SHOP_OUTSIDE_IMG', 'SHOP_INSIDE_IMG', 'CHECKSTAND_IMG'], '请上传完整门店照片');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function validateSettlement(array $s)
|
|
|
+ {
|
|
|
+ foreach (['accountType', 'accountName', 'accountNo', 'bankName', 'bankNo', 'clearingBankNo', 'provinceCode', 'cityCode'] as $key) {
|
|
|
+ if (empty($s[$key])) util::fail('结算信息未填写完整');
|
|
|
+ }
|
|
|
+ if (($s['accountType'] ?? '58') === '57') {
|
|
|
+ self::requireAttachments($s['attachments'] ?? [], ['OPENING_PERMIT'], '请上传开户许可证');
|
|
|
+ } else {
|
|
|
+ self::requireAttachments($s['attachments'] ?? [], ['BANK_CARD'], '请上传银行卡照片');
|
|
|
+ if (empty($s['isLegalPerson'])) {
|
|
|
+ self::requireAttachments($s['attachments'] ?? [], ['ID_CARD_FRONT', 'ID_CARD_BEHIND', 'LETTER_OF_AUTHORIZATION'], '请上传结算人证件与法人授权函');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function requireAttachments(array $attachments, array $types, $message)
|
|
|
+ {
|
|
|
+ $present = array_column($attachments, 'imgType');
|
|
|
+ foreach ($types as $type) {
|
|
|
+ if (!in_array($type, $present, true)) {
|
|
|
+ util::fail($message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function version($application, array $request, array $response)
|
|
|
+ {
|
|
|
+ LakalaApplicationVersionClass::add([
|
|
|
+ 'applicationId' => $application->id, 'mainId' => $application->mainId,
|
|
|
+ 'versionNo' => $application->versionNo, 'type' => $application->type,
|
|
|
+ 'formData' => $application->formData, 'requestData' => self::encode($request),
|
|
|
+ 'responseData' => self::encode($response), 'createTime' => date('Y-m-d H:i:s'),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function operation($application, $accountId, $type, $bizType, array $request, array $response, $status, $error = '')
|
|
|
+ {
|
|
|
+ return LakalaOperationClass::add([
|
|
|
+ 'applicationId' => $application->id,
|
|
|
+ 'accountId' => $accountId,
|
|
|
+ 'mainId' => $application->mainId,
|
|
|
+ 'operationType' => $type,
|
|
|
+ 'bizType' => $bizType,
|
|
|
+ 'status' => $status,
|
|
|
+ 'reviewRelatedId' => $response['data']['review_related_id'] ?? '',
|
|
|
+ 'requestData' => self::encode($request),
|
|
|
+ 'responseData' => self::encode($response),
|
|
|
+ 'errorMessage' => mb_substr($error, 0, 500),
|
|
|
+ 'createTime' => date('Y-m-d H:i:s'),
|
|
|
+ 'updateTime' => date('Y-m-d H:i:s'),
|
|
|
+ ], true);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function application($id, $mainId, $shopId, $object = false)
|
|
|
+ {
|
|
|
+ $application = LakalaApplicationClass::getByCondition(['id' => $id, 'mainId' => $mainId, 'shopId' => $shopId], $object);
|
|
|
+ if (empty($application)) {
|
|
|
+ util::fail('没有找到进件申请');
|
|
|
+ }
|
|
|
+ return $application;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function merchantInfoTermMap($value)
|
|
|
+ {
|
|
|
+ $result = [];
|
|
|
+ $terminalInfos = $value['terminal_info'];
|
|
|
+ foreach($terminalInfos as $term) {
|
|
|
+ if ($term['term_type_name'] == '专业化扫码') {
|
|
|
+ $result['scanTermNo'][] = $term['active_no_vo_list'][0]['term_no'];
|
|
|
+ }
|
|
|
+ if ($term['term_type_name'] == '聚合收银台') {
|
|
|
+ $list = $term['active_no_vo_list'];
|
|
|
+ foreach($list as $item) {
|
|
|
+ if ($item['busi_type_code'] == 'QR_CODE_CARD') {
|
|
|
+ $result['b2BTermNo'][] = $item['term_no'];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function encode($value)
|
|
|
+ {
|
|
|
+ return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static function decode($value)
|
|
|
+ {
|
|
|
+ if (is_array($value)) return $value;
|
|
|
+ $decoded = json_decode((string)$value, true);
|
|
|
+ return is_array($decoded) ? $decoded : [];
|
|
|
+ }
|
|
|
+}
|