Compare commits
4 Commits
1.0.0-rc.1
...
1.0.0-rc.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a189abf0b | ||
|
|
34fc1c33bf | ||
|
|
61e34b4f4e | ||
|
|
7fe2770d45 |
@@ -93,7 +93,8 @@ RUN mkdir -p /out \
|
|||||||
--exclude=.editorconfig --exclude=phpunit.xml \
|
--exclude=.editorconfig --exclude=phpunit.xml \
|
||||||
--exclude=.travis.yml --exclude=composer.lock --exclude=.styleci.yml \
|
--exclude=.travis.yml --exclude=composer.lock --exclude=.styleci.yml \
|
||||||
--exclude=Makefile --exclude=.gitkeep --exclude=test \
|
--exclude=Makefile --exclude=.gitkeep --exclude=test \
|
||||||
artisan app build config database vendor public resources routes stubs bootstrap storage composer.json \
|
--exclude=resources/shop \
|
||||||
|
artisan app config database vendor public resources routes stubs bootstrap storage composer.json \
|
||||||
&& xz -T0 -9e /out/app.tar \
|
&& xz -T0 -9e /out/app.tar \
|
||||||
&& mv /out/app.tar.xz /out/opensem-prod.tar.xz
|
&& mv /out/app.tar.xz /out/opensem-prod.tar.xz
|
||||||
|
|
||||||
|
|||||||
48
Gruntfile.js
@@ -19,8 +19,8 @@ var jsSite = [
|
|||||||
jsBootstrap,
|
jsBootstrap,
|
||||||
'node_modules/jquery-serializejson/jquery.serializejson.min.js',
|
'node_modules/jquery-serializejson/jquery.serializejson.min.js',
|
||||||
'node_modules/currency.js/dist/currency.min.js',
|
'node_modules/currency.js/dist/currency.min.js',
|
||||||
'build/js/plugins/smooth_products/js/smoothproducts.min.js',
|
'resources/shop/js/plugins/smooth_products/js/smoothproducts.min.js',
|
||||||
'build/js/site.js',
|
'resources/shop/js/site.js',
|
||||||
]
|
]
|
||||||
|
|
||||||
var cssSite = [
|
var cssSite = [
|
||||||
@@ -28,8 +28,8 @@ var cssSite = [
|
|||||||
'node_modules/@fortawesome/fontawesome-free/css/all.min.css',
|
'node_modules/@fortawesome/fontawesome-free/css/all.min.css',
|
||||||
'node_modules/animate.css/animate.min.css',
|
'node_modules/animate.css/animate.min.css',
|
||||||
'node_modules/icheck-bootstrap/icheck-bootstrap.min.css',
|
'node_modules/icheck-bootstrap/icheck-bootstrap.min.css',
|
||||||
'build/js/plugins/smooth_products/css/smoothproducts.css',
|
'resources/shop/js/plugins/smooth_products/css/smoothproducts.css',
|
||||||
'build/css/site.css',
|
'resources/shop/css/site.css',
|
||||||
]
|
]
|
||||||
|
|
||||||
var jsAdminLTE = [
|
var jsAdminLTE = [
|
||||||
@@ -41,15 +41,15 @@ var jsAdminLTE = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
var jsCoreInclude = [
|
var jsCoreInclude = [
|
||||||
'build/js/include/core/objectLength.js',
|
'resources/shop/js/include/core/objectLength.js',
|
||||||
'build/js/include/core/url.js',
|
'resources/shop/js/include/core/url.js',
|
||||||
'build/js/include/core/user.js',
|
'resources/shop/js/include/core/user.js',
|
||||||
'build/js/include/form/radio.js',
|
'resources/shop/js/include/form/radio.js',
|
||||||
'build/js/include/form/upload.js',
|
'resources/shop/js/include/form/upload.js',
|
||||||
'build/js/include/form/validator.js',
|
'resources/shop/js/include/form/validator.js',
|
||||||
'build/js/include/layout/animate.js',
|
'resources/shop/js/include/layout/animate.js',
|
||||||
'build/js/include/layout/scroll.js',
|
'resources/shop/js/include/layout/scroll.js',
|
||||||
'build/js/include/layout/tooltip.js',
|
'resources/shop/js/include/layout/tooltip.js',
|
||||||
]
|
]
|
||||||
|
|
||||||
var jsBundle = [
|
var jsBundle = [
|
||||||
@@ -84,7 +84,7 @@ var jsMain = [
|
|||||||
var cssPrint = [
|
var cssPrint = [
|
||||||
// 'node_modules/bootstrap/dist/css/bootstrap.min.css',
|
// 'node_modules/bootstrap/dist/css/bootstrap.min.css',
|
||||||
'cssIcons',
|
'cssIcons',
|
||||||
'build/print.css'
|
'resources/shop/print.css'
|
||||||
]
|
]
|
||||||
|
|
||||||
var cssBundle = [
|
var cssBundle = [
|
||||||
@@ -109,7 +109,7 @@ var cssIcons = [
|
|||||||
var cssMain = [
|
var cssMain = [
|
||||||
cssBundle,
|
cssBundle,
|
||||||
cssIcons,
|
cssIcons,
|
||||||
'build/css/main.css',
|
'resources/shop/css/main.css',
|
||||||
]
|
]
|
||||||
|
|
||||||
var jsDataTables = [
|
var jsDataTables = [
|
||||||
@@ -251,31 +251,31 @@ module.exports = function(grunt) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
expand: true,
|
expand: true,
|
||||||
cwd: 'build/fonts',
|
cwd: 'resources/shop/fonts',
|
||||||
src: ['**'],
|
src: ['**'],
|
||||||
dest: 'public/fonts/'
|
dest: 'public/fonts/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expand: true,
|
expand: true,
|
||||||
cwd: 'build/img',
|
cwd: 'resources/shop/img',
|
||||||
src: ['**'],
|
src: ['**'],
|
||||||
dest: 'public/img/'
|
dest: 'public/img/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expand: true,
|
expand: true,
|
||||||
cwd: 'build/lang',
|
cwd: 'resources/shop/lang',
|
||||||
src: ['**'],
|
src: ['**'],
|
||||||
dest: 'public/assets/lang/'
|
dest: 'public/assets/lang/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expand: true,
|
expand: true,
|
||||||
cwd: 'build/plugins',
|
cwd: 'resources/shop/plugins',
|
||||||
src: ['**'],
|
src: ['**'],
|
||||||
dest: 'public/assets/plugins/'
|
dest: 'public/assets/plugins/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expand: true,
|
expand: true,
|
||||||
cwd: 'build/assets/tpl',
|
cwd: 'resources/shop/assets/tpl',
|
||||||
src: ['**'],
|
src: ['**'],
|
||||||
dest: 'public/assets/tpl/'
|
dest: 'public/assets/tpl/'
|
||||||
},
|
},
|
||||||
@@ -395,7 +395,7 @@ module.exports = function(grunt) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
expand: true,
|
expand: true,
|
||||||
cwd: 'build/plugins/pdfjs/',
|
cwd: 'resources/shop/plugins/pdfjs/',
|
||||||
src: ['**'],
|
src: ['**'],
|
||||||
dest: 'public/assets/plugins/pdfjs',
|
dest: 'public/assets/plugins/pdfjs',
|
||||||
},
|
},
|
||||||
@@ -461,7 +461,7 @@ module.exports = function(grunt) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
expand: true,
|
expand: true,
|
||||||
cwd: 'build/js/include/plugins/datatables_lang/',
|
cwd: 'resources/shop/js/include/plugins/datatables_lang/',
|
||||||
src: ['*.json'],
|
src: ['*.json'],
|
||||||
dest: 'public/assets/plugins/datatables_lang',
|
dest: 'public/assets/plugins/datatables_lang',
|
||||||
},
|
},
|
||||||
@@ -539,7 +539,7 @@ module.exports = function(grunt) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
expand: true,
|
expand: true,
|
||||||
cwd: 'build/js/include/',
|
cwd: 'resources/shop/js/include/',
|
||||||
src: ['boilerplate.js'],
|
src: ['boilerplate.js'],
|
||||||
dest: 'public/assets/plugins',
|
dest: 'public/assets/plugins',
|
||||||
},
|
},
|
||||||
@@ -548,7 +548,7 @@ module.exports = function(grunt) {
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
dist: {
|
dist: {
|
||||||
files: ['build/js/*', 'build/css/*'],
|
files: ['resources/shop/js/*', 'resources/shop/css/*'],
|
||||||
// tasks: ['concat', 'copy']
|
// tasks: ['concat', 'copy']
|
||||||
tasks: ['concat']
|
tasks: ['concat']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ class InvoiceController extends Controller
|
|||||||
|
|
||||||
public function pdf($uuid)
|
public function pdf($uuid)
|
||||||
{
|
{
|
||||||
\Debugbar::disable();
|
if (app()->bound('debugbar')) {
|
||||||
|
app('debugbar')->disable();
|
||||||
|
}
|
||||||
|
|
||||||
return InvoicePDF::getByUUID($uuid);
|
return InvoicePDF::getByUUID($uuid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,19 @@
|
|||||||
namespace App\Http\Controllers\Shop;
|
namespace App\Http\Controllers\Shop;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Repositories\Core\User\ShopCart;
|
||||||
|
use App\Repositories\Shop\Paybox as PayboxGateway;
|
||||||
use App\Repositories\Shop\Contents;
|
use App\Repositories\Shop\Contents;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
|
||||||
class PayboxController extends Controller
|
class PayboxController extends Controller
|
||||||
{
|
{
|
||||||
public function accepted()
|
public function accepted()
|
||||||
{
|
{
|
||||||
|
ShopCart::clear();
|
||||||
|
|
||||||
return view('paybox.paybox', ['content' => Contents::getPayboxConfirmedContent()]);
|
return view('paybox.paybox', ['content' => Contents::getPayboxConfirmedContent()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,8 +36,20 @@ class PayboxController extends Controller
|
|||||||
|
|
||||||
public function process(Request $request)
|
public function process(Request $request)
|
||||||
{
|
{
|
||||||
$data = $request->all();
|
$invoiceId = $request->input('order_number');
|
||||||
|
|
||||||
return view('paybox.send', $data);
|
if (! $invoiceId) {
|
||||||
|
Log::warning('Paybox callback missing order_number', ['payload' => $request->all()]);
|
||||||
|
|
||||||
|
return response('Missing order_number', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = PayboxGateway::verifyPayment($invoiceId);
|
||||||
|
|
||||||
|
if (! $success) {
|
||||||
|
return response('KO', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response('OK');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Repositories\Shop;
|
namespace App\Repositories\Shop;
|
||||||
|
|
||||||
|
use App\Models\Shop\Invoice;
|
||||||
use App\Models\Shop\InvoicePayment;
|
use App\Models\Shop\InvoicePayment;
|
||||||
|
use App\Repositories\Core\DateTime;
|
||||||
use Bnb\PayboxGateway\Requests\Paybox\AuthorizationWithCapture;
|
use Bnb\PayboxGateway\Requests\Paybox\AuthorizationWithCapture;
|
||||||
use Bnb\PayboxGateway\Requests\PayboxDirect\Capture;
|
use Bnb\PayboxGateway\Requests\PayboxDirect\Capture;
|
||||||
use Bnb\PayboxGateway\Responses\Exceptions\InvalidSignature;
|
use Bnb\PayboxGateway\Responses\Exceptions\InvalidSignature;
|
||||||
use Bnb\PayboxGateway\Responses\Paybox\Verify;
|
use Bnb\PayboxGateway\Responses\Paybox\Verify;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class Paybox
|
class Paybox
|
||||||
@@ -23,17 +26,141 @@ class Paybox
|
|||||||
|
|
||||||
public static function verifyPayment($invoiceId)
|
public static function verifyPayment($invoiceId)
|
||||||
{
|
{
|
||||||
$invoice = Invoices::get($invoiceId);
|
$invoice = Invoices::get($invoiceId, ['order']);
|
||||||
$payboxVerify = App::make(Verify::class);
|
|
||||||
try {
|
if (! $invoice) {
|
||||||
$success = $payboxVerify->isSuccess($invoice->total_shipped);
|
Log::warning('Paybox callback received for unknown invoice', [
|
||||||
if ($success) {
|
'invoice_id' => $invoiceId,
|
||||||
// process order here after making sure it was real payment
|
'payload' => request()->all(),
|
||||||
}
|
]);
|
||||||
echo 'OK';
|
|
||||||
} catch (InvalidSignature $e) {
|
return false;
|
||||||
Log::alert('Invalid payment signature detected');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$payboxVerify = App::make(Verify::class);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$isSuccessful = $payboxVerify->isSuccess($invoice->total_shipped);
|
||||||
|
} catch (InvalidSignature $e) {
|
||||||
|
Log::alert('Invalid payment signature detected', [
|
||||||
|
'invoice_id' => $invoiceId,
|
||||||
|
'payload' => request()->except('signature'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $isSuccessful) {
|
||||||
|
Log::warning('Paybox payment verification failed', [
|
||||||
|
'invoice_id' => $invoiceId,
|
||||||
|
'response_code' => $payboxVerify->getResponseCode(),
|
||||||
|
'payload' => request()->except('signature'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::finalizeInvoicePayment($invoice);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function finalizeInvoicePayment(Invoice $invoice)
|
||||||
|
{
|
||||||
|
$order = $invoice->order;
|
||||||
|
|
||||||
|
if (! $order) {
|
||||||
|
Log::error('Paybox payment cannot be finalized: missing related order', [
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = request();
|
||||||
|
$referenceParts = array_filter([
|
||||||
|
$request->input('call_number'),
|
||||||
|
$request->input('transaction_number'),
|
||||||
|
]);
|
||||||
|
$reference = $referenceParts ? implode('-', $referenceParts) : $request->input('authorization_number');
|
||||||
|
if (! $reference) {
|
||||||
|
$reference = 'paybox-'.$invoice->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = $request->except('signature');
|
||||||
|
$existingPayment = InvoicePayment::where('invoice_id', $invoice->id)
|
||||||
|
->where('reference', $reference)
|
||||||
|
->first();
|
||||||
|
$shouldNotify = false;
|
||||||
|
$validatedTotal = InvoicePayment::where('invoice_id', $invoice->id)
|
||||||
|
->validated()
|
||||||
|
->sum('amount');
|
||||||
|
|
||||||
|
if (! $existingPayment && (float) $validatedTotal >= (float) $invoice->total_shipped) {
|
||||||
|
Log::info('Paybox payment ignored: invoice already fully settled', [
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'order_id' => $order->id,
|
||||||
|
'reference' => $reference,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::transaction(function () use ($invoice, $order, $reference, $payload, $existingPayment, &$shouldNotify) {
|
||||||
|
$attributes = [
|
||||||
|
'payment_type' => 1,
|
||||||
|
'amount' => $invoice->total_shipped,
|
||||||
|
'date' => DateTime::getDate(),
|
||||||
|
'data' => json_encode($payload, JSON_UNESCAPED_UNICODE),
|
||||||
|
'validated' => 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($existingPayment) {
|
||||||
|
$previousValidationState = (int) ($existingPayment->validated ?? 0);
|
||||||
|
$existingPayment->fill($attributes);
|
||||||
|
|
||||||
|
if ($existingPayment->isDirty()) {
|
||||||
|
$existingPayment->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($previousValidationState !== 1 && (int) $existingPayment->validated === 1) {
|
||||||
|
$shouldNotify = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
InvoicePayment::create($attributes + [
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'reference' => $reference,
|
||||||
|
]);
|
||||||
|
$shouldNotify = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Invoices::checkPayments($invoice->id);
|
||||||
|
|
||||||
|
$paidStatus = Orders::getStatusByName('Préparation');
|
||||||
|
if ($paidStatus !== '' && (int) $order->status !== (int) $paidStatus) {
|
||||||
|
$order->status = $paidStatus;
|
||||||
|
$order->save();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($shouldNotify) {
|
||||||
|
try {
|
||||||
|
OrderMails::sendOrderConfirmed($order->id);
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
Log::error('Unable to send order confirmation email after Paybox payment', [
|
||||||
|
'order_id' => $order->id,
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'exception' => $exception->getMessage(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::info('Paybox payment finalized successfully', [
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'order_id' => $order->id,
|
||||||
|
'reference' => $reference,
|
||||||
|
'notified' => $shouldNotify,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getPreviousAuthorizedRequest($request)
|
public static function getPreviousAuthorizedRequest($request)
|
||||||
|
|||||||
0
resources/shop/assets/tpl/.gitkeep
Normal file
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
BIN
resources/shop/img/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
0
resources/shop/lang/.gitkeep
Normal file
0
resources/shop/plugins/.gitkeep
Normal file
0
resources/shop/plugins/pdfjs/.gitkeep
Normal file
0
resources/shop/print.css
Normal file
@@ -13,8 +13,8 @@
|
|||||||
<meta name="description" content="Vente de semences, variété anciennes">
|
<meta name="description" content="Vente de semences, variété anciennes">
|
||||||
<meta name="keywords" content="">
|
<meta name="keywords" content="">
|
||||||
|
|
||||||
<link rel="icon" type="image/vnd.microsoft.icon" href="img/favicon.ico">
|
<link rel="icon" type="image/vnd.microsoft.icon" href="{{ asset('img/favicon.ico') }}">
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="img/favicon.ico">
|
<link rel="shortcut icon" type="image/x-icon" href="{{ asset('img/favicon.ico') }}">
|
||||||
<link rel="stylesheet" href="/css/site.min.css?{{ date('Ymd') }}" type="text/css" media="all">
|
<link rel="stylesheet" href="/css/site.min.css?{{ date('Ymd') }}" type="text/css" media="all">
|
||||||
|
|
||||||
@stack('css')
|
@stack('css')
|
||||||
|
|||||||