diff --git a/plugins/Bestseller/Admin/View/DesignBuilders/Bestseller.php b/plugins/Bestseller/Admin/View/DesignBuilders/Bestseller.php new file mode 100644 index 00000000..fd236855 --- /dev/null +++ b/plugins/Bestseller/Admin/View/DesignBuilders/Bestseller.php @@ -0,0 +1,45 @@ + + * @created 2022-07-08 17:09:15 + * @modified 2022-07-08 17:09:15 + */ + +namespace Plugin\Bestseller\Admin\View\DesignBuilders; + +use Illuminate\Contracts\View\View; +use Illuminate\View\Component; + +class Bestseller extends Component +{ + /** + * Create a new component instance. + * + * @return void + */ + public function __construct() + { + } + + /** + * Get the view / contents that represent the component. + * + * @return View + */ + public function render(): View + { + $data['register'] = [ + 'code' => 'bestseller', + 'sort' => 0, + 'name' => trans('Bestseller::common.module_name'), + 'icon' => plugin_asset('Bestseller', 'image/icon.png'), + 'view_path' => 'Bestseller::shop/design_module_bestseller', + ]; + + return view('Bestseller::admin/design_module_bestseller', $data); + } +} diff --git a/plugins/Bestseller/Bootstrap.php b/plugins/Bestseller/Bootstrap.php new file mode 100644 index 00000000..4dee1b78 --- /dev/null +++ b/plugins/Bestseller/Bootstrap.php @@ -0,0 +1,43 @@ + + * @created 2022-07-20 15:35:59 + * @modified 2022-07-20 15:35:59 + */ + +namespace Plugin\Bestseller; + +use Plugin\Bestseller\Repositories\ProductRepo; + +class Bootstrap +{ + public function boot() + { + /** + * Add module for admin design. + */ + add_hook_filter('admin.design.index.data', function ($data) { + $data['editors'][] = 'editor-bestseller'; + + return $data; + }); + + /** + * Get module content for home page and preview. + */ + add_hook_filter('service.design.module.content', function ($data) { + $module = $data['module_code'] ?? ''; + + if ($module == 'bestseller') { + $data['title'] = $data['title'][locale()] ?? ''; + $data['products'] = ProductRepo::getBestSellerProducts($data['limit']); + } + + return $data; + }); + } +} diff --git a/plugins/Bestseller/Lang/en/common.php b/plugins/Bestseller/Lang/en/common.php new file mode 100644 index 00000000..bcc7a8e9 --- /dev/null +++ b/plugins/Bestseller/Lang/en/common.php @@ -0,0 +1,15 @@ + + * @created 2022-07-28 16:19:06 + * @modified 2022-07-28 16:19:06 + */ + +return [ + 'module_name' => 'Bestseller', + 'limit' => 'Limit', +]; diff --git a/plugins/Bestseller/Lang/zh_cn/common.php b/plugins/Bestseller/Lang/zh_cn/common.php new file mode 100644 index 00000000..e962884f --- /dev/null +++ b/plugins/Bestseller/Lang/zh_cn/common.php @@ -0,0 +1,15 @@ + + * @created 2022-07-28 16:19:06 + * @modified 2022-07-28 16:19:06 + */ + +return [ + 'module_name' => '热卖模块', + 'limit' => '数量限制', +]; diff --git a/plugins/Bestseller/Repositories/ProductRepo.php b/plugins/Bestseller/Repositories/ProductRepo.php new file mode 100644 index 00000000..525ca388 --- /dev/null +++ b/plugins/Bestseller/Repositories/ProductRepo.php @@ -0,0 +1,36 @@ + + * @created 2023-03-08 11:56:17 + * @modified 2023-03-08 11:56:17 + */ + +namespace Plugin\Bestseller\Repositories; + +use Beike\Shop\Http\Resources\ProductSimple; + +class ProductRepo +{ + /** + * Get best seller + * + * @param $limit + * @return array + */ + public static function getBestSellerProducts($limit): array + { + $products = \Beike\Repositories\ProductRepo::getBuilder([ + 'active' => 1, + 'sort' => 'products.sales', + 'order' => 'desc', + ]) + ->whereHas('masterSku') + ->limit($limit)->get(); + + return ProductSimple::collection($products)->jsonSerialize(); + } +} diff --git a/plugins/Bestseller/Static/image/icon.png b/plugins/Bestseller/Static/image/icon.png new file mode 100644 index 00000000..00aafdab Binary files /dev/null and b/plugins/Bestseller/Static/image/icon.png differ diff --git a/plugins/Bestseller/Static/image/logo.png b/plugins/Bestseller/Static/image/logo.png new file mode 100644 index 00000000..cd2f2986 Binary files /dev/null and b/plugins/Bestseller/Static/image/logo.png differ diff --git a/plugins/Bestseller/Views/admin/design_module_bestseller.blade.php b/plugins/Bestseller/Views/admin/design_module_bestseller.blade.php new file mode 100644 index 00000000..f0bfb050 --- /dev/null +++ b/plugins/Bestseller/Views/admin/design_module_bestseller.blade.php @@ -0,0 +1,54 @@ + + + + +{{-- 定义模块的配置项 --}} +@push('footer-script') + +@endpush + diff --git a/plugins/Bestseller/Views/shop/design_module_bestseller.blade.php b/plugins/Bestseller/Views/shop/design_module_bestseller.blade.php new file mode 100644 index 00000000..9bba7f74 --- /dev/null +++ b/plugins/Bestseller/Views/shop/design_module_bestseller.blade.php @@ -0,0 +1,67 @@ +
+ @include('design._partial._module_tool') +
+
+
{{ $content['title'] }}
+ @if ($content['products']) +
+
+ @foreach ($content['products'] as $product) +
+ @include('shared.product') +
+ @endforeach +
+
+
+
+
+ @elseif (!$content['products'] and $design) +
+ @for ($s = 0; $s < 4; $s++) +
+
+
+
请配置商品
+
+ 66.66 + 99.99 +
+
+
+ @endfor +
+ @endif +
+
+ + +
diff --git a/plugins/Bestseller/config.json b/plugins/Bestseller/config.json new file mode 100644 index 00000000..f4ae6961 --- /dev/null +++ b/plugins/Bestseller/config.json @@ -0,0 +1,18 @@ +{ + "code": "bestseller", + "name": { + "zh_cn": "热卖商品模块", + "en": "Hot Items Module" + }, + "description": { + "zh_cn": "首页装修热卖商品模块", + "en": "Home Decoration Hot Products Module" + }, + "type": "feature", + "version": "v1.0.0", + "icon": "/image/logo.png", + "author": { + "name": "成都光大网络科技有限公司", + "email": "yangjin@guangda.work" + } +} diff --git a/plugins/FlatShipping/Bootstrap.php b/plugins/FlatShipping/Bootstrap.php new file mode 100644 index 00000000..21c5371b --- /dev/null +++ b/plugins/FlatShipping/Bootstrap.php @@ -0,0 +1,65 @@ + + * @created 2022-07-20 15:35:59 + * @modified 2022-07-20 15:35:59 + */ + +namespace Plugin\FlatShipping; + +use Beike\Admin\Http\Resources\PluginResource; +use Beike\Plugin\Plugin; +use Beike\Shop\Services\CheckoutService; + +class Bootstrap +{ + /** + * 获取固定运费方式 + * + * @param CheckoutService $checkout + * @param Plugin $plugin + * @return array + * @throws \Exception + */ + public function getQuotes(CheckoutService $checkout, Plugin $plugin): array + { + $code = $plugin->code; + $pluginResource = (new PluginResource($plugin))->jsonSerialize(); + $quotes[] = [ + 'type' => 'shipping', + 'code' => "{$code}.0", + 'name' => $pluginResource['name'], + 'description' => $pluginResource['description'], + 'icon' => $pluginResource['icon'], + 'cost' => $this->getShippingFee($checkout), + ]; + + return $quotes; + } + + /** + * 计算固定运费 + * + * @param CheckoutService $checkout + * @return float|int + */ + public function getShippingFee(CheckoutService $checkout): float|int + { + $totalService = $checkout->totalService; + $amount = $totalService->amount; + $shippingType = plugin_setting('flat_shipping.type', 'fixed'); + $shippingValue = plugin_setting('flat_shipping.value', 0); + if ($shippingType == 'fixed') { + return $shippingValue; + } elseif ($shippingType == 'percent') { + return $amount * $shippingValue / 100; + } + + return 0; + + } +} diff --git a/plugins/FlatShipping/Lang/en/common.php b/plugins/FlatShipping/Lang/en/common.php new file mode 100644 index 00000000..7461686f --- /dev/null +++ b/plugins/FlatShipping/Lang/en/common.php @@ -0,0 +1,17 @@ + + * @created 2022-07-28 16:19:06 + * @modified 2022-07-28 16:19:06 + */ + +return [ + 'way' => 'Way', + 'flat_shipping' => 'Flat Shipping', + 'percentage' => 'Percentage', + 'shipping_value' => 'Shipping Value', +]; diff --git a/plugins/FlatShipping/Lang/zh_cn/common.php b/plugins/FlatShipping/Lang/zh_cn/common.php new file mode 100644 index 00000000..a7d395a0 --- /dev/null +++ b/plugins/FlatShipping/Lang/zh_cn/common.php @@ -0,0 +1,17 @@ + + * @created 2022-07-28 16:19:06 + * @modified 2022-07-28 16:19:06 + */ + +return [ + 'way' => '方式', + 'flat_shipping' => '固定运费', + 'percentage' => '百分比', + 'shipping_value' => '运费值', +]; diff --git a/public/plugin/alipay/css/demo.css b/plugins/FlatShipping/Static/css/demo.css similarity index 100% rename from public/plugin/alipay/css/demo.css rename to plugins/FlatShipping/Static/css/demo.css diff --git a/public/plugin/flat_shipping/image/logo.png b/plugins/FlatShipping/Static/image/logo.png similarity index 100% rename from public/plugin/flat_shipping/image/logo.png rename to plugins/FlatShipping/Static/image/logo.png diff --git a/public/plugin/alipay/js/demo.js b/plugins/FlatShipping/Static/js/demo.js similarity index 100% rename from public/plugin/alipay/js/demo.js rename to plugins/FlatShipping/Static/js/demo.js diff --git a/plugins/FlatShipping/columns.php b/plugins/FlatShipping/columns.php new file mode 100644 index 00000000..ed2e24f7 --- /dev/null +++ b/plugins/FlatShipping/columns.php @@ -0,0 +1,29 @@ + + * @created 2022-06-29 21:16:23 + * @modified 2022-06-29 21:16:23 + */ + +return [ + [ + 'name' => 'type', + 'label_key' => 'common.flat_shipping', + 'type' => 'select', + 'options' => [ + ['value' => 'fixed', 'label_key' => 'common.flat_shipping'], + ['value' => 'percent', 'label_key' => 'common.percentage'], + ], + 'required' => true, + ], + [ + 'name' => 'value', + 'label_key' => 'common.shipping_value', + 'type' => 'string', + 'required' => true, + ], +]; diff --git a/plugins/FlatShipping/config.json b/plugins/FlatShipping/config.json new file mode 100644 index 00000000..3cd9300c --- /dev/null +++ b/plugins/FlatShipping/config.json @@ -0,0 +1,18 @@ +{ + "code": "flat_shipping", + "name": { + "zh_cn": "固定运费", + "en": "Flat Rate Shipping" + }, + "description": { + "zh_cn": "按订单总额收取固定运费", + "en": "Fixed shipping fee by order total" + }, + "type": "shipping", + "version": "v1.0.0", + "icon": "/image/logo.png", + "author": { + "name": "成都光大网络科技有限公司", + "email": "yangjin@guangda.work" + } +} diff --git a/plugins/LatestProducts/Bootstrap.php b/plugins/LatestProducts/Bootstrap.php new file mode 100644 index 00000000..bbfa640d --- /dev/null +++ b/plugins/LatestProducts/Bootstrap.php @@ -0,0 +1,149 @@ + + * @created 2022-07-20 15:35:59 + * @modified 2022-07-20 15:35:59 + */ + +namespace Plugin\LatestProducts; + +class Bootstrap +{ + /** + * 去除注释后可以观察网站头部以及产品详情页页面变化 + */ + public function boot() + { + $this->addLatestProducts(); + + // $this->modifyHeader(); + // $this->modifyProductDetail(); + + // $this->modifyAdminProductEdit(); + // $this->modifySetting(); + // $this->handlePaidOrder(); + } + + /** + * 在前台网页头部添加二级菜单链接 + */ + private function addLatestProducts() + { + add_hook_filter('menu.content', function ($data) { + $data[] = [ + 'name' => trans('LatestProducts::header.latest_products'), + 'link' => shop_route('latest_products'), + ]; + + return $data; + }, 0); + } + + /** + * 修改前台全局 header 模板演示 + */ + private function modifyHeader() + { + add_hook_blade('header.top.currency', function ($callback, $output, $data) { + return '货币前' . $output; + }); + + add_hook_blade('header.top.language', function ($callback, $output, $data) { + return $output . '语言后'; + }); + + add_hook_blade('header.top.telephone', function ($callback, $output, $data) { + return '电话前' . $output; + }); + + add_hook_blade('header.menu.logo', function ($callback, $output, $data) { + return $output . 'Logo后'; + }); + + add_hook_blade('header.menu.icon', function ($callback, $output, $data) { + $view = view('LatestProducts::shop.header_icon')->render(); + + return $output . $view; + }); + } + + /** + * 修改产品详情页演示 + * 1. 通过数据 hook 修改产品详情页产品名称 + * 2. 通过模板 hook 在产品详情页名称上面添加 Hot 标签 + * 3. 通过模板 hook 在产品详情页品牌下面添加信息 + * 4. 通过模板 hook 在产品详情页立即购买后添加按钮 + */ + private function modifyProductDetail() + { + // 通过数据 hook 修改产品详情页产品名称 + add_hook_filter('product.show.data', function ($product) { + $product['product']['name'] = '[疯狂热销]' . $product['product']['name']; + + return $product; + }); + + // 通过模板 hook 在产品详情页名称上面添加 Hot 标签 + add_hook_blade('product.detail.name', function ($callback, $output, $data) { + $badge = 'Hot'; + + return $badge . $output; + }); + + // 通过模板 hook 在产品详情页品牌下面添加信息 + add_hook_blade('product.detail.brand', function ($callback, $output, $data) { + return $output . '
Brand 2:品牌 2
'; + }); + + // 通过模板 hook 在产品详情页立即购买后添加按钮 + add_hook_blade('product.detail.buy.after', function ($callback, $output, $data) { + $view = ''; + + return $output . $view; + }); + } + + /** + * 后台产品编辑页添加自定义字段演示 + */ + private function modifyAdminProductEdit() + { + add_hook_blade('admin.product.edit.extra', function ($callback, $output, $data) { + $view = view('LatestProducts::admin.product.edit_extra_field', $data)->render(); + + return $output . $view; + }, 1); + } + + /** + * 系统设置添加新 tab + */ + private function modifySetting() + { + add_hook_blade('admin.setting.nav.after', function ($callback, $output, $data) { + return view('LatestProducts::admin.setting.nav')->render(); + }); + + add_hook_blade('admin.setting.after', function ($callback, $output, $data) { + return view('LatestProducts::admin.setting.tab')->render(); + }); + } + + /** + * 修改订单状态机流程演示 + */ + private function handlePaidOrder() + { + add_hook_filter('service.state_machine.machines', function ($data) { + $data['machines']['unpaid']['paid'][] = function () { + // 这里写订单由 unpaid 变为 paid 执行的逻辑 + }; + + return $data; + }, 0); + } +} diff --git a/plugins/LatestProducts/Controllers/MenusController.php b/plugins/LatestProducts/Controllers/MenusController.php new file mode 100644 index 00000000..44fd5e19 --- /dev/null +++ b/plugins/LatestProducts/Controllers/MenusController.php @@ -0,0 +1,48 @@ + + * @created 2022-07-21 10:00:25 + * @modified 2022-07-21 10:00:25 + */ + +namespace Plugin\LatestProducts\Controllers; + +use Beike\Repositories\ProductRepo; +use Beike\Shop\Http\Controllers\Controller; +use Beike\Shop\Http\Resources\ProductSimple; + +class MenusController extends Controller +{ + public function getRoutes(): array + { + return [ + 'method' => __METHOD__, + 'route_list' => [], + ]; + } + + public function latestProducts() + { + $products = ProductRepo::getBuilder( + [ + // 'active' => 1, + 'sort' => 'created_at', + 'order' => 'desc', + ]) + ->whereHas('masterSku') + ->with('inCurrentWishlist') + ->orderByDesc('created_at') + ->paginate(perPage()); + + $data = [ + 'products' => $products, + 'items' => ProductSimple::collection($products)->jsonSerialize(), + ]; + + return view('LatestProducts::shop.latest_products', $data); + } +} diff --git a/plugins/LatestProducts/Lang/en/header.php b/plugins/LatestProducts/Lang/en/header.php new file mode 100644 index 00000000..6d49946c --- /dev/null +++ b/plugins/LatestProducts/Lang/en/header.php @@ -0,0 +1,14 @@ + + * @created 2022-07-28 16:19:06 + * @modified 2022-07-28 16:19:06 + */ + +return [ + 'latest_products' => 'Latest Products', +]; diff --git a/plugins/LatestProducts/Lang/zh_cn/header.php b/plugins/LatestProducts/Lang/zh_cn/header.php new file mode 100644 index 00000000..5b82feb6 --- /dev/null +++ b/plugins/LatestProducts/Lang/zh_cn/header.php @@ -0,0 +1,14 @@ + + * @created 2022-07-28 16:19:06 + * @modified 2022-07-28 16:19:06 + */ + +return [ + 'latest_products' => '最新商品', +]; diff --git a/plugins/LatestProducts/Routes/admin.php b/plugins/LatestProducts/Routes/admin.php new file mode 100644 index 00000000..07f85855 --- /dev/null +++ b/plugins/LatestProducts/Routes/admin.php @@ -0,0 +1,15 @@ + + * @created 2022-08-04 16:17:53 + * @modified 2022-08-04 16:17:53 + */ + +use Illuminate\Support\Facades\Route; +use Plugin\LatestProducts\Controllers\MenusController; + +Route::get('/routes', [MenusController::class, 'getRoutes'])->name('routes'); diff --git a/plugins/LatestProducts/Routes/shop.php b/plugins/LatestProducts/Routes/shop.php new file mode 100644 index 00000000..9bf997cb --- /dev/null +++ b/plugins/LatestProducts/Routes/shop.php @@ -0,0 +1,15 @@ + + * @created 2022-08-04 16:17:44 + * @modified 2022-08-04 16:17:44 + */ + +use Illuminate\Support\Facades\Route; +use Plugin\LatestProducts\Controllers\MenusController; + +Route::get('/latest_products', [MenusController::class, 'latestProducts'])->name('latest_products'); diff --git a/public/plugin/flat_shipping/css/demo.css b/plugins/LatestProducts/Static/css/demo.css similarity index 100% rename from public/plugin/flat_shipping/css/demo.css rename to plugins/LatestProducts/Static/css/demo.css diff --git a/plugins/LatestProducts/Static/image/logo.png b/plugins/LatestProducts/Static/image/logo.png new file mode 100644 index 00000000..37ff0126 Binary files /dev/null and b/plugins/LatestProducts/Static/image/logo.png differ diff --git a/public/plugin/flat_shipping/js/demo.js b/plugins/LatestProducts/Static/js/demo.js similarity index 100% rename from public/plugin/flat_shipping/js/demo.js rename to plugins/LatestProducts/Static/js/demo.js diff --git a/plugins/LatestProducts/Views/admin/product/edit_extra_field.blade.php b/plugins/LatestProducts/Views/admin/product/edit_extra_field.blade.php new file mode 100644 index 00000000..f0bad4da --- /dev/null +++ b/plugins/LatestProducts/Views/admin/product/edit_extra_field.blade.php @@ -0,0 +1 @@ + diff --git a/plugins/LatestProducts/Views/shop/header_icon.blade.php b/plugins/LatestProducts/Views/shop/header_icon.blade.php new file mode 100644 index 00000000..fa2210f4 --- /dev/null +++ b/plugins/LatestProducts/Views/shop/header_icon.blade.php @@ -0,0 +1,3 @@ + diff --git a/plugins/LatestProducts/Views/shop/latest_products.blade.php b/plugins/LatestProducts/Views/shop/latest_products.blade.php new file mode 100644 index 00000000..7cec3144 --- /dev/null +++ b/plugins/LatestProducts/Views/shop/latest_products.blade.php @@ -0,0 +1,21 @@ +@extends('layout.master') +@section('content') + +
+
+ @foreach ($items as $product) +
@include('shared.product')
+ @endforeach +
+ {{ $products->links('shared/pagination/bootstrap-4') }} +
+@endsection diff --git a/plugins/LatestProducts/Views/shop/product_button.blade.php b/plugins/LatestProducts/Views/shop/product_button.blade.php new file mode 100644 index 00000000..f95f97e2 --- /dev/null +++ b/plugins/LatestProducts/Views/shop/product_button.blade.php @@ -0,0 +1,3 @@ + diff --git a/plugins/LatestProducts/config.json b/plugins/LatestProducts/config.json new file mode 100644 index 00000000..1561cf5b --- /dev/null +++ b/plugins/LatestProducts/config.json @@ -0,0 +1,12 @@ +{ + "code": "latest_products", + "name": "最新商品列表", + "description": "首页菜单添加最新商品列表功能", + "type": "feature", + "version": "v1.0.0", + "icon": "/image/logo.png", + "author": { + "name": "成都光大网络科技有限公司", + "email": "yangjin@guangda.work" + } +} diff --git a/plugins/Openai/Bootstrap.php b/plugins/Openai/Bootstrap.php new file mode 100644 index 00000000..1e503d20 --- /dev/null +++ b/plugins/Openai/Bootstrap.php @@ -0,0 +1,48 @@ + + * @created 2023-02-27 13:57:38 + * @modified 2023-02-27 13:57:38 + */ + +namespace Plugin\Openai; + +class Bootstrap +{ + public function boot() + { + add_hook_filter('admin.sidebar.setting.prefix', function ($data) { + $data[] = 'openai'; + + return $data; + }); + + add_hook_filter('admin.sidebar.setting_routes', function ($data) { + $data[] = [ + 'route' => 'openai.index', + 'prefixes' => ['openai'], + 'title' => 'ChatGPT', + ]; + + return $data; + }); + + add_hook_filter('role.permissions.plugin', function ($data) { + $data[] = [ + 'title' => 'OpenAI', + 'permissions' => [ + [ + 'code' => 'openai_index', + 'name' => 'ChatGPT', + ], + ], + ]; + + return $data; + }); + } +} diff --git a/plugins/Openai/Controllers/OpenaiController.php b/plugins/Openai/Controllers/OpenaiController.php new file mode 100644 index 00000000..60ffc3b7 --- /dev/null +++ b/plugins/Openai/Controllers/OpenaiController.php @@ -0,0 +1,90 @@ + + * @created 2023-02-27 16:13:08 + * @modified 2023-02-27 16:13:08 + */ + +namespace Plugin\Openai\Controllers; + +use Beike\Admin\Http\Controllers\Controller; +use Illuminate\Http\Request; +use Plugin\Openai\Services\OpenAIService; + +class OpenaiController extends Controller +{ + /** + * OpenAI home page. + * + * @return mixed + */ + public function index() + { + $plugin = plugin('openai'); + + $error = ''; + $baseUrl = config('beike.api_url') . '/api/openai'; + $apiType = plugin_setting('openai.api_type'); + if ($apiType == 'own') { + $apiKey = plugin_setting('openai.api_key'); + if (empty($apiKey)) { + $error = trans('Openai::common.empty_api_key'); + } + $baseUrl = config('app.url') . '/admin/openai'; + } + + $data = [ + 'name' => $plugin->getLocaleName(), + 'description' => $plugin->getLocaleDescription(), + 'type' => $apiType, + 'base' => $baseUrl, + 'error' => $error, + ]; + + return view('Openai::admin.openai', $data); + } + + /** + * Send chat completions with OpenAI API + * + * @param Request $request + * @return array|mixed + * @throws \Throwable + */ + public function completions(Request $request) + { + try { + $result = (new OpenAIService())->requestAI($request->all()); + } catch (\Exception $e) { + $result = [ + 'error' => $e->getMessage(), + ]; + } + + return $result; + } + + /** + * Get histories + * + * @param Request $request + * @return array|mixed + */ + public function histories(Request $request) + { + try { + $perPage = $request->get('per_page', 10); + $result = (new OpenAIService())->getOpenaiLogs($perPage); + } catch (\Exception $e) { + $result = [ + 'error' => $e->getMessage(), + ]; + } + + return $result; + } +} diff --git a/plugins/Openai/Lang/en/common.php b/plugins/Openai/Lang/en/common.php new file mode 100644 index 00000000..efdcdf17 --- /dev/null +++ b/plugins/Openai/Lang/en/common.php @@ -0,0 +1,27 @@ + + * @created 2022-08-11 15:26:18 + * @modified 2022-08-11 15:26:18 + */ + +return [ + // Text + 'title' => 'OpenAI intelligent chat assistant', + 'sub_title' => 'Based on OpenAI GPT3.0 integrated development If you have any questions, please consult qq group 639108380', + 'no_question' => 'Please enter the search content in the box below', + 'enter_question' => 'Please enter a question', + 'loading' => 'loading...', + 'no_more' => 'no more', + 'qa_q' => 'ask', + 'qa_a' => 'answer', + 'number_free' => 'The remaining free times of the day', + 'api_type' => 'API Method', + 'own' => 'Own Key', + 'beikeshop' => 'BeikeShop', + 'empty_api_key' => 'API Key is empty, please go to the plugin settings - OpenAI - Edit and fill in the API Key first.', +]; diff --git a/plugins/Openai/Lang/zh_cn/common.php b/plugins/Openai/Lang/zh_cn/common.php new file mode 100644 index 00000000..7531ee85 --- /dev/null +++ b/plugins/Openai/Lang/zh_cn/common.php @@ -0,0 +1,27 @@ + + * @created 2022-08-11 15:26:18 + * @modified 2022-08-11 15:26:18 + */ + +return [ + // Text + 'title' => 'OpenAI 智能聊天助手', + 'sub_title' => '基于OpenAI GPT3.0 集成开发 如有疑问详询qq群639108380', + 'no_question' => '请在下面输入框搜索内容', + 'enter_question' => '请输入问题', + 'loading' => '加载中...', + 'no_more' => '没有更多了', + 'qa_q' => '问', + 'qa_a' => '答', + 'number_free' => '当日剩余免费次数', + 'api_type' => 'API 方式', + 'own' => '自有Key', + 'beikeshop' => 'BeikeShop平台', + 'empty_api_key' => 'API Key 为空, 请先到插件设置 - OpenAI - 编辑 填写API Key', +]; diff --git a/plugins/Openai/Lang/zh_hk/common.php b/plugins/Openai/Lang/zh_hk/common.php new file mode 100644 index 00000000..cee4b66b --- /dev/null +++ b/plugins/Openai/Lang/zh_hk/common.php @@ -0,0 +1,27 @@ + + * @created 2022-08-11 15:26:18 + * @modified 2022-08-11 15:26:18 + */ + +return [ + // Text + 'title' => 'OpenAI 智能聊天助手', + 'sub_title' => '基於OpenAI GPT3.0 集成開發 如有疑問詳詢qq群639108380', + 'no_question' => '請在下面輸入框搜索內容', + 'enter_question' => '請輸入問題', + 'loading' => '加載中...', + 'no_more' => '没有更多了', + 'qa_q' => '問', + 'qa_a' => '答', + 'number_free' => '當日剩餘免費次數', + 'api_type' => 'API 方式', + 'own' => '自有 Key', + 'beikeshop' => 'BeikeShop 平台', + 'empty_api_key' => 'API Key 為空,請先到插件設置 - OpenAI - 編輯 填寫 API Key', +]; diff --git a/plugins/Openai/Libraries/OpenAI/Base.php b/plugins/Openai/Libraries/OpenAI/Base.php new file mode 100644 index 00000000..43a8cbe6 --- /dev/null +++ b/plugins/Openai/Libraries/OpenAI/Base.php @@ -0,0 +1,161 @@ + + * @created 2023-02-22 20:31:42 + * @modified 2023-02-22 20:31:42 + */ + +namespace Plugin\Openai\Libraries\OpenAI; + +use Exception; + +class Base +{ + /** + * @var string|bool|mixed + */ + private string $apiKey = ''; + + /** + * @var int + */ + protected int $maxTokens = 1000; + + /** + * @var float + */ + protected float $temperature = 0.5; + + /** + * @var int + */ + protected int $number = 1; + + /** + * @var string + */ + protected string $prompt; + + /** + * OpenAI constructor. + * @param string|null $apiKey + */ + public function __construct(?string $apiKey = '') + { + if ($apiKey) { + $this->apiKey = $apiKey; + } + if (empty($this->apiKey)) { + $this->apiKey = env('OPENAI_API_KEY'); + } + } + + /** + * Get OpenAI instance. + * + * @param string|null $apiKey + * @return Base + */ + public static function getInstance(?string $apiKey = ''): static + { + return new self($apiKey); + } + + /** + * 设置 max_tokens的值 + * 一般来说,max_tokens值越大,模型的表现就越好, + * 但是需要考虑到计算资源的限制,max_tokens值不宜过大。 + * 一般来说,max_tokens值可以设置在比较合理的范围内,比如500到1000之间。 + * + * @param int $maxTokens + * @return $this + */ + public function setMaxTokens(int $maxTokens): static + { + $this->maxTokens = $maxTokens; + + return $this; + } + + /** + * 设置temperature参数值, 参数的范围是0.0到2.0之间。 + * 在较低的温度下,模型会生成更加安全的文本, + * 而在较高的温度下,模型会生成更加创新的文本。 + * + * @param float $temperature + * @return $this + */ + public function setTemperature(float $temperature): static + { + $this->temperature = $temperature; + + return $this; + } + + /** + * 设置 n 参数值 + * 指定了返回结果的数量。n参数越大,返回的结果数量也越多。 + * + * @param int $number + * @return $this + */ + public function setNumber(int $number): static + { + $this->number = $number; + + return $this; + } + + /** + * 设置 prompt 参数值 + * 用于指定一段文本,用于提供给模型参考,以便于生成更加相关的文本 + * + * @param string $prompt + * @return $this + * @throws Exception + */ + public function setPrompt(string $prompt): static + { + $this->prompt = trim($prompt); + if (empty($this->prompt)) { + throw new Exception('prompt 不能为空!'); + } + + return $this; + } + + /** + * 发送请求到 OpenAI + * + * @param $url + * @param $data + * @return mixed + * @throws Exception + */ + protected function request($url, $data): mixed + { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $this->apiKey, + ]); + + $response = curl_exec($ch); + if ($response === false) { + throw new \Exception(curl_error($ch)); + } + curl_close($ch); + + return json_decode($response, true); + } +} diff --git a/plugins/Openai/Libraries/OpenAI/Chat.php b/plugins/Openai/Libraries/OpenAI/Chat.php new file mode 100644 index 00000000..336af1f2 --- /dev/null +++ b/plugins/Openai/Libraries/OpenAI/Chat.php @@ -0,0 +1,66 @@ + + * @created 2023-03-02 14:30:10 + * @modified 2023-03-02 14:30:10 + */ + +namespace Plugin\Openai\Libraries\OpenAI; + +class Chat extends Base +{ + /** + * @var array 聊天上下文 + */ + private array $messages; + + /** + * @param string|null $apiKey + * @return static + */ + public static function getInstance(?string $apiKey = ''): static + { + return new self($apiKey); + } + + /** + * https://platform.openai.com/docs/guides/chat/introduction + * + * @param $messages + * @param $prompt + * @return Chat + */ + public function setMessages($messages, $prompt): self + { + $messages[] = ['role' => 'user', 'content' => $prompt]; + $this->messages = $messages; + + return $this; + } + + /** + * 发送请求到 OpenAI + * + * @return mixed + * @throws \Exception + */ + public function create(): mixed + { + $model = 'gpt-3.5-turbo'; + $url = 'https://api.openai.com/v1/chat/completions'; + $data = [ + 'messages' => $this->messages, + 'max_tokens' => $this->maxTokens, + 'temperature' => $this->temperature, + 'n' => $this->number, + 'stop' => '\n', + 'model' => $model, + ]; + + return $this->request($url, $data); + } +} diff --git a/plugins/Openai/Libraries/OpenAI/Completion.php b/plugins/Openai/Libraries/OpenAI/Completion.php new file mode 100644 index 00000000..a30f822a --- /dev/null +++ b/plugins/Openai/Libraries/OpenAI/Completion.php @@ -0,0 +1,33 @@ + + * @created 2023-03-02 14:37:15 + * @modified 2023-03-02 14:37:15 + */ + +namespace Plugin\Openai\Libraries\OpenAI; + +class Completion extends Base +{ + /** + * @throws \Exception + */ + public function create() + { + $model = 'text-davinci-003'; + $url = 'https://api.openai.com/v1/completions'; + $data = [ + 'prompt' => $this->prompt, + 'max_tokens' => $this->maxTokens, + 'temperature' => $this->temperature, + 'n' => $this->number, + 'stop' => '\n', + 'model' => $model, + ]; + $this->request($url, $data); + } +} diff --git a/plugins/Openai/Migrations/2023_02_27_173221_add_openai_logs.php b/plugins/Openai/Migrations/2023_02_27_173221_add_openai_logs.php new file mode 100644 index 00000000..f668eebe --- /dev/null +++ b/plugins/Openai/Migrations/2023_02_27_173221_add_openai_logs.php @@ -0,0 +1,38 @@ +id(); + $table->integer('user_id')->index('user_id'); + $table->text('question'); + $table->text('answer'); + $table->string('request_ip'); + $table->text('user_agent'); + $table->timestamps(); + }); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('openai_logs'); + } +}; diff --git a/plugins/Openai/Models/OpenaiLog.php b/plugins/Openai/Models/OpenaiLog.php new file mode 100644 index 00000000..e6b8114f --- /dev/null +++ b/plugins/Openai/Models/OpenaiLog.php @@ -0,0 +1,32 @@ + + * @created 2023-03-13 16:48:17 + * @modified 2023-03-13 16:48:17 + */ + +namespace Plugin\Openai\Models; + +use Illuminate\Database\Eloquent\Model; + +class OpenaiLog extends Model +{ + public $timestamps = true; + + protected $table = 'openai_logs'; + + protected $fillable = [ + 'user_id', 'question', 'answer', 'request_ip', 'user_agent', + ]; + + protected $appends = ['created_format']; + + public function getCreatedFormatAttribute() + { + return $this->created_at->format('Y-m-d H:i:s'); + } +} diff --git a/plugins/Openai/Routes/admin.php b/plugins/Openai/Routes/admin.php new file mode 100644 index 00000000..184a3ed2 --- /dev/null +++ b/plugins/Openai/Routes/admin.php @@ -0,0 +1,18 @@ + + * @created 2022-08-04 16:17:53 + * @modified 2022-08-04 16:17:53 + */ + +use Illuminate\Support\Facades\Route; +use Plugin\Openai\Controllers\OpenaiController; + +Route::middleware('can:openai_index')->get('/openai', [OpenaiController::class, 'index'])->name('openai.index'); + +Route::middleware('can:openai_index')->get('/openai/histories', [OpenaiController::class, 'histories'])->name('openai.histories'); +Route::middleware('can:openai_index')->post('/openai/completions', [OpenaiController::class, 'completions'])->name('openai.completions'); diff --git a/plugins/Openai/Services/OpenAIService.php b/plugins/Openai/Services/OpenAIService.php new file mode 100644 index 00000000..8eff9252 --- /dev/null +++ b/plugins/Openai/Services/OpenAIService.php @@ -0,0 +1,120 @@ + + * @created 2023-03-13 16:42:52 + * @modified 2023-03-13 16:42:52 + */ + +namespace Plugin\Openai\Services; + +use Plugin\Openai\Libraries\OpenAI\Chat; +use Plugin\Openai\Models\OpenaiLog; + +class OpenAIService +{ + /** + * 发起 OpenAI 请求 + * + * @param $data + * @return mixed + * @throws \Throwable + */ + public function requestAI($data) + { + $prompt = $data['prompt'] ?? ''; + $apiKey = plugin_setting('openai.api_key'); + + $openAI = Chat::getInstance($apiKey); + $messages = $this->getChatMessages(); + $response = $openAI->setMessages($messages, $prompt)->create(); + + $result['prompt'] = $prompt; + + $error = trim($response['error']['message'] ?? ''); + if ($error) { + $result['error'] = $error; + } else { + $content = trim($response['choices'][0]['message']['content'] ?? ''); + + $response['choices'][0]['text'] = $content; + + $result['response'] = $response; + $newLog = $this->createOpenaiLog($prompt, $content); + $result['created_format'] = $newLog->created_format; + } + + return $result; + } + + /** + * @param $question + * @param $answer + * @return OpenaiLog + * @throws \Throwable + */ + private function createOpenaiLog($question, $answer): OpenaiLog + { + $user = current_user(); + $newOpenaiLog = new OpenaiLog([ + 'user_id' => $user->id ?? 0, + 'question' => trim($question), + 'answer' => trim($answer), + 'request_ip' => request()->getClientIp(), + 'user_agent' => request()->userAgent(), + ]); + $newOpenaiLog->saveOrFail(); + + return $newOpenaiLog; + } + + /** + * 获取聊天记录 + * + * @param int $perPage + * @return mixed + */ + public function getOpenaiLogs(int $perPage = 10) + { + $user = current_user(); + + return OpenaiLog::query() + ->select(['user_id', 'question', 'answer', 'created_at']) + ->where('user_id', $user->id) + ->orderByDesc('created_at') + ->paginate($perPage); + } + + /** + * https://platform.openai.com/docs/guides/chat/introduction + * + * messages=[ + * {"role": "system", "content": "You are a helpful assistant."}, + * {"role": "user", "content": "Who won the world series in 2020?"}, + * {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."}, + * {"role": "user", "content": "Where was it played?"} + * ] + * + * @return array + */ + public function getChatMessages() + { + $logs = OpenaiLog::query() + ->select(['user_id', 'question', 'answer', 'created_at']) + ->limit(5) + ->get(); + + $messages[] = [ + 'role' => 'system', 'content' => 'You are a helpful assistant.', + ]; + foreach ($logs as $log) { + $messages[] = ['role' => 'user', 'content' => $log->question]; + $messages[] = ['role' => 'assistant', 'content' => $log->answer]; + } + + return $messages; + } +} diff --git a/plugins/Openai/Static/image/logo.png b/plugins/Openai/Static/image/logo.png new file mode 100644 index 00000000..d510068d Binary files /dev/null and b/plugins/Openai/Static/image/logo.png differ diff --git a/plugins/Openai/Views/admin/openai.blade.php b/plugins/Openai/Views/admin/openai.blade.php new file mode 100644 index 00000000..17480c6c --- /dev/null +++ b/plugins/Openai/Views/admin/openai.blade.php @@ -0,0 +1,307 @@ +@extends('admin::layouts.master') + +@section('title', $name) + +@push('header') + + + +@endpush + +@section('content') +
+
+
+
{{ __('Openai::common.no_question') }}
+
+
+ + +
+
+
+
+ @if ($type != 'own') +
{{ __('Openai::common.number_free') }}: + {{ __('Openai::common.loading') }} +
+ @endif + @if ($error) +
+ + {{ $error }} +
+ @endif +
{{ $description }}
+
+
+ + + +@endsection diff --git a/plugins/Openai/columns.php b/plugins/Openai/columns.php new file mode 100644 index 00000000..6f076006 --- /dev/null +++ b/plugins/Openai/columns.php @@ -0,0 +1,31 @@ + + * @created 2023-03-13 16:08:41 + * @modified 2023-03-13 16:08:41 + */ + +return [ + [ + 'name' => 'api_type', + 'label_key' => 'common.api_type', + 'type' => 'select', + 'options' => [ + ['value' => 'own', 'label_key' => 'common.own'], + ['value' => 'beikeshop', 'label_key' => 'common.beikeshop'], + ], + 'required' => true, + 'description' => '如果选择 BeikeShop 平台, 则 API Key 可以留空', + ], + [ + 'name' => 'api_key', + 'label' => 'API Key', + 'type' => 'string', + 'required' => false, + 'description' => '获取 API Key', + ], +]; diff --git a/plugins/Openai/config.json b/plugins/Openai/config.json new file mode 100644 index 00000000..fe658e11 --- /dev/null +++ b/plugins/Openai/config.json @@ -0,0 +1,18 @@ +{ + "code": "openai", + "name": { + "zh_cn": "ChatGPT 智能聊天助手", + "en": "Integration with OpenAI ChatGPT API" + }, + "description": { + "zh_cn": "ChatGPT 智能聊天助手, 基于 OpenAI API(gpt-3.5-turbo) 开发, 如有疑问详询QQ群 639108380", + "en": "Integration with OpenAI ChatGPT API" + }, + "type": "feature", + "version": "v1.1.0", + "icon": "/image/logo.png", + "author": { + "name": "成都光大网络科技有限公司", + "email": "yangjin@guangda.work" + } +} diff --git a/plugins/Paypal/Controllers/PaypalController.php b/plugins/Paypal/Controllers/PaypalController.php new file mode 100644 index 00000000..2798948b --- /dev/null +++ b/plugins/Paypal/Controllers/PaypalController.php @@ -0,0 +1,118 @@ + + * @created 2022-08-10 18:57:56 + * @modified 2022-08-10 18:57:56 + * + * https://www.zongscan.com/demo333/1311.html + * https://clickysoft.com/how-to-integrate-paypal-payment-gateway-in-laravel/ + * https://www.positronx.io/how-to-integrate-paypal-payment-gateway-in-laravel/ + */ + +namespace Plugin\Paypal\Controllers; + +use Beike\Repositories\OrderPaymentRepo; +use Beike\Repositories\OrderRepo; +use Beike\Services\StateMachineService; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; +use Srmklive\PayPal\Services\PayPal; + +class PaypalController +{ + private PayPal $paypalClient; + + private function initPaypal() + { + $paypalSetting = plugin_setting('paypal'); + $config = [ + 'mode' => $paypalSetting['sandbox_mode'] ? 'sandbox' : 'live', + 'sandbox' => [ + 'client_id' => $paypalSetting['sandbox_client_id'], + 'client_secret' => $paypalSetting['sandbox_secret'], + ], + 'live' => [ + 'client_id' => $paypalSetting['live_client_id'], + 'client_secret' => $paypalSetting['live_secret'], + ], + 'payment_action' => 'Sale', + 'currency' => 'USD', + 'notify_url' => '', + 'locale' => 'en_US', + 'validate_ssl' => false, + ]; + config(['paypal' => null]); + $this->paypalClient = new PayPal($config); + $token = $this->paypalClient->getAccessToken(); + $this->paypalClient->setAccessToken($token); + } + + /** + * 创建 paypal 订单 + * @param Request $request + * @return JsonResponse + * @throws \Throwable + */ + public function create(Request $request): JsonResponse + { + $this->initPaypal(); + $data = \json_decode($request->getContent(), true); + $orderNumber = $data['orderNumber']; + $customer = current_customer(); + $order = OrderRepo::getOrderByNumber($orderNumber, $customer); + $total = round($order->total, 2); + + $paypalOrder = $this->paypalClient->createOrder([ + 'intent' => 'CAPTURE', + 'purchase_units' => [ + [ + 'amount' => [ + 'currency_code' => $order->currency_code, + 'value' => $total, + ], + 'description' => 'test', + ], + ], + ]); + + return response()->json($paypalOrder); + } + + /** + * 客户同意后扣款回调 + * @param Request $request + * @return JsonResponse + * @throws \Throwable + */ + public function capture(Request $request): JsonResponse + { + $this->initPaypal(); + $data = \json_decode($request->getContent(), true); + $orderNumber = $data['orderNumber']; + $customer = current_customer(); + $order = OrderRepo::getOrderByNumber($orderNumber, $customer); + + OrderPaymentRepo::createOrUpdatePayment($order->id, ['request' => $data]); + $paypalOrderId = $data['paypalOrderId']; + $result = $this->paypalClient->capturePaymentOrder($paypalOrderId); + OrderPaymentRepo::createOrUpdatePayment($order->id, ['response' => $result]); + + try { + DB::beginTransaction(); + if ($result['status'] === 'COMPLETED') { + StateMachineService::getInstance($order)->changeStatus(StateMachineService::PAID); + DB::commit(); + } + } catch (\Exception $e) { + DB::rollBack(); + dd($e); + } + + return response()->json($result); + } +} diff --git a/plugins/Paypal/Lang/en/setting.php b/plugins/Paypal/Lang/en/setting.php new file mode 100644 index 00000000..e6b4211a --- /dev/null +++ b/plugins/Paypal/Lang/en/setting.php @@ -0,0 +1,15 @@ + + * @created 2022-08-11 15:26:18 + * @modified 2022-08-11 15:26:18 + */ + +return [ + 'sandbox_mode' => 'Sandbox Mode', + 'enabled' => 'Enabled', +]; diff --git a/plugins/Paypal/Lang/zh_cn/setting.php b/plugins/Paypal/Lang/zh_cn/setting.php new file mode 100644 index 00000000..098ac7db --- /dev/null +++ b/plugins/Paypal/Lang/zh_cn/setting.php @@ -0,0 +1,15 @@ + + * @created 2022-08-11 15:26:18 + * @modified 2022-08-11 15:26:18 + */ + +return [ + 'sandbox_mode' => '沙盒模式', + 'enabled' => '开启', +]; diff --git a/plugins/Paypal/Routes/admin.php b/plugins/Paypal/Routes/admin.php new file mode 100644 index 00000000..bff13b1f --- /dev/null +++ b/plugins/Paypal/Routes/admin.php @@ -0,0 +1,10 @@ + + * @created 2022-08-12 10:33:01 + * @modified 2022-08-12 10:33:01 + */ diff --git a/plugins/Paypal/Routes/shop.php b/plugins/Paypal/Routes/shop.php new file mode 100644 index 00000000..d40b5028 --- /dev/null +++ b/plugins/Paypal/Routes/shop.php @@ -0,0 +1,18 @@ + + * @created 2022-08-12 10:33:13 + * @modified 2022-08-12 10:33:13 + */ + +use Illuminate\Support\Facades\Route; +use Plugin\Paypal\Controllers\PaypalController; + +Route::group(['prefix' => 'paypal'], function () { + Route::post('/create', [PaypalController::class, 'create']); + Route::post('/capture', [PaypalController::class, 'capture']); +}); diff --git a/plugins/Paypal/Static/image/logo.png b/plugins/Paypal/Static/image/logo.png new file mode 100644 index 00000000..5f64640b Binary files /dev/null and b/plugins/Paypal/Static/image/logo.png differ diff --git a/plugins/Paypal/Views/checkout/payment.blade.php b/plugins/Paypal/Views/checkout/payment.blade.php new file mode 100644 index 00000000..58bb8523 --- /dev/null +++ b/plugins/Paypal/Views/checkout/payment.blade.php @@ -0,0 +1,73 @@ + +
+ + +@if($payment_setting['sandbox_mode']) + +@else + +@endif + + diff --git a/plugins/Paypal/columns.php b/plugins/Paypal/columns.php new file mode 100644 index 00000000..266a4f36 --- /dev/null +++ b/plugins/Paypal/columns.php @@ -0,0 +1,56 @@ + + * @created 2022-06-29 21:16:23 + * @modified 2022-06-29 21:16:23 + */ + +return [ + [ + 'name' => 'sandbox_client_id', + 'label' => 'Sandbox Client ID', + 'type' => 'string', + 'required' => true, + 'rules' => 'required|size:80', + 'description' => '沙盒模式 Client ID', + ], + [ + 'name' => 'sandbox_secret', + 'label' => 'Sandbox Secret', + 'type' => 'string', + 'required' => true, + 'rules' => 'required|size:80', + 'description' => '沙盒模式 Secret', + ], + [ + 'name' => 'live_client_id', + 'label' => 'Live Client ID', + 'type' => 'string', + 'required' => true, + 'rules' => 'required|size:80', + 'description' => '正式环境 Client ID', + ], + [ + 'name' => 'live_secret', + 'label' => 'Live Secret', + 'type' => 'string', + 'required' => true, + 'rules' => 'required|size:80', + 'description' => '正式环境 Secret', + ], + [ + 'name' => 'sandbox_mode', + 'label_key' => 'setting.sandbox_mode', + 'type' => 'select', + 'options' => [ + ['value' => '1', 'label_key' => 'setting.enabled'], + ['value' => '0', 'label' => '关闭'], + ], + 'required' => true, + 'description' => '', + ], +]; diff --git a/plugins/Paypal/config.json b/plugins/Paypal/config.json new file mode 100644 index 00000000..252c7b0e --- /dev/null +++ b/plugins/Paypal/config.json @@ -0,0 +1,12 @@ +{ + "code": "paypal", + "name": "PayPal", + "description": "PayPal 支付 PayPal Developer", + "type": "payment", + "version": "v1.0.0", + "icon": "/image/logo.png", + "author": { + "name": "成都光大网络科技有限公司", + "email": "yangjin@guangda.work" + } +} diff --git a/plugins/Social/Bootstrap.php b/plugins/Social/Bootstrap.php new file mode 100644 index 00000000..fa572c7a --- /dev/null +++ b/plugins/Social/Bootstrap.php @@ -0,0 +1,48 @@ + + * @created 2022-10-12 17:33:29 + * @modified 2022-10-12 17:33:29 + */ + +namespace Plugin\Social; + +class Bootstrap +{ + public function boot() + { + $this->addSocialData(); + + add_hook_blade('admin.plugin.form', function ($callback, $output, $data) { + $code = $data['plugin']->code; + if ($code == 'social') { + return view('Social::admin.config_form', $data)->render(); + } + + return $output; + }, 1); + } + + /** + * 增加第三方登录方式 + */ + private function addSocialData() + { + add_hook_filter('login.social.buttons', function ($buttons) { + $providers = plugin_setting('social.setting'); + if (empty($providers)) { + return $buttons; + } + + foreach ($providers as $provider) { + $buttons[] = view('Social::shop/social_button', ['provider' => $provider])->render(); + } + + return $buttons; + }); + } +} diff --git a/plugins/Social/Controllers/AdminSocialController.php b/plugins/Social/Controllers/AdminSocialController.php new file mode 100644 index 00000000..dd8c4e56 --- /dev/null +++ b/plugins/Social/Controllers/AdminSocialController.php @@ -0,0 +1,29 @@ + + * @created 2022-09-30 18:46:38 + * @modified 2022-09-30 18:46:38 + */ + +namespace Plugin\Social\Controllers; + +use Beike\Admin\Http\Controllers\Controller; +use Beike\Repositories\SettingRepo; +use Illuminate\Http\Request; + +class AdminSocialController extends Controller +{ + /** + * @throws \Throwable + */ + public function saveSetting(Request $request): array + { + SettingRepo::storeValue('setting', $request->all(), 'social', 'plugin'); + + return json_success('保存成功'); + } +} diff --git a/plugins/Social/Controllers/ShopSocialController.php b/plugins/Social/Controllers/ShopSocialController.php new file mode 100644 index 00000000..e2e012dd --- /dev/null +++ b/plugins/Social/Controllers/ShopSocialController.php @@ -0,0 +1,72 @@ + + * @created 2022-09-30 18:46:38 + * @modified 2022-09-30 18:46:38 + */ + +namespace Plugin\Social\Controllers; + +use Beike\Admin\Http\Controllers\Controller; +use Beike\Models\Customer; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Config; +use Laravel\Socialite\Facades\Socialite; +use Plugin\Social\Repositories\CustomerRepo; + +class ShopSocialController extends Controller +{ + public function initSocial() + { + $providerSettings = plugin_setting('social.setting', []); + foreach ($providerSettings as $providerSetting) { + $provider = $providerSetting['provider']; + if (empty($provider)) { + continue; + } + $config = [ + 'client_id' => $providerSetting['key'], + 'client_secret' => $providerSetting['secret'], + 'redirect' => shop_route('social.callback', $provider), + ]; + Config::set("services.{$provider}", $config); + } + } + + /** + * @param $provider + * @return mixed + */ + public function redirect($provider) + { + try { + $this->initSocial(); + + return Socialite::driver($provider)->redirect(); + } catch (\Exception $e) { + exit($e->getMessage()); + } + } + + /** + * @param $provider + * @return mixed + */ + public function callback($provider) + { + try { + $this->initSocial(); + $userData = Socialite::driver($provider)->user(); + $customer = CustomerRepo::createCustomer($provider, $userData); + Auth::guard(Customer::AUTH_GUARD)->login($customer); + + return view('Social::shop/callback'); + } catch (\Exception $e) { + exit($e->getMessage()); + } + } +} diff --git a/plugins/Social/Lang/en/providers.php b/plugins/Social/Lang/en/providers.php new file mode 100644 index 00000000..18c9d669 --- /dev/null +++ b/plugins/Social/Lang/en/providers.php @@ -0,0 +1,36 @@ + + * @created 2022-10-13 11:30:33 + * @modified 2022-10-13 11:30:33 + */ + +return [ + 'alipay' => 'Alipay', + 'azure' => 'Azure', + 'dingtalk' => 'Dingtalk', + 'douyin' => 'Douyin', + 'douban' => 'Douban', + 'facebook' => 'Facebook', + 'feishu' => 'Feishu', + 'figma' => 'Figma', + 'github' => 'Github', + 'gitee' => 'Gitee', + 'google' => 'Google', + 'line' => 'Line', + 'linkedin' => 'Linkedin', + 'open-wework' => 'Open-wework', + 'outlook' => 'Outlook', + 'qcloud' => 'Qcloud', + 'qq' => 'QQ', + 'taobao' => 'Taobao', + 'tapd' => 'Tapd', + 'twitter' => 'Twitter', + 'wechat' => 'Wechat', + 'wework' => 'Wework', + 'weibo' => 'Weibo', +]; diff --git a/plugins/Social/Lang/en/setting.php b/plugins/Social/Lang/en/setting.php new file mode 100644 index 00000000..7d38c294 --- /dev/null +++ b/plugins/Social/Lang/en/setting.php @@ -0,0 +1,60 @@ + + * @created 2022-08-11 15:26:18 + * @modified 2022-08-11 15:26:18 + */ + +return [ + // Text + 'text_module' => 'Modules', + 'text_success' => 'Success: You have modified module OpenCart OmniAuth!', + 'text_copyright' => 'OpenCart.cn OmniAuth © %s', + 'text_omni_explain' => 'This plugin supports Facebook, Twitter, Google etc.', + 'text_omni_explain_2' => 'To use a third-party login, you need to apply to the corresponding platform, and fill in the obtained ID and key to the corresponding input box.', + 'text_wechat_title' => 'WeChat scan code login application address', + 'text_wechat_info' => 'WeChat open platform', + 'text_qq_title' => 'QQ login application address', + 'text_qq_info' => 'QQ interconnection', + 'text_weibo_title' => 'Weibo login application address', + 'text_weibo_info' => 'Weibo open platform', + 'text_facebook_title' => 'Facebook login application address', + 'text_google_title' => 'Google login application address', + 'text_twitter_title' => 'Twitter login application address', + 'text_help_msg' => 'help information', + + 'copy_success' => 'Copy Success', + + // Entry + 'entry_status' => 'Status', + 'entry_bind' => 'Force Bind', + 'entry_debug' => 'Debug Mode', + + 'entry_provider' => 'Provider', + 'entry_key' => 'Client ID', + 'entry_secret' => 'Client Secret', + 'entry_scope' => 'Flags (optional)', + 'entry_callback' => 'Callback URL', + 'entry_sort_order' => 'Sort Order', + + // Button + 'button_add_row' => 'Add Provider', + + // Error + 'error_permission' => 'Warning: You do not have permission to modify module OpenCart OmniAuth!', + + // Providers + 'wechat' => 'WeChat', + 'wechatofficial' => 'WeChatOfficial', + 'qq' => 'QQ', + 'weibo' => 'Weibo', + 'facebook' => 'Facebook', + 'google' => 'Google', + 'twitter' => 'Twitter', + + 'instagram' => 'Instagram', +]; diff --git a/plugins/Social/Lang/zh_cn/providers.php b/plugins/Social/Lang/zh_cn/providers.php new file mode 100644 index 00000000..a81983f4 --- /dev/null +++ b/plugins/Social/Lang/zh_cn/providers.php @@ -0,0 +1,36 @@ + + * @created 2022-10-13 11:30:33 + * @modified 2022-10-13 11:30:33 + */ + +return [ + 'alipay' => '支付宝', + 'azure' => 'Azure', + 'dingtalk' => '钉钉', + 'douyin' => '抖音', + 'douban' => '豆瓣', + 'facebook' => 'Facebook', + 'feishu' => '飞书', + 'figma' => 'Figma', + 'github' => 'GitHub', + 'gitee' => 'Gitee', + 'google' => 'Google', + 'line' => 'Line', + 'linkedin' => 'Linkedin', + 'open-wework' => 'open-wework', + 'outlook' => 'Outlook', + 'qcloud' => '腾讯云', + 'qq' => 'QQ', + 'taobao' => '淘宝', + 'tapd' => 'Tapd', + 'twitter' => 'Twitter', + 'wechat' => '微信', + 'wework' => '企业微信', + 'weibo' => '微博', +]; diff --git a/plugins/Social/Lang/zh_cn/setting.php b/plugins/Social/Lang/zh_cn/setting.php new file mode 100644 index 00000000..99c0be3d --- /dev/null +++ b/plugins/Social/Lang/zh_cn/setting.php @@ -0,0 +1,49 @@ + + * @created 2022-08-11 15:26:18 + * @modified 2022-08-11 15:26:18 + */ + +return [ + // Text + 'text_module' => '模块', + 'text_success' => '成功: 您成功修改第三方登录配置!', + 'text_copyright' => 'OpenCart.cn 获取帮助 © %s', + 'text_omni_explain' => '本模块支持微Facebook, Twitter, Google等第三方登录', + 'text_omni_explain_2' => '要使用第三方登录, 需要到对应平台申请开通, 并把获取到的 ID 和密钥填写到上面对应的输入框', + 'text_wechat_title' => '微信扫码登录申请地址', + 'text_wechat_info' => '微信开放平台', + 'text_qq_title' => 'QQ登录申请地址', + 'text_qq_info' => 'QQ互联', + 'text_weibo_title' => '微博登录申请地址', + 'text_weibo_info' => '微博开放平台', + 'text_facebook_title' => 'Facebook登录申请地址', + 'text_google_title' => 'Google登录申请地址', + 'text_twitter_title' => 'Twitter登录申请地址', + 'text_help_msg' => '帮助信息', + + 'copy_success' => '复制成功', + + // Entry + 'entry_status' => '状态', + 'entry_bind' => '强制绑定', + 'entry_debug' => '调试模式', + + 'entry_provider' => '类型', + 'entry_key' => 'Client ID', + 'entry_secret' => 'Client Secret', + 'entry_scope' => 'Flags (可选项)', + 'entry_callback' => '回调地址', + 'entry_sort_order' => '排序', + + // Button + 'button_add_row' => '添加类型', + + // Error + 'error_permission' => '警告: 您没有权限修改此配置!', +]; diff --git a/plugins/Social/Migrations/2022_10_13_100354_create_customer_socials.php b/plugins/Social/Migrations/2022_10_13_100354_create_customer_socials.php new file mode 100644 index 00000000..796a242c --- /dev/null +++ b/plugins/Social/Migrations/2022_10_13_100354_create_customer_socials.php @@ -0,0 +1,37 @@ +id(); + $table->integer('customer_id'); + $table->string('provider'); + $table->string('user_id'); + $table->string('union_id'); + $table->text('access_token'); + $table->text('extra'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('customer_socials'); + } +}; diff --git a/plugins/Social/Models/CustomerSocial.php b/plugins/Social/Models/CustomerSocial.php new file mode 100644 index 00000000..20419eca --- /dev/null +++ b/plugins/Social/Models/CustomerSocial.php @@ -0,0 +1,30 @@ + + * @created 2022-10-13 10:35:44 + * @modified 2022-10-13 10:35:44 + */ + +namespace Plugin\Social\Models; + +use Beike\Models\Customer; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + +class CustomerSocial extends Model +{ + public $table = 'customer_socials'; + + public $fillable = [ + 'customer_id', 'provider', 'user_id', 'union_id', 'access_token', 'extra', + ]; + + public function customer(): BelongsTo + { + return $this->belongsTo(Customer::class); + } +} diff --git a/plugins/Social/Repositories/CustomerRepo.php b/plugins/Social/Repositories/CustomerRepo.php new file mode 100644 index 00000000..2b7dc358 --- /dev/null +++ b/plugins/Social/Repositories/CustomerRepo.php @@ -0,0 +1,120 @@ + + * @created 2022-10-13 09:57:13 + * @modified 2022-10-13 09:57:13 + */ + +namespace Plugin\Social\Repositories; + +use Beike\Models\Customer; +use Beike\Shop\Services\AccountService; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Str; +use Laravel\Socialite\Two\User; +use Plugin\Social\Models\CustomerSocial; + +class CustomerRepo +{ + /** + * 允许的第三方服务 + */ + private const PROVIDERS = [ + 'facebook', + 'twitter', + 'google', + ]; + + public static function allProviders(): array + { + $items = []; + foreach (self::PROVIDERS as $provider) { + $items[] = [ + 'code' => $provider, + 'label' => trans("Social::providers.{$provider}"), + ]; + } + + return $items; + } + + /** + * 创建客户, 关联第三方用户数据 + * + * @param $provider + * @param User $userData + * @return Customer + */ + public static function createCustomer($provider, User $userData): Customer + { + $social = self::getCustomerByProvider($provider, $userData->getId()); + $customer = $social->customer ?? null; + if ($customer) { + return $customer; + } + + $email = $userData->getEmail(); + if (empty($email)) { + $email = strtolower(Str::random(8)) . "@{$provider}.com"; + } + $customer = Customer::query()->where('email', $email)->first(); + if (empty($customer)) { + $customerData = [ + 'from' => $provider, + 'email' => $email, + 'name' => $userData->getName(), + 'avatar' => $userData->getAvatar(), + ]; + $customer = AccountService::register($customerData); + } + + self::createSocial($customer, $provider, $userData); + + return $customer; + } + + /** + * @param $customer + * @param $provider + * @param User $userData + * @return Model|Builder + */ + public static function createSocial($customer, $provider, User $userData): Model|Builder + { + $social = self::getCustomerByProvider($provider, $userData->getId()); + if ($social) { + return $social; + } + + $socialData = [ + 'customer_id' => $customer->id, + 'provider' => $provider, + 'user_id' => $userData->getId(), + 'union_id' => '', + 'access_token' => $userData->token, + 'extra' => json_encode($userData->getRaw()), + ]; + + return CustomerSocial::query()->create($socialData); + } + + /** + * 通过 provider 和 user_id 获取已存在 social + * @param $provider + * @param $userId + * @return Model|Builder|null + */ + private static function getCustomerByProvider($provider, $userId): Model|Builder|null + { + return CustomerSocial::query() + ->with(['customer']) + ->where('provider', $provider) + ->where('user_id', $userId) + ->first(); + } +} diff --git a/plugins/Social/Routes/admin.php b/plugins/Social/Routes/admin.php new file mode 100644 index 00000000..0bf948a1 --- /dev/null +++ b/plugins/Social/Routes/admin.php @@ -0,0 +1,15 @@ + + * @created 2022-08-04 16:17:53 + * @modified 2022-08-04 16:17:53 + */ + +use Illuminate\Support\Facades\Route; +use Plugin\Social\Controllers\AdminSocialController; + +Route::post('/social/setting', [AdminSocialController::class, 'saveSetting'])->name('plugin.social.setting'); diff --git a/plugins/Social/Routes/shop.php b/plugins/Social/Routes/shop.php new file mode 100644 index 00000000..50f26314 --- /dev/null +++ b/plugins/Social/Routes/shop.php @@ -0,0 +1,16 @@ + + * @created 2022-09-30 18:52:54 + * @modified 2022-09-30 18:52:54 + */ + +use Illuminate\Support\Facades\Route; +use Plugin\Social\Controllers\ShopSocialController; + +Route::get('/social/redirect/{provider}', [ShopSocialController::class, 'redirect'])->name('social.redirect'); +Route::get('/social/callbacks/{provider}', [ShopSocialController::class, 'callback'])->name('social.callback'); diff --git a/public/plugin/social/image/alipay.png b/plugins/Social/Static/image/alipay.png similarity index 100% rename from public/plugin/social/image/alipay.png rename to plugins/Social/Static/image/alipay.png diff --git a/public/plugin/social/image/azure.png b/plugins/Social/Static/image/azure.png similarity index 100% rename from public/plugin/social/image/azure.png rename to plugins/Social/Static/image/azure.png diff --git a/public/plugin/social/image/dingtalk.png b/plugins/Social/Static/image/dingtalk.png similarity index 100% rename from public/plugin/social/image/dingtalk.png rename to plugins/Social/Static/image/dingtalk.png diff --git a/public/plugin/social/image/douban.png b/plugins/Social/Static/image/douban.png similarity index 100% rename from public/plugin/social/image/douban.png rename to plugins/Social/Static/image/douban.png diff --git a/public/plugin/social/image/douyin.png b/plugins/Social/Static/image/douyin.png similarity index 100% rename from public/plugin/social/image/douyin.png rename to plugins/Social/Static/image/douyin.png diff --git a/public/plugin/social/image/facebook.png b/plugins/Social/Static/image/facebook.png similarity index 100% rename from public/plugin/social/image/facebook.png rename to plugins/Social/Static/image/facebook.png diff --git a/public/plugin/social/image/feishu.png b/plugins/Social/Static/image/feishu.png similarity index 100% rename from public/plugin/social/image/feishu.png rename to plugins/Social/Static/image/feishu.png diff --git a/public/plugin/social/image/figma.png b/plugins/Social/Static/image/figma.png similarity index 100% rename from public/plugin/social/image/figma.png rename to plugins/Social/Static/image/figma.png diff --git a/public/plugin/social/image/github.png b/plugins/Social/Static/image/github.png similarity index 100% rename from public/plugin/social/image/github.png rename to plugins/Social/Static/image/github.png diff --git a/public/plugin/social/image/google.png b/plugins/Social/Static/image/google.png similarity index 100% rename from public/plugin/social/image/google.png rename to plugins/Social/Static/image/google.png diff --git a/public/plugin/social/image/line.png b/plugins/Social/Static/image/line.png similarity index 100% rename from public/plugin/social/image/line.png rename to plugins/Social/Static/image/line.png diff --git a/public/plugin/social/image/linkedin.png b/plugins/Social/Static/image/linkedin.png similarity index 100% rename from public/plugin/social/image/linkedin.png rename to plugins/Social/Static/image/linkedin.png diff --git a/public/plugin/social/image/logo.png b/plugins/Social/Static/image/logo.png similarity index 100% rename from public/plugin/social/image/logo.png rename to plugins/Social/Static/image/logo.png diff --git a/public/plugin/social/image/qq.png b/plugins/Social/Static/image/qq.png similarity index 100% rename from public/plugin/social/image/qq.png rename to plugins/Social/Static/image/qq.png diff --git a/public/plugin/social/image/wechat.png b/plugins/Social/Static/image/wechat.png similarity index 100% rename from public/plugin/social/image/wechat.png rename to plugins/Social/Static/image/wechat.png diff --git a/public/plugin/social/image/weibo.png b/plugins/Social/Static/image/weibo.png similarity index 100% rename from public/plugin/social/image/weibo.png rename to plugins/Social/Static/image/weibo.png diff --git a/plugins/Social/Views/admin/config_form.blade.php b/plugins/Social/Views/admin/config_form.blade.php new file mode 100644 index 00000000..1108797f --- /dev/null +++ b/plugins/Social/Views/admin/config_form.blade.php @@ -0,0 +1,187 @@ +@section('page-title-right') + +@endsection + +
+ {{--
+
{{ $plugin->name }}
+ +
--}} + {{-- --}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ __('Social::setting.entry_provider') }}{{ __('Social::setting.entry_status') }}{{ __('Social::setting.entry_key') }}{{ __('Social::setting.entry_secret') }}{{ __('Social::setting.entry_callback') }}{{ __('Social::setting.entry_sort_order') }}
+ + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ + + + + +
+
+
{{ __('common.no_data') }}
+ +
+
+
+
+ +
{{ __('Social::setting.text_help_msg') }}
+
    +
  1. {{ __('Social::setting.text_omni_explain') }}
  2. +
  3. {{ __('Social::setting.text_omni_explain_2') }}
  4. +
  5. {{ __('Social::setting.text_facebook_title') }} + Facebook +
  6. +
  7. {{ __('Social::setting.text_twitter_title') }} + Twitter +
  8. +
  9. {{ __('Social::setting.text_google_title') }} + Google +
  10. + ....... +
+ + + + + diff --git a/plugins/Social/Views/shop/callback.blade.php b/plugins/Social/Views/shop/callback.blade.php new file mode 100644 index 00000000..ba9b029d --- /dev/null +++ b/plugins/Social/Views/shop/callback.blade.php @@ -0,0 +1,9 @@ + diff --git a/plugins/Social/Views/shop/social_button.blade.php b/plugins/Social/Views/shop/social_button.blade.php new file mode 100644 index 00000000..dcd250d1 --- /dev/null +++ b/plugins/Social/Views/shop/social_button.blade.php @@ -0,0 +1,4 @@ + diff --git a/plugins/Social/config.json b/plugins/Social/config.json new file mode 100644 index 00000000..f3907ed0 --- /dev/null +++ b/plugins/Social/config.json @@ -0,0 +1,12 @@ +{ + "code": "social", + "name": "Social", + "description": "第三方登录(包括Facebook、Twitter、Google)", + "type": "social", + "version": "v1.0.0", + "icon": "/image/logo.png", + "author": { + "name": "成都光大网络科技有限公司", + "email": "yangjin@guangda.work" + } +} diff --git a/plugins/Stripe/Controllers/StripeController.php b/plugins/Stripe/Controllers/StripeController.php new file mode 100644 index 00000000..fe865bcd --- /dev/null +++ b/plugins/Stripe/Controllers/StripeController.php @@ -0,0 +1,54 @@ + + * @created 2022-08-08 15:58:36 + * @modified 2022-08-08 15:58:36 + */ + +namespace Plugin\Stripe\Controllers; + +use Beike\Repositories\OrderPaymentRepo; +use Beike\Repositories\OrderRepo; +use Beike\Services\StateMachineService; +use Beike\Shop\Http\Controllers\Controller; +use Illuminate\Http\Request; +use Plugin\Stripe\Services\StripePaymentService; + +class StripeController extends Controller +{ + /** + * 订单支付扣款 + * + * @param Request $request + * @return array + * @throws \Throwable + */ + public function capture(Request $request) + { + try { + $number = request('order_number'); + $customer = current_customer(); + $order = OrderRepo::getOrderByNumber($number, $customer); + $creditCardData = $request->all(); + + OrderPaymentRepo::createOrUpdatePayment($order->id, ['request' => $creditCardData]); + $result = (new StripePaymentService($order))->capture($creditCardData); + OrderPaymentRepo::createOrUpdatePayment($order->id, ['response' => $result]); + + if ($result) { + StateMachineService::getInstance($order)->setShipment()->changeStatus(StateMachineService::PAID); + + return json_success(trans('Stripe::common.capture_success')); + } + + return json_success(trans('Stripe::common.capture_fail')); + + } catch (\Exception $e) { + return json_fail($e->getMessage()); + } + } +} diff --git a/plugins/Stripe/Lang/en/common.php b/plugins/Stripe/Lang/en/common.php new file mode 100644 index 00000000..d369e986 --- /dev/null +++ b/plugins/Stripe/Lang/en/common.php @@ -0,0 +1,31 @@ + + * @created 2022-07-28 16:19:06 + * @modified 2022-07-28 16:19:06 + */ + +return [ + 'publishable_key' => 'Publishable Key', + + 'title_info' => 'Card information', + 'cardnum' => 'Cardnum', + 'expiration_date' => 'Expiration Date', + 'year' => 'Year', + 'month' => 'Month', + 'cvv' => 'Cvv', + 'remenber' => 'Keep this card in mind for future use', + 'btn_submit' => 'Submit', + + 'error_cardnum' => 'Please enter the card number', + 'error_cvv' => 'Please enter the security code', + 'error_year' => 'Please select the year', + 'error_month' => 'Please select a month', + + 'capture_success' => 'Capture Successfully', + 'capture_fail' => 'Capture Failed', +]; diff --git a/plugins/Stripe/Lang/zh_cn/common.php b/plugins/Stripe/Lang/zh_cn/common.php new file mode 100644 index 00000000..d1fd6e14 --- /dev/null +++ b/plugins/Stripe/Lang/zh_cn/common.php @@ -0,0 +1,31 @@ + + * @created 2022-07-28 16:19:06 + * @modified 2022-07-28 16:19:06 + */ + +return [ + 'publishable_key' => '公钥', + + 'title_info' => '卡信息', + 'cardnum' => '卡号', + 'expiration_date' => '截止日期', + 'year' => '选择年', + 'month' => '选择月', + 'cvv' => '安全码', + 'remenber' => '记住这张卡以便将来使用', + 'btn_submit' => '提交支付', + + 'error_cardnum' => '请输入卡号', + 'error_cvv' => '请输入安全码', + 'error_year' => '请选择年', + 'error_month' => '请选择月', + + 'capture_success' => '支付成功', + 'capture_fail' => '支付失败', +]; diff --git a/plugins/Stripe/Routes/shop.php b/plugins/Stripe/Routes/shop.php new file mode 100644 index 00000000..fdf208ca --- /dev/null +++ b/plugins/Stripe/Routes/shop.php @@ -0,0 +1,15 @@ + + * @created 2022-08-04 16:17:44 + * @modified 2022-08-04 16:17:44 + */ + +use Illuminate\Support\Facades\Route; +use Plugin\Stripe\Controllers\StripeController; + +Route::post('/stripe/capture', [StripeController::class, 'capture'])->name('stripe_capture'); diff --git a/plugins/Stripe/Services/StripePaymentService.php b/plugins/Stripe/Services/StripePaymentService.php new file mode 100644 index 00000000..f097009f --- /dev/null +++ b/plugins/Stripe/Services/StripePaymentService.php @@ -0,0 +1,74 @@ + + * @created 2022-08-08 16:09:21 + * @modified 2022-08-08 16:09:21 + */ + +namespace Plugin\Stripe\Services; + +use Beike\Shop\Services\PaymentService; +use Stripe\Exception\ApiErrorException; +use Stripe\Stripe; + +class StripePaymentService extends PaymentService +{ + // 零位十进制货币 https://stripe.com/docs/currencies#special-cases + public const ZERO_DECIMAL = [ + 'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', + 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF', + ]; + + /** + * @throws ApiErrorException + * @throws \Exception + */ + public function capture($creditCardData): bool + { + $apiKey = plugin_setting('stripe.secret_key'); + Stripe::setApiKey($apiKey); + $tokenId = $creditCardData['token'] ?? ''; + if (empty($tokenId)) { + throw new \Exception('Invalid token'); + } + $currency = $this->order->currency_code; + + if (! in_array($currency, self::ZERO_DECIMAL)) { + $total = round($this->order->total, 2) * 100; + } else { + $total = floor($this->order->total); + } + + $stripeChargeParameters = [ + 'amount' => $total, + 'currency' => $currency, + 'metadata' => [ + 'orderId' => $this->order->id, + ], + 'source' => $tokenId, + // 'customer' => $this->createCustomer(), + ]; + + $charge = \Stripe\Charge::create($stripeChargeParameters); + + return $charge['paid'] && $charge['captured']; + } + + /** + * 创建 stripe customer + * @return mixed + * @throws ApiErrorException + */ + private function createCustomer(): mixed + { + $customer = \Stripe\Customer::create([ + 'email' => $this->order->email, + ]); + + return $customer['id']; + } +} diff --git a/public/plugin/stripe/css/demo.css b/plugins/Stripe/Static/css/demo.css similarity index 86% rename from public/plugin/stripe/css/demo.css rename to plugins/Stripe/Static/css/demo.css index 30fdbc42..04cd16f6 100644 --- a/public/plugin/stripe/css/demo.css +++ b/plugins/Stripe/Static/css/demo.css @@ -4,5 +4,5 @@ */ #bk-stripe-app .form-wrap { - max-width: 400px; + /* max-width: 500px; */ } \ No newline at end of file diff --git a/public/plugin/stripe/image/logo.png b/plugins/Stripe/Static/image/logo.png similarity index 100% rename from public/plugin/stripe/image/logo.png rename to plugins/Stripe/Static/image/logo.png diff --git a/public/plugin/stripe/image/pay-1.png b/plugins/Stripe/Static/image/pay-1.png similarity index 100% rename from public/plugin/stripe/image/pay-1.png rename to plugins/Stripe/Static/image/pay-1.png diff --git a/public/plugin/stripe/image/pay-2.png b/plugins/Stripe/Static/image/pay-2.png similarity index 100% rename from public/plugin/stripe/image/pay-2.png rename to plugins/Stripe/Static/image/pay-2.png diff --git a/public/plugin/stripe/image/pay-3.png b/plugins/Stripe/Static/image/pay-3.png similarity index 100% rename from public/plugin/stripe/image/pay-3.png rename to plugins/Stripe/Static/image/pay-3.png diff --git a/public/plugin/stripe/image/pay-4.png b/plugins/Stripe/Static/image/pay-4.png similarity index 100% rename from public/plugin/stripe/image/pay-4.png rename to plugins/Stripe/Static/image/pay-4.png diff --git a/public/plugin/stripe/image/pay-5.png b/plugins/Stripe/Static/image/pay-5.png similarity index 100% rename from public/plugin/stripe/image/pay-5.png rename to plugins/Stripe/Static/image/pay-5.png diff --git a/public/plugin/stripe/image/pay-image.png b/plugins/Stripe/Static/image/pay-image.png similarity index 100% rename from public/plugin/stripe/image/pay-image.png rename to plugins/Stripe/Static/image/pay-image.png diff --git a/public/plugin/stripe/js/demo.js b/plugins/Stripe/Static/js/demo.js similarity index 100% rename from public/plugin/stripe/js/demo.js rename to plugins/Stripe/Static/js/demo.js diff --git a/plugins/Stripe/Views/checkout/payment.blade.php b/plugins/Stripe/Views/checkout/payment.blade.php new file mode 100644 index 00000000..9a405686 --- /dev/null +++ b/plugins/Stripe/Views/checkout/payment.blade.php @@ -0,0 +1,170 @@ + + + + +
+
+
+
{{ __('Stripe::common.title_info') }}
+
+
+ +
+
+
+
Cardholder Name
+
+ +
+
+
@{{ errors.cardholderName }}
+ +
+
Credit Card Number
+
+
+
+
@{{ errors.cardNumber }}
+ +
+
Expiration Date
+
+
+
+
@{{ errors.cardExpiry }}
+
+
CVV
+
+
+
@{{ errors.cardCvc }}
+
+
+
+ +
+
+
+ \ No newline at end of file diff --git a/plugins/Stripe/columns.php b/plugins/Stripe/columns.php new file mode 100644 index 00000000..ec130ee0 --- /dev/null +++ b/plugins/Stripe/columns.php @@ -0,0 +1,40 @@ + + * @created 2022-06-29 21:16:23 + * @modified 2022-06-29 21:16:23 + */ + +return [ + [ + 'name' => 'publishable_key', + 'label_key' => 'common.publishable_key', + 'type' => 'string', + 'required' => true, + 'rules' => 'required|min:32', + 'description' => '公钥(Publishable key)', + ], + [ + 'name' => 'secret_key', + 'label' => '密钥', + 'type' => 'string', + 'required' => true, + 'rules' => 'required|min:32', + 'description' => '密钥(Secret key)', + ], + [ + 'name' => 'test_mode', + 'label' => '测试模式', + 'type' => 'select', + 'options' => [ + ['value' => '1', 'label' => '开启'], + ['value' => '0', 'label' => '关闭'], + ], + 'required' => true, + 'description' => '如开启测试模式请填写测试公钥和密钥, 关闭测试模式则填写正式公钥和密钥', + ], +]; diff --git a/plugins/Stripe/config.json b/plugins/Stripe/config.json new file mode 100644 index 00000000..c87af7c3 --- /dev/null +++ b/plugins/Stripe/config.json @@ -0,0 +1,12 @@ +{ + "code": "stripe", + "name": "Stripe 支付", + "description": "Stripe 支付 Stripe", + "type": "payment", + "version": "v1.0.0", + "icon": "/image/logo.png", + "author": { + "name": "成都光大网络科技有限公司", + "email": "yangjin@guangda.work" + } +} diff --git a/public/plugin/.gitignore b/public/plugin/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/public/plugin/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/public/plugin/alipay/image/logo.png b/public/plugin/alipay/image/logo.png deleted file mode 100644 index 4c21b8dc..00000000 Binary files a/public/plugin/alipay/image/logo.png and /dev/null differ diff --git a/public/plugin/paypal/image/logo.png b/public/plugin/paypal/image/logo.png deleted file mode 100644 index e078b425..00000000 Binary files a/public/plugin/paypal/image/logo.png and /dev/null differ