mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
8320021e08
@ -1 +1 @@
|
||||
5.5.94
|
||||
5.5.95
|
@ -53,6 +53,7 @@ class RecurringInvoiceFactory
|
||||
$invoice->remaining_cycles = -1;
|
||||
$invoice->paid_to_date = 0;
|
||||
$invoice->auto_bill_enabled = false;
|
||||
$invoice->is_proforma = false;
|
||||
$invoice->auto_bill = 'off';
|
||||
|
||||
return $invoice;
|
||||
|
@ -46,6 +46,7 @@ class RecurringInvoiceToInvoiceFactory
|
||||
$invoice->custom_value4 = $recurring_invoice->custom_value4;
|
||||
$invoice->amount = $recurring_invoice->amount;
|
||||
$invoice->uses_inclusive_taxes = $recurring_invoice->uses_inclusive_taxes;
|
||||
$invoice->is_proforma = $recurring_invoice->is_proforma;
|
||||
|
||||
$invoice->custom_surcharge1 = $recurring_invoice->custom_surcharge1;
|
||||
$invoice->custom_surcharge2 = $recurring_invoice->custom_surcharge2;
|
||||
|
@ -913,7 +913,6 @@ class BaseController extends Controller
|
||||
* List response
|
||||
*
|
||||
* @param mixed $query
|
||||
* @return void
|
||||
*/
|
||||
protected function listResponse($query)
|
||||
{
|
||||
@ -1010,7 +1009,6 @@ class BaseController extends Controller
|
||||
* Item Response
|
||||
*
|
||||
* @param mixed $item
|
||||
* @return void
|
||||
*/
|
||||
protected function itemResponse($item)
|
||||
{
|
||||
|
@ -80,6 +80,7 @@ class PrePaymentController extends Controller
|
||||
$invoice = $invoice_repo->save($data, $invoice)
|
||||
->service()
|
||||
->markSent()
|
||||
->applyNumber()
|
||||
->fillDefaults()
|
||||
->save();
|
||||
|
||||
@ -107,8 +108,10 @@ class PrePaymentController extends Controller
|
||||
'hashed_ids' => $invoices->pluck('hashed_id'),
|
||||
'total' => $total,
|
||||
'pre_payment' => true,
|
||||
'frequency_id' => $request->frequency_id,
|
||||
'remaining_cycles' => $request->remaining_cycles,
|
||||
'is_recurring' => $request->is_recurring == 'on' ? true : false,
|
||||
];
|
||||
|
||||
|
||||
return $this->render('invoices.payment', $data);
|
||||
|
||||
|
@ -12,19 +12,22 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Webhook;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Factory\WebhookFactory;
|
||||
use App\Filters\WebhookFilters;
|
||||
use App\Http\Requests\Webhook\CreateWebhookRequest;
|
||||
use App\Http\Requests\Webhook\DestroyWebhookRequest;
|
||||
use App\Http\Requests\Webhook\EditWebhookRequest;
|
||||
use App\Http\Requests\Webhook\ShowWebhookRequest;
|
||||
use App\Http\Requests\Webhook\StoreWebhookRequest;
|
||||
use App\Http\Requests\Webhook\UpdateWebhookRequest;
|
||||
use App\Models\Webhook;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Jobs\Util\WebhookSingle;
|
||||
use App\Repositories\BaseRepository;
|
||||
use App\Transformers\WebhookTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Response;
|
||||
use App\Http\Requests\Webhook\EditWebhookRequest;
|
||||
use App\Http\Requests\Webhook\ShowWebhookRequest;
|
||||
use App\Http\Requests\Webhook\RetryWebhookRequest;
|
||||
use App\Http\Requests\Webhook\StoreWebhookRequest;
|
||||
use App\Http\Requests\Webhook\CreateWebhookRequest;
|
||||
use App\Http\Requests\Webhook\UpdateWebhookRequest;
|
||||
use App\Http\Requests\Webhook\DestroyWebhookRequest;
|
||||
|
||||
class WebhookController extends BaseController
|
||||
{
|
||||
@ -487,4 +490,28 @@ class WebhookController extends BaseController
|
||||
|
||||
return $this->listResponse(Webhook::withTrashed()->whereIn('id', $this->transformKeys($ids)));
|
||||
}
|
||||
|
||||
public function retry(RetryWebhookRequest $request, Webhook $webhook)
|
||||
{
|
||||
match($request->entity) {
|
||||
'invoice' => $includes ='client',
|
||||
'payment' => $includes ='invoices,client',
|
||||
'project' => $includes ='client',
|
||||
'purchase_order' => $includes ='vendor',
|
||||
'quote' => $includes ='client',
|
||||
default => $includes = ''
|
||||
};
|
||||
|
||||
$class = 'App\Models\\'.ucfirst(Str::camel($request->entity));
|
||||
|
||||
$entity = $class::withTrashed()->where('id', $this->decodePrimaryKey($request->entity_id))->company()->first();
|
||||
|
||||
if(!$entity){
|
||||
return response()->json(['message' => ctrans('texts.record_not_found')], 400);
|
||||
}
|
||||
|
||||
WebhookSingle::dispatchSync($webhook->id, $entity, auth()->user()->company()->db, $includes);
|
||||
|
||||
return $this->itemResponse($webhook);
|
||||
}
|
||||
}
|
||||
|
35
app/Http/Requests/Webhook/RetryWebhookRequest.php
Normal file
35
app/Http/Requests/Webhook/RetryWebhookRequest.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Webhook;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class RetryWebhookRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'entity' => 'required|bail|in:client,credit,invoice,product,task,payment,quote,purchase_order,expense,project,vendor',
|
||||
'entity_id' => 'required|bail|string',
|
||||
];
|
||||
}
|
||||
}
|
@ -138,9 +138,9 @@ class PortalComposer
|
||||
$data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar'];
|
||||
}
|
||||
|
||||
if(property_exists($this->settings, 'client_initiated_payments') && $this->settings->client_initiated_payments) {
|
||||
// if(property_exists($this->settings, 'client_initiated_payments') && $this->settings->client_initiated_payments) {
|
||||
$data[] = ['title' => ctrans('texts.pre_payment'), 'url' => 'client.pre_payments.index', 'icon' => 'dollar-sign'];
|
||||
}
|
||||
// }
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class RecurringExpensesCron
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle() : void
|
||||
public function handle(): void
|
||||
{
|
||||
/* Get all expenses where the send date is less than NOW + 30 minutes() */
|
||||
nlog('Sending recurring expenses '.Carbon::now()->format('Y-m-d h:i:s'));
|
||||
|
@ -11,27 +11,33 @@
|
||||
|
||||
namespace App\Jobs\Util;
|
||||
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\Events\Invoice\InvoiceWasEmailed;
|
||||
use App\Jobs\Entity\EmailEntity;
|
||||
use App\Jobs\Ninja\TransactionLog;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\TransactionEvent;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesReminders;
|
||||
use App\Models\Invoice;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\Jobs\Entity\EmailEntity;
|
||||
use App\Models\TransactionEvent;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Jobs\Ninja\TransactionLog;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use App\Utils\Traits\MakesReminders;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use App\Events\Invoice\InvoiceWasEmailed;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use App\Events\Invoice\InvoiceReminderWasEmailed;
|
||||
|
||||
class ReminderJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesReminders, MakesDates;
|
||||
use Dispatchable;
|
||||
use InteractsWithQueue;
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
use MakesReminders;
|
||||
use MakesDates;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
@ -44,7 +50,7 @@ class ReminderJob implements ShouldQueue
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle() :void
|
||||
public function handle(): void
|
||||
{
|
||||
set_time_limit(0);
|
||||
|
||||
@ -73,7 +79,7 @@ class ReminderJob implements ShouldQueue
|
||||
});
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
|
||||
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
@ -94,7 +100,7 @@ class ReminderJob implements ShouldQueue
|
||||
})
|
||||
->with('invitations')->chunk(50, function ($invoices) {
|
||||
// if ($invoice->refresh() && $invoice->isPayable()) {
|
||||
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
$this->sendReminderForInvoice($invoice);
|
||||
}
|
||||
@ -125,22 +131,17 @@ class ReminderJob implements ShouldQueue
|
||||
$enabled_reminder = 'enable_reminder_endless';
|
||||
}
|
||||
|
||||
//check if this reminder needs to be emailed
|
||||
//15-01-2022 - insert addition if block if send_reminders is definitely set
|
||||
if (in_array($reminder_template, ['reminder1', 'reminder2', 'reminder3', 'reminder_endless', 'endless_reminder']) &&
|
||||
$invoice->client->getSetting($enabled_reminder) &&
|
||||
$invoice->client->getSetting('send_reminders') &&
|
||||
(Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) {
|
||||
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
|
||||
if ($invitation->contact && !$invitation->contact->trashed() && $invitation->contact->email) {
|
||||
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template)->delay(now()->addSeconds(3));
|
||||
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template);
|
||||
nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}");
|
||||
$invoice->entityEmailEvent($invitation, $reminder_template);
|
||||
}
|
||||
});
|
||||
|
||||
if ($invoice->invitations->count() > 0) {
|
||||
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $reminder_template));
|
||||
}
|
||||
}
|
||||
$invoice->service()->setReminder()->save();
|
||||
} else {
|
||||
@ -156,7 +157,7 @@ class ReminderJob implements ShouldQueue
|
||||
* @param string $template
|
||||
* @return Invoice
|
||||
*/
|
||||
private function calcLateFee($invoice, $template) :Invoice
|
||||
private function calcLateFee($invoice, $template): Invoice
|
||||
{
|
||||
$late_fee_amount = 0;
|
||||
$late_fee_percent = 0;
|
||||
@ -196,7 +197,7 @@ class ReminderJob implements ShouldQueue
|
||||
*
|
||||
* @return Invoice
|
||||
*/
|
||||
private function setLateFee($invoice, $amount, $percent) :Invoice
|
||||
private function setLateFee($invoice, $amount, $percent): Invoice
|
||||
{
|
||||
App::forgetInstance('translator');
|
||||
$t = app('translator');
|
||||
@ -217,7 +218,7 @@ class ReminderJob implements ShouldQueue
|
||||
$fee += round($invoice->balance * $percent / 100, 2);
|
||||
}
|
||||
|
||||
$invoice_item = new InvoiceItem;
|
||||
$invoice_item = new InvoiceItem();
|
||||
$invoice_item->type_id = '5';
|
||||
$invoice_item->product_key = trans('texts.fee');
|
||||
$invoice_item->notes = ctrans('texts.late_fee_added', ['date' => $this->translateDate(now()->startOfDay(), $invoice->client->date_format(), $invoice->client->locale())]);
|
||||
|
@ -104,8 +104,10 @@ class WebhookSingle implements ShouldQueue
|
||||
|
||||
$resource = new Item($this->entity, $transformer, $this->entity->getEntityType());
|
||||
$data = $manager->createData($resource)->toArray();
|
||||
|
||||
$headers = is_array($subscription->headers) ? $subscription->headers : [];
|
||||
|
||||
$this->postData($subscription, $data, $subscription->headers);
|
||||
$this->postData($subscription, $data, $headers);
|
||||
}
|
||||
|
||||
private function postData($subscription, $data, $headers = [])
|
||||
|
@ -11,33 +11,47 @@
|
||||
|
||||
namespace App\Listeners\Payment;
|
||||
|
||||
use App\Models\Activity;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Utils\Traits\Notifications\UserNotifies;
|
||||
use App\Repositories\ActivityRepository;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use App\Utils\Traits\Notifications\UserNotifies;
|
||||
|
||||
class PaymentEmailedActivity implements ShouldQueue
|
||||
{
|
||||
|
||||
use UserNotifies;
|
||||
protected $activity_repo;
|
||||
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
* @param ActivityRepository $activity_repo
|
||||
*/
|
||||
public function __construct()
|
||||
public function __construct(ActivityRepository $activity_repo)
|
||||
{
|
||||
$this->activity_repo = $activity_repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return bool
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
MultiDB::setDb($event->company->db);
|
||||
$payment = $event->payment;
|
||||
|
||||
$fields = new \stdClass();
|
||||
|
||||
$user_id = array_key_exists('user_id', $event->event_vars) ? $event->event_vars['user_id'] : $event->payment->user_id;
|
||||
|
||||
$fields->user_id = $user_id;
|
||||
$fields->client_id = $event->payment->client_id;
|
||||
$fields->company_id = $event->payment->company_id;
|
||||
$fields->activity_type_id = Activity::PAYMENT_EMAILED;
|
||||
$fields->payment_id = $event->payment->id;
|
||||
|
||||
$this->activity_repo->save($fields, $event->payment, $event->event_vars);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,62 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Listeners\Subscription;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use App\Models\Company;
|
||||
use App\Notifications\Ninja\RenewalFailureNotification;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Imdhemy\Purchases\Events\AppStore\DidRenew;
|
||||
|
||||
class AppStoreRenewSubscription implements ShouldQueue
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @param ActivityRepository $activity_repo
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(DidRenew $event)
|
||||
{
|
||||
$inapp_transaction_id = $event->getSubscriptionId(); //$subscription_id
|
||||
|
||||
nlog("inapp upgrade processing for = {$inapp_transaction_id}");
|
||||
|
||||
MultiDB::findAndSetDbByInappTransactionId($inapp_transaction_id);
|
||||
|
||||
$account = Account::where('inapp_transaction_id', $inapp_transaction_id)->first();
|
||||
|
||||
if (!$account) {
|
||||
$ninja_company = Company::on('db-ninja-01')->find(config('ninja.ninja_default_company_id'));
|
||||
$ninja_company->notification(new RenewalFailureNotification("{$inapp_transaction_id}"))->ninja();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($account->plan_term == 'month') {
|
||||
$account->plan_expires = now()->addMonth();
|
||||
} elseif ($account->plan_term == 'year') {
|
||||
$account->plan_expires = now()->addYear();
|
||||
}
|
||||
|
||||
$account->save();
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Listeners\Subscription;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use App\Models\Company;
|
||||
use App\Notifications\Ninja\RenewalFailureNotification;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRenewed;
|
||||
|
||||
class PlayStoreRenewSubscription implements ShouldQueue
|
||||
{
|
||||
public function handle(SubscriptionRenewed $event)
|
||||
{
|
||||
$notification = $event->getServerNotification();
|
||||
nlog("google");
|
||||
nlog($notification);
|
||||
$in_app_identifier = $event->getSubscriptionIdentifier();
|
||||
|
||||
$parts = explode("..", $in_app_identifier);
|
||||
|
||||
MultiDB::findAndSetDbByInappTransactionId($parts[0]);
|
||||
|
||||
$expirationTime = $event->getSubscription()->getExpiryTime();
|
||||
|
||||
$account = Account::where('inapp_transaction_id', 'like', $parts[0]."%")->first();
|
||||
|
||||
if ($account) {
|
||||
$account->update(['plan_expires' => Carbon::parse($expirationTime)]);
|
||||
}
|
||||
|
||||
if (!$account) {
|
||||
$ninja_company = Company::on('db-ninja-01')->find(config('ninja.ninja_default_company_id'));
|
||||
$ninja_company->notification(new RenewalFailureNotification("{$in_app_identifier}"))->ninja();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
@ -388,6 +388,13 @@ class PaymentEmailEngine extends BaseEmailEngine
|
||||
*/
|
||||
private function buildViewButton(string $link, string $text): string
|
||||
{
|
||||
|
||||
|
||||
if ($this->settings->email_style == 'plain') {
|
||||
return '<a href="'. $link .'" target="_blank">'. $text .'</a>';
|
||||
}
|
||||
|
||||
|
||||
return '
|
||||
<div>
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
|
@ -116,7 +116,7 @@ class TemplateEmail extends Mailable
|
||||
}//remove whitespace if any has been inserted.
|
||||
}
|
||||
|
||||
$this->subject($this->build_email->getSubject())
|
||||
$this->subject(str_replace("<br>", "", $this->build_email->getSubject()))
|
||||
->text('email.template.text', [
|
||||
'text_body' => $this->build_email->getTextBody(),
|
||||
'whitelabel' => $this->client->user->account->isPaid() ? true : false,
|
||||
|
@ -284,6 +284,8 @@ class Activity extends StaticModel
|
||||
|
||||
const ACCEPT_PURCHASE_ORDER = 137;
|
||||
|
||||
const PAYMENT_EMAILED = 138;
|
||||
|
||||
protected $casts = [
|
||||
'is_system' => 'boolean',
|
||||
'updated_at' => 'timestamp',
|
||||
|
@ -117,13 +117,10 @@ class Gateway extends StaticModel
|
||||
switch ($this->id) {
|
||||
case 1:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Authorize.net
|
||||
break;
|
||||
case 3:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true]]; //eWay
|
||||
break;
|
||||
case 11:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true]]; //Payfast
|
||||
break;
|
||||
case 7:
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']], // Mollie
|
||||
@ -136,31 +133,29 @@ class Gateway extends StaticModel
|
||||
return [
|
||||
GatewayType::PAYPAL => ['refund' => false, 'token_billing' => false],
|
||||
]; //Paypal
|
||||
break;
|
||||
case 20:
|
||||
case 56:
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => ['payment_intent.succeeded']],
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'customer.source.updated','payment_intent.processing']],
|
||||
GatewayType::DIRECT_DEBIT => ['refund' => false, 'token_billing' => false, 'webhooks' => ['payment_intent.processing','payment_intent.succeeded','payment_intent.partially_funded']],
|
||||
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']],
|
||||
GatewayType::DIRECT_DEBIT => ['refund' => false, 'token_billing' => false, 'webhooks' => ['payment_intent.processing','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', 'payment_intent.processing', 'payment_intent.succeeded', 'mandate.updated']],
|
||||
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::KLARNA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::BACS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.processing', 'payment_intent.succeeded', 'mandate.updated', 'payment_intent.payment_failed']],
|
||||
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::KLARNA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::FPX => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
|
||||
];
|
||||
break;
|
||||
case 39:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']]]; //Checkout
|
||||
break;
|
||||
case 46:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true]]; //Paytrace
|
||||
case 49:
|
||||
@ -168,40 +163,16 @@ class Gateway extends StaticModel
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true],
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']],
|
||||
]; //WePay
|
||||
break;
|
||||
case 50:
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], //Braintree
|
||||
GatewayType::PAYPAL => ['refund' => true, 'token_billing' => true],
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']],
|
||||
];
|
||||
break;
|
||||
case 56: //Stripe
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => ['payment_intent.succeeded']],
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'customer.source.updated','payment_intent.processing']],
|
||||
GatewayType::DIRECT_DEBIT => ['refund' => false, 'token_billing' => false, 'webhooks' => ['payment_intent.processing','payment_intent.succeeded','payment_intent.partially_funded']],
|
||||
GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false],
|
||||
GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false],
|
||||
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::KLARNA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::BACS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.processing', 'payment_intent.succeeded', 'mandate.updated']],
|
||||
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'payment_intent.succeeded']],
|
||||
GatewayType::FPX => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
|
||||
];
|
||||
break;
|
||||
case 57:
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => false, 'token_billing' => true], //Square
|
||||
];
|
||||
break;
|
||||
case 52:
|
||||
return [
|
||||
GatewayType::BANK_TRANSFER => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']], // GoCardless
|
||||
@ -214,16 +185,13 @@ class Gateway extends StaticModel
|
||||
return [
|
||||
GatewayType::HOSTED_PAGE => ['refund' => false, 'token_billing' => false, 'webhooks' => [' ']], // Razorpay
|
||||
];
|
||||
break;
|
||||
case 59:
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true], // Forte
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']],
|
||||
];
|
||||
break;
|
||||
default:
|
||||
return [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -721,7 +721,7 @@ class Invoice extends BaseModel
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function entityEmailEvent($invitation, $reminder_template, $template)
|
||||
public function entityEmailEvent($invitation, $reminder_template, $template = '')
|
||||
{
|
||||
switch ($reminder_template) {
|
||||
case 'invoice':
|
||||
@ -737,6 +737,7 @@ class Invoice extends BaseModel
|
||||
event(new InvoiceReminderWasEmailed($invitation, $invitation->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), Activity::INVOICE_REMINDER3_SENT));
|
||||
break;
|
||||
case 'reminder_endless':
|
||||
case 'endless_reminder':
|
||||
event(new InvoiceReminderWasEmailed($invitation, $invitation->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null), Activity::INVOICE_REMINDER_ENDLESS_SENT));
|
||||
break;
|
||||
default:
|
||||
|
@ -171,6 +171,7 @@ use App\Listeners\Activity\VendorUpdatedActivity;
|
||||
use App\Listeners\Contact\UpdateContactLastLogin;
|
||||
use App\Listeners\Invoice\InvoiceDeletedActivity;
|
||||
use App\Listeners\Payment\PaymentBalanceActivity;
|
||||
use App\Listeners\Payment\PaymentEmailedActivity;
|
||||
use App\Listeners\Quote\QuoteCreatedNotification;
|
||||
use App\Listeners\Quote\QuoteEmailedNotification;
|
||||
use App\Events\Invoice\InvoiceWasEmailedAndFailed;
|
||||
@ -454,7 +455,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
InvitationViewedListener::class,
|
||||
],
|
||||
PaymentWasEmailed::class => [
|
||||
// PaymentEmailedActivity::class,
|
||||
PaymentEmailedActivity::class,
|
||||
],
|
||||
PaymentWasEmailedAndFailed::class => [
|
||||
// PaymentEmailFailureActivity::class,
|
||||
|
@ -34,6 +34,7 @@ class InstantPayment
|
||||
use MakesHash;
|
||||
use MakesDates;
|
||||
|
||||
/** $request mixed */
|
||||
public Request $request;
|
||||
|
||||
public function __construct(Request $request)
|
||||
@ -214,7 +215,16 @@ class InstantPayment
|
||||
$credit_totals = 0;
|
||||
}
|
||||
|
||||
$hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals, 'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals)), 'pre_payment' => $this->request->pre_payment];
|
||||
/** $hash_data = mixed[] */
|
||||
$hash_data = [
|
||||
'invoices' => $payable_invoices->toArray(),
|
||||
'credits' => $credit_totals,
|
||||
'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals)),
|
||||
'pre_payment' => $this->request->pre_payment,
|
||||
'frequency_id' => $this->request->frequency_id,
|
||||
'remaining_cycles' => $this->request->remaining_cycles,
|
||||
'is_recurring' => $this->request->is_recurring,
|
||||
];
|
||||
|
||||
if ($this->request->query('hash')) {
|
||||
$hash_data['billing_context'] = Cache::get($this->request->query('hash'));
|
||||
@ -256,6 +266,8 @@ class InstantPayment
|
||||
'payment_method_id' => $payment_method_id,
|
||||
'amount_with_fee' => $invoice_totals + $fee_totals,
|
||||
'client' => $client,
|
||||
'pre_payment' => $this->request->pre_payment,
|
||||
'is_recurring' => $this->request->is_recurring,
|
||||
];
|
||||
|
||||
if ($is_credit_payment || $totals <= 0) {
|
||||
|
@ -41,7 +41,7 @@ class EmailMailable extends Mailable
|
||||
public function envelope()
|
||||
{
|
||||
return new Envelope(
|
||||
subject: $this->email_object->subject,
|
||||
subject: str_replace("<br>","",$this->email_object->subject),
|
||||
tags: [$this->email_object->company_key],
|
||||
replyTo: $this->email_object->reply_to,
|
||||
from: $this->email_object->from,
|
||||
|
@ -40,6 +40,13 @@ class ApplyNumber extends AbstractService
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
/** Do no give pro forma invoices a proper invoice number */
|
||||
if($this->invoice->is_proforma) {
|
||||
$this->invoice->number = ctrans('texts.pre_payment') . " " . now()->format('Y-m-d : H:i:s');
|
||||
$this->invoice->saveQuietly();
|
||||
return $this->invoice;
|
||||
}
|
||||
|
||||
switch ($this->client->getSetting('counter_number_applied')) {
|
||||
case 'when_saved':
|
||||
$this->trySaving();
|
||||
|
@ -121,7 +121,17 @@ class AutoBillInvoice extends AbstractService
|
||||
|
||||
$payment_hash = PaymentHash::create([
|
||||
'hash' => Str::random(64),
|
||||
'data' => ['amount_with_fee' => $amount + $fee, 'invoices' => [['invoice_id' => $this->invoice->hashed_id, 'amount' => $amount, 'invoice_number' => $this->invoice->number]]],
|
||||
'data' => [
|
||||
'amount_with_fee' => $amount + $fee,
|
||||
'invoices' => [
|
||||
[
|
||||
'invoice_id' => $this->invoice->hashed_id,
|
||||
'amount' => $amount,
|
||||
'invoice_number' => $this->invoice->number,
|
||||
'pre_payment' => $this->invoice->is_proforma,
|
||||
],
|
||||
],
|
||||
],
|
||||
'fee_total' => $fee,
|
||||
'fee_invoice_id' => $this->invoice->id,
|
||||
]);
|
||||
|
@ -11,9 +11,11 @@
|
||||
|
||||
namespace App\Services\Payment;
|
||||
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Payment;
|
||||
use App\Models\ClientContact;
|
||||
use App\Jobs\Payment\EmailPayment;
|
||||
use App\Events\Payment\PaymentWasEmailed;
|
||||
|
||||
class SendEmail
|
||||
{
|
||||
@ -36,6 +38,8 @@ class SendEmail
|
||||
// $invoice->invitations->each(function ($invitation) {
|
||||
// if (!$invitation->contact->trashed() && $invitation->contact->email) {
|
||||
EmailPayment::dispatch($this->payment, $this->payment->company, $this->contact);
|
||||
|
||||
event(new PaymentWasEmailed($this->payment, $this->payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
@ -11,12 +11,14 @@
|
||||
|
||||
namespace App\Services\Payment;
|
||||
|
||||
use App\Events\Invoice\InvoiceWasUpdated;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Factory\RecurringInvoiceFactory;
|
||||
use App\Events\Invoice\InvoiceWasUpdated;
|
||||
|
||||
class UpdateInvoicePayment
|
||||
{
|
||||
@ -93,9 +95,36 @@ class UpdateInvoicePayment
|
||||
$invoice->is_deleted = true;
|
||||
$invoice->deleted_at = now();
|
||||
$invoice->saveQuietly();
|
||||
|
||||
if (property_exists($this->payment_hash->data, 'is_recurring') && $this->payment_hash->data->is_recurring == "1") {
|
||||
$recurring_invoice = RecurringInvoiceFactory::create($invoice->company_id, $invoice->user_id);
|
||||
$recurring_invoice->client_id = $invoice->client_id;
|
||||
$recurring_invoice->line_items = $invoice->line_items;
|
||||
$recurring_invoice->frequency_id = $this->payment_hash->data->frequency_id ?: RecurringInvoice::FREQUENCY_MONTHLY;
|
||||
$recurring_invoice->date = now();
|
||||
$recurring_invoice->remaining_cycles = $this->payment_hash->data->remaining_cycles;
|
||||
$recurring_invoice->auto_bill = 'always';
|
||||
$recurring_invoice->auto_bill_enabled = true;
|
||||
$recurring_invoice->due_date_days = 'on_receipt';
|
||||
$recurring_invoice->next_send_date = now()->format('Y-m-d');
|
||||
$recurring_invoice->next_send_date_client = now()->format('Y-m-d');
|
||||
$recurring_invoice->amount = $invoice->amount;
|
||||
$recurring_invoice->balance = $invoice->amount;
|
||||
$recurring_invoice->status_id = RecurringInvoice::STATUS_ACTIVE;
|
||||
$recurring_invoice->is_proforma = true;
|
||||
|
||||
$recurring_invoice->saveQuietly();
|
||||
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
|
||||
$recurring_invoice->next_send_date_client = $recurring_invoice->nextSendDateClient();
|
||||
$recurring_invoice->service()->applyNumber()->save();
|
||||
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (strlen($invoice->number) > 1 && str_starts_with($invoice->number, "####"))
|
||||
$invoice->number = '';
|
||||
|
||||
|
@ -49,7 +49,8 @@ class TriggeredActions extends AbstractService
|
||||
$this->quote = $this->quote->service()->convert()->save();
|
||||
}
|
||||
|
||||
if ($this->request->has('approve') && $this->request->input('approve') == 'true' && in_array($this->quote->status_id, [Quote::STATUS_SENT, Quote::STATUS_DRAFT])) {
|
||||
// if ($this->request->has('approve') && $this->request->input('approve') == 'true' && in_array($this->quote->status_id, [Quote::STATUS_SENT, Quote::STATUS_DRAFT])) {
|
||||
if ($this->request->has('approve') && $this->request->input('approve') == 'true') {
|
||||
$this->quote = $this->quote->service()->approveWithNoCoversion()->save();
|
||||
}
|
||||
|
||||
|
@ -1087,7 +1087,7 @@ class SubscriptionService
|
||||
}
|
||||
|
||||
|
||||
private function setAutoBillFlag($auto_bill)
|
||||
private function setAutoBillFlag($auto_bill): bool
|
||||
{
|
||||
if ($auto_bill == 'always' || $auto_bill == 'optout') {
|
||||
return true;
|
||||
|
@ -87,7 +87,7 @@ class ExpenseTransformer extends EntityTransformer
|
||||
'currency_id' => (string) $expense->currency_id ?: '',
|
||||
'category_id' => $this->encodePrimaryKey($expense->category_id),
|
||||
'payment_type_id' => (string) $expense->payment_type_id ?: '',
|
||||
'recurring_expense_id' => (string) $expense->recurring_expense_id ?: '',
|
||||
'recurring_expense_id' => (string) $this->encodePrimaryKey($expense->recurring_expense_id) ?: '',
|
||||
'is_deleted' => (bool) $expense->is_deleted,
|
||||
'should_be_invoiced' => (bool) $expense->should_be_invoiced,
|
||||
'invoice_documents' => (bool) $expense->invoice_documents,
|
||||
|
@ -808,6 +808,13 @@ html {
|
||||
*/
|
||||
private function buildViewButton(string $link, string $text): string
|
||||
{
|
||||
|
||||
|
||||
if ($this->settings->email_style == 'plain') {
|
||||
return '<a href="'. $link .'" target="_blank">'. $text .'</a>';
|
||||
}
|
||||
|
||||
|
||||
return '
|
||||
<div>
|
||||
<!--[if (gte mso 9)|(IE)]>
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
use Imdhemy\Purchases\Events\AppStore\DidRenew;
|
||||
use App\Listeners\Subscription\AppStoreRenewSubscription;
|
||||
use App\Listeners\Subscription\PlayStoreRenewSubscription;
|
||||
use Imdhemy\Purchases\Events\GooglePlay\SubscriptionRenewed;
|
||||
|
||||
return [
|
||||
@ -97,8 +96,9 @@ return [
|
||||
/* \Imdhemy\Purchases\Events\GooglePlay\SubscriptionRecovered::class => [
|
||||
\App\Listeners\GooglePlay\SubscriptionRecovered::class,
|
||||
],*/
|
||||
SubscriptionRenewed::class => [PlayStoreRenewSubscription::class],
|
||||
DidRenew::class => [AppStoreRenewSubscription::class],
|
||||
|
||||
DidRenew::class => class_exists(\Modules\Admin\Listeners\Subscription\AppleAutoRenew::class) ? [\Modules\Admin\Listeners\Subscription\AppleAutoRenew::class] : [],
|
||||
SubscriptionRenewed::class => class_exists(\Modules\Admin\Listeners\Subscription\GoogleAutoRenew::class) ? [\Modules\Admin\Listeners\Subscription\GoogleAutoRenew::class] : [],
|
||||
|
||||
],
|
||||
|
||||
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.5.94',
|
||||
'app_tag' => '5.5.94',
|
||||
'app_version' => '5.5.95',
|
||||
'app_tag' => '5.5.95',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
@ -213,4 +213,5 @@ return [
|
||||
'config_name' => env("YODLEE_CONFIG_NAME", false),
|
||||
],
|
||||
'licenses' => env('LICENSES',false),
|
||||
'google_application_credentials' => env("GOOGLE_APPLICATION_CREDENTIALS", false),
|
||||
];
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?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::table('recurring_invoices', function (Blueprint $table) {
|
||||
$table->boolean('is_proforma')->default(false);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -5021,7 +5021,12 @@ $LANG = array(
|
||||
'payment_type_Klarna' => 'Klarna',
|
||||
'payment_type_Interac E Transfer' => 'Interac E Transfer',
|
||||
'pre_payment' => 'Pre Payment',
|
||||
'client_remaining_cycles_helper' => 'The number of times this invoice will be generated',
|
||||
'number_of_payments' => 'Number of payments',
|
||||
'number_of_payments_helper' => 'The number of times this payment will be made',
|
||||
'pre_payment_indefinitely' => 'Continue until cancelled',
|
||||
'notification_payment_emailed' => 'Payment :payment was emailed to :client',
|
||||
'notification_payment_emailed_subject' => 'Payment :payment was emailed',
|
||||
'record_not_found' => 'Record not found',
|
||||
);
|
||||
|
||||
|
||||
|
@ -21,6 +21,10 @@
|
||||
$checked_off = '';
|
||||
}
|
||||
|
||||
if (isset($pre_payment) && $pre_payment == '1' && isset($is_recurring) && $is_recurring == '1') {
|
||||
$token_billing_string = 'true';
|
||||
}
|
||||
|
||||
@endphp
|
||||
|
||||
@if($token_billing)
|
||||
|
@ -7,6 +7,11 @@
|
||||
if($gateway_instance->token_billing == 'off' || $gateway_instance->token_billing == 'optin'){
|
||||
$token_billing_string = 'false';
|
||||
}
|
||||
|
||||
if (isset($pre_payment) && $pre_payment == '1' && isset($is_recurring) && $is_recurring == '1') {
|
||||
$token_billing_string = 'true';
|
||||
}
|
||||
|
||||
|
||||
@endphp
|
||||
|
||||
|
@ -14,6 +14,9 @@
|
||||
<input type="hidden" name="payment_method_id" id="payment_method_id">
|
||||
<input type="hidden" name="signature">
|
||||
<input type="hidden" name="pre_payment" value="{{ isset($pre_payment) ? $pre_payment : false }}">
|
||||
<input type="hidden" name="is_recurring" value="{{ isset($is_recurring) ? $is_recurring : false }}">
|
||||
<input type="hidden" name="frequency_id" value="{{ isset($frequency_id) ? $frequency_id : false }}">
|
||||
<input type="hidden" name="remaining_cycles" value="{{ isset($remaining_cycles) ? $remaining_cycles : false }}">
|
||||
|
||||
<div class="container mx-auto">
|
||||
<div class="grid grid-cols-6 gap-4">
|
||||
|
@ -56,15 +56,15 @@
|
||||
@endcomponent
|
||||
|
||||
<div x-cloak x-show="show">
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.cycles_remaining')])
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.number_of_payments')])
|
||||
<select name="remaining_cycles" class="form-select input w-full bg-white">
|
||||
<option value="-1" selected>{{ ctrans('texts.freq_indefinitely')}}</option>
|
||||
<option value="-1" selected>{{ ctrans('texts.pre_payment_indefinitely')}}</option>
|
||||
@for($i = 1; $i < 60; $i++)
|
||||
<option value={{$i}}>{{$i}}</option>
|
||||
@endfor
|
||||
</select>
|
||||
<span class="py-2">
|
||||
<label for="remaining_cycles" class="col-form-label text-center col-lg-3 text-gray-900">{{ ctrans ('texts.client_remaining_cycles_helper')}}</label>
|
||||
<label for="remaining_cycles" class="col-form-label text-center col-lg-3 text-gray-900">{{ ctrans ('texts.number_of_payments_helper')}}</label>
|
||||
</span>
|
||||
@endcomponent
|
||||
@component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.frequency')])
|
||||
@ -73,14 +73,14 @@
|
||||
<option value="2">{{ ctrans('texts.freq_weekly') }}</option>
|
||||
<option value="3">{{ ctrans('texts.freq_two_weeks') }}</option>
|
||||
<option value="4">{{ ctrans('texts.freq_four_weeks') }}</option>
|
||||
<option value="5">{{ ctrans('texts.freq_monthly') }}</option>
|
||||
<option value="5" selected>{{ ctrans('texts.freq_monthly') }}</option>
|
||||
<option value="6">{{ ctrans('texts.freq_two_months') }}</option>
|
||||
<option value="7">{{ ctrans('texts.freq_three_months') }}</option>
|
||||
<option value="8">{{ ctrans('texts.') }}</option>
|
||||
<option value="9">{{ ctrans('texts.') }}</option>
|
||||
<option value="10">{{ ctrans('texts.') }}</option>
|
||||
<option value="11">{{ ctrans('texts.') }}</option>
|
||||
<option value="12">{{ ctrans('texts.') }}</option>
|
||||
<option value="8">{{ ctrans('texts.freq_four_months') }}</option>
|
||||
<option value="9">{{ ctrans('texts.freq_six_months') }}</option>
|
||||
<option value="10">{{ ctrans('texts.freq_annually') }}</option>
|
||||
<option value="11">{{ ctrans('texts.freq_two_years') }}</option>
|
||||
<option value="12">{{ ctrans('texts.freq_three_years') }}</option>
|
||||
</select>
|
||||
@endcomponent
|
||||
</div>
|
||||
|
@ -336,6 +336,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
|
||||
|
||||
Route::resource('webhooks', WebhookController::class);
|
||||
Route::post('webhooks/bulk', [WebhookController::class, 'bulk'])->name('webhooks.bulk');
|
||||
Route::post('webhooks/{webhook}/retry', [WebhookController::class, 'retry'])->name('webhooks.retry');
|
||||
|
||||
/*Subscription and Webhook routes */
|
||||
// Route::post('hooks', [SubscriptionController::class, 'subscribe'])->name('hooks.subscribe');
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Jobs\Util\WebhookSingle;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
@ -45,6 +46,39 @@ class WebhookAPITest extends TestCase
|
||||
$this->withoutExceptionHandling();
|
||||
}
|
||||
|
||||
public function testWebhookRetry()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'target_url' => 'http://hook.com',
|
||||
'event_id' => 1, //create client
|
||||
'format' => 'JSON',
|
||||
'headers' => []
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson("/api/v1/webhooks", $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$data = [
|
||||
'entity' => 'client',
|
||||
'entity_id' => $this->client->hashed_id,
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson("/api/v1/webhooks/".$arr['data']['id']."/retry", $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
}
|
||||
|
||||
public function testWebhookGetFilter()
|
||||
{
|
||||
$response = $this->withHeaders([
|
||||
|
Loading…
x
Reference in New Issue
Block a user