From d6a4f4b4ca0e3d96ddd55e038aac3212ab38d955 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 24 Jul 2024 12:27:09 +1000 Subject: [PATCH] Improvements for task imports --- app/Console/Commands/BackupUpdate.php | 1 - app/Export/CSV/BaseExport.php | 1 + app/Export/CSV/InvoiceExport.php | 6 +- app/Export/Decorators/InvoiceDecorator.php | 1 + app/Http/Controllers/ChartController.php | 2 +- .../ValidationRules/Account/BlackListRule.php | 3 +- app/Import/Providers/BaseImport.php | 2 + .../Transformer/Csv/TaskTransformer.php | 6 +- app/Models/Gateway.php | 30 ++--- app/Models/Project.php | 2 +- app/PaymentDrivers/Forte/ACH.php | 5 +- app/PaymentDrivers/Forte/CreditCard.php | 4 +- .../Stripe/Jobs/ChargeRefunded.php | 70 ++++++++---- app/PaymentDrivers/StripePaymentDriver.php | 104 ++++++++++-------- app/Services/Payment/RefundPayment.php | 10 +- app/Services/Template/TemplateService.php | 3 +- composer.lock | 16 +-- lang/en/texts.php | 2 +- routes/api.php | 1 + 19 files changed, 155 insertions(+), 114 deletions(-) diff --git a/app/Console/Commands/BackupUpdate.php b/app/Console/Commands/BackupUpdate.php index 33b421ea384d..b2a16bff2502 100644 --- a/app/Console/Commands/BackupUpdate.php +++ b/app/Console/Commands/BackupUpdate.php @@ -177,7 +177,6 @@ class BackupUpdate extends Command $doc_bin = $document->getFile(); } catch(\Exception $e) { nlog("Exception:: BackupUpdate::" . $e->getMessage()); - nlog($e->getMessage()); } if ($doc_bin) { diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 374e7783c3fe..9dbf186b90b1 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -172,6 +172,7 @@ class BaseExport 'tax_rate3' => 'invoice.tax_rate3', 'recurring_invoice' => 'invoice.recurring_id', 'auto_bill' => 'invoice.auto_bill_enabled', + 'project' => 'invoice.project', ]; protected array $recurring_invoice_report_keys = [ diff --git a/app/Export/CSV/InvoiceExport.php b/app/Export/CSV/InvoiceExport.php index d87c49ff943c..39ece67a28a9 100644 --- a/app/Export/CSV/InvoiceExport.php +++ b/app/Export/CSV/InvoiceExport.php @@ -153,9 +153,9 @@ class InvoiceExport extends BaseExport private function decorateAdvancedFields(Invoice $invoice, array $entity): array { - // if (in_array('invoice.status', $this->input['report_keys'])) { - // $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id); - // } + if (in_array('invoice.project', $this->input['report_keys'])) { + $entity['invoice.project'] = $invoice->project ? $invoice->project->name : ''; + } if (in_array('invoice.recurring_id', $this->input['report_keys'])) { $entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? ''; diff --git a/app/Export/Decorators/InvoiceDecorator.php b/app/Export/Decorators/InvoiceDecorator.php index 35985decba66..b6579b7f546d 100644 --- a/app/Export/Decorators/InvoiceDecorator.php +++ b/app/Export/Decorators/InvoiceDecorator.php @@ -92,6 +92,7 @@ class InvoiceDecorator extends Decorator implements DecoratorInterface { return $invoice->recurring_invoice ? $invoice->recurring_invoice->number : ''; } + public function auto_bill_enabled(Invoice $invoice) { return $invoice->auto_bill_enabled ? ctrans('texts.yes') : ctrans('texts.no'); diff --git a/app/Http/Controllers/ChartController.php b/app/Http/Controllers/ChartController.php index 394e762d9749..07fc7fc238e4 100644 --- a/app/Http/Controllers/ChartController.php +++ b/app/Http/Controllers/ChartController.php @@ -66,7 +66,7 @@ class ChartController extends BaseController return response()->json($cs->chart_summary($request->input('start_date'), $request->input('end_date')), 200); } - public function calculatedField(ShowCalculatedFieldRequest $request) + public function calculatedFields(ShowCalculatedFieldRequest $request) { /** @var \App\Models\User auth()->user() */ diff --git a/app/Http/ValidationRules/Account/BlackListRule.php b/app/Http/ValidationRules/Account/BlackListRule.php index 1d65de052fa6..0d5e5a13a83f 100644 --- a/app/Http/ValidationRules/Account/BlackListRule.php +++ b/app/Http/ValidationRules/Account/BlackListRule.php @@ -19,8 +19,9 @@ use Illuminate\Contracts\Validation\ValidationRule; */ class BlackListRule implements ValidationRule { - /** Bad domains +/- dispoable email domains */ + /** Bad domains +/- disposable email domains */ private array $blacklist = [ + 'padvn.com', 'anonaddy.me', 'nqmo.com', 'wireconnected.com', diff --git a/app/Import/Providers/BaseImport.php b/app/Import/Providers/BaseImport.php index b2897352cc4a..0366970037f6 100644 --- a/app/Import/Providers/BaseImport.php +++ b/app/Import/Providers/BaseImport.php @@ -473,6 +473,8 @@ class BaseImport $tasks = $this->groupTasks($tasks, $task_number_key); + nlog($tasks); + foreach ($tasks as $raw_task) { $task_data = []; diff --git a/app/Import/Transformer/Csv/TaskTransformer.php b/app/Import/Transformer/Csv/TaskTransformer.php index edd8737131cb..54636349f050 100644 --- a/app/Import/Transformer/Csv/TaskTransformer.php +++ b/app/Import/Transformer/Csv/TaskTransformer.php @@ -46,6 +46,7 @@ class TaskTransformer extends BaseTransformer 'company_id' => $this->company->id, 'number' => $this->getString($task_data, 'task.number'), 'user_id' => $this->getString($task_data, 'task.user_id'), + 'rate' => $this->getFloat($task_data, 'task.rate'), 'client_id' => $clientId, 'project_id' => $this->getProjectId($projectId, $clientId), 'description' => $this->getString($task_data, 'task.description'), @@ -87,8 +88,7 @@ class TaskTransformer extends BaseTransformer $is_billable = true; } - if(isset($item['task.start_date']) && - isset($item['task.end_date'])) { + if(isset($item['task.start_date'])) { $start_date = $this->resolveStartDate($item); $end_date = $this->resolveEndDate($item); } elseif(isset($item['task.duration'])) { @@ -136,7 +136,7 @@ class TaskTransformer extends BaseTransformer private function resolveEndDate($item) { - $stub_end_date = $item['task.end_date']; + $stub_end_date = isset($item['task.end_date']) ? $item['task.end_date'] : $item['task.start_date']; $stub_end_date .= isset($item['task.end_time']) ? " ".$item['task.end_time'] : ''; try { diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index 46ae2abc56f6..2fcdfe7ee4d0 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -139,23 +139,23 @@ class Gateway extends StaticModel case 20: case 56: return [ - GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => ['payment_intent.succeeded', 'payment_intent.payment_failed']], - GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'customer.source.updated', 'payment_intent.processing', 'payment_intent.payment_failed', 'charge.failed']], - GatewayType::DIRECT_DEBIT => ['refund' => false, 'token_billing' => false, 'webhooks' => ['payment_intent.processing', 'payment_intent.succeeded', 'payment_intent.partially_funded', 'payment_intent.payment_failed']], + GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => ['payment_intent.succeeded', 'charge.refunded', 'payment_intent.payment_failed']], + GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.refunded','charge.succeeded', 'customer.source.updated', 'payment_intent.processing', 'payment_intent.payment_failed', 'charge.failed']], + GatewayType::DIRECT_DEBIT => ['refund' => false, 'token_billing' => false, 'webhooks' => ['payment_intent.processing', 'charge.refunded', 'payment_intent.succeeded', 'payment_intent.partially_funded', 'payment_intent.payment_failed']], GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false], GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false], - GatewayType::BACS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.processing', 'payment_intent.succeeded', 'mandate.updated', 'payment_intent.payment_failed']], - GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], - GatewayType::KLARNA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], - GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], - GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], - GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], - GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], - GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], - GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], - GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], - GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], - GatewayType::FPX => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed',]], + GatewayType::BACS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.processing', 'payment_intent.succeeded', 'mandate.updated', 'payment_intent.payment_failed']], + GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], + GatewayType::KLARNA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], + GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], + GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], + GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], + GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], + GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], + GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], + GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], + GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']], + GatewayType::FPX => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed',]], ]; case 39: return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']]]; //Checkout diff --git a/app/Models/Project.php b/app/Models/Project.php index d341a3fb8569..c786dcec0836 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -129,7 +129,7 @@ class Project extends BaseModel public function invoices(): HasMany { - return $this->hasMany(Invoice::class); + return $this->hasMany(Invoice::class)->withTrashed(); } public function quotes(): HasMany diff --git a/app/PaymentDrivers/Forte/ACH.php b/app/PaymentDrivers/Forte/ACH.php index aef043d50a9e..8ea313e77200 100644 --- a/app/PaymentDrivers/Forte/ACH.php +++ b/app/PaymentDrivers/Forte/ACH.php @@ -170,6 +170,9 @@ class ACH ]; $payment = $this->forte->createPayment($data, Payment::STATUS_COMPLETED); - return redirect('client/invoices')->withSuccess('Invoice paid.'); + // return redirect('client/invoices')->withSuccess('Invoice paid.'); + + return redirect()->route('client.payments.show', ['payment' => $payment->hashed_id]); + } } diff --git a/app/PaymentDrivers/Forte/CreditCard.php b/app/PaymentDrivers/Forte/CreditCard.php index 67c404190137..5a317f4ec8a7 100644 --- a/app/PaymentDrivers/Forte/CreditCard.php +++ b/app/PaymentDrivers/Forte/CreditCard.php @@ -187,6 +187,8 @@ class CreditCard 'gateway_type_id' => GatewayType::CREDIT_CARD, ]; $payment = $this->forte->createPayment($data, Payment::STATUS_COMPLETED); - return redirect('client/invoices')->withSuccess('Invoice paid.'); + // return redirect('client/invoices')->withSuccess('Invoice paid.'); + return redirect()->route('client.payments.show', ['payment' => $payment->hashed_id]); + } } diff --git a/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php b/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php index 36f766dd7303..405f696742e4 100644 --- a/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php +++ b/app/PaymentDrivers/Stripe/Jobs/ChargeRefunded.php @@ -11,18 +11,22 @@ namespace App\PaymentDrivers\Stripe\Jobs; -use App\Libraries\MultiDB; use App\Models\Company; -use App\Models\CompanyGateway; use App\Models\Payment; +use App\Libraries\MultiDB; use App\Models\PaymentHash; -use App\PaymentDrivers\Stripe\Utilities; +use App\Services\Email\Email; use Illuminate\Bus\Queueable; +use App\Models\CompanyGateway; +use App\Services\Email\EmailObject; +use Illuminate\Support\Facades\App; +use Illuminate\Mail\Mailables\Address; +use Illuminate\Queue\SerializesModels; +use App\PaymentDrivers\Stripe\Utilities; +use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; -use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; -use Illuminate\Queue\SerializesModels; class ChargeRefunded implements ShouldQueue { @@ -36,19 +40,10 @@ class ChargeRefunded implements ShouldQueue public $deleteWhenMissingModels = true; - public $stripe_request; - - public $company_key; - - private $company_gateway_id; - public $payment_completed = false; - public function __construct($stripe_request, $company_key, $company_gateway_id) + public function __construct(public array $stripe_request, private string $company_key) { - $this->stripe_request = $stripe_request; - $this->company_key = $company_key; - $this->company_gateway_id = $company_gateway_id; } public function handle() @@ -64,8 +59,8 @@ class ChargeRefunded implements ShouldQueue $payment_hash_key = $source['metadata']['payment_hash'] ?? null; - $company_gateway = CompanyGateway::query()->find($this->company_gateway_id); $payment_hash = PaymentHash::query()->where('hash', $payment_hash_key)->first(); + $company_gateway = $payment_hash->payment->company_gateway; $stripe_driver = $company_gateway->driver()->init(); @@ -79,7 +74,7 @@ class ChargeRefunded implements ShouldQueue ->first(); //don't touch if already refunded - if(!$payment || in_array($payment->status_id, [Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])) { + if(!$payment || $payment->status_id == Payment::STATUS_REFUNDED || $payment->is_deleted){ return; } @@ -94,8 +89,19 @@ class ChargeRefunded implements ShouldQueue return; } - if($payment->status_id == Payment::STATUS_COMPLETED) { + usleep(rand(200000,300000)); + $payment = $payment->fresh(); + if($payment->status_id == Payment::STATUS_PARTIALLY_REFUNDED){ + //determine the delta in the refunded amount - how much has already been refunded and only apply the delta. + + if(floatval($payment->refunded) >= floatval($amount_refunded)) + return; + + $amount_refunded -= $payment->refunded; + + } + $invoice_collection = $payment->paymentables ->where('paymentable_type', 'invoices') ->map(function ($pivot) { @@ -117,9 +123,24 @@ class ChargeRefunded implements ShouldQueue ]; }); - } elseif($invoice_collection->sum('amount') != $amount_refunded) { - //too many edges cases at this point, return early + } + elseif($invoice_collection->sum('amount') != $amount_refunded) { + + $refund_text = "A partial refund was processed for Payment #{$payment_hash->payment->number}.

This payment is associated with multiple invoices, so you will need to manually apply the refund to the correct invoice/s."; + + App::setLocale($payment_hash->payment->company->getLocale()); + + $mo = new EmailObject(); + $mo->subject = "Refund processed in Stripe for multiple invoices, action required."; + $mo->body = $refund_text; + $mo->text_body = $refund_text; + $mo->company_key = $payment_hash->payment->company->company_key; + $mo->html_template = 'email.template.generic'; + $mo->to = [new Address($payment_hash->payment->company->owner()->email, $payment_hash->payment->company->owner()->present()->name())]; + + Email::dispatch($mo, $payment_hash->payment->company); return; + } $invoices = $invoice_collection->toArray(); @@ -131,20 +152,21 @@ class ChargeRefunded implements ShouldQueue 'date' => now()->format('Y-m-d'), 'gateway_refund' => false, 'email_receipt' => false, + 'via_webhook' => true, ]; nlog($data); $payment->refund($data); - $payment->private_notes .= 'Refunded via Stripe'; - return; - } + $payment->private_notes .= 'Refunded via Stripe '; + + $payment->saveQuietly(); } public function middleware() { - return [new WithoutOverlapping($this->company_gateway_id)]; + return [new WithoutOverlapping($this->company_key)]; } } diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index 69920e31e6b5..d91e39fc3763 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -12,54 +12,55 @@ namespace App\PaymentDrivers; -use App\Exceptions\PaymentFailed; -use App\Exceptions\StripeConnectFailure; -use App\Http\Requests\Payments\PaymentWebhookRequest; -use App\Http\Requests\Request; -use App\Jobs\Util\SystemLogger; -use App\Models\Client; -use App\Models\ClientGatewayToken; -use App\Models\GatewayType; -use App\Models\Payment; -use App\Models\PaymentHash; -use App\Models\SystemLog; -use App\PaymentDrivers\Stripe\ACH; -use App\PaymentDrivers\Stripe\ACSS; -use App\PaymentDrivers\Stripe\Alipay; -use App\PaymentDrivers\Stripe\BACS; -use App\PaymentDrivers\Stripe\Bancontact; -use App\PaymentDrivers\Stripe\BankTransfer; -use App\PaymentDrivers\Stripe\BECS; -use App\PaymentDrivers\Stripe\BrowserPay; -use App\PaymentDrivers\Stripe\Charge; -use App\PaymentDrivers\Stripe\Connect\Verify; -use App\PaymentDrivers\Stripe\CreditCard; -use App\PaymentDrivers\Stripe\EPS; -use App\PaymentDrivers\Stripe\FPX; -use App\PaymentDrivers\Stripe\GIROPAY; -use App\PaymentDrivers\Stripe\iDeal; -use App\PaymentDrivers\Stripe\ImportCustomers; -use App\PaymentDrivers\Stripe\Jobs\PaymentIntentFailureWebhook; -use App\PaymentDrivers\Stripe\Jobs\PaymentIntentPartiallyFundedWebhook; -use App\PaymentDrivers\Stripe\Jobs\PaymentIntentProcessingWebhook; -use App\PaymentDrivers\Stripe\Jobs\PaymentIntentWebhook; -use App\PaymentDrivers\Stripe\Klarna; -use App\PaymentDrivers\Stripe\PRZELEWY24; -use App\PaymentDrivers\Stripe\SEPA; -use App\PaymentDrivers\Stripe\SOFORT; -use App\PaymentDrivers\Stripe\Utilities; -use App\Utils\Traits\MakesHash; use Exception; -use Illuminate\Http\RedirectResponse; -use Laracasts\Presenter\Exceptions\PresenterException; +use Stripe\Stripe; use Stripe\Account; use Stripe\Customer; -use Stripe\Exception\ApiErrorException; +use App\Models\Client; +use App\Models\Payment; +use Stripe\SetupIntent; +use Stripe\StripeClient; +use App\Models\SystemLog; use Stripe\PaymentIntent; use Stripe\PaymentMethod; -use Stripe\SetupIntent; -use Stripe\Stripe; -use Stripe\StripeClient; +use App\Models\GatewayType; +use App\Models\PaymentHash; +use App\Http\Requests\Request; +use App\Jobs\Util\SystemLogger; +use App\Utils\Traits\MakesHash; +use App\Exceptions\PaymentFailed; +use App\Models\ClientGatewayToken; +use App\PaymentDrivers\Stripe\ACH; +use App\PaymentDrivers\Stripe\EPS; +use App\PaymentDrivers\Stripe\FPX; +use App\PaymentDrivers\Stripe\ACSS; +use App\PaymentDrivers\Stripe\BACS; +use App\PaymentDrivers\Stripe\BECS; +use App\PaymentDrivers\Stripe\SEPA; +use App\PaymentDrivers\Stripe\iDeal; +use App\PaymentDrivers\Stripe\Alipay; +use App\PaymentDrivers\Stripe\Charge; +use App\PaymentDrivers\Stripe\Klarna; +use App\PaymentDrivers\Stripe\SOFORT; +use Illuminate\Http\RedirectResponse; +use App\PaymentDrivers\Stripe\GIROPAY; +use Stripe\Exception\ApiErrorException; +use App\Exceptions\StripeConnectFailure; +use App\PaymentDrivers\Stripe\Utilities; +use App\PaymentDrivers\Stripe\Bancontact; +use App\PaymentDrivers\Stripe\BrowserPay; +use App\PaymentDrivers\Stripe\CreditCard; +use App\PaymentDrivers\Stripe\PRZELEWY24; +use App\PaymentDrivers\Stripe\BankTransfer; +use App\PaymentDrivers\Stripe\Connect\Verify; +use App\PaymentDrivers\Stripe\ImportCustomers; +use App\PaymentDrivers\Stripe\Jobs\ChargeRefunded; +use App\Http\Requests\Payments\PaymentWebhookRequest; +use Laracasts\Presenter\Exceptions\PresenterException; +use App\PaymentDrivers\Stripe\Jobs\PaymentIntentWebhook; +use App\PaymentDrivers\Stripe\Jobs\PaymentIntentFailureWebhook; +use App\PaymentDrivers\Stripe\Jobs\PaymentIntentProcessingWebhook; +use App\PaymentDrivers\Stripe\Jobs\PaymentIntentPartiallyFundedWebhook; class StripePaymentDriver extends BaseDriver { @@ -670,31 +671,39 @@ class StripePaymentDriver extends BaseDriver public function processWebhookRequest(PaymentWebhookRequest $request) { + nlog($request->all()); + if ($request->type === 'customer.source.updated') { $ach = new ACH($this); $ach->updateBankAccount($request->all()); } if ($request->type === 'payment_intent.processing') { - PaymentIntentProcessingWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(10, 12))); + PaymentIntentProcessingWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(5)); return response()->json([], 200); } //payment_intent.succeeded - this will confirm or cancel the payment if ($request->type === 'payment_intent.succeeded') { - PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(10, 15))); + PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(5)); return response()->json([], 200); } if ($request->type === 'payment_intent.partially_funded') { - PaymentIntentPartiallyFundedWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(10, 15))); + PaymentIntentPartiallyFundedWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(5)); return response()->json([], 200); } if (in_array($request->type, ['payment_intent.payment_failed', 'charge.failed'])) { - PaymentIntentFailureWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(5, 10))); + PaymentIntentFailureWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(2)); + + return response()->json([], 200); + } + + if ($request->type === 'charge.refunded' && $request->data['object']['status'] == 'succeeded') { + ChargeRefunded::dispatch($request->data, $request->company_key)->delay(now()->addSeconds(5)); return response()->json([], 200); } @@ -702,7 +711,6 @@ class StripePaymentDriver extends BaseDriver if ($request->type === 'charge.succeeded') { foreach ($request->data as $transaction) { - $payment = Payment::query() ->where('company_id', $this->company_gateway->company_id) ->where(function ($query) use ($transaction) { diff --git a/app/Services/Payment/RefundPayment.php b/app/Services/Payment/RefundPayment.php index a17baa67051e..ed7b51deed20 100644 --- a/app/Services/Payment/RefundPayment.php +++ b/app/Services/Payment/RefundPayment.php @@ -44,7 +44,6 @@ class RefundPayment ->setStatus() //sets status of payment ->updatePaymentables() //update the paymentable items ->adjustInvoices() - ->finalize() ->save(); if (array_key_exists('email_receipt', $this->refund_data) && $this->refund_data['email_receipt'] == 'true') { @@ -52,10 +51,11 @@ class RefundPayment EmailRefundPayment::dispatch($this->payment, $this->payment->company, $contact); } - $notes = ctrans('texts.refunded') . " : {$this->total_refund} - " . ctrans('texts.gateway_refund') . " : "; - $notes .= $this->refund_data['gateway_refund'] !== false ? ctrans('texts.yes') : ctrans('texts.no'); - + $is_gateway_refund = ($this->refund_data['gateway_refund'] !== false || $this->refund_failed || (isset($this->refund_data['via_webhook']) && $this->refund_data['via_webhook'] !== false)) ? ctrans('texts.yes') : ctrans('texts.no'); + $notes = ctrans('texts.refunded') . " : {$this->total_refund} - " . ctrans('texts.gateway_refund') . " : " . $is_gateway_refund; + $this->createActivity($notes); + $this->finalize(); return $this->payment; } @@ -178,7 +178,7 @@ class RefundPayment */ private function setStatus() { - if ($this->total_refund == $this->payment->amount) { + if ($this->total_refund == $this->payment->amount || floatval($this->payment->amount) == floatval($this->payment->refunded)) { $this->payment->status_id = Payment::STATUS_REFUNDED; } else { $this->payment->status_id = Payment::STATUS_PARTIALLY_REFUNDED; diff --git a/app/Services/Template/TemplateService.php b/app/Services/Template/TemplateService.php index 5a033c2f5383..13adb454d7f1 100644 --- a/app/Services/Template/TemplateService.php +++ b/app/Services/Template/TemplateService.php @@ -1023,7 +1023,8 @@ class TemplateService 'vat_number' => $project->client->vat_number ?? '', 'currency' => $project->client->currency()->code ?? 'USD', ] : [], - 'user' => $this->userInfo($project->user) + 'user' => $this->userInfo($project->user), + 'invoices' => $this->processInvoices($project->invoices) ]; } diff --git a/composer.lock b/composer.lock index 541f0c04527b..4d331a5d006e 100644 --- a/composer.lock +++ b/composer.lock @@ -5407,16 +5407,16 @@ }, { "name": "league/commonmark", - "version": "2.4.2", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf" + "reference": "0026475f5c9a104410ae824cb5a4d63fa3bdb1df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/91c24291965bd6d7c46c46a12ba7492f83b1cadf", - "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/0026475f5c9a104410ae824cb5a4d63fa3bdb1df", + "reference": "0026475f5c9a104410ae824cb5a4d63fa3bdb1df", "shasum": "" }, "require": { @@ -5429,8 +5429,8 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.3", - "commonmark/commonmark.js": "0.30.0", + "commonmark/cmark": "0.31.0", + "commonmark/commonmark.js": "0.31.0", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", "erusev/parsedown": "^1.0", @@ -5452,7 +5452,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "2.6-dev" } }, "autoload": { @@ -5509,7 +5509,7 @@ "type": "tidelift" } ], - "time": "2024-02-02T11:59:32+00:00" + "time": "2024-07-22T18:18:14+00:00" }, { "name": "league/config", diff --git a/lang/en/texts.php b/lang/en/texts.php index e2bad2efdae3..f00d22cdcf4c 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5124,7 +5124,7 @@ $lang = array( 'all_contacts' => 'All Contacts', 'insert_below' => 'Insert Below', 'nordigen_handler_subtitle' => 'Bank account authentication. Selecting your institution to complete the request with your account credentials.', - 'nordigen_handler_error_heading_unknown' => 'An error has occured', + 'nordigen_handler_error_heading_unknown' => 'An error has occurred', 'nordigen_handler_error_contents_unknown' => 'An unknown error has occurred! Reason:', 'nordigen_handler_error_heading_token_invalid' => 'Invalid Token', 'nordigen_handler_error_contents_token_invalid' => 'The provided token was invalid. Contact support for help, if this issue persists.', diff --git a/routes/api.php b/routes/api.php index 0b0ffa772b15..2a5a2350f191 100644 --- a/routes/api.php +++ b/routes/api.php @@ -164,6 +164,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('charts/totals_v2', [ChartController::class, 'totalsV2'])->name('chart.totals_v2'); Route::post('charts/chart_summary_v2', [ChartController::class, 'chart_summaryV2'])->name('chart.chart_summary_v2'); + Route::post('charts/calculated_fields', [ChartController::class, 'calculatedFields'])->name('chart.calculated_fields'); Route::post('claim_license', [LicenseController::class, 'index'])->name('license.index');