update cache

This commit is contained in:
wuhui_zzw 2023-08-16 14:00:10 +08:00
parent 70c2160aca
commit 39c5134312
188 changed files with 934 additions and 10294 deletions

28
.env
View File

@ -1,28 +0,0 @@
APP_NAME='wyyl'
APP_ENV=
APP_KEY=base64:PItUypiY6FmV8oQTVIcIHyNQJAuuS36FmEs8exQbYAw=
APP_DEBUG=true
APP_LOG_LEVEL=
APP_URL=http://43.153.17.83
BEIKE_API_URL=https://beikeshop.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=wyyl
DB_USERNAME=wyyl
DB_PASSWORD='wyyl@2023'
BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_CONNECTION=sync
MAIL_DRIVER=
MAIL_HOST=
MAIL_PORT=
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=

2
.gitignore vendored
View File

@ -4,7 +4,7 @@
/public/hot
/public/storage
/public/upload/avatar
/public/build/beike/*
#/public/build/beike/*
/public/install/css/*
/public/sitemap.xml
/storage/*.key

0
artisan Executable file → Normal file
View File

6920
package-lock.json generated

File diff suppressed because it is too large Load Diff

8
plugins/.gitignore vendored
View File

@ -1,8 +0,0 @@
*
!Bestseller
!FlatShipping
!LatestProducts
!Openai
!Paypal
!Social
!Stripe

View File

@ -1,45 +0,0 @@
<?php
/**
* Render.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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);
}
}

View File

@ -1,43 +0,0 @@
<?php
/**
* bootstrap.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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;
});
}
}

View File

@ -1,15 +0,0 @@
<?php
/**
* dd.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-07-28 16:19:06
* @modified 2022-07-28 16:19:06
*/
return [
'module_name' => 'Bestseller',
'limit' => 'Limit',
];

View File

@ -1,15 +0,0 @@
<?php
/**
* dd.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-07-28 16:19:06
* @modified 2022-07-28 16:19:06
*/
return [
'module_name' => '热卖模块',
'limit' => '数量限制',
];

View File

@ -1,36 +0,0 @@
<?php
/**
* ProductRepo.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

@ -1,54 +0,0 @@
<template id="module-editor-bestseller-template">
<div class="image-edit-wrapper">
<div class="module-editor-row">{{ __('admin/builder.text_set_up') }}</div>
<div class="module-edit-group">
<div class="module-edit-title">{{ __('admin/builder.text_module_title') }}</div>
<text-i18n v-model="module.title"></text-i18n>
</div>
<div class="module-edit-group">
<div class="module-edit-title">{{ __('Bestseller::common.limit') }}</div>
<el-input type="number" v-model="module.limit" size="small"></el-input>
</div>
</div>
</template>
<script type="text/javascript">
Vue.component('module-editor-bestseller', {
template: '#module-editor-bestseller-template',
props: ['module'],
data: function () {
return {
//
}
},
watch: {
module: {
handler: function (val) {
this.$emit('on-changed', val);
},
deep: true
}
},
});
</script>
{{-- 定义模块的配置项 --}}
@push('footer-script')
<script>
register = @json($register);
register.make = {
style: {
background_color: ''
},
title: languagesFill('{{ __('admin/builder.text_module_title') }}'),
limit: 8,
}
app.source.modules.push(register)
</script>
@endpush

View File

@ -1,67 +0,0 @@
<section class="module-item {{ $design ? 'module-item-design' : ''}}" id="module-{{ $module_id }}">
@include('design._partial._module_tool')
<div class="module-info module-product mb-3 mb-md-5 swiper-style-plus">
<div class="container position-relative">
<div class="module-title">{{ $content['title'] }}</div>
@if ($content['products'])
<div class="swiper module-bs-product-{{ $module_id }} module-slideshow">
<div class="swiper-wrapper">
@foreach ($content['products'] as $product)
<div class="swiper-slide">
@include('shared.product')
</div>
@endforeach
</div>
</div>
<div class="swiper-pagination rectangle module-bs-product-{{ $module_id }}-pagination"></div>
<div class="swiper-button-prev product-bs-{{ $module_id }}-prev"></div>
<div class="swiper-button-next product-bs-{{ $module_id }}-next"></div>
@elseif (!$content['products'] and $design)
<div class="row">
@for ($s = 0; $s < 4; $s++)
<div class="col-6 col-md-4 col-lg-3">
<div class="product-wrap">
<div class="image"><a href="javascript:void(0)"><img src="{{ asset('catalog/placeholder.png') }}" class="img-fluid"></a></div>
<div class="product-name">请配置商品</div>
<div class="product-price">
<span class="price-new">66.66</span>
<span class="price-lod">99.99</span>
</div>
</div>
</div>
@endfor
</div>
@endif
</div>
</div>
<script>
new Swiper ('.module-bs-product-{{ $module_id }}', {
watchSlidesProgress: true,
breakpoints: {
320: {
slidesPerView: 2,
slidesPerGroup: 2,
spaceBetween: 10,
},
768: {
slidesPerView: 4,
slidesPerGroup: 4,
spaceBetween: 30,
},
},
spaceBetween: 30,
// 如果需要分页器
pagination: {
el: '.module-bs-product-{{ $module_id }}-pagination',
clickable: true,
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.product-bs-{{ $module_id }}-next',
prevEl: '.product-bs-{{ $module_id }}-prev',
},
})
</script>
</section>

View File

@ -1,18 +0,0 @@
{
"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"
}
}

View File

@ -1,65 +0,0 @@
<?php
/**
* bootstrap.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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;
}
}

View File

@ -1,17 +0,0 @@
<?php
/**
* dd.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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',
];

View File

@ -1,17 +0,0 @@
<?php
/**
* dd.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-07-28 16:19:06
* @modified 2022-07-28 16:19:06
*/
return [
'way' => '方式',
'flat_shipping' => '固定运费',
'percentage' => '百分比',
'shipping_value' => '运费值',
];

View File

@ -1,8 +0,0 @@
/**
这里是插件css, 请在blade里面使用以下代码引入
<link rel="stylesheet" href="{{ asset('plugin/stripe/css/demo.css') }}">
*/
#bk-stripe-app .form-wrap {
max-width: 400px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -1,4 +0,0 @@
/**
* 这里是插件js, 请在blade里面使用以下代码引入
* <script src="{{ asset('plugin/stripe/js/demo.js') }}"></script>
*/

View File

@ -1,29 +0,0 @@
<?php
/**
* Flat Shipping 字段
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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,
],
];

View File

@ -1,18 +0,0 @@
{
"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"
}
}

View File

@ -1,149 +0,0 @@
<?php
/**
* bootstrap.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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 = '<span class="badge" style="background-color: #FF4D00; color: #ffffff; border-color: #FF4D00">Hot</span>';
return $badge . $output;
});
// 通过模板 hook 在产品详情页品牌下面添加信息
add_hook_blade('product.detail.brand', function ($callback, $output, $data) {
return $output . '<div class="d-flex"><span class="title text-muted">Brand 2:</span>品牌 2</div>';
});
// 通过模板 hook 在产品详情页立即购买后添加按钮
add_hook_blade('product.detail.buy.after', function ($callback, $output, $data) {
$view = '<button class="btn btn-dark ms-3 fw-bold"><i class="bi bi-bag-fill me-1"></i>新增按钮</button>';
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);
}
}

View File

@ -1,48 +0,0 @@
<?php
/**
* MenusController.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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);
}
}

View File

@ -1,14 +0,0 @@
<?php
/**
* dd.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-07-28 16:19:06
* @modified 2022-07-28 16:19:06
*/
return [
'latest_products' => 'Latest Products',
];

View File

@ -1,14 +0,0 @@
<?php
/**
* dd.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-07-28 16:19:06
* @modified 2022-07-28 16:19:06
*/
return [
'latest_products' => '最新商品',
];

View File

@ -1,15 +0,0 @@
<?php
/**
* admin.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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');

View File

@ -1,15 +0,0 @@
<?php
/**
* shop.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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');

View File

@ -1,8 +0,0 @@
/**
这里是插件css, 请在blade里面使用以下代码引入
<link rel="stylesheet" href="{{ asset('plugin/stripe/css/demo.css') }}">
*/
#bk-stripe-app .form-wrap {
max-width: 400px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -1,4 +0,0 @@
/**
* 这里是插件js, 请在blade里面使用以下代码引入
* <script src="{{ asset('plugin/stripe/js/demo.js') }}"></script>
*/

View File

@ -1 +0,0 @@
<x-admin-form-input name="custom_field" :title="'自定义字段'" :value="old('custom_field', $product->custom_field ?? '')" />

View File

@ -1,3 +0,0 @@
<li class="nav-item">
<a href="https://beikeshop.com" class="nav-link"><i class="iconfont">图标后&#xe619;</i></a>
</li>

View File

@ -1,21 +0,0 @@
@extends('layout.master')
@section('content')
<div class="breadcrumb-wrap">
<div class="container">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">@lang('shop/common.home')</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ __('LatestProducts::header.latest_products') }}</li>
</ol>
</nav>
</div>
</div>
<div class="container">
<div class="row">
@foreach ($items as $product)
<div class="col-6 col-md-3">@include('shared.product')</div>
@endforeach
</div>
{{ $products->links('shared/pagination/bootstrap-4') }}
</div>
@endsection

View File

@ -1,3 +0,0 @@
<button class="btn btn-dark ms-3 fw-bold">
<i class="bi bi-bag-fill me-1"></i>DIY
</button>

View File

@ -1,12 +0,0 @@
{
"code": "latest_products",
"name": "最新商品列表",
"description": "首页菜单添加最新商品列表功能",
"type": "feature",
"version": "v1.0.0",
"icon": "/image/logo.png",
"author": {
"name": "成都光大网络科技有限公司",
"email": "yangjin@guangda.work"
}
}

View File

@ -1,48 +0,0 @@
<?php
/**
* Bootstrap.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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;
});
}
}

View File

@ -1,90 +0,0 @@
<?php
/**
* OpenaiController.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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;
}
}

View File

@ -1,27 +0,0 @@
<?php
/**
* setting.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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.',
];

View File

@ -1,27 +0,0 @@
<?php
/**
* setting.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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',
];

View File

@ -1,27 +0,0 @@
<?php
/**
* setting.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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',
];

View File

@ -1,161 +0,0 @@
<?php
/**
* OpenAI.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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);
}
}

View File

@ -1,66 +0,0 @@
<?php
/**
* Chat.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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);
}
}

View File

@ -1,33 +0,0 @@
<?php
/**
* Completion.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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);
}
}

View File

@ -1,38 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (! Schema::hasTable('openai_logs')) {
Schema::create('openai_logs', function (Blueprint $table) {
$table->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');
}
};

View File

@ -1,32 +0,0 @@
<?php
/**
* OpenaiLog.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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');
}
}

View File

@ -1,18 +0,0 @@
<?php
/**
* admin.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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');

View File

@ -1,120 +0,0 @@
<?php
/**
* OpenAIService.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,307 +0,0 @@
@extends('admin::layouts.master')
@section('title', $name)
@push('header')
<script src="{{ asset('vendor/marked/marked.min.js') }}"></script>
<script src="{{ asset('vendor/highlight/highlight.min.js') }}"></script>
<link rel="stylesheet" type="text/css" href="{{ asset('vendor/highlight/atom-one-dark.min.css') }}">
@endpush
@section('content')
<div class="row">
<div class="col-md-7 col-12 answer-wrap">
<div class="border p-3 bg-white" id="answer">
<div class="not-answer"><i class="bi bi-activity"></i> {{ __('Openai::common.no_question') }}</div>
</div>
<div class="input-group mb-3 mt-4">
<input type="text" id="ai-input" class="form-control rounded-0 form-control-lg"
placeholder="{{ __('Openai::common.enter_question') }}" {{ $error ? 'disabled' : '' }} aria-label="{{ __('Openai::common.enter_question') }}"
aria-describedby="button-addon2">
<button class="btn btn-primary px-4 rounded-0" {{ $error ? 'disabled' : '' }} type="button" id="ai-submit"><i class="bi bi-send-fill"></i>
{{ __('common.confirm') }}</button>
</div>
</div>
<div class="col-md-5 col-12">
<div class="mb-2"><i class="bi bi-megaphone text-secondary fs-3"></i> </div>
@if ($type != 'own')
<div class="number-free mb-3 fs-5">{{ __('Openai::common.number_free') }}:
<span>{{ __('Openai::common.loading') }}</span>
</div>
@endif
@if ($error)
<div class="alert alert-danger alert-dismissible">
<i class="bi bi-exclamation-triangle-fill"></i>
{{ $error }}
</div>
@endif
<div class="text-secondary">{{ $description }}</div>
</div>
</div>
<script>
let last_page = 0;
let current_page = 1;
marked.setOptions({
highlight: function(code, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(lang, code, true).value;
} else {
return hljs.highlightAuto(code).value;
}
}
});
$('#answer').scroll(function() {
if ($(this).scrollTop() == 0) {
if (current_page < last_page) {
if (!$('.text-loading').length) {
$('#answer').prepend(
'<div class="text-center py-3 text-secondary text-loading"><i class="bi bi-activity"></i> {{ __('Openai::common.loading') }}</div>'
);
}
clearTimeout(timer);
var timer = setTimeout(function() {
loadHistories(current_page + 1);
}, 300);
}
if (current_page == last_page) {
if (!$('.text-loading').length) {
$('#answer').prepend(
'<div class="text-center py-3 text-secondary text-loading"><i class="bi bi-activity"></i> {{ __('Openai::common.no_more') }}</div>'
);
}
}
}
});
$('#answer').height($(window).height() - 260);
$(document).ready(function() {
@if ($type != 'own')
loadQuantities();
@endif
loadHistories(1 , function() {
// 获取 answer .answer-list 内容高度
let height = 0;
$('.answer-list').each(function() {
height += $(this).height();
})
let answerHeight = $('#answer').height();
if (height < answerHeight) {
loadHistories(current_page + 1);
}
});
$('#ai-input').keydown(function(e) {
if (e.keyCode == 13) {
$('#ai-submit').click();
}
})
$('#ai-submit').click(function() {
var question = $('#ai-input').val();
if (!question) {
return;
}
const $btn = $(this);
const btnHtml = $(this).html();
const loadHtml = '<span class="spinner-border spinner-border-sm"></span>';
let html = '';
$.ajax({
url: `{{ $base }}/completions`,
type: 'POST',
headers: {
'token': '{{ system_setting('base.developer_token') ?? '' }}'
},
data: {
prompt: question,
domain: config.app_url,
},
beforeSend: function() {
$btn.html(loadHtml).prop('disabled', true)
},
complete: function() {
$btn.html(btnHtml).prop('disabled', false)
},
success: function(data) {
if ($('.not-answer').length) {
$('.not-answer').remove();
}
if (data.error) {
layer.msg(data.error);
return;
}
$('.number-free span').text(data.available)
let answer = marked.parse(data.response.choices[0].text);
html += '<div class="answer-list">',
html += '<div class="created-at"><span>' + data.created_format + '</span></div>',
html += '<div class="d-flex mb-2"><div class="text-secondary">{{ __('Openai::common.qa_q') }}</div><div class="w-100">' + question + '</div></div>',
html += '<div class="d-flex"><div class="text-secondary">{{ __('Openai::common.qa_a') }}</div><div class="w-100">' + answer + '</div></div>'
html += '</div>'
$('#ai-input').val('');
$('#answer').append(html);
$('#answer').scrollTop($('#answer')[0].scrollHeight);
}
})
})
})
function loadQuantities() {
$.ajax({
url: `{{ $base }}/quantities?domain=${config.app_url}`,
headers: {
'token': '{{ system_setting('base.developer_token') ?? '' }}'
},
success: function(data) {
$('.number-free span').text(data.available)
}
})
}
function loadHistories(page = 1, callback = null) {
$.ajax({
url: `{{ $base }}/histories?domain=${config.app_url}&page=${page}`,
headers: {
'token': '{{ system_setting('base.developer_token') ?? '' }}'
},
success: function(data) {
$('.text-loading').remove();
last_page = data.last_page;
current_page = data.current_page;
if (data.data && data.data.length) {
$('.not-answer').remove();
// data.data 倒叙
data.data.reverse();
let html = '';
data.data.forEach(function(item, index) {
html += '<div class="answer-list ' + (!index ? 'first' : '') + '">',
html += '<div class="created-at"><span>' + item.created_format + '</span></div>',
html += '<div class="d-flex mb-2"><div class="text-secondary">{{ __('Openai::common.qa_q') }}</div><div class="w-100">' + item.question + '</div></div>',
html += '<div class="d-flex"><div class="text-secondary">{{ __('Openai::common.qa_a') }}</div><div class="w-100">' + marked.parse(item.answer) + '</div></div>'
html += '</div>'
})
$('#answer').prepend(html);
if (page == 1) {
$('#answer').scrollTop($('#answer')[0].scrollHeight);
} else {
$('#answer').scrollTop($('#answer .answer-list.first:eq(1)').offset().top - 100 - $('#answer')
.offset().top);
}
if (callback) {
callback();
}
}
}
})
}
</script>
<style>
body {
background-color: #f4f4f4;
}
#answer {
overflow-y: auto;
border-radius: 0.25rem;
}
.not-answer {
text-align: center;
padding: 100px 0;
font-size: 20px;
color: #999;
}
.answer-wrap {
max-width: 740px;
}
.answer-list {
/* padding-bottom: 20px; */
/* margin-bottom: 20px; */
white-space: pre-wrap;
/* border-bottom: 1px solid #eee; */
}
.answer-list p {
margin-bottom: 0;
}
.answer-list:last-child {
/* border-bottom: none; */
/* margin-bottom: 0; */
/* padding-bottom: 0; */
}
.created-at {
text-align: center;
color: #999;
font-size: 12px;
margin: 30px 0;
position: relative;
}
.created-at span {
background-color: #fff;
padding: 0 10px;
position: relative;
}
.created-at:before {
content: '';
display: inline-block;
width: 100%;
height: 1px;
background-color: #eee;
position: absolute;
top: 50%;
left: 0;
}
.answer-wrap pre {
display: block;
background-color: #f3f3f3;
padding: .5rem !important;
overflow-y: auto;
font-weight: 300;
font-family: Menlo, monospace;
border-radius: .3rem;
margin-bottom: 0;
}
.answer-wrap pre {
background-color: #283646 !important;
}
.answer-wrap pre>code {
border: 0px !important;
background-color: #283646 !important;
color: #FFF;
}
.answer-wrap ol, .answer-wrap ul, .answer-wrap dl {
margin-bottom: 0;
padding-left: 14px;
line-height: 1;
}
</style>
@endsection

View File

@ -1,31 +0,0 @@
<?php
/**
* columns.php
*
* @copyright 2023 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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' => '<a target="_blank" href="https://platform.openai.com/account/api-keys">获取 API Key</a>',
],
];

View File

@ -1,18 +0,0 @@
{
"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"
}
}

View File

@ -1,118 +0,0 @@
<?php
/**
* PaypalController.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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);
}
}

View File

@ -1,15 +0,0 @@
<?php
/**
* setting.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-11 15:26:18
* @modified 2022-08-11 15:26:18
*/
return [
'sandbox_mode' => 'Sandbox Mode',
'enabled' => 'Enabled',
];

View File

@ -1,15 +0,0 @@
<?php
/**
* setting.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-11 15:26:18
* @modified 2022-08-11 15:26:18
*/
return [
'sandbox_mode' => '沙盒模式',
'enabled' => '开启',
];

View File

@ -1,10 +0,0 @@
<?php
/**
* admin.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-12 10:33:01
* @modified 2022-08-12 10:33:01
*/

View File

@ -1,18 +0,0 @@
<?php
/**
* shop.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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']);
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -1,73 +0,0 @@
<!-- Set up a container element for the button -->
<div id="paypal-button-container" class="mt-4"></div>
<!-- Include the PayPal JavaScript SDK -->
@if($payment_setting['sandbox_mode'])
<script src="https://www.paypal.com/sdk/js?client-id={{ plugin_setting('paypal.sandbox_client_id') }}&currency=USD"></script>
@else
<script src="https://www.paypal.com/sdk/js?client-id={{ plugin_setting('paypal.live_client_id') }}&currency=USD"></script>
@endif
<script>
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
// Call your server to set up the transaction
createOrder: function (data, actions) {
const token = $('meta[name="csrf-token"]').attr('content')
return fetch('/paypal/create', {
method: 'POST',
headers: {
'X-CSRF-Token': token
},
body: JSON.stringify({
orderNumber: "{{$order->number}}",
})
}).then(function (res) {
return res.json();
}).then(function (orderData) {
if (orderData.error) {
layer.alert(orderData.error.details[0].description, {
title: '{{ __('common.text_hint') }}',
closeBtn: 0,
area: ['400px', 'auto'],
btn: ['{{ __('common.confirm') }}']
}, function(index) {
window.location.reload();
layer.close(index);
});
}
return orderData.id;
});
},
// Call your server to finalize the transaction
onApprove: function (data, actions) {
const token = $('meta[name="csrf-token"]').attr('content')
return fetch('/paypal/capture', {
method: 'POST',
headers: {
'X-CSRF-Token': token
},
body: JSON.stringify({
orderNumber: "{{$order->number}}",
paypalOrderId: data.orderID,
payment_gateway_id: $("#payapalId").val(),
})
}).then(function (res) {
// console.log(res.json());
return res.json();
}).then(function (orderData) {
// Successful capture! For demo purposes:
console.log('Capture result', orderData, JSON.stringify(orderData, null, 2));
let captureStatus = orderData.status
if (captureStatus === 'COMPLETED') {
@if (current_customer())
window.location.href = "{{ shop_route('account.order.show', $order->number) }}"
@else
location = "{{ shop_route('checkout.success', ['order_number' => $order->number]) }}"
@endif
}
});
}
}).render('#paypal-button-container');
</script>

View File

@ -1,56 +0,0 @@
<?php
/**
* Stripe 字段
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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' => '',
],
];

View File

@ -1,12 +0,0 @@
{
"code": "paypal",
"name": "PayPal",
"description": "PayPal 支付 <a href='https://developer.paypal.com/' target='_blank'>PayPal Developer</a>",
"type": "payment",
"version": "v1.0.0",
"icon": "/image/logo.png",
"author": {
"name": "成都光大网络科技有限公司",
"email": "yangjin@guangda.work"
}
}

View File

@ -1,48 +0,0 @@
<?php
/**
* Bootstrap.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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;
});
}
}

View File

@ -1,29 +0,0 @@
<?php
/**
* SocialController.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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('保存成功');
}
}

View File

@ -1,72 +0,0 @@
<?php
/**
* SocialController.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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());
}
}
}

View File

@ -1,36 +0,0 @@
<?php
/**
* providers.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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',
];

View File

@ -1,60 +0,0 @@
<?php
/**
* setting.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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 <a href="http://www.opencart.cn" target="_blank">OmniAuth</a> &copy; %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',
];

View File

@ -1,36 +0,0 @@
<?php
/**
* providers.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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' => '微博',
];

View File

@ -1,49 +0,0 @@
<?php
/**
* setting.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @created 2022-08-11 15:26:18
* @modified 2022-08-11 15:26:18
*/
return [
// Text
'text_module' => '模块',
'text_success' => '成功: 您成功修改第三方登录配置!',
'text_copyright' => 'OpenCart.cn <a href="http://www.opencart.cn" target="_blank">获取帮助</a> &copy; %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' => '警告: 您没有权限修改此配置!',
];

View File

@ -1,37 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('customer_socials', function (Blueprint $table) {
$table->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');
}
};

View File

@ -1,30 +0,0 @@
<?php
/**
* CustomerSocial.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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);
}
}

View File

@ -1,120 +0,0 @@
<?php
/**
* CustomerRepo.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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();
}
}

View File

@ -1,15 +0,0 @@
<?php
/**
* admin.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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');

View File

@ -1,16 +0,0 @@
<?php
/**
* shop.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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');

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -1,187 +0,0 @@
@section('page-title-right')
<button type="button" class="btn btn-primary save-btn" onclick="app.submit('form')">{{ __('common.save') }}</button>
@endsection
<div class="mb-5" id="app">
{{-- <div class="d-flex justify-content-between align-items-center border-bottom pb-3 mb-4">
<h6 class="mb-0">{{ $plugin->name }}</h6>
<button type="button" @click="addRow()" class="btn btn-sm btn-outline-primary">{{ __('common.add') }}</button>
</div> --}}
{{-- <button type="button" @click="addRow()" class="btn btn-sm btn-outline-primary">{{ __('common.add') }}</button> --}}
<el-form ref="form" :model="form" class="form-wrap" :inline-message="true">
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 150px">{{ __('Social::setting.entry_provider') }}</th>
<th>{{ __('Social::setting.entry_status') }}</th>
<th>{{ __('Social::setting.entry_key') }}</th>
<th>{{ __('Social::setting.entry_secret') }}</th>
<th>{{ __('Social::setting.entry_callback') }}</th>
<th style="width: 100px">{{ __('Social::setting.entry_sort_order') }}</th>
<th class="text-end"></th>
</tr>
</thead>
<tbody v-if="form.social.length">
<tr v-for="(item, index) in form.social" :key="index">
<td>
<el-form-item prop="provider" class="mb-0">
<el-select size="small" v-model="item.provider" @change="(e) => {providerChange(e, index)}" placeholder="{{ __('Social::setting.provider') }}">
<el-option
v-for="item in providers"
:key="item.code"
:label="item.label"
:disabled="item.disabled"
:value="item.code">
</el-option>
</el-select>
</el-form-item>
</td>
<td>
<el-form-item label="" prop="entry_status" class="mb-0">
<el-switch v-model="item.status" :active-value="1" :inactive-value="0"></el-switch>
</el-form-item>
</td>
<td>
<el-form-item
label="" :prop="`social[${index}].key`" class="mb-0"
:rules="[
{ required: true, message: '{{ __('common.error_required', ['name' => __('Social::setting.entry_key')]) }}', trigger: ['blur', 'change'] },]"
>
<el-input size="small" v-model="item.key" placeholder="{{ __('Social::setting.entry_key') }}"></el-input>
</el-form-item>
</td>
<td>
<el-form-item
label="" :prop="`social[${index}].secret`" class="mb-0"
:rules="[
{ required: true, message: '{{ __('common.error_required', ['name' => __('Social::setting.entry_secret')]) }}', trigger: ['blur', 'change'] },]"
>
<el-input size="small" v-model="item.secret" placeholder="{{ __('Social::setting.entry_secret') }}"></el-input>
</el-form-item>
</td>
<td>
<el-form-item label="" class="mb-0">
<div class="input-group">
<input size="small" class="form-control" :value="item.callback" placeholder="{{ __('Social::setting.entry_callback') }}"></input>
<a href="javascript:void(0)" class="btn btn-outline-secondary opacity-75 copy-code" :data-clipboard-text="item.callback" @click="copyCode"><i class="bi bi-front"></i></a>
</div>
</el-form-item>
</td>
<td>
<el-form-item label="" prop="sort_order" class="mb-0">
<el-input size="small" v-model="item.sort_order" placeholder="{{ __('Social::setting.entry_sort_order') }}"></el-input>
</el-form-item>
</td>
<td class="text-end">
<button type="button" @click="form.social.splice(index, 1)" class="btn btn-outline-danger btn-sm ml-1"><i class="bi bi-x-lg"></i></button>
</td>
</tr>
<tr>
<td colspan="6"></td>
<td class="text-end"><button type="button" @click="addRow()" class="btn btn-sm btn-outline-primary">{{ __('common.add') }}</button></td>
</tr>
</tbody>
<tbody v-else>
<td colspan="7">
<div class="d-flex align-items-center justify-content-center p-4">
<div class="text-secondary fs-5 me-2">{{ __('common.no_data') }}</div>
<button type="button" @click="addRow()" class="btn btn-sm btn-outline-primary">{{ __('common.add') }}</button>
</div>
</td>
</tbody>
</table>
</el-form>
</div>
<h6 class="border-bottom pb-3 mb-4">{{ __('Social::setting.text_help_msg') }}</h6>
<ol class="list-group list-group-numbered lh-lg text-secondary">
<li>{{ __('Social::setting.text_omni_explain') }}</li>
<li>{{ __('Social::setting.text_omni_explain_2') }}</li>
<li>{{ __('Social::setting.text_facebook_title') }}
<a target="_blank" href="https://developers.facebook.com/">Facebook</a>
</li>
<li>{{ __('Social::setting.text_twitter_title') }}
<a target="_blank" href="https://developer.twitter.com/">Twitter</a>
</li>
<li>{{ __('Social::setting.text_google_title') }}
<a target="_blank" href="https://console.developers.google.com/">Google</a>
</li>
.......
</ol>
<style>
.el-form-item__error--inline {
margin-left: 0;
}
.el-form-item__content {
line-height: 1;
}
</style>
<script src="{{ asset('vendor/clipboard/clipboard.min.js') }}"></script>
<script>
new ClipboardJS('.copy-code')
let app = new Vue({
el: '#app',
data: {
form: {
social: @json($plugin->getSetting('setting') ?? []),
},
source: {
providers: @json(Plugin\Social\Repositories\CustomerRepo::allProviders())
},
rules: {
}
},
computed: {
providers() {
let providers = @json(Plugin\Social\Repositories\CustomerRepo::allProviders());
providers.forEach(e => {
if (this.form.social.some(s => s.provider == e.code)) {
e.disabled = true;
}
})
return providers;
}
},
methods: {
submit(form) {
this.$refs[form].validate((valid) => {
if (!valid) {
this.$message.error('{{ __('common.error_form') }}');
return;
}
$http.post("{{ admin_route('plugin.social.setting') }}", this.form.social).then((res) => {
layer.msg(res.message)
})
});
},
providerChange(e, index) {
this.form.social[index].callback = `{{ shop_route('home.index') }}/social/callbacks/${e}`
},
addRow() {
let providers = this.source.providers.filter(e => !this.form.social.some(s => s.provider == e.code))
if (providers.length) {
this.form.social.push({provider: providers[0].code, status: 1, key: '', secret: '', callback: `{{ shop_route('home.index') }}/social/callbacks/${this.source.providers[0].code}`, sort_order: this.form.social.length})
}
},
copyCode() {
layer.msg('{{ __('Social::setting.copy_success') }}');
}
}
})
</script>

View File

@ -1,9 +0,0 @@
<script>
const url = "{{ shop_route('account.index') }}";
if (window.opener === null) {
window.location.href = url;
} else {
window.opener.location = url;
window.close();
}
</script>

View File

@ -1,4 +0,0 @@
<button type="button" class="btn border fw-bold w-100 mb-3" class="provider-btn" onclick="bk.openWin('{{ shop_route('social.redirect', $provider['provider']) }}')">
<img src="{{ plugin_resize('social' , '/image/' . $provider['provider'] . '.png') }}" class="img-fluid wh-20 me-2">
{{ __("Social::providers.".$provider['provider']) }}
</button>

View File

@ -1,12 +0,0 @@
{
"code": "social",
"name": "Social",
"description": "第三方登录(包括Facebook、Twitter、Google)",
"type": "social",
"version": "v1.0.0",
"icon": "/image/logo.png",
"author": {
"name": "成都光大网络科技有限公司",
"email": "yangjin@guangda.work"
}
}

View File

@ -1,54 +0,0 @@
<?php
/**
* StripeController.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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());
}
}
}

View File

@ -1,31 +0,0 @@
<?php
/**
* dd.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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',
];

View File

@ -1,31 +0,0 @@
<?php
/**
* dd.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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' => '支付失败',
];

View File

@ -1,15 +0,0 @@
<?php
/**
* shop.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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');

View File

@ -1,74 +0,0 @@
<?php
/**
* StripePaymentService.php
*
* @copyright 2022 beikeshop.com - All Rights Reserved
* @link https://beikeshop.com
* @author Edward Yang <yangjin@guangda.work>
* @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'];
}
}

View File

@ -1,8 +0,0 @@
/**
这里是插件css, 请在blade里面使用以下代码引入
<link rel="stylesheet" href="{{ asset('plugin/stripe/css/demo.css') }}">
*/
#bk-stripe-app .form-wrap {
/* max-width: 500px; */
}

Some files were not shown because too many files have changed in this diff Show More