1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-25 02:37:10 +02:00

Merge pull request #6910 from turbo124/v5-stable

V5 stable
This commit is contained in:
David Bomba 2021-10-27 14:32:40 +11:00 committed by GitHub
commit c450ccc4bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 470 additions and 322 deletions

View File

@ -1 +1 @@
5.3.27 5.3.29

View File

@ -52,6 +52,8 @@ class Handler extends ExceptionHandler
MaxAttemptsExceededException::class, MaxAttemptsExceededException::class,
CommandNotFoundException::class, CommandNotFoundException::class,
ValidationException::class, ValidationException::class,
ModelNotFoundException::class,
NotFoundHttpException::class,
]; ];
/** /**

View File

@ -15,6 +15,7 @@ use App\Http\Requests\Activity\DownloadHistoricalEntityRequest;
use App\Models\Activity; use App\Models\Activity;
use App\Transformers\ActivityTransformer; use App\Transformers\ActivityTransformer;
use App\Utils\HostedPDF\NinjaPdf; use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom; use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\Pdf\PdfMaker; use App\Utils\Traits\Pdf\PdfMaker;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
@ -147,7 +148,12 @@ class ActivityController extends BaseController
*/ */
if($backup && $backup->filename && Storage::disk(config('filesystems.default'))->exists($backup->filename)){ //disk if($backup && $backup->filename && Storage::disk(config('filesystems.default'))->exists($backup->filename)){ //disk
$html_backup = file_get_contents(Storage::disk(config('filesystems.default'))->path($backup->filename));
if(Ninja::isHosted())
$html_backup = file_get_contents(Storage::disk(config('filesystems.default'))->url($backup->filename));
else
$html_backup = file_get_contents(Storage::disk(config('filesystems.default'))->path($backup->filename));
} }
elseif($backup && $backup->html_backup){ //db elseif($backup && $backup->html_backup){ //db
$html_backup = $backup->html_backup; $html_backup = $backup->html_backup;

View File

@ -41,7 +41,16 @@ class ContactLoginController extends Controller
// $company = null; // $company = null;
// }else // }else
if (strpos($request->getHost(), 'invoicing.co') !== false) { $company = false;
if($request->has('company_key')){
MultiDB::findAndSetDbByCompanyKey($request->input('company_key'));
$company = Company::where('company_key', $request->input('company_key'))->first();
}
if (!$company && strpos($request->getHost(), 'invoicing.co') !== false) {
$subdomain = explode('.', $request->getHost())[0]; $subdomain = explode('.', $request->getHost())[0];
MultiDB::findAndSetDbByDomain(['subdomain' => $subdomain]); MultiDB::findAndSetDbByDomain(['subdomain' => $subdomain]);
@ -72,8 +81,8 @@ class ContactLoginController extends Controller
{ {
Auth::shouldUse('contact'); Auth::shouldUse('contact');
if(Ninja::isHosted() && $request->has('db')) if(Ninja::isHosted() && $request->has('company_key'))
MultiDB::setDb($request->input('db')); MultiDB::findAndSetDbByCompanyKey($request->input('company_key'));
$this->validateLogin($request); $this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle // If the class is using the ThrottlesLogins trait, we can automatically throttle

View File

@ -27,7 +27,7 @@ class SwitchCompanyController extends Controller
->where('id', $this->transformKeys($contact)) ->where('id', $this->transformKeys($contact))
->first(); ->first();
auth()->guard('contact')->user()->login($client_contact, true); auth()->guard('contact')->login($client_contact, true);
return redirect('/client/dashboard'); return redirect('/client/dashboard');
} }

View File

@ -240,7 +240,7 @@ class CompanyController extends BaseController
/* /*
* Create token * Create token
*/ */
$user_agent = request()->input('token_name') ?: request()->server('HTTP_USER_AGENT'); $user_agent = request()->has('token_name') ? request()->input('token_name') : request()->server('HTTP_USER_AGENT');
$company_token = CreateCompanyToken::dispatchNow($company, auth()->user(), $user_agent); $company_token = CreateCompanyToken::dispatchNow($company, auth()->user(), $user_agent);

View File

@ -683,8 +683,6 @@ class PaymentController extends BaseController
{ {
$payment = $request->payment(); $payment = $request->payment();
// nlog($request->all());
$payment = $payment->refund($request->all()); $payment = $payment->refund($request->all());
return $this->itemResponse($payment); return $this->itemResponse($payment);

View File

@ -42,6 +42,9 @@ class StoreRecurringExpenseRequest extends Request
$rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id; $rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id;
$rules['frequency_id'] = 'required|integer|digits_between:1,12'; $rules['frequency_id'] = 'required|integer|digits_between:1,12';
$rules['tax_amount1'] = 'numeric';
$rules['tax_amount2'] = 'numeric';
$rules['tax_amount3'] = 'numeric';
return $this->globalRules($rules); return $this->globalRules($rules);
} }

View File

@ -43,6 +43,10 @@ class UpdateRecurringExpenseRequest extends Request
$rules['number'] = Rule::unique('recurring_expenses')->where('company_id', auth()->user()->company()->id)->ignore($this->recurring_expense->id); $rules['number'] = Rule::unique('recurring_expenses')->where('company_id', auth()->user()->company()->id)->ignore($this->recurring_expense->id);
} }
$rules['tax_amount1'] = 'numeric';
$rules['tax_amount2'] = 'numeric';
$rules['tax_amount3'] = 'numeric';
return $this->globalRules($rules); return $this->globalRules($rules);
} }

View File

@ -33,9 +33,10 @@ class UpdateTaskStatusRequest extends Request
{ {
$rules = []; $rules = [];
if ($this->input('name')) { // 26/10/2021 we disable this as it prevent updating existing task status meta data where the same name already exists
$rules['name'] = Rule::unique('task_statuses')->where('company_id', auth()->user()->company()->id)->ignore($this->task_status->id); // if ($this->input('name')) {
} // $rules['name'] = Rule::unique('task_statuses')->where('company_id', auth()->user()->company()->id)->ignore($this->task_status->id);
// }
return $rules; return $rules;

View File

@ -51,7 +51,7 @@ class PaymentAppliedValidAmount implements Rule
$payment_amounts = 0; $payment_amounts = 0;
$invoice_amounts = 0; $invoice_amounts = 0;
$payment_amounts = $payment->amount - $payment->applied; $payment_amounts = $payment->amount - $payment->refunded - $payment->applied;
if (request()->input('credits') && is_array(request()->input('credits'))) { if (request()->input('credits') && is_array(request()->input('credits'))) {
foreach (request()->input('credits') as $credit) { foreach (request()->input('credits') as $credit) {

View File

@ -26,7 +26,10 @@ class ValidAmount implements Rule
*/ */
public function passes($attribute, $value) public function passes($attribute, $value)
{ {
return trim($value, '-1234567890.,') === ''; return is_numeric((string)$value);
//return filter_var((string)$value, FILTER_VALIDATE_FLOAT);
// return preg_match('^(?=.)([+-]?([0-9]*)(\.([0-9]+))?)$^', (string)$value);
// return trim($value, '-1234567890.,') === '';
} }

View File

@ -71,7 +71,7 @@ class RecurringInvoicesCron
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db); SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);
} }
catch(\Exception $e){ catch(\Exception $e){
nlog("Unable to sending recurring invoice {$recurring_invoice->id}"); nlog("Unable to sending recurring invoice {$recurring_invoice->id} ". $e->getMessage());
} }
}); });
@ -107,7 +107,7 @@ class RecurringInvoicesCron
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db); SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);
} }
catch(\Exception $e){ catch(\Exception $e){
nlog("Unable to sending recurring invoice {$recurring_invoice->id}"); nlog("Unable to sending recurring invoice {$recurring_invoice->id} ". $e->getMessage());
} }
}); });

View File

@ -15,6 +15,7 @@ use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob; use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject; use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Mail\Admin\ClientPaymentFailureObject;
use App\Mail\Admin\EntityNotificationMailer; use App\Mail\Admin\EntityNotificationMailer;
use App\Mail\Admin\PaymentFailureObject; use App\Mail\Admin\PaymentFailureObject;
use App\Models\Client; use App\Models\Client;
@ -102,6 +103,24 @@ class PaymentFailedMailer implements ShouldQueue
}); });
//add client payment failures here. //add client payment failures here.
nlog("pre client failure email");
if($contact = $this->client->primary_contact()->first())
{
nlog("inside failure");
$mail_obj = (new ClientPaymentFailureObject($this->client, $this->error, $this->company, $this->payment_hash))->build();
$nmo = new NinjaMailerObject;
$nmo->mailable = new NinjaMailer($mail_obj);
$nmo->company = $this->company;
$nmo->to_user = $contact;
$nmo->settings = $settings;
NinjaMailerJob::dispatch($nmo);
}
} }

View File

@ -73,13 +73,14 @@ class SendRecurring implements ShouldQueue
$invoice->date = now()->format('Y-m-d'); $invoice->date = now()->format('Y-m-d');
$invoice->due_date = $this->recurring_invoice->calculateDueDate(now()->format('Y-m-d')); $invoice->due_date = $this->recurring_invoice->calculateDueDate(now()->format('Y-m-d'));
$invoice->recurring_id = $this->recurring_invoice->id; $invoice->recurring_id = $this->recurring_invoice->id;
$invoice->saveQuietly();
if($invoice->client->getSetting('auto_email_invoice')) if($invoice->client->getSetting('auto_email_invoice'))
{ {
$invoice = $invoice->service() $invoice = $invoice->service()
->markSent() ->markSent()
->applyNumber() ->applyNumber()
// ->createInvitations() //need to only link invitations to those in the recurring invoice //->createInvitations() //need to only link invitations to those in the recurring invoice
->fillDefaults() ->fillDefaults()
->save(); ->save();

View File

@ -14,7 +14,6 @@ namespace App\Mail\Admin;
use App\Models\Invoice; use App\Models\Invoice;
use App\Utils\HtmlEngine; use App\Utils\HtmlEngine;
use App\Utils\Ninja; use App\Utils\Ninja;
use App\Utils\Number;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\App;
use stdClass; use stdClass;
@ -91,7 +90,7 @@ class ClientPaymentFailureObject
return return
ctrans( ctrans(
'texts.notification_invoice_payment_failed_subject', 'texts.notification_invoice_payment_failed_subject',
['invoice' => $this->client->present()->name()] ['invoice' => implode(",", $this->invoices->pluck('number')->toArray())]
); );
} }
@ -110,7 +109,7 @@ class ClientPaymentFailureObject
] ]
), ),
'greeting' => ctrans('texts.email_salutation', ['name' => $this->client->present()->name]), 'greeting' => ctrans('texts.email_salutation', ['name' => $this->client->present()->name]),
'message' => $this->error, 'message' => ctrans('texts.client_payment_failure_body', ['invoice' => implode(",", $this->invoices->pluck('number')->toArray()), 'amount' => $this->getAmount()]),
'signature' => $signature, 'signature' => $signature,
'logo' => $this->company->present()->logo(), 'logo' => $this->company->present()->logo(),
'settings' => $this->client->getMergedSettings(), 'settings' => $this->client->getMergedSettings(),

View File

@ -92,7 +92,7 @@ class ClientContact extends Authenticatable implements HasLocalePreference
'custom_value4', 'custom_value4',
'email', 'email',
'is_primary', 'is_primary',
// 'client_id', 'send_email',
]; ];
/** /**

View File

@ -145,7 +145,9 @@ class Gateway extends StaticModel
GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']], GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
]; ];
break; break;
case 57: case 57:

View File

@ -84,7 +84,7 @@ class GatewayType extends StaticModel
case self::EPS: case self::EPS:
return ctrans('texts.eps'); return ctrans('texts.eps');
case self::BECS: case self::BECS:
return ctrans('tets.becs'); return ctrans('texts.becs');
case self::ACSS: case self::ACSS:
return ctrans('texts.acss'); return ctrans('texts.acss');
case self::DIRECT_DEBIT: case self::DIRECT_DEBIT:

View File

@ -221,6 +221,19 @@ class BaseDriver extends AbstractPaymentDriver
{ {
$this->confirmGatewayFee(); $this->confirmGatewayFee();
/*Never create a payment with a duplicate transaction reference*/
if(array_key_exists('transaction_reference', $data)){
$_payment = Payment::where('transaction_reference', $data['transaction_reference'])
->where('client_id', $this->client->id)
->first();
if($_payment)
return $_payment;
}
$payment = PaymentFactory::create($this->client->company->id, $this->client->user->id); $payment = PaymentFactory::create($this->client->company->id, $this->client->user->id);
$payment->client_id = $this->client->id; $payment->client_id = $this->client->id;
$payment->company_gateway_id = $this->company_gateway->id; $payment->company_gateway_id = $this->company_gateway->id;

View File

@ -13,7 +13,6 @@ namespace App\PaymentDrivers\Braintree;
use App\Exceptions\PaymentFailed; use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Request;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;
use App\Models\GatewayType; use App\Models\GatewayType;
@ -23,6 +22,7 @@ use App\Models\SystemLog;
use App\PaymentDrivers\BraintreePaymentDriver; use App\PaymentDrivers\BraintreePaymentDriver;
use App\PaymentDrivers\Common\MethodInterface; use App\PaymentDrivers\Common\MethodInterface;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
class ACH implements MethodInterface class ACH implements MethodInterface
{ {

View File

@ -12,8 +12,6 @@
namespace App\PaymentDrivers; namespace App\PaymentDrivers;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;
use App\Models\GatewayType; use App\Models\GatewayType;
@ -27,7 +25,6 @@ use App\PaymentDrivers\Braintree\CreditCard;
use App\PaymentDrivers\Braintree\PayPal; use App\PaymentDrivers\Braintree\PayPal;
use Braintree\Gateway; use Braintree\Gateway;
use Exception; use Exception;
use Illuminate\Http\Request;
class BraintreePaymentDriver extends BaseDriver class BraintreePaymentDriver extends BaseDriver
{ {
@ -40,7 +37,7 @@ class BraintreePaymentDriver extends BaseDriver
/** /**
* @var Gateway; * @var Gateway;
*/ */
public $gateway; public Gateway $gateway;
public static $methods = [ public static $methods = [
GatewayType::CREDIT_CARD => CreditCard::class, GatewayType::CREDIT_CARD => CreditCard::class,
@ -118,8 +115,7 @@ class BraintreePaymentDriver extends BaseDriver
]); ]);
if ($result->success) { if ($result->success) {
$address = $this->gateway->address()->create([
$address = $this->gateway->address()->create([
'customerId' => $result->customer->id, 'customerId' => $result->customer->id,
'firstName' => $this->client->present()->name, 'firstName' => $this->client->present()->name,
'streetAddress' => $this->client->address1, 'streetAddress' => $this->client->address1,
@ -135,12 +131,9 @@ class BraintreePaymentDriver extends BaseDriver
{ {
$this->init(); $this->init();
try{ try {
$response = $this->gateway->transaction()->refund($payment->transaction_reference, $amount); $response = $this->gateway->transaction()->refund($payment->transaction_reference, $amount);
} catch (Exception $e) { } catch (Exception $e) {
$data = [ $data = [
'transaction_reference' => null, 'transaction_reference' => null,
'transaction_response' => json_encode($e->getMessage()), 'transaction_response' => json_encode($e->getMessage()),
@ -154,24 +147,19 @@ class BraintreePaymentDriver extends BaseDriver
return $data; return $data;
} }
if($response->success) if ($response->success) {
{
$data = [ $data = [
'transaction_reference' => $response->id, 'transaction_reference' => $payment->transaction_reference,
'transaction_response' => json_encode($response), 'transaction_response' => json_encode($response),
'success' => (bool)$response->success, 'success' => (bool) $response->success,
'description' => $response->status, 'description' => ctrans('texts.plan_refunded'),
'code' => 0, 'code' => 0,
]; ];
SystemLogger::dispatch(['server_response' => $response, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_BRAINTREE, $this->client, $this->client->company); SystemLogger::dispatch(['server_response' => $response, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_BRAINTREE, $this->client, $this->client->company);
return $data; return $data;
} else {
}
else{
$error = $response->errors->deepAll()[0]; $error = $response->errors->deepAll()[0];
$data = [ $data = [
@ -185,7 +173,6 @@ class BraintreePaymentDriver extends BaseDriver
SystemLogger::dispatch(['server_response' => $response, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_BRAINTREE, $this->client, $this->client->company); SystemLogger::dispatch(['server_response' => $response, 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_BRAINTREE, $this->client, $this->client->company);
return $data; return $data;
} }
} }

View File

@ -14,7 +14,7 @@ namespace App\PaymentDrivers\CheckoutCom;
use App\Exceptions\PaymentFailed; use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Request; use Illuminate\Http\Request;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;
use App\Models\GatewayType; use App\Models\GatewayType;
use App\PaymentDrivers\CheckoutComPaymentDriver; use App\PaymentDrivers\CheckoutComPaymentDriver;
@ -112,7 +112,7 @@ class CreditCard implements MethodInterface
$data['currency'] = $this->checkout->client->getCurrencyCode(); $data['currency'] = $this->checkout->client->getCurrencyCode();
$data['value'] = $this->checkout->convertToCheckoutAmount($data['total']['amount_with_fee'], $this->checkout->client->getCurrencyCode()); $data['value'] = $this->checkout->convertToCheckoutAmount($data['total']['amount_with_fee'], $this->checkout->client->getCurrencyCode());
$data['raw_value'] = $data['total']['amount_with_fee']; $data['raw_value'] = $data['total']['amount_with_fee'];
$data['customer_email'] = $this->checkout->client->present()->email; $data['customer_email'] = $this->checkout->client->present()->email();
return render('gateways.checkout.credit_card.pay', $data); return render('gateways.checkout.credit_card.pay', $data);
} }
@ -173,6 +173,10 @@ class CreditCard implements MethodInterface
$payment = new Payment($method, $this->checkout->payment_hash->data->currency); $payment = new Payment($method, $this->checkout->payment_hash->data->currency);
$payment->amount = $this->checkout->payment_hash->data->value; $payment->amount = $this->checkout->payment_hash->data->value;
$payment->reference = $this->checkout->getDescription(); $payment->reference = $this->checkout->getDescription();
$payment->customer = [
'name' => $this->checkout->client->present()->name() ,
'email' => $this->checkout->client->present()->email(),
];
$this->checkout->payment_hash->data = array_merge((array)$this->checkout->payment_hash->data, ['checkout_payment_ref' => $payment]); $this->checkout->payment_hash->data = array_merge((array)$this->checkout->payment_hash->data, ['checkout_payment_ref' => $payment]);
$this->checkout->payment_hash->save(); $this->checkout->payment_hash->save();

View File

@ -84,8 +84,7 @@ trait Utilities
public function processUnsuccessfulPayment(Payment $_payment, $throw_exception = true) public function processUnsuccessfulPayment(Payment $_payment, $throw_exception = true)
{ {
$this->getParent()->sendFailureMail($_payment->status . " " . optional($_payment)->response_summary);
$this->getParent()->sendFailureMail($_payment->status . " " . $_payment->response_summary);
$message = [ $message = [
'server_response' => $_payment, 'server_response' => $_payment,
@ -102,7 +101,7 @@ trait Utilities
); );
if ($throw_exception) { if ($throw_exception) {
throw new PaymentFailed($_payment->status . " " . $_payment->response_summary, $_payment->http_code); throw new PaymentFailed($_payment->status . " " . optional($_payment)->response_summary, $_payment->http_code);
} }
} }

View File

@ -338,7 +338,9 @@ class CheckoutComPaymentDriver extends BaseDriver
$this->setPaymentHash($request->getPaymentHash()); $this->setPaymentHash($request->getPaymentHash());
try { try {
$payment = $this->gateway->payments()->details($request->query('cko-session-id')); $payment = $this->gateway->payments()->details(
$request->query('cko-session-id')
);
if ($payment->approved) { if ($payment->approved) {
return $this->processSuccessfulPayment($payment); return $this->processSuccessfulPayment($payment);

View File

@ -12,7 +12,7 @@
namespace App\PaymentDrivers\Common; namespace App\PaymentDrivers\Common;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Request; use Illuminate\Http\Request;
interface MethodInterface interface MethodInterface
{ {

View File

@ -14,7 +14,7 @@ namespace App\PaymentDrivers\GoCardless;
use App\Exceptions\PaymentFailed; use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Request; use Illuminate\Http\Request;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;
use App\Models\GatewayType; use App\Models\GatewayType;

View File

@ -14,7 +14,7 @@ namespace App\PaymentDrivers\GoCardless;
use App\Exceptions\PaymentFailed; use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Request; use Illuminate\Http\Request;
use App\Jobs\Mail\PaymentFailureMailer; use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;

View File

@ -14,7 +14,7 @@ namespace App\PaymentDrivers\GoCardless;
use App\Exceptions\PaymentFailed; use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Request; use Illuminate\Http\Request;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;
use App\Models\GatewayType; use App\Models\GatewayType;

View File

@ -13,7 +13,7 @@
namespace App\PaymentDrivers\Mollie; namespace App\PaymentDrivers\Mollie;
use App\Exceptions\PaymentFailed; use App\Exceptions\PaymentFailed;
use App\Http\Requests\Request; use Illuminate\Http\Request;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType; use App\Models\GatewayType;

View File

@ -14,7 +14,7 @@ namespace App\PaymentDrivers\Mollie;
use App\Exceptions\PaymentFailed; use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Request; use Illuminate\Http\Request;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType; use App\Models\GatewayType;
use App\Models\Payment; use App\Models\Payment;

View File

@ -14,7 +14,7 @@ namespace App\PaymentDrivers\Mollie;
use App\Exceptions\PaymentFailed; use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Request; use Illuminate\Http\Request;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType; use App\Models\GatewayType;
use App\Models\Payment; use App\Models\Payment;

View File

@ -13,7 +13,7 @@
namespace App\PaymentDrivers\Mollie; namespace App\PaymentDrivers\Mollie;
use App\Exceptions\PaymentFailed; use App\Exceptions\PaymentFailed;
use App\Http\Requests\Request; use Illuminate\Http\Request;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType; use App\Models\GatewayType;

View File

@ -93,7 +93,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
return $response->redirect(); return $response->redirect();
} }
$this->sendFailureMail($response->getData()); $this->sendFailureMail($response->getMessage());
$message = [ $message = [
'server_response' => $response->getMessage(), 'server_response' => $response->getMessage(),

View File

@ -15,7 +15,7 @@ namespace App\PaymentDrivers\Razorpay;
use App\Exceptions\PaymentFailed; use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Request; use Illuminate\Http\Request;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType; use App\Models\GatewayType;
use App\Models\Payment; use App\Models\Payment;

View File

@ -13,16 +13,20 @@
namespace App\PaymentDrivers\Square; namespace App\PaymentDrivers\Square;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use Illuminate\Http\Request;
use App\Models\ClientGatewayToken; use App\Models\ClientGatewayToken;
use App\Models\GatewayType; use App\Models\GatewayType;
use App\Models\Payment; use App\Models\Payment;
use App\Models\PaymentType; use App\Models\PaymentType;
use App\PaymentDrivers\Common\MethodInterface;
use App\PaymentDrivers\SquarePaymentDriver; use App\PaymentDrivers\SquarePaymentDriver;
use App\Utils\Traits\MakesHash; use App\Utils\Traits\MakesHash;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\View\View;
use Square\Http\ApiResponse; use Square\Http\ApiResponse;
class CreditCard class CreditCard implements MethodInterface
{ {
use MakesHash; use MakesHash;
@ -34,90 +38,27 @@ class CreditCard
$this->square_driver->init(); $this->square_driver->init();
} }
public function authorizeView($data) /**
* Authorization page for credit card.
*
* @param array $data
* @return View
*/
public function authorizeView($data): View
{ {
$data['gateway'] = $this->square_driver; $data['gateway'] = $this->square_driver;
return render('gateways.square.credit_card.authorize', $data); return render('gateways.square.credit_card.authorize', $data);
} }
public function authorizeResponse($request) /**
* Handle authorization for credit card.
*
* @param Request $request
* @return RedirectResponse
*/
public function authorizeResponse($request): RedirectResponse
{ {
/* Step one - process a $1 payment - but don't complete it*/
$payment = false;
$amount_money = new \Square\Models\Money();
$amount_money->setAmount(100); //amount in cents
$amount_money->setCurrency($this->square_driver->client->currency()->code);
$body = new \Square\Models\CreatePaymentRequest(
$request->sourceId,
Str::random(32),
$amount_money
);
$body->setAutocomplete(false);
$body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId'));
$body->setReferenceId(Str::random(16));
$api_response = $this->square_driver->square->getPaymentsApi()->createPayment($body);
if ($api_response->isSuccess()) {
$result = $api_response->getBody();
$payment = json_decode($result);
} else {
$errors = $api_response->getErrors();
return $this->processUnsuccessfulPayment($errors);
}
/* Step 3 create the card */
$card = new \Square\Models\Card();
$card->setCardholderName($this->square_driver->client->present()->name());
// $card->setBillingAddress($billing_address);
$card->setCustomerId($this->findOrCreateClient());
$card->setReferenceId(Str::random(8));
$body = new \Square\Models\CreateCardRequest(
Str::random(32),
$payment->payment->id,
$card
);
$api_response = $this->square_driver
->square
->getCardsApi()
->createCard($body);
$card = false;
if ($api_response->isSuccess()) {
$card = $api_response->getBody();
$card = json_decode($card);
} else {
$errors = $api_response->getErrors();
return $this->processUnsuccessfulPayment($errors);
}
/* Create the token in Invoice Ninja*/
$cgt = [];
$cgt['token'] = $card->card->id;
$cgt['payment_method_id'] = GatewayType::CREDIT_CARD;
$payment_meta = new \stdClass;
$payment_meta->exp_month = $card->card->exp_month;
$payment_meta->exp_year = $card->card->exp_year;
$payment_meta->brand = $card->card->card_brand;
$payment_meta->last4 = $card->card->last_4;
$payment_meta->type = GatewayType::CREDIT_CARD;
$cgt['payment_meta'] = $payment_meta;
$token = $this->square_driver->storeGatewayToken($cgt, [
'gateway_customer_reference' => $this->findOrCreateClient(),
]);
return redirect()->route('client.payment_methods.index'); return redirect()->route('client.payment_methods.index');
} }
@ -170,8 +111,9 @@ class CreditCard
$body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId')); $body->setLocationId($this->square_driver->company_gateway->getConfigField('locationId'));
$body->setReferenceId(Str::random(16)); $body->setReferenceId(Str::random(16));
if($request->has('verificationToken') && $request->input('verificationToken')) if ($request->has('verificationToken') && $request->input('verificationToken')) {
$body->setVerificationToken($request->input('verificationToken')); $body->setVerificationToken($request->input('verificationToken'));
}
if ($request->shouldUseToken()) { if ($request->shouldUseToken()) {
$body->setCustomerId($cgt->gateway_customer_reference); $body->setCustomerId($cgt->gateway_customer_reference);
@ -181,66 +123,12 @@ class CreditCard
$response = $this->square_driver->square->getPaymentsApi()->createPayment($body); $response = $this->square_driver->square->getPaymentsApi()->createPayment($body);
if ($response->isSuccess()) { if ($response->isSuccess()) {
if ($request->shouldStoreToken()) {
$this->storePaymentMethod($response);
}
return $this->processSuccessfulPayment($response); return $this->processSuccessfulPayment($response);
} }
return $this->processUnsuccessfulPayment($response); return $this->processUnsuccessfulPayment($response);
} }
private function storePaymentMethod(ApiResponse $response)
{
$payment = \json_decode($response->getBody());
$billing_address = new \Square\Models\Address();
$billing_address->setAddressLine1($this->square_driver->client->address1);
$billing_address->setAddressLine2($this->square_driver->client->address2);
$billing_address->setLocality($this->square_driver->client->city);
$billing_address->setAdministrativeDistrictLevel1($this->square_driver->client->state);
$billing_address->setPostalCode($this->square_driver->client->postal_code);
$billing_address->setCountry($this->square_driver->client->country->iso_3166_2);
$card = new \Square\Models\Card();
$card->setCardholderName($this->square_driver->client->present()->first_name(). " " .$this->square_driver->client->present()->last_name());
$card->setCustomerId($this->findOrCreateClient());
$card->setReferenceId(Str::random(8));
$card->setBillingAddress($billing_address);
$body = new \Square\Models\CreateCardRequest(Str::random(32), $payment->payment->id, $card);
/** @var ApiResponse */
$api_response = $this->square_driver
->square
->getCardsApi()
->createCard($body);
if (!$api_response->isSuccess()) {
return $this->processUnsuccessfulPayment($api_response);
}
$card = \json_decode($api_response->getBody());
$cgt = [];
$cgt['token'] = $card->card->id;
$cgt['payment_method_id'] = GatewayType::CREDIT_CARD;
$payment_meta = new \stdClass;
$payment_meta->exp_month = $card->card->exp_month;
$payment_meta->exp_year = $card->card->exp_year;
$payment_meta->brand = $card->card->card_brand;
$payment_meta->last4 = $card->card->last_4;
$payment_meta->type = GatewayType::CREDIT_CARD;
$cgt['payment_meta'] = $payment_meta;
$this->square_driver->storeGatewayToken($cgt, [
'gateway_customer_reference' => $this->findOrCreateClient(),
]);
}
private function processSuccessfulPayment(ApiResponse $response) private function processSuccessfulPayment(ApiResponse $response)
{ {
$body = json_decode($response->getBody()); $body = json_decode($response->getBody());
@ -301,9 +189,9 @@ class CreditCard
$customers = $api_response->getBody(); $customers = $api_response->getBody();
$customers = json_decode($customers); $customers = json_decode($customers);
if(count(array($api_response->getBody(),1)) == 0) if (count([$api_response->getBody(),1]) == 0) {
$customers = false; $customers = false;
}
} else { } else {
$errors = $api_response->getErrors(); $errors = $api_response->getErrors();
} }

View File

@ -141,8 +141,8 @@ class BECS
$method = $this->stripe->getStripePaymentMethod($intent->payment_method); $method = $this->stripe->getStripePaymentMethod($intent->payment_method);
$payment_meta = new \stdClass; $payment_meta = new \stdClass;
$payment_meta->brand = (string) \sprintf('%s (%s)', $method->sepa_debit->bank_code, ctrans('texts.becs')); $payment_meta->brand = (string) \sprintf('%s (%s)', $method->au_becs_debit->bank_code, ctrans('texts.becs'));
$payment_meta->last4 = (string) $method->sepa_debit->last4; $payment_meta->last4 = (string) $method->au_becs_debit->last4;
$payment_meta->state = 'authorized'; $payment_meta->state = 'authorized';
$payment_meta->type = GatewayType::BECS; $payment_meta->type = GatewayType::BECS;

View File

@ -208,7 +208,7 @@ class StripePaymentDriver extends BaseDriver
&& $this->client->currency() && $this->client->currency()
&& ($this->client->currency()->code == 'AUD') && ($this->client->currency()->code == 'AUD')
&& isset($this->client->country) && isset($this->client->country)
&& in_array($this->client->country->iso_3166_3, ["AUS", "DEU"])) && in_array($this->client->country->iso_3166_3, ['AUS']))
$types[] = GatewayType::BECS; $types[] = GatewayType::BECS;
if ($this->client if ($this->client

View File

@ -185,7 +185,7 @@ class WePayPaymentDriver extends BaseDriver
} }
if (! isset($objectType)) { if (! isset($objectType)) {
throw new Exception('Could not find object id parameter'); throw new \Exception('Could not find object id parameter');
} }
if ($objectType == 'credit_card') { if ($objectType == 'credit_card') {

View File

@ -113,25 +113,26 @@ class BaseRepository
* @param $action * @param $action
* *
* @return int * @return int
* @deprecated - this doesn't appear to be used anywhere?
*/ */
public function bulk($ids, $action) // public function bulk($ids, $action)
{ // {
if (! $ids) { // if (! $ids) {
return 0; // return 0;
} // }
$ids = $this->transformKeys($ids); // $ids = $this->transformKeys($ids);
$entities = $this->findByPublicIdsWithTrashed($ids); // $entities = $this->findByPublicIdsWithTrashed($ids);
foreach ($entities as $entity) { // foreach ($entities as $entity) {
if (auth()->user()->can('edit', $entity)) { // if (auth()->user()->can('edit', $entity)) {
$this->$action($entity); // $this->$action($entity);
} // }
} // }
return count($entities); // return count($entities);
} // }
/* Returns an invoice if defined as a key in the $resource array*/ /* Returns an invoice if defined as a key in the $resource array*/
public function getInvitation($invitation, $resource) public function getInvitation($invitation, $resource)

View File

@ -65,7 +65,7 @@ class ClientRepository extends BaseRepository
$client->fill($data); $client->fill($data);
$client->save(); $client->save();
if (!isset($client->number) || empty($client->number)) { if (!isset($client->number) || empty($client->number) || strlen($client->number) == 0) {
$client->number = $this->getNextClientNumber($client); $client->number = $this->getNextClientNumber($client);
} }

View File

@ -134,9 +134,9 @@ class InvoiceService
* *
* @return InvoiceService Parent class object * @return InvoiceService Parent class object
*/ */
public function updateBalance($balance_adjustment) public function updateBalance($balance_adjustment, bool $is_draft = false)
{ {
$this->invoice = (new UpdateBalance($this->invoice, $balance_adjustment))->run(); $this->invoice = (new UpdateBalance($this->invoice, $balance_adjustment, $is_draft))->run();
if ((int)$this->invoice->balance == 0) { if ((int)$this->invoice->balance == 0) {
$this->invoice->next_send_date = null; $this->invoice->next_send_date = null;
@ -339,6 +339,10 @@ class InvoiceService
public function removeUnpaidGatewayFees() public function removeUnpaidGatewayFees()
{ {
//return early if type three does not exist.
if(!collect($this->invoice->line_items)->contains('type_id', 3))
return $this;
$this->invoice->line_items = collect($this->invoice->line_items) $this->invoice->line_items = collect($this->invoice->line_items)
->reject(function ($item) { ->reject(function ($item) {
return $item->type_id == '3'; return $item->type_id == '3';

View File

@ -33,7 +33,7 @@ class MarkSent extends AbstractService
{ {
/* Return immediately if status is not draft */ /* Return immediately if status is not draft */
if ($this->invoice->fresh()->status_id != Invoice::STATUS_DRAFT) { if ($this->invoice && $this->invoice->fresh()->status_id != Invoice::STATUS_DRAFT) {
return $this->invoice; return $this->invoice;
} }
@ -47,7 +47,7 @@ class MarkSent extends AbstractService
->service() ->service()
->applyNumber() ->applyNumber()
->setDueDate() ->setDueDate()
->updateBalance($this->invoice->amount) ->updateBalance($this->invoice->amount, true)
->deletePdf() ->deletePdf()
->setReminder() ->setReminder()
->save(); ->save();

View File

@ -20,10 +20,13 @@ class UpdateBalance extends AbstractService
public $balance_adjustment; public $balance_adjustment;
public function __construct($invoice, $balance_adjustment) private $is_draft;
public function __construct($invoice, $balance_adjustment, bool $is_draft)
{ {
$this->invoice = $invoice; $this->invoice = $invoice;
$this->balance_adjustment = $balance_adjustment; $this->balance_adjustment = $balance_adjustment;
$this->is_draft = $is_draft;
} }
public function run() public function run()
@ -34,7 +37,7 @@ class UpdateBalance extends AbstractService
$this->invoice->balance += floatval($this->balance_adjustment); $this->invoice->balance += floatval($this->balance_adjustment);
if ($this->invoice->balance == 0) { if ($this->invoice->balance == 0 && !$this->is_draft) {
$this->invoice->status_id = Invoice::STATUS_PAID; $this->invoice->status_id = Invoice::STATUS_PAID;
} }

View File

@ -115,15 +115,22 @@ class DeletePayment
->updatePaidToDate($net_deletable * -1) ->updatePaidToDate($net_deletable * -1)
->save(); ->save();
// $paymentable_invoice->client
// ->service()
// ->updatePaidToDate($net_deletable * -1)
// ->save();
} }
}); });
} }
else {
/* If there are no invoices - then we need to still adjust the total client->paid_to_date amount*/
$this->payment
->client
->service()
->updatePaidToDate(($this->payment->amount - $this->payment->applied)*-1)
->save();
}
return $this; return $this;
} }

View File

@ -267,9 +267,17 @@ class RefundPayment
// $this->credit_note->ledger()->updateCreditBalance($adjustment_amount, $ledger_string); // $this->credit_note->ledger()->updateCreditBalance($adjustment_amount, $ledger_string);
$client = $this->payment->client->fresh(); $client = $this->payment->client->fresh();
//$client->service()->updatePaidToDate(-1 * $this->total_refund)->save();
$client->service()->updatePaidToDate(-1 * $refunded_invoice['amount'])->save(); $client->service()->updatePaidToDate(-1 * $refunded_invoice['amount'])->save();
} }
else{
//if we are refunding and no payments have been tagged, then we need to decrement the client->paid_to_date by the total refund amount.
$client = $this->payment->client->fresh();
$client->service()->updatePaidToDate(-1 * $this->total_refund)->save();
}
return $this; return $this;
} }

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.3.27', 'app_version' => '5.3.29',
'app_tag' => '5.3.27', 'app_tag' => '5.3.29',
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''), 'api_secret' => env('API_SECRET', ''),

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"/js/app.js": "/js/app.js?id=019831a9b0c0aff43c7f", "/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
"/css/app.css": "/css/app.css?id=df1ea83ea621533ac837", "/css/app.css": "/css/app.css?id=f7f7b35aa3f417a3eca3",
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4", "/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1", "/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7", "/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",
@ -17,7 +17,7 @@
"/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=73b66e88e2daabcd6549", "/js/clients/payments/mollie-credit-card.js": "/js/clients/payments/mollie-credit-card.js?id=73b66e88e2daabcd6549",
"/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=c2b5f7831e1a46dd5fb2", "/js/clients/payments/paytrace-credit-card.js": "/js/clients/payments/paytrace-credit-card.js?id=c2b5f7831e1a46dd5fb2",
"/js/clients/payments/razorpay-aio.js": "/js/clients/payments/razorpay-aio.js?id=817ab3b2b94ee37b14eb", "/js/clients/payments/razorpay-aio.js": "/js/clients/payments/razorpay-aio.js?id=817ab3b2b94ee37b14eb",
"/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=070c86b293b532c5a56c", "/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=13ea3ff41d9417ef0140",
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=81c2623fc1e5769b51c7", "/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=81c2623fc1e5769b51c7",
"/js/clients/payments/stripe-acss.js": "/js/clients/payments/stripe-acss.js?id=4a85142c085723991d28", "/js/clients/payments/stripe-acss.js": "/js/clients/payments/stripe-acss.js?id=4a85142c085723991d28",
"/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=665ddf663500767f1a17", "/js/clients/payments/stripe-alipay.js": "/js/clients/payments/stripe-alipay.js?id=665ddf663500767f1a17",
@ -36,6 +36,6 @@
"/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=5c35d28cf0a3286e7c45", "/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=5c35d28cf0a3286e7c45",
"/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=2a99d83305ba87bfa6cc", "/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=2a99d83305ba87bfa6cc",
"/js/clients/statements/view.js": "/js/clients/statements/view.js?id=ca3ec4cea0de824f3a36", "/js/clients/statements/view.js": "/js/clients/statements/view.js?id=ca3ec4cea0de824f3a36",
"/js/setup/setup.js": "/js/setup/setup.js?id=03ea88a737e59eb2bd5a", "/js/setup/setup.js": "/js/setup/setup.js?id=8d454e7090f119552a6c",
"/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ad" "/css/card-js.min.css": "/css/card-js.min.css?id=62afeb675235451543ad"
} }

View File

@ -43,57 +43,39 @@ class SquareCreditCard {
} }
} }
// ,
// function(err,verification) {
// if (err == null) {
// console.log("no error");
// console.log(verification);
// verificationToken = verificationResults.token;
// }
// console.log(err);
// die("verify buyer");
// }
async completePaymentWithoutToken(e) { async completePaymentWithoutToken(e) {
document.getElementById('errors').hidden = true; document.getElementById('errors').hidden = true;
e.target.parentElement.disabled = true; e.target.parentElement.disabled = true;
let result = await this.card.tokenize(); let result = await this.card.tokenize();
console.log("square token = " + result.token);
/* SCA */ /* SCA */
let verificationToken; let verificationToken;
try { try {
const verificationDetails = { const verificationDetails = {
amount: document.querySelector('meta[name=amount]').content, amount: document.querySelector('meta[name=amount]').content,
billingContact: JSON.parse(document.querySelector('meta[name=square_contact]').content), billingContact: JSON.parse(
currencyCode: document.querySelector('meta[name=currencyCode]').content, document.querySelector('meta[name=square_contact]').content
intent: 'CHARGE' ),
}; currencyCode: document.querySelector('meta[name=currencyCode]')
.content,
console.log(verificationDetails); intent: 'CHARGE',
};
const verificationResults = await this.payments.verifyBuyer( const verificationResults = await this.payments.verifyBuyer(
result.token, result.token,
verificationDetails verificationDetails
); );
verificationToken = verificationResults.token; verificationToken = verificationResults.token;
} } catch (typeError) {
catch(typeError){ e.target.parentElement.disabled = true
console.log(typeError);
} }
console.debug('Verification Token:', verificationToken); document.querySelector(
'input[name="verificationToken"]'
document.querySelector('input[name="verificationToken"]').value = ).value = verificationToken;
verificationToken;
if (result.status === 'OK') { if (result.status === 'OK') {
document.getElementById('sourceId').value = result.token; document.getElementById('sourceId').value = result.token;
@ -125,23 +107,20 @@ class SquareCreditCard {
/* SCA */ /* SCA */
async verifyBuyer(token) { async verifyBuyer(token) {
console.log("in verify buyer");
const verificationDetails = { const verificationDetails = {
amount: document.querySelector('meta[name=amount]').content, amount: document.querySelector('meta[name=amount]').content,
billingContact: document.querySelector('meta[name=square_contact]').content, billingContact: document.querySelector('meta[name=square_contact]')
currencyCode: document.querySelector('meta[name=currencyCode]').content, .content,
intent: 'CHARGE' currencyCode: document.querySelector('meta[name=currencyCode]')
.content,
intent: 'CHARGE',
}; };
const verificationResults = await this.payments.verifyBuyer( const verificationResults = await this.payments.verifyBuyer(
token, token,
verificationDetails verificationDetails
); );
console.log(" verification toke = " + verificationResults.token);
return verificationResults.token; return verificationResults.token;
} }

View File

@ -1400,7 +1400,7 @@ $LANG = array(
'more_options' => 'More options', 'more_options' => 'More options',
'credit_card' => 'Credit Card', 'credit_card' => 'Credit Card',
'bank_transfer' => 'Bank Transfer', 'bank_transfer' => 'Bank Transfer',
'no_transaction_reference' => 'We did not recieve a payment transaction reference from the gateway.', 'no_transaction_reference' => 'We did not receive a payment transaction reference from the gateway.',
'use_bank_on_file' => 'Use Bank on File', 'use_bank_on_file' => 'Use Bank on File',
'auto_bill_email_message' => 'This invoice will automatically be billed to the payment method on file on the due date.', 'auto_bill_email_message' => 'This invoice will automatically be billed to the payment method on file on the due date.',
'bitcoin' => 'Bitcoin', 'bitcoin' => 'Bitcoin',
@ -4328,13 +4328,14 @@ $LANG = array(
'giropay_law' => 'By entering your Customer information (such as name, sort code and account number) you (the Customer) agree that this information is given voluntarily.', 'giropay_law' => 'By entering your Customer information (such as name, sort code and account number) you (the Customer) agree that this information is given voluntarily.',
'eps' => 'EPS', 'eps' => 'EPS',
'becs' => 'BECS Direct Debit', 'becs' => 'BECS Direct Debit',
'becs_mandate' => 'By providing your bank account details, you agree to this <a href="https://stripe.com/au-becs-dd-service-agreement/legal">Direct Debit Request and the Direct Debit Request service agreement</a>, and authorise Stripe Payments Australia Pty Ltd ACN 160 180 343 Direct Debit User ID number 507156 (“Stripe”) to debit your account through the Bulk Electronic Clearing System (BECS) on behalf of :company (the “Merchant”) for any amounts separately communicated to you by the Merchant. You certify that you are either an account holder or an authorised signatory on the account listed above.', 'becs_mandate' => 'By providing your bank account details, you agree to this <a class="underline" href="https://stripe.com/au-becs-dd-service-agreement/legal">Direct Debit Request and the Direct Debit Request service agreement</a>, and authorise Stripe Payments Australia Pty Ltd ACN 160 180 343 Direct Debit User ID number 507156 (“Stripe”) to debit your account through the Bulk Electronic Clearing System (BECS) on behalf of :company (the “Merchant”) for any amounts separately communicated to you by the Merchant. You certify that you are either an account holder or an authorised signatory on the account listed above.',
'you_need_to_accept_the_terms_before_proceeding' => 'You need to accept the terms before proceeding.', 'you_need_to_accept_the_terms_before_proceeding' => 'You need to accept the terms before proceeding.',
'direct_debit' => 'Direct Debit', 'direct_debit' => 'Direct Debit',
'clone_to_expense' => 'Clone to expense', 'clone_to_expense' => 'Clone to expense',
'checkout' => 'Checkout', 'checkout' => 'Checkout',
'acss' => 'Pre-authorized debit payments', 'acss' => 'Pre-authorized debit payments',
'invalid_amount' => 'Invalid amount. Number/Decimal values only.' 'invalid_amount' => 'Invalid amount. Number/Decimal values only.',
'client_payment_failure_body' => 'Payment for Invoice :invoice for amount :amount failed.',
); );
return $LANG; return $LANG;

View File

@ -1,37 +1,7 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title' @extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')])
=> ctrans('texts.payment_type_credit_card')])
@section('gateway_head')
<meta name="square-appId" content="{{ $gateway->company_gateway->getConfigField('applicationId') }}">
<meta name="square-locationId" content="{{ $gateway->company_gateway->getConfigField('locationId') }}">
<meta name="square-authorize" content="true">
@endsection
@section('gateway_content') @section('gateway_content')
<form action="{{ route('client.payment_methods.store', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}"
method="post" id="server_response">
@csrf
<input type="text" name="sourceId" id="sourceId" hidden>
</form>
<div class="alert alert-failure mb-4" hidden id="errors"></div>
@component('portal.ninja2020.components.general.card-element-single') @component('portal.ninja2020.components.general.card-element-single')
<div id="card-container"></div> {{ __('texts.payment_method_cannot_be_preauthorized') }}
<div id="payment-status-container"></div>
@endcomponent
@component('portal.ninja2020.gateways.includes.pay_now', ['id' => 'authorize-card'])
{{ ctrans('texts.add_payment_method') }}
@endcomponent @endcomponent
@endsection @endsection
@section('gateway_footer')
@if ($gateway->company_gateway->getConfigField('testMode'))
<script type="text/javascript" src="https://sandbox.web.squarecdn.com/v1/square.js"></script>
@else
<script type="text/javascript" src="https://web.squarecdn.com/v1/square.js"></script>
@endif
<script src="{{ asset('js/clients/payments/square-credit-card.js') }}"></script>
@endsection

View File

@ -12,17 +12,20 @@
<label for="becs-name"> <label for="becs-name">
<input class="input w-full" id="becs-name" type="text" placeholder="{{ ctrans('texts.bank_account_holder') }}" required> <input class="input w-full" id="becs-name" type="text" placeholder="{{ ctrans('texts.bank_account_holder') }}" required>
</label> </label>
<label for="becs-email" >
<label for="becs-email">
<input class="input w-full" id="becs-email-address" type="email" placeholder="{{ ctrans('texts.email') }}" required> <input class="input w-full" id="becs-email-address" type="email" placeholder="{{ ctrans('texts.email') }}" required>
</label> </label>
<label> <label>
<div class="border p-4 rounded"> <div class="border p-4 rounded mt-2">
<div id="becs-iban"></div> <div id="becs-iban"></div>
</div> </div>
</label> </label>
<div id="mandate-acceptance">
<div id="mandate-acceptance" class="mt-2">
<input type="checkbox" id="becs-mandate-acceptance" class="input mr-4"> <input type="checkbox" id="becs-mandate-acceptance" class="input mr-4">
<label for="becs-mandate-acceptance">{{ctrans('texts.becs_mandat')}}</label> <label for="becs-mandate-acceptance">{!! ctrans('texts.becs_mandate') !!}</label>
</div> </div>
</form> </form>
@endcomponent @endcomponent

View File

@ -0,0 +1,103 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace Tests\Feature\Payments;
use App\Models\Payment;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\WithoutEvents;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Validation\ValidationException;
use Tests\MockAccountData;
use Tests\MockUnitData;
use Tests\TestCase;
/**
* @test
*/
class UnappliedPaymentDeleteTest extends TestCase
{
use MakesHash;
use DatabaseTransactions;
use MockUnitData;
use WithoutEvents;
public function setUp() :void
{
parent::setUp();
$this->faker = \Faker\Factory::create();
$this->makeTestData();
$this->withoutExceptionHandling();
$this->withoutMiddleware(
ThrottleRequests::class
);
}
public function testUnappliedPaymentDelete()
{
$data = [
'amount' => 1000,
'client_id' => $this->client->hashed_id,
'invoices' => [
],
'date' => '2020/12/12',
];
$response = null;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/payments', $data);
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
$this->assertNotNull($message);
}
if ($response){
$arr = $response->json();
$response->assertStatus(200);
$payment_id = $arr['data']['id'];
$payment = Payment::with('client')->find($this->decodePrimaryKey($payment_id));
$this->assertEquals(1000, $payment->amount);
$this->assertEquals(1000, $payment->client->paid_to_date);
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->delete('/api/v1/payments/'. $payment_id);
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
$this->assertNotNull($message);
}
$response->assertStatus(200);
$this->assertEquals(0, $this->client->fresh()->paid_to_date);
}
}
}

View File

@ -0,0 +1,110 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace Tests\Feature\Payments;
use App\Models\Payment;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Foundation\Testing\WithoutEvents;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Validation\ValidationException;
use Tests\MockAccountData;
use Tests\MockUnitData;
use Tests\TestCase;
/**
* @test
*/
class UnappliedPaymentRefundTest extends TestCase
{
use MakesHash;
use DatabaseTransactions;
use MockUnitData;
use WithoutEvents;
public function setUp() :void
{
parent::setUp();
$this->faker = \Faker\Factory::create();
$this->makeTestData();
$this->withoutExceptionHandling();
$this->withoutMiddleware(
ThrottleRequests::class
);
}
public function testUnappliedPaymentRefund()
{
$data = [
'amount' => 1000,
'client_id' => $this->client->hashed_id,
'invoices' => [
],
'date' => '2020/12/12',
];
$response = null;
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/payments', $data);
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
$this->assertNotNull($message);
}
if ($response){
$arr = $response->json();
$response->assertStatus(200);
$this->assertEquals(1000, $this->client->fresh()->paid_to_date);
$payment_id = $arr['data']['id'];
$this->assertEquals(1000, $arr['data']['amount']);
$payment = Payment::whereId($this->decodePrimaryKey($payment_id))->first();
$data = [
'id' => $this->encodePrimaryKey($payment->id),
'amount' => 500,
'date' => '2020/12/12',
];
try {
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->post('/api/v1/payments/refund', $data);
} catch (ValidationException $e) {
$message = json_decode($e->validator->getMessageBag(), 1);
$this->assertNotNull($message);
}
$response->assertStatus(200);
$this->assertEquals(500, $this->client->fresh()->paid_to_date);
}
}
}

View File

@ -32,6 +32,10 @@ class DownloadHistoricalInvoiceTest extends TestCase
parent::setUp(); parent::setUp();
$this->makeTestData(); $this->makeTestData();
if (config('ninja.testvars.travis') !== false) {
$this->markTestSkipped('Skip test for Travis');
}
} }
private function mockActivity() private function mockActivity()

View File

@ -10,6 +10,7 @@
*/ */
namespace Tests\Unit; namespace Tests\Unit;
use App\Factory\InvoiceItemFactory;
use App\Utils\Traits\UserSessionAttributes; use App\Utils\Traits\UserSessionAttributes;
use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\Session;
use Tests\TestCase; use Tests\TestCase;
@ -19,13 +20,10 @@ use Tests\TestCase;
*/ */
class CollectionMergingTest extends TestCase class CollectionMergingTest extends TestCase
{ {
use UserSessionAttributes;
public function setUp() :void public function setUp() :void
{ {
parent::setUp(); parent::setUp();
Session::start();
} }
public function testUniqueValues() public function testUniqueValues()
@ -62,4 +60,21 @@ class CollectionMergingTest extends TestCase
$intersect = $collection->intersectByKeys($collection->flatten(1)->unique()); $intersect = $collection->intersectByKeys($collection->flatten(1)->unique());
$this->assertEquals(11, $intersect->count()); $this->assertEquals(11, $intersect->count());
} }
public function testExistenceInCollection()
{
$items = InvoiceItemFactory::generate(5);
$this->assertFalse(collect($items)->contains('type_id', "3"));
$this->assertFalse(collect($items)->contains('type_id', 3));
$item = InvoiceItemFactory::create();
$item->type_id = "3";
$items[] = $item;
$this->assertTrue(collect($items)->contains('type_id', "3"));
$this->assertTrue(collect($items)->contains('type_id', 3));
}
} }