diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index df2f808..ea52a85 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -52,8 +52,8 @@ class LoginController extends Controller if (Auth::attempt($credentials)) { // 通过认证.. return response()->json(['token' => $request->user()->api_token]); - }else { - return response()->json(['error' => 'auth login fail']); } + + return response()->json(['error' => 'auth login fail']); } } diff --git a/app/Http/Controllers/Shop/ShopsController.php b/app/Http/Controllers/Shop/ShopsController.php index f4fd384..5ec522d 100644 --- a/app/Http/Controllers/Shop/ShopsController.php +++ b/app/Http/Controllers/Shop/ShopsController.php @@ -7,12 +7,20 @@ use App\Models\Shop; use App\Http\Resources\ShopsResource; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; +use App\Services\Business\BusinessFactory; +use Illuminate\Validation\Rule; class ShopsController extends Controller { public function index() { $shops = Shop::query()->paginate(); + foreach ($shops as $shop) { + $shop->authUrl = ''; + if ('妙选' !== $shop->plat_id && '未授权' === $shop->status) { + $shop->authUrl = BusinessFactory::init()->make($shop->plat_id)->getAuthUrl($shop->id, $shop->getOriginal('plat_id')); + } + } return ShopsResource::collection($shops); } @@ -42,4 +50,46 @@ class ShopsController extends Controller return response($this->res, $this->res['httpCode']); } + + public function authBind(Request $request) + { + [$shopId, $platId] = explode('_', $request->get('state')); + $shop = new Shop(); + $platList = $shop->getPlatList(); + $shop = $shop->find($shopId); + if ($platList[$platId] === $shop->plat_id) { + BusinessFactory::init()->make($shop->plat_id)->authCallback($request->get('code'), $shop->id); + } else { + $this->res = [ + 'httpCode' => 403, + 'errorCode' => 403400, + 'errorMessage' => '信息不匹配', + ]; + } + + return response($this->res, $this->res['httpCode']); + } + + public function business(Request $request) + { + $validator = Validator::make($request->all(), [ + 'type' => ['required', 'string', Rule::in(['goods', 'orders'])], + 'erp_shop_id' => ['required', 'integer', 'exists:shops,id'], + ]); + if ($validator->fails()) { + $this->setValidatorFailResponse($validator->getMessageBag()->getMessages()); + + return response($this->res, $this->res['httpCode']); + } + $shop = new Shop(); + $shop = $shop->find($request->get('erp_shop_id')); + $business = BusinessFactory::init()->make($shop->plat_id); + $business->setShop($shop); + if ('goods' === $request->get('type')) { + $business->downloadGoodsAndBind($request->get('data')); + } + if ('orders' === $request->get('type')) { + $business->downloadOrders(); + } + } } diff --git a/app/Models/BusinessGoodsSku.php b/app/Models/BusinessGoodsSku.php new file mode 100644 index 0000000..468332d --- /dev/null +++ b/app/Models/BusinessGoodsSku.php @@ -0,0 +1,13 @@ +setCode($code); + $this->setShop($shopId); + $this->auth(); + + return $this; + } + + public function setShopWithId($shopId) + { + $this->shop = Shop::query()->find($shopId); + + return $this; + } + + public function setShop(Shop $shop) + { + $this->shop = $shop; + + return $this; + } + + public function getShop() + { + return $this->shop; + } + + protected function setCode($code) + { + $this->code = $code; + + return $this; + } + + protected function getCode() + { + return $this->code; + } + + public function formDataPostRequest($url, $params) + { + $headers = [ + 'headers' => ['Content-type' => 'multipart/form-data;charset=UTF-8'], + 'form_params' => $params + ]; + $res = (new Client())->makeCurlRequest('POST', $url, $headers); + $res = json_decode($res->getBody()->getContents(), true); + if (isset($res['error_response'])) { + $log = new Log(); + $log->module = 'plat'; + $log->action = 'POST'; + $log->target_type = 'kuaituantuan'; + $log->target_id = 0; + $log->target_field = '更新库存'; + $log->user_id = 1; + $log->message = json_encode($res, 256); + $log->save(); + + throw new \Exception($res['error_response']['error_msg'], $res['error_response']['error_code']); + } + + return $res; + } +} diff --git a/app/Services/Business/BusinessFactory.php b/app/Services/Business/BusinessFactory.php new file mode 100644 index 0000000..b34277c --- /dev/null +++ b/app/Services/Business/BusinessFactory.php @@ -0,0 +1,25 @@ +platList['快团团'] = KuaiTuanTuan::class; + } + + public function make($platName) + { + return new $this->platList[$platName]; + } + + public static function init() + { + return new self(); + } +} diff --git a/app/Services/Business/KuaiTuanTuan/Goods.php b/app/Services/Business/KuaiTuanTuan/Goods.php new file mode 100644 index 0000000..7f4e540 --- /dev/null +++ b/app/Services/Business/KuaiTuanTuan/Goods.php @@ -0,0 +1,63 @@ + $activityNo, // 非必填,团号(团号和创建时间只能传一个) + 'page' => $page, + 'size' => $size, +// 'update_time_end' => '', // 非必填,结束最后更新时间(毫秒级时间戳) +// 'update_time_start' => '', // 非必填,起始最后更新时间(毫秒级时间戳) +// 'create_time_end' => '', // 非必填,开始时间结束(毫秒级时间戳) +// 'create_time_start' => '' // 非必填, 开始时间起始(毫秒级时间戳) + ]; + + return [$type, $appendParams]; + } + + public static function bindGoods(array $goodsList, $shopId) + { + foreach ($goodsList as $businessGood) { + $skuList = $businessGood['sku_list']; + unset($businessGood['sku_list']); + foreach ($skuList as $sku) { + $sku['spec_list'] = json_encode($sku['spec_list'], 256); + $data = array_merge($businessGood, $sku); + BusinessGoodsSku::updateOrCreate( + ['shop_id' => $shopId, 'activity_no' => $businessGood['activity_no'], 'goods_id' => $businessGood['goods_id'], 'sku_id' => $sku['sku_id']], + $data + ); + } + } + } + + public function downloadSingle($goodsId) + { + $type = 'pdd.ktt.goods.query.single'; + $appendParams = [ + 'goods_id' => $goodsId + ]; + } + + public static function incrQuantity($goodsId, $quantity, $skuId, $modifyType = 2) + { + $type = 'pdd.ktt.goods.incr.quantity'; + $appendParams = [ + 'goods_id' => $goodsId, + 'quantity_delta' => $quantity, + 'sku_id' => $skuId, + // 非必填 + 'modify_quantity_type' => $modifyType, // 修改库存的类型,不传或1代表增量修改,2代表全量修改 + ]; + + return [$type, $appendParams]; + } +} + diff --git a/app/Services/Business/KuaiTuanTuan/KuaiTuanTuan.php b/app/Services/Business/KuaiTuanTuan/KuaiTuanTuan.php new file mode 100644 index 0000000..60dd313 --- /dev/null +++ b/app/Services/Business/KuaiTuanTuan/KuaiTuanTuan.php @@ -0,0 +1,137 @@ + '', + 'client_id' => '', + 'access_token' => '', // 非必填,通过code获取的access_token + 'timestamp' => '', + 'data_type' => '', // 非必填,响应格式,即返回数据的格式,JSON或者XML(二选一),默认JSON,注意是大写 + 'version' => '', // 非必填, API协议版本号,默认为V1,可不填 + 'sign' => '' + ]; + + public function __construct() + { + } + + public function getAuthUrl($shopId, $platId) + { + $state = $shopId . '_' . $platId; + + return "https://oauth.pinduoduo.com/authorize/ktt?client_id={$this->clientId}&redirect_uri={$this->redirectUri}&state={$state}"; + } + + public function auth() + { + $accessToken = $this->getAccessTokenWithCode(); + $accessToken['pop_auth_token_create_response'] = json_encode($accessToken, 256); + $this->shop->update($accessToken); + + return $this->shop; + } + + public function getAccessTokenWithCode() + { + $type = 'pdd.pop.auth.token.create'; + $res = $this->request($type, ['code' => $this->code]); + + return $res['pop_auth_token_create_response']; + } + + public function getAccessToken() + { + return ''; + } + + public function getSign($params) + { + $params = ksort($params); + $str = ''; + foreach ($params as $key => $val) { + $str .= $key . $val; + } + $str = $this->clientSecret . $str . $this->clientSecret; + + return strtoupper(md5($str)); + } + + public function request($type, $appendParams = [], $url = 'http://gw-api.pinduoduo.com/api/router') + { + $publicParams = [ + 'type' => $type, + 'client_id' => $this->clientId, + 'timestamp' => time() + ]; + if ('pdd.pop.auth.token.create' !== $type) { + $publicParams['access_token'] = $this->getAccessToken(); + } + $publicParams = array_merge($publicParams, $appendParams); + $publicParams['sign'] = $this->getSign($publicParams); + + return $this->formDataPostRequest($url, $publicParams); + } + + public function downloadGoodsAndBind($page = 1) + { + [$type, $appendParams] = Goods::downloadGoods($this->shop->owner_name, $page); + $res = $this->formDataPostRequest($type, $appendParams); + $goods = $res['ktt_goods_query_list_response']['goods_list']; + Goods::bindGoods($goods, $this->shop->id); + $pageNum = ceil($res['total'] / $appendParams['size']); + if ($pageNum > $page && 10 >= $page) { + $this->downloadGoodsAndBind($page + 1); + } + } + + public function incrQuantity($skuId) + { + $goodsSku = GoodsSku::query()->find($skuId); + $business = BusinessGoodsSku::query()->where('shop_id', $this->shop->id)->where('self_sku_id', $skuId)->first(['goods_id', 'sku_id']); + [$type, $appendParams] = Goods::incrQuantity($business->goodsId, $goodsSku->stock, $business->sku_id); + $this->formDataPostRequest($type, $appendParams); + $log = new Log(); + $log->module = 'plat'; + $log->action = 'POST'; + $log->target_type = 'kuaituantuan'; + $log->target_id = 0; + $log->target_field = $type; + $log->user_id = 1; + $log->message = 'success'; + $log->save(); + } + + public function downloadOrders($beginTime, $endTime, $activityNo, $downloadType = 'default') + { + if ('increment' === $downloadType) { + [$type, $appendParams] = Order::downloadIncrementOrders($beginTime, $endTime, $activityNo); + } else { + [$type, $appendParams] = Order::downloadOrders($beginTime, $endTime, $activityNo); + } + $res = $this->formDataPostRequest($type, $appendParams); + + + return $res['ktt_order_list_response']['order_list']; + } + + public function bindGoods($goods) + { + // TODO: Implement bindGoods() method. + } +} diff --git a/app/Services/Business/KuaiTuanTuan/Order.php b/app/Services/Business/KuaiTuanTuan/Order.php new file mode 100644 index 0000000..fed5f51 --- /dev/null +++ b/app/Services/Business/KuaiTuanTuan/Order.php @@ -0,0 +1,70 @@ + $beginTime, // 成交启始时间, 必填,毫秒时间戳 + 'confirm_at_end' => $endTime, // 成交结束时间,必填, 毫秒时间戳,成交结束时间 - 成交启始时间 <= 24h + 'page_number' => 1, // 页码, 必填 + 'page_size' => 100, // 数量, 必填, 1~100 + // 非必填 + 'activity_no' => $activityNo, // 团号 + 'after_sales_status' => 0, // 售后状态, 可选 0-未发起售后 1-退款中 2-退款成功 3-待处理 4-拒绝退款 6-待(顾客)退货 7-待(团长)确认退货 8-(顾客)撤销 9-(系统)关闭 + 'cancel_status' => 0, // 取消状态, 可选 0-未取消 1-已取消 +// 'shipping_status' => '', // 发货状态 0-未发货 1-已发货 2-部分发货 3-已收货 +// 'verification_status' => '', // 核销状态, 可选 0-未核销 1-已核销 2-部分核销 + ]; + + return [$type, $appendParams]; + } + + /** + * 快团团增量查订单 + */ + public static function downloadIncrementOrders($beginTime, $endTime, $activityNo) + { + $type = 'pdd.ktt.increment.order.query'; + $appendParams = [ + 'start_updated_at' => $beginTime, // 更新起始时间 + 'end_updated_at' => $endTime, // 更新结束时间 + 'page_number' => 1, // 页码 + 'page_size' => 100, // 数量 + // 非必填 + 'activity_no' => $activityNo, // 团号 + 'after_sales_status' => 0, // 售后状态, 可选 0-未发起售后 1-退款中 2-退款成功 3-待处理 4-拒绝退款 6-待(顾客)退货 7-待(团长)确认退货 8-(顾客)撤销 9-(系统)关闭 + 'cancel_status' => 0, // 取消状态, 可选 0-未取消 1-已取消 +// 'shipping_status' => '', // 发货状态 0-未发货 1-已发货 2-部分发货 3-已收货 +// 'verification_status' => '', // 核销状态, 可选 0-未核销 1-已核销 2-部分核销 + ]; + + return [$type, $appendParams]; + } + + // 下载订单事件之后去统计 然后更新库存 + public static function saveOrders(array $orders, $shopId) + { + foreach ($orders as $order) { + $orderRecord = BusinessOrder::updateOrCreate( + ['shop_id' => $shopId, 'order_sn' => $order['order_sn']], + $order + ); + foreach ($order['sub_order_list'] as $item) { + $orderRecord = BusinessOrder::updateOrCreate( + ['shop_id' => $shopId, 'business_order_id' => $orderRecord->id, 'goods_id' => $item['goods_id'], 'sku_id' => $item['sku_id']], + $item + ); + } + } + } +} + diff --git a/app/Services/Business/MiaoXuan/Goods.php b/app/Services/Business/MiaoXuan/Goods.php new file mode 100644 index 0000000..06ceddf --- /dev/null +++ b/app/Services/Business/MiaoXuan/Goods.php @@ -0,0 +1,30 @@ + $shopId, 'goods_id' => $item['goods_id'], 'sku_id' => $item['sku_id']], + $item + ); + } + } + + public static function incrQuantity($shopId, $quantity, $businessGoods) + { + return [ + 'stock' => $quantity, + 'business_sku_id' => $businessGoods['sku_id'], + 'business_goods_id' => $businessGoods['goods_id'], + 'erp_shop_id' => $shopId, + 'erp_sku_id' => $businessGoods['external_sku_id'] + ]; + } +} + diff --git a/app/Services/Business/MiaoXuan/MiaoXuan.php b/app/Services/Business/MiaoXuan/MiaoXuan.php new file mode 100644 index 0000000..041d2de --- /dev/null +++ b/app/Services/Business/MiaoXuan/MiaoXuan.php @@ -0,0 +1,49 @@ +find($skuId); + $business = BusinessGoodsSku::query()->where('shop_id', $this->shop->id)->where('self_sku_id', $skuId)->first(['goods_id', 'sku_id', 'external_sku_id'])->toArray(); + $appendParams = Goods::incrQuantity($this->shop->id, $goodsSku->stock, $business); + + $this->formDataPostRequest($type, $appendParams); + $log = new Log(); + $log->module = 'plat'; + $log->action = 'POST'; + $log->target_type = 'miaoxuan'; + $log->target_id = 0; + $log->target_field = '更新库存'; + $log->user_id = 1; + $log->message = 'success'; + $log->save(); + } + + public function downloadOrders($beginTime, $endTime, $activityNo, $downloadType = 'default') + { + Order::saveOrders($beginTime, $endTime, $activityNo); + } + + public function bindGoods($goods) + { + Goods::bindGoods($goods, $this->shop->id); + } +} diff --git a/app/Services/Business/MiaoXuan/Order.php b/app/Services/Business/MiaoXuan/Order.php new file mode 100644 index 0000000..c8489a4 --- /dev/null +++ b/app/Services/Business/MiaoXuan/Order.php @@ -0,0 +1,26 @@ + $shopId, 'order_sn' => $order['order_sn']], + $order + ); + foreach ($order['sub_order_list'] as $item) { + $orderRecord = BusinessOrder::updateOrCreate( + ['shop_id' => $shopId, 'business_order_id' => $orderRecord->id, 'goods_id' => $item['goods_id'], 'sku_id' => $item['sku_id']], + $item + ); + } + } + } +} + diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index fa3372e..cb1446a 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -22,7 +22,7 @@ $factory->define(User::class, function (Faker $faker) { 'name' => 'erpAdmin', 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), - 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password + 'password' => 'password', // password 'api_token' => Str::random(60), 'remember_token' => Str::random(10), ]; diff --git a/database/migrations/2022_08_05_030834_create_business_goods_skus_table.php b/database/migrations/2022_08_05_030834_create_business_goods_skus_table.php new file mode 100644 index 0000000..8dce10b --- /dev/null +++ b/database/migrations/2022_08_05_030834_create_business_goods_skus_table.php @@ -0,0 +1,56 @@ +bigIncrements('id'); + $table->bigInteger('shop_id'); + $table->bigInteger('self_sku_id')->nullable(); + $table->string('activity_no')->nullable(); + $table->string('category_name')->nullable(); + $table->mediumInteger('create_time')->nullable(); + $table->text('goods_desc')->nullable(); + $table->string('goods_id')->nullable(); + $table->text('goods_image_list')->nullable(); + $table->string('goods_name')->nullable(); + $table->integer('is_activity_delete')->nullable(); + $table->integer('limit_buy')->nullable(); + $table->mediumInteger('market_price')->nullable(); + $table->mediumInteger('update_time')->nullable(); + $table->string('external_sku_id')->nullable(); + $table->mediumInteger('goods_purchase_price')->nullable(); + $table->mediumInteger('price_in_fen')->nullable(); + $table->mediumInteger('quantity')->nullable(); + $table->integer('quantity_type')->nullable(); + $table->mediumInteger('reserve_quantity')->nullable(); + $table->mediumInteger('sku_id')->nullable(); + $table->mediumInteger('sold_quantity')->nullable(); + $table->text('spec_list')->nullable(); + $table->string('spec_name')->nullable(); + $table->string('thumb_url')->nullable(); + $table->mediumInteger('total_quantity')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('business_goods'); + } +} diff --git a/database/migrations/2022_08_05_093629_create_business_orders_table.php b/database/migrations/2022_08_05_093629_create_business_orders_table.php new file mode 100644 index 0000000..b5b0311 --- /dev/null +++ b/database/migrations/2022_08_05_093629_create_business_orders_table.php @@ -0,0 +1,71 @@ +bigIncrements('id'); + $table->integer('shop_id'); + $table->bigInteger('activity_no')->nullable(); + $table->string('activity_title')->nullable(); + $table->mediumInteger('after_sales_status')->nullable(); + $table->string('business_note')->nullable(); + $table->string('buyer_memo')->nullable(); + $table->integer('cancel_status')->nullable(); + $table->mediumInteger('confirm_at')->nullable(); + $table->mediumInteger('discount_amount')->nullable(); + $table->string('help_sell_nickname')->nullable(); + $table->string('inner_transaction_id')->nullable(); + $table->boolean('is_supplier')->nullable(); + $table->integer('logistics_type')->nullable(); + $table->integer('mall_activity_type')->nullable(); + $table->string('nick_name')->nullable(); + $table->mediumInteger('order_amount')->nullable(); + $table->string('order_sn')->nullable(); + $table->integer('participate_no')->nullable(); + $table->mediumInteger('platform_discount_amount')->nullable(); + $table->string('receiver_address_city')->nullable(); + $table->string('receiver_address_detail')->nullable(); + $table->string('receiver_address_district')->nullable(); + $table->string('receiver_address_province')->nullable(); + $table->string('receiver_mobile')->nullable(); + $table->string('receiver_name')->nullable(); + $table->string('secret_remark')->nullable(); + $table->string('self_pick_site_no')->nullable(); + $table->string('self_pick_up_address')->nullable(); + $table->string('self_pick_up_contact_mobile')->nullable(); + $table->string('self_pick_up_contact_name')->nullable(); + $table->string('self_pick_up_site_name')->nullable(); + $table->mediumInteger('service_amount')->nullable(); + $table->mediumInteger('shipping_amount')->nullable(); + $table->integer('shipping_status')->nullable(); + $table->string('supply_activity_no')->nullable(); + $table->integer('supply_participate_no')->nullable(); + $table->mediumInteger('theoretical_refund_amount')->nullable(); + $table->string('transaction_id')->nullable(); + $table->mediumInteger('updated_at')->nullable(); + $table->integer('verification_status')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('business_orders'); + } +} diff --git a/database/migrations/2022_08_05_093658_create_business_order_items_table.php b/database/migrations/2022_08_05_093658_create_business_order_items_table.php new file mode 100644 index 0000000..325103c --- /dev/null +++ b/database/migrations/2022_08_05_093658_create_business_order_items_table.php @@ -0,0 +1,54 @@ +bigIncrements('id'); + $table->bigInteger('shop_id'); + $table->bigInteger('business_order_id'); + $table->integer('already_cancel_number')->nullable(); + $table->integer('cancel_status')->nullable(); + $table->string('category_name')->nullable(); + $table->string('external_sku_id')->nullable(); + $table->mediumInteger('goods_amount')->nullable(); + $table->mediumInteger('goods_cost_price')->nullable(); + $table->mediumInteger('goods_id')->nullable(); + $table->string('goods_name')->nullable(); + $table->integer('goods_number')->nullable(); + $table->mediumInteger('goods_price')->nullable(); + $table->mediumInteger('goods_purchase_price')->nullable(); + $table->string('goods_specification')->nullable(); + $table->mediumInteger('help_sell_amount')->nullable(); + $table->boolean('is_supplier')->nullable(); + $table->integer('need_verification_number')->nullable(); + $table->integer('shipping_status')->nullable(); + $table->mediumInteger('sku_id')->nullable(); + $table->string('sub_order_sn')->nullable(); + $table->mediumInteger('theoretically_refund_amount')->nullable(); + $table->string('thumb_url')->nullable(); + $table->integer('verification_number')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('business_order_items'); + } +} diff --git a/routes/api.php b/routes/api.php index 4a047f0..b322c4f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -35,7 +35,7 @@ Route::middleware(['auth:api', 'check.permissions'])->group(function () { Route::resource('shops', 'Shop\ShopsController', ['only' => ['index', 'store', 'show', 'update', 'destroy']]); // 角色 Route::resource('roles', 'Role\RolesController', ['only' => ['index', 'store', 'show', 'update']]); - Route::post('roles/{id}/permissions', [RolesController::class, 'addPermissions'])->name('role.permission'); + Route::post('roles/{id}/permissions', [RolesController::class, 'addPermissions'])->name('roles.permission'); // 权限 Route::resource('permissions', 'Permission\PermissionsController', ['only' => ['index', // 'store', 'show', 'update', 'destroy' @@ -49,4 +49,7 @@ Route::post('/auth/login', [LoginController::class, 'login'])->name('auth.login' Route::resource('menus', 'Menu\MenusController', ['only' => ['index', // 'store', 'show', 'update', 'destroy' ]])->middleware('auth:api'); + Route::get('shop_platforms', [ShopsController::class, 'getPlatList'])->name('plat.list')->middleware('auth:api'); + +Route::post('business', [ShopsController::class, 'business'])->name('shop.put.business'); diff --git a/routes/web.php b/routes/web.php index 20cc097..17deaf8 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,6 +2,7 @@ use App\Http\Controllers\Goods\GoodsSkusController; use App\Http\Controllers\Goods\GoodsController; +use App\Http\Controllers\Shop\ShopsController; /* |-------------------------------------------------------------------------- @@ -33,3 +34,5 @@ Route::get('/register', function () { Route::get('goods_skus/export', [GoodsSkusController::class, 'export'])->name('goods_skus.export')->middleware('check.permissions'); Route::get('goods/import/template', [GoodsController::class, 'download'])->name('download.goods_import.template'); + +Route::get('callback', [ShopsController::class, 'authBind'])->name('shop.auth_bind.callback');