diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php
index 9fde1fee410b..2e201f637fe9 100644
--- a/app/DataMapper/CompanySettings.php
+++ b/app/DataMapper/CompanySettings.php
@@ -518,7 +518,12 @@ class CompanySettings extends BaseSettings
public string $payment_flow = 'default'; //smooth
+ public string $email_subject_payment_failed = '';
+ public string $email_template_payment_failed = '';
+
public static $casts = [
+ 'email_template_payment_failed' => 'string',
+ 'email_subject_payment_failed' => 'string',
'payment_flow' => 'string',
'enable_quote_reminder1' => 'bool',
'quote_num_days_reminder1' => 'int',
diff --git a/app/DataMapper/EmailTemplateDefaults.php b/app/DataMapper/EmailTemplateDefaults.php
index 7633b7edd073..a63b7f89011d 100644
--- a/app/DataMapper/EmailTemplateDefaults.php
+++ b/app/DataMapper/EmailTemplateDefaults.php
@@ -30,6 +30,7 @@ class EmailTemplateDefaults
'email_template_custom2',
'email_template_custom3',
'email_template_purchase_order',
+ 'email_template_payment_failed'
];
public static function getDefaultTemplate($template, $locale)
@@ -39,6 +40,8 @@ class EmailTemplateDefaults
switch ($template) {
/* Template */
+ case 'email_template_payment_failed':
+ return self::emailPaymentFailedTemplate();
case 'email_template_invoice':
return self::emailInvoiceTemplate();
case 'email_template_quote':
@@ -73,6 +76,9 @@ class EmailTemplateDefaults
case 'email_subject_invoice':
return self::emailInvoiceSubject();
+ case 'email_subject_payment_failed':
+ return self::emailPaymentFailedSubject();
+
case 'email_subject_quote':
return self::emailQuoteSubject();
@@ -127,6 +133,16 @@ class EmailTemplateDefaults
}
}
+ public static function emailPaymentFailedSubject()
+ {
+ return ctrans('texts.notification_invoice_payment_failed_subject', ['invoice' => '$number']);
+ }
+
+ public static function emailPaymentFailedTemplate()
+ {
+ return '
$client
'.ctrans('texts.client_payment_failure_body', ['invoice' => '$number', 'amount' => '$amount']).'
$gateway_payment_error
$payment_button
';
+ }
+
public static function emailQuoteReminder1Subject()
{
return ctrans('texts.quote_reminder_subject', ['quote' => '$number', 'company' => '$company.name']);
@@ -135,9 +151,7 @@ class EmailTemplateDefaults
public static function emailQuoteReminder1Body()
{
- $invoice_message = '$client
'.self::transformText('quote_reminder_message').'
$view_button
';
-
- return $invoice_message;
+ return '$client
'.self::transformText('quote_reminder_message').'
$view_button
';
}
diff --git a/app/Events/Invoice/InvoiceAutoBillFailed.php b/app/Events/Invoice/InvoiceAutoBillFailed.php
new file mode 100644
index 000000000000..ce8d94a8ceb1
--- /dev/null
+++ b/app/Events/Invoice/InvoiceAutoBillFailed.php
@@ -0,0 +1,35 @@
+payment_hash) {
- // $amount = array_sum(array_column($this->payment_hash->invoices(), 'amount')) + $this->payment_hash->fee_total;
+
$amount = $this->payment_hash?->amount_with_fee() ?: 0;
$invoice = Invoice::query()->whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
}
diff --git a/app/Listeners/Invoice/InvoiceAutoBillFailedActivity.php b/app/Listeners/Invoice/InvoiceAutoBillFailedActivity.php
new file mode 100644
index 000000000000..6db4087daa5e
--- /dev/null
+++ b/app/Listeners/Invoice/InvoiceAutoBillFailedActivity.php
@@ -0,0 +1,59 @@
+activity_repo = $activity_repo;
+ }
+
+ /**
+ * Handle the event.
+ *
+ * @param object $event
+ * @return void
+ */
+ public function handle($event)
+ {
+ MultiDB::setDB($event->company->db);
+
+ $fields = new stdClass();
+
+ $user_id = isset($event->event_vars['user_id']) ? $event->event_vars['user_id'] : $event->invoice->user_id;
+
+ $fields->user_id = $user_id;
+ $fields->client_id = $event->invoice->client_id;
+ $fields->company_id = $event->invoice->company_id;
+ $fields->activity_type_id = Activity::AUTOBILL_FAILURE;
+ $fields->invoice_id = $event->invoice->id;
+ $fields->notes = $event->notes ?? '';
+
+ $this->activity_repo->save($fields, $event->invoice, $event->event_vars);
+
+ }
+}
diff --git a/app/Listeners/Invoice/InvoiceAutoBillSuccessActivity.php b/app/Listeners/Invoice/InvoiceAutoBillSuccessActivity.php
new file mode 100644
index 000000000000..c936fdd49a30
--- /dev/null
+++ b/app/Listeners/Invoice/InvoiceAutoBillSuccessActivity.php
@@ -0,0 +1,58 @@
+activity_repo = $activity_repo;
+ }
+
+ /**
+ * Handle the event.
+ *
+ * @param object $event
+ * @return void
+ */
+ public function handle($event)
+ {
+ MultiDB::setDB($event->company->db);
+
+ $fields = new stdClass();
+
+ $user_id = isset($event->event_vars['user_id']) ? $event->event_vars['user_id'] : $event->invoice->user_id;
+
+ $fields->user_id = $user_id;
+ $fields->client_id = $event->invoice->client_id;
+ $fields->company_id = $event->invoice->company_id;
+ $fields->activity_type_id = Activity::AUTOBILL_SUCCESS;
+ $fields->invoice_id = $event->invoice->id;
+
+ $this->activity_repo->save($fields, $event->invoice, $event->event_vars);
+
+ }
+}
diff --git a/app/Mail/Admin/ClientPaymentFailureObject.php b/app/Mail/Admin/ClientPaymentFailureObject.php
index c0dc385072fa..842d0f84fb26 100644
--- a/app/Mail/Admin/ClientPaymentFailureObject.php
+++ b/app/Mail/Admin/ClientPaymentFailureObject.php
@@ -11,12 +11,14 @@
namespace App\Mail\Admin;
+use stdClass;
+use App\Utils\Ninja;
use App\Models\Invoice;
use App\Utils\HtmlEngine;
-use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\App;
-use stdClass;
+use App\DataMapper\EmailTemplateDefaults;
+use App\Utils\Number;
class ClientPaymentFailureObject
{
@@ -60,20 +62,20 @@ class ClientPaymentFailureObject
}
App::forgetInstance('translator');
- /* Init a new copy of the translator*/
$t = app('translator');
- /* Set the locale*/
App::setLocale($this->client->locale());
- /* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->company->settings));
$this->invoices = Invoice::withTrashed()->whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get();
+ $data = $this->getData();
+
$mail_obj = new stdClass();
$mail_obj->amount = $this->getAmount();
- $mail_obj->subject = $this->getSubject();
+ $mail_obj->subject = $data['subject'];
$mail_obj->data = $this->getData();
- $mail_obj->markdown = 'email.client.generic';
+
+ $mail_obj->markdown = 'email.template.client';
$mail_obj->tag = $this->company->company_key;
$mail_obj->text_view = 'email.template.text';
@@ -82,16 +84,32 @@ class ClientPaymentFailureObject
private function getAmount()
{
- return array_sum(array_column($this->payment_hash->invoices(), 'amount')) + $this->payment_hash->fee_total;
+ $amount = array_sum(array_column($this->payment_hash->invoices(), 'amount')) + $this->payment_hash->fee_total;
+
+ return Number::formatMoney($amount, $this->client);
}
private function getSubject()
{
- return
- ctrans(
- 'texts.notification_invoice_payment_failed_subject',
- ['invoice' => implode(',', $this->invoices->pluck('number')->toArray())]
- );
+
+ if(strlen($this->client->getSetting('email_subject_payment_failed') ?? '') > 2){
+ return $this->client->getSetting('email_subject_payment_failed');
+ }
+ else {
+ return EmailTemplateDefaults::getDefaultTemplate('email_subject_payment_failed', $this->client->locale());
+ }
+
+ }
+
+ private function getBody()
+ {
+
+ if(strlen($this->client->getSetting('email_template_payment_failed') ?? '') > 2) {
+ return $this->client->getSetting('email_template_payment_failed');
+ } else {
+ return EmailTemplateDefaults::getDefaultTemplate('email_template_payment_failed', $this->client->locale());
+ }
+
}
private function getData()
@@ -104,17 +122,17 @@ class ClientPaymentFailureObject
$signature = $this->client->getSetting('email_signature');
$html_variables = (new HtmlEngine($invitation))->makeValues();
+
+ $html_variables['$gateway_payment_error'] = $this->error ?? '';
+ $html_variables['$total'] = $this->getAmount();
+
$signature = str_replace(array_keys($html_variables), array_values($html_variables), $signature);
+ $subject = str_replace(array_keys($html_variables), array_values($html_variables), $this->getSubject());
+ $content = str_replace(array_keys($html_variables), array_values($html_variables), $this->getBody());
$data = [
- 'title' => ctrans(
- 'texts.notification_invoice_payment_failed_subject',
- [
- 'invoice' => $this->invoices->first()->number,
- ]
- ),
- 'greeting' => ctrans('texts.email_salutation', ['name' => $this->client->present()->name()]),
- 'content' => ctrans('texts.client_payment_failure_body', ['invoice' => implode(',', $this->invoices->pluck('number')->toArray()), 'amount' => $this->getAmount()]),
+ 'subject' => $subject,
+ 'body' => $content,
'signature' => $signature,
'logo' => $this->company->present()->logo(),
'settings' => $this->client->getMergedSettings(),
diff --git a/app/Models/Activity.php b/app/Models/Activity.php
index c2f7d6c50198..cc57c9a0af39 100644
--- a/app/Models/Activity.php
+++ b/app/Models/Activity.php
@@ -265,6 +265,10 @@ class Activity extends StaticModel
public const QUOTE_REMINDER1_SENT = 142;
+ public const AUTOBILL_SUCCESS = 143; //:invoice auto billing succeeded
+
+ public const AUTOBILL_FAILURE = 144; //:invoice autobilling failed :note
+
protected $casts = [
'is_system' => 'boolean',
'updated_at' => 'timestamp',
@@ -286,7 +290,6 @@ class Activity extends StaticModel
return $this->encodePrimaryKey($this->id);
}
-
public function getEntityType()
{
return self::class;
diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php
index 186058f241d7..dece6ea475e3 100644
--- a/app/Models/BaseModel.php
+++ b/app/Models/BaseModel.php
@@ -31,6 +31,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundExceptio
* @package App\Models
* @property-read mixed $hashed_id
* @property string $number
+ * @property object|null $e_invoice
* @property int $company_id
* @property int $id
* @property int $user_id
@@ -296,17 +297,12 @@ class BaseModel extends Model
}
// special catch here for einvoicing eventing
- if($event_id == Webhook::EVENT_SENT_INVOICE && $this->e_invoice){
- $this->handleEinvoiceSending();
+ if($event_id == Webhook::EVENT_SENT_INVOICE && ($this instanceof Invoice) && $this->e_invoice){
+ // Einvoice
}
}
- private function handleEinvoiceSending()
- {
-
- }
-
/**
* Returns the base64 encoded PDF string of the entity
* @deprecated - unused implementation
diff --git a/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php b/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php
index 244be05bfc1d..db354bce9cd0 100644
--- a/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php
+++ b/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php
@@ -49,6 +49,18 @@ class UpdatePaymentMethods
$this->addOrUpdateCard($method, $customer->id, $client, GatewayType::CREDIT_CARD);
}
+ $link_methods = PaymentMethod::all(
+ [
+ 'customer' => $customer->id,
+ 'type' => 'link',
+ ],
+ $this->stripe->stripe_connect_auth
+ );
+
+ foreach ($link_methods as $method) {
+ $this->addOrUpdateCard($method, $customer->id, $client, GatewayType::CREDIT_CARD);
+ }
+
$alipay_methods = PaymentMethod::all(
[
'customer' => $customer->id,
@@ -217,9 +229,14 @@ class UpdatePaymentMethods
private function buildPaymentMethodMeta(PaymentMethod $method, $type_id)
{
+ nlog($method->type);
+
switch ($type_id) {
case GatewayType::CREDIT_CARD:
+ if($method->type == 'link')
+ return new \stdClass();
+
/**
* @class \Stripe\PaymentMethod $method
* @property \Stripe\StripeObject $card
@@ -240,7 +257,7 @@ class UpdatePaymentMethods
return $payment_meta;
case GatewayType::ALIPAY:
case GatewayType::SOFORT:
-
+
return new \stdClass();
case GatewayType::SEPA:
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index 57ddd8d7e19a..caa924253cc0 100644
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -155,6 +155,8 @@ use App\Listeners\Activity\TaskUpdatedActivity;
use App\Listeners\Invoice\InvoiceEmailActivity;
use App\Listeners\SendVerificationNotification;
use App\Events\Credit\CreditWasEmailedAndFailed;
+use App\Events\Invoice\InvoiceAutoBillFailed;
+use App\Events\Invoice\InvoiceAutoBillSuccess;
use App\Listeners\Activity\CreatedQuoteActivity;
use App\Listeners\Activity\DeleteClientActivity;
use App\Listeners\Activity\DeleteCreditActivity;
@@ -250,6 +252,8 @@ use App\Events\RecurringExpense\RecurringExpenseWasArchived;
use App\Events\RecurringExpense\RecurringExpenseWasRestored;
use App\Events\RecurringInvoice\RecurringInvoiceWasArchived;
use App\Events\RecurringInvoice\RecurringInvoiceWasRestored;
+use App\Listeners\Invoice\InvoiceAutoBillFailedActivity;
+use App\Listeners\Invoice\InvoiceAutoBillSuccessActivity;
use App\Listeners\PurchaseOrder\CreatePurchaseOrderActivity;
use App\Listeners\PurchaseOrder\PurchaseOrderViewedActivity;
use App\Listeners\PurchaseOrder\UpdatePurchaseOrderActivity;
@@ -426,6 +430,12 @@ class EventServiceProvider extends ServiceProvider
ExpenseRestoredActivity::class,
],
//Invoices
+ InvoiceAutoBillSuccess::class => [
+ InvoiceAutoBillSuccessActivity::class,
+ ],
+ InvoiceAutoBillFailed::class => [
+ InvoiceAutoBillFailedActivity::class,
+ ],
InvoiceWasMarkedSent::class => [
],
InvoiceWasUpdated::class => [
diff --git a/app/Providers/StaticServiceProvider.php b/app/Providers/StaticServiceProvider.php
index 00aa774c204f..848bddb77b1c 100644
--- a/app/Providers/StaticServiceProvider.php
+++ b/app/Providers/StaticServiceProvider.php
@@ -222,6 +222,10 @@ class StaticServiceProvider extends ServiceProvider
'subject' => EmailTemplateDefaults::emailPaymentSubject(),
'body' => EmailTemplateDefaults::emailPaymentTemplate(),
],
+ 'payment_failed' => [
+ 'subject' => EmailTemplateDefaults::emailPaymentFailedSubject(),
+ 'body' => EmailTemplateDefaults::emailPaymentFailedTemplate(),
+ ],
'quote_reminder1' => [
'subject' => EmailTemplateDefaults::emailQuoteReminder1Subject(),
'body' => EmailTemplateDefaults::emailQuoteReminder1Body(),
diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php
index 15e4ef6454d1..cfb686dac5bd 100644
--- a/app/Services/Invoice/AutoBillInvoice.php
+++ b/app/Services/Invoice/AutoBillInvoice.php
@@ -23,6 +23,8 @@ use App\Models\PaymentHash;
use App\Models\PaymentType;
use Illuminate\Support\Str;
use App\DataMapper\InvoiceItem;
+use App\Events\Invoice\InvoiceAutoBillFailed;
+use App\Events\Invoice\InvoiceAutoBillSuccess;
use App\Factory\PaymentFactory;
use App\Services\AbstractService;
use App\Models\ClientGatewayToken;
@@ -157,6 +159,8 @@ class AutoBillInvoice extends AbstractService
} catch (\Exception $e) {
nlog('payment NOT captured for '.$this->invoice->number.' with error '.$e->getMessage());
+ event(new InvoiceAutoBillFailed($this->invoice, $this->invoice->company, Ninja::eventVars(), $e->getMessage()));
+
}
$this->invoice->auto_bill_tries += 1;
@@ -170,6 +174,7 @@ class AutoBillInvoice extends AbstractService
if ($payment) {
info('Auto Bill payment captured for '.$this->invoice->number);
+ event(new InvoiceAutoBillSuccess($this->invoice, $this->invoice->company, Ninja::eventVars()));
}
}
diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php
index 89b7d470e598..e39f257e068a 100644
--- a/app/Utils/HtmlEngine.php
+++ b/app/Utils/HtmlEngine.php
@@ -733,6 +733,7 @@ class HtmlEngine
$data['$payment.number'] = ['value' => '', 'label' => ctrans('texts.payment_number')];
$data['$payment.transaction_reference'] = ['value' => '', 'label' => ctrans('texts.transaction_reference')];
$data['$payment.refunded'] = ['value' => '', 'label' => ctrans('texts.refund')];
+ $data['$gateway_payment_error'] = ['value' => '', 'label' => ctrans('texts.error')];
if ($this->entity_string == 'invoice' && $this->entity->net_payments()->exists()) {
$payment_list = '
';
diff --git a/lang/en/texts.php b/lang/en/texts.php
index 55ded895bd16..372bccf2f5e6 100644
--- a/lang/en/texts.php
+++ b/lang/en/texts.php
@@ -5321,6 +5321,9 @@ $lang = array(
'applies_to' => 'Applies To',
'accept_purchase_order' => 'Accept Purchase Order',
'round_to_seconds' => 'Round To Seconds',
+ 'activity_142' => 'Quote :number Reminder 1 Sent',
+ 'activity_143' => 'Auto Bill succeeded for Invoice :invoice',
+ 'activity_144' => 'Auto Bill failed for Invoice :invoice. :notes',
);
return $lang;