update cache
28
.env
|
|
@ -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=
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
*
|
||||
!Bestseller
|
||||
!FlatShipping
|
||||
!LatestProducts
|
||||
!Openai
|
||||
!Paypal
|
||||
!Social
|
||||
!Stripe
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
];
|
||||
|
|
@ -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' => '数量限制',
|
||||
];
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
|
@ -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
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
];
|
||||
|
|
@ -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' => '运费值',
|
||||
];
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
/**
|
||||
这里是插件css, 请在blade里面使用以下代码引入
|
||||
<link rel="stylesheet" href="{{ asset('plugin/stripe/css/demo.css') }}">
|
||||
*/
|
||||
|
||||
#bk-stripe-app .form-wrap {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 6.5 KiB |
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* 这里是插件js, 请在blade里面使用以下代码引入
|
||||
* <script src="{{ asset('plugin/stripe/js/demo.js') }}"></script>
|
||||
*/
|
||||
|
|
@ -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,
|
||||
],
|
||||
];
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
];
|
||||
|
|
@ -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' => '最新商品',
|
||||
];
|
||||
|
|
@ -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');
|
||||
|
|
@ -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');
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
/**
|
||||
这里是插件css, 请在blade里面使用以下代码引入
|
||||
<link rel="stylesheet" href="{{ asset('plugin/stripe/css/demo.css') }}">
|
||||
*/
|
||||
|
||||
#bk-stripe-app .form-wrap {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 7.8 KiB |
|
|
@ -1,4 +0,0 @@
|
|||
/**
|
||||
* 这里是插件js, 请在blade里面使用以下代码引入
|
||||
* <script src="{{ asset('plugin/stripe/js/demo.js') }}"></script>
|
||||
*/
|
||||
|
|
@ -1 +0,0 @@
|
|||
<x-admin-form-input name="custom_field" :title="'自定义字段'" :value="old('custom_field', $product->custom_field ?? '')" />
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<li class="nav-item">
|
||||
<a href="https://beikeshop.com" class="nav-link"><i class="iconfont">图标后</i></a>
|
||||
</li>
|
||||
|
|
@ -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
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<button class="btn btn-dark ms-3 fw-bold">
|
||||
<i class="bi bi-bag-fill me-1"></i>DIY
|
||||
</button>
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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.',
|
||||
];
|
||||
|
|
@ -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',
|
||||
];
|
||||
|
|
@ -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',
|
||||
];
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 22 KiB |
|
|
@ -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
|
||||
|
|
@ -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>',
|
||||
],
|
||||
];
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
];
|
||||
|
|
@ -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' => '开启',
|
||||
];
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
@ -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']);
|
||||
});
|
||||
|
Before Width: | Height: | Size: 8.8 KiB |
|
|
@ -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') }}¤cy=USD"></script>
|
||||
@else
|
||||
<script src="https://www.paypal.com/sdk/js?client-id={{ plugin_setting('paypal.live_client_id') }}¤cy=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>
|
||||
|
|
@ -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' => '',
|
||||
],
|
||||
];
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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('保存成功');
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
];
|
||||
|
|
@ -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> © %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',
|
||||
];
|
||||
|
|
@ -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' => '微博',
|
||||
];
|
||||
|
|
@ -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> © %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' => '警告: 您没有权限修改此配置!',
|
||||
];
|
||||
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
|
|
@ -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');
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
];
|
||||
|
|
@ -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' => '支付失败',
|
||||
];
|
||||
|
|
@ -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');
|
||||
|
|
@ -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'];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
/**
|
||||
这里是插件css, 请在blade里面使用以下代码引入
|
||||
<link rel="stylesheet" href="{{ asset('plugin/stripe/css/demo.css') }}">
|
||||
*/
|
||||
|
||||
#bk-stripe-app .form-wrap {
|
||||
/* max-width: 500px; */
|
||||
}
|
||||