1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-08 20:22:42 +01:00

Merge pull request #7159 from turbo124/v5-develop

v5.3.51
This commit is contained in:
David Bomba 2022-01-28 07:34:44 +11:00 committed by GitHub
commit 2fe5d6feba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 506 additions and 84 deletions

View File

@ -1 +1 @@
5.3.49
5.3.51

View File

@ -76,7 +76,24 @@ class GmailTransport extends Transport
}
$this->gmail->send();
/**
* Google is very strict with their
* sending limits, if we hit 429s, sleep and
* retry again later.
*/
try{
$this->gmail->send();
}
catch(\Google\Service\Exception $e)
{
nlog("gmail exception");
nlog($e->getErrors());
sleep(5);
$this->gmail->send();
}
$this->sendPerformed($message);

View File

@ -152,6 +152,7 @@ class ContactLoginController extends Controller
public function logout()
{
Auth::guard('contact')->logout();
request()->session()->invalidate();
return redirect('/client/login');
}

View File

@ -98,6 +98,7 @@ class InvitationController extends Controller
$client_contact->email = Str::random(15) . "@example.com"; $client_contact->save();
if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) {
request()->session()->invalidate();
auth()->guard('contact')->loginUsingId($client_contact->id, true);
} elseif ((bool) $invitation->contact->client->getSetting('enable_client_portal_password') !== false) {
@ -106,6 +107,7 @@ class InvitationController extends Controller
} else {
nlog("else - default - login contact");
request()->session()->invalidate();
auth()->guard('contact')->loginUsingId($client_contact->id, true);
}

View File

@ -90,12 +90,15 @@ class PaymentController extends Controller
public function response(PaymentResponseRequest $request)
{
$gateway = CompanyGateway::findOrFail($request->input('company_gateway_id'));
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->payment_hash])->first();
$payment_hash = PaymentHash::where('hash', $request->payment_hash)->first();
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice ? $invoice->client : auth()->user()->client;
return $gateway
->driver(auth()->user()->client)
// ->driver(auth()->user()->client)
->driver($client)
->setPaymentMethod($request->input('payment_method_id'))
->setPaymentHash($payment_hash)
->checkRequirements()

View File

@ -401,7 +401,7 @@ class DesignController extends BaseController
}
$design->design = $d;
$design->save();
// $design->save();
/*
This is required as the base template does not know to inject the table elements

View File

@ -18,6 +18,7 @@ use App\Factory\CloneQuoteToInvoiceFactory;
use App\Factory\QuoteFactory;
use App\Filters\QuoteFilters;
use App\Http\Requests\Quote\ActionQuoteRequest;
use App\Http\Requests\Quote\BulkActionQuoteRequest;
use App\Http\Requests\Quote\CreateQuoteRequest;
use App\Http\Requests\Quote\DestroyQuoteRequest;
use App\Http\Requests\Quote\EditQuoteRequest;
@ -510,7 +511,7 @@ class QuoteController extends BaseController
* ),
* )
*/
public function bulk()
public function bulk(BulkActionQuoteRequest $request)
{
$action = request()->input('action');

View File

@ -454,6 +454,7 @@ class BillingPortalPurchase extends Component
$contact = ClientContact::query()
->where('email', $this->email)
->where('company_id', $this->subscription->company_id)
->first();
$mailer = new NinjaMailerObject();

View File

@ -37,6 +37,7 @@ class ContactKeyLogin
{
if (Auth::guard('contact')->check()) {
Auth::guard('contact')->logout();
$request->session()->invalidate();
}
if ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) {

View File

@ -35,7 +35,7 @@ class SessionDomains
if (strpos($domain_name, 'invoicing.co') !== false)
{
config(['session.domain' => '.invoicing.co']);
// config(['session.domain' => '.invoicing.co']);
}
else{

View File

@ -34,10 +34,10 @@ class UpdateExpenseRequest extends Request
public function rules()
{
/* Ensure we have a client name, and that all emails are unique*/
$rules = [];
// $rules['country_id'] = 'integer|nullable';
$rules['country_id'] = 'integer|nullable';
$rules['contacts.*.email'] = 'nullable|distinct';
// $rules['contacts.*.email'] = 'nullable|distinct';
if (isset($this->number)) {
$rules['number'] = Rule::unique('expenses')->where('company_id', auth()->user()->company()->id)->ignore($this->expense->id);

View File

@ -0,0 +1,41 @@
<?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://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\Quote;
use App\Http\Requests\Request;
use App\Http\ValidationRules\Quote\ConvertableQuoteRule;
class BulkActionQuoteRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize() : bool
{
return true;
}
public function rules()
{
$input = $this->all();
$rules = [];
if($input['action'] == 'convert_to_invoice')
$rules['action'] = [new ConvertableQuoteRule()];
return $rules;
}
}

View File

@ -74,6 +74,7 @@ class PaymentAppliedValidAmount implements Rule
}
}
return $payment_amounts >= $invoice_amounts;
// nlog("{round($payment_amounts,3)} >= {round($invoice_amounts,3)}");
return round($payment_amounts,3) >= round($invoice_amounts,3);
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* Quote Ninja (https://quoteninja.com).
*
* @link https://github.com/quoteninja/quoteninja source repository
*
* @copyright Copyright (c) 2021. Quote Ninja LLC (https://quoteninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\ValidationRules\Quote;
use App\Models\Quote;
use App\Utils\Traits\MakesHash;
use Illuminate\Contracts\Validation\Rule;
/**
* Class ConvertableQuoteRule.
*/
class ConvertableQuoteRule implements Rule
{
use MakesHash;
public function __construct()
{
}
/**
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
return $this->checkQuoteIsConvertable(); //if it exists, return false!
}
/**
* @return string
*/
public function message()
{
return ctrans('texts.quote_has_expired');
}
/**
* @return bool
*/
private function checkQuoteIsConvertable() : bool
{
$ids = request()->input('ids');
$quotes = Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
foreach($quotes as $quote){
if(!$quote->service()->isConvertable())
return false;
}
return true;
}
}

View File

@ -45,7 +45,7 @@ class InvoiceTransformer extends BaseTransformer {
'client_id' => $this->getClient( $this->getString( $invoice_data, 'client.name' ), $this->getString( $invoice_data, 'client.email' ) ),
'discount' => $this->getFloat( $invoice_data, 'invoice.discount' ),
'po_number' => $this->getString( $invoice_data, 'invoice.po_number' ),
'date' => isset( $invoice_data['invoice.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['invoice.date'] ) ) : null,
'date' => isset( $invoice_data['invoice.date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['invoice.date'] ) ) : now()->format('Y-m-d'),
'due_date' => isset( $invoice_data['invoice.due_date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['invoice.due_date'] ) ) : null,
'terms' => $this->getString( $invoice_data, 'invoice.terms' ),
'public_notes' => $this->getString( $invoice_data, 'invoice.public_notes' ),
@ -72,7 +72,7 @@ class InvoiceTransformer extends BaseTransformer {
'status_id' => $invoiceStatusMap[ $status =
strtolower( $this->getString( $invoice_data, 'invoice.status' ) ) ] ??
Invoice::STATUS_SENT,
'viewed' => $status === 'viewed',
// 'viewed' => $status === 'viewed',
'archived' => $status === 'archived',
];

View File

@ -35,7 +35,8 @@ class InvoiceTransformer extends BaseTransformer {
'company_id' => $this->maps['company']->id,
'client_id' => $this->getClient( $customer_name = $this->getString( $invoice_data, 'Customer' ), null ),
'number' => $invoice_number = $this->getString( $invoice_data, 'Invoice Number' ),
'date' => isset( $invoice_data['Invoice Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Transaction Date'] ) ) : null,
'date' => date( 'Y-m-d', strtotime( $invoice_data['Transaction Date'] ) ) ?: now()->format('Y-m-d'), //27-01-2022
// 'date' => isset( $invoice_data['Invoice Date'] ) ? date( 'Y-m-d', strtotime( $invoice_data['Transaction Date'] ) ) : null,
'currency_id' => $this->getCurrencyByCode( $invoice_data, 'Currency' ),
'status_id' => Invoice::STATUS_SENT,
];

View File

@ -1381,7 +1381,13 @@ class CompanyImport implements ShouldQueue
$new_obj->company_id = $this->company->id;
$new_obj->fill($obj_array);
$new_obj->save(['timestamps' => false]);
$new_obj->number = $this->getNextRecurringExpenseNumber($client = Client::find($obj_array['client_id']), $new_obj);
$new_obj->number = $this->getNextRecurringExpenseNumber($new_obj);
}
elseif($class == 'App\Models\CompanyLedger'){
$new_obj = $class::firstOrNew(
[$match_key => $obj->{$match_key}, 'company_id' => $this->company->id],
$obj_array,
);
}
else{
$new_obj = $class::withTrashed()->firstOrNew(

View File

@ -211,15 +211,15 @@ class NinjaMailerJob implements ShouldQueue
}
//17-01-2022 - ensure we have a token otherwise we fail gracefully to default sending engine
if(strlen($user->oauth_user_token) == 0){
$this->nmo->settings->email_sending_method = 'default';
return $this->setMailDriver();
}
// if(strlen($user->oauth_user_token) == 0){
// $this->nmo->settings->email_sending_method = 'default';
// return $this->setMailDriver();
// }
$google->getClient()->setAccessToken(json_encode($user->oauth_user_token));
//need to slow down gmail requests otherwise we hit 429's
sleep(rand(1,3));
sleep(rand(2,6));
}
catch(\Exception $e) {
$this->logMailError('Gmail Token Invalid', $this->company->clients()->first());

View File

@ -97,7 +97,7 @@ class Gateway extends StaticModel
];
case 15:
return [
GatewayType::PAYPAL => ['refund' => true, 'token_billing' => false]
GatewayType::PAYPAL => ['refund' => false, 'token_billing' => false]
]; //Paypal
break;
case 20:
@ -140,15 +140,15 @@ class Gateway extends StaticModel
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable','charge.succeeded']],
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']], //Stripe
GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::PRZELEWY24 => ['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::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::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded']],
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded','payment_intent.succeeded']], //Stripe
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']],
];
break;
case 57:

View File

@ -13,6 +13,7 @@ namespace App\Models;
use App\Events\Payment\PaymentWasRefunded;
use App\Events\Payment\PaymentWasVoided;
use App\Models\GatewayType;
use App\Services\Ledger\LedgerService;
use App\Services\Payment\PaymentService;
use App\Utils\Ninja;
@ -148,6 +149,11 @@ class Payment extends BaseModel
return $this->belongsTo(PaymentType::class);
}
public function gateway_type()
{
return $this->belongsTo(GatewayType::class);
}
public function paymentables()
{
return $this->hasMany(Paymentable::class);

View File

@ -156,7 +156,8 @@ class AuthorizePaymentMethod
$paymentOne = new PaymentType();
$paymentOne->setOpaqueData($op);
$contact = $this->authorize->client->primary_contact()->first();
$contact = $this->authorize->client->primary_contact()->first() ?: $this->authorize->client->contacts()->first();
$billto = false;
if ($contact) {

View File

@ -418,12 +418,15 @@ class BaseDriver extends AbstractPaymentDriver
throw new PaymentFailed($error, $e->getCode());
}
public function sendFailureMail($error = '')
public function sendFailureMail($error)
{
if (!is_null($this->payment_hash)) {
$this->unWindGatewayFees($this->payment_hash);
}
if(!$error)
$error = '';
PaymentFailedMailer::dispatch(
$this->payment_hash,

View File

@ -143,6 +143,10 @@ class ACSS
'payment_method_types' => ['acss_debit'],
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::ACSS,
],
'payment_method_options' => [
'acss_debit' => [
'mandate_options' => [
@ -150,7 +154,6 @@ class ACSS
'interval_description' => 'when any invoice becomes due',
'transaction_type' => 'personal' // TODO: check if is company or personal https://stripe.com/docs/payments/acss-debit
],
'currency' => $this->stripe->client->currency()->code,
]
]
], $this->stripe->stripe_connect_auth);

View File

@ -48,6 +48,10 @@ class ApplePay
$data['intent'] = \Stripe\PaymentIntent::create([
'amount' => $data['stripe_amount'],
'currency' => $this->stripe_driver->client->getCurrencyCode(),
'metadata' => [
'payment_hash' => $this->stripe_driver->payment_hash->hash,
'gateway_type_id' => GatewayType::APPLE_PAY,
],
], $this->stripe_driver->stripe_connect_auth);
$this->stripe_driver->payment_hash->data = array_merge((array) $this->stripe_driver->payment_hash->data, ['stripe_amount' => $data['stripe_amount']]);

View File

@ -56,7 +56,10 @@ class BECS
'setup_future_usage' => 'off_session',
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::BECS,
],
], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret;

View File

@ -52,6 +52,10 @@ class Bancontact
'payment_method_types' => ['bancontact'],
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::BANCONTACT,
],
], $this->stripe->stripe_connect_auth);

View File

@ -71,6 +71,10 @@ class BrowserPay implements MethodInterface
'currency' => $this->stripe->client->getCurrencyCode(),
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::APPLE_PAY,
],
];
$data['gateway'] = $this->stripe;

View File

@ -78,6 +78,10 @@ class Charge
'customer' => $cgt->gateway_customer_reference,
'confirm' => true,
'description' => $description,
'metadata' => [
'payment_hash' => $payment_hash->hash,
'gateway_type_id' => GatewayType::CREDIT_CARD,
],
];
$response = $this->stripe->createPaymentIntent($data, $this->stripe->stripe_connect_auth);

View File

@ -63,10 +63,13 @@ class CreditCard
'currency' => $this->stripe->client->getCurrencyCode(),
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::CREDIT_CARD,
],
'setup_future_usage' => 'off_session',
];
$payment_intent_data['setup_future_usage'] = 'off_session';
$data['intent'] = $this->stripe->createPaymentIntent($payment_intent_data);
$data['gateway'] = $this->stripe;

View File

@ -52,7 +52,10 @@ class EPS
'payment_method_types' => ['eps'],
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::EPS,
],
], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret;

View File

@ -53,7 +53,10 @@ class FPX
'payment_method_types' => ['fpx'],
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::FPX,
],
], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret;

View File

@ -52,7 +52,10 @@ class GIROPAY
'payment_method_types' => ['giropay'],
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::GIROPAY,
],
], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret;

View File

@ -0,0 +1,201 @@
<?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://www.elastic.co/licensing/elastic-license
*/
namespace App\PaymentDrivers\Stripe\Jobs;
use App\Jobs\Util\SystemLogger;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Stripe\Utilities;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class PaymentIntentWebhook implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, Utilities;
public $tries = 1; //number of retries
public $deleteWhenMissingModels = true;
public $stripe_request;
public $company_key;
private $company_gateway_id;
public $payment_completed = false;
public function __construct($stripe_request, $company_key, $company_gateway_id)
{
$this->stripe_request = $stripe_request;
$this->company_key = $company_key;
$this->company_gateway_id = $company_gateway_id;
}
public function handle()
{
// nlog($this->stripe_request);
// nlog(optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['gateway_type_id']);
// nlog(optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['payment_hash']);
// nlog(optional($this->stripe_request['object']['charges']['data'][0]['payment_method_details']['card'])['brand']);
MultiDB::findAndSetDbByCompanyKey($this->company_key);
$company = Company::where('company_key', $this->company_key)->first();
foreach ($this->stripe_request as $transaction) {
if(array_key_exists('payment_intent', $transaction))
{
$payment = Payment::query()
->where('company_id', $company->id)
->where(function ($query) use ($transaction) {
$query->where('transaction_reference', $transaction['payment_intent'])
->orWhere('transaction_reference', $transaction['id']);
})
->first();
}
else
{
$payment = Payment::query()
->where('company_id', $company->id)
->where('transaction_reference', $transaction['id'])
->first();
}
if ($payment) {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
$this->payment_completed = true;
}
}
if($this->payment_completed)
return;
if(optional($this->stripe_request['object']['charges']['data'][0])['id']){
$company = Company::where('company_key', $this->company_key)->first();
$payment = Payment::query()
->where('company_id', $company->id)
->where('transaction_reference', $this->stripe_request['object']['charges']['data'][0]['id'])
->first();
//return early
if($payment && $payment->status_id == Payment::STATUS_COMPLETED){
nlog(" payment found and status correct - returning ");
return;
}
elseif($payment){
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
}
$hash = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['payment_hash'];
$payment_hash = PaymentHash::where('hash', $hash)->first();
nlog("no payment found");
if(optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('card', $this->stripe_request['object']['allowed_source_types']))
{
nlog("hash found");
$hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash'];
$payment_hash = PaymentHash::where('hash', $hash)->first();
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateCreditCardPayment($payment_hash, $client);
}
}
}
private function updateCreditCardPayment($payment_hash, $client)
{
$company_gateway = CompanyGateway::find($this->company_gateway_id);
$payment_method_type = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['gateway_type_id'];
$driver = $company_gateway->driver($client)->init()->setPaymentMethod($payment_method_type);
$payment_hash->data = array_merge((array) $payment_hash->data, $this->stripe_request);
$payment_hash->save();
$driver->setPaymentHash($payment_hash);
$data = [
'payment_method' => $payment_hash->data->object->payment_method,
'payment_type' => PaymentType::parseCardType(strtolower(optional($this->stripe_request['object']['charges']['data'][0]['payment_method_details']['card'])['brand'])) ?: PaymentType::CREDIT_CARD_OTHER,
'amount' => $payment_hash->data->amount_with_fee,
'transaction_reference' => $this->stripe_request['object']['charges']['data'][0]['id'],
'gateway_type_id' => GatewayType::CREDIT_CARD,
];
$payment = $driver->createPayment($data, Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
['response' => $this->stripe_request, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_STRIPE,
$client,
$client->company,
);
}
//charge # optional($this->stripe_request['object']['charges']['data'][0])['id']
//metadata # optional($this->stripe_request['object']['charges']['data'][0]['metadata']['gateway_type_id']
//metadata # optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']
/**
*
* $intent = \Stripe\PaymentIntent::retrieve('{{PAYMENT_INTENT_ID}}');
$charges = $intent->charges->data;
*
*
* $payment = Payment::query()
->where('company_id', $request->getCompany()->id)
->where('transaction_reference', $transaction['id'])
->first();
if ($payment) {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();
}
* */
}

View File

@ -52,7 +52,10 @@ class PRZELEWY24
'payment_method_types' => ['p24'],
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::PRZELEWY24,
],
], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret;

View File

@ -54,6 +54,10 @@ class SEPA
'setup_future_usage' => 'off_session',
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::SEPA,
],
], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret;

View File

@ -52,7 +52,10 @@ class SOFORT
'payment_method_types' => ['sofort'],
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::SOFORT,
],
], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret;

View File

@ -18,7 +18,7 @@ trait Utilities
public function convertFromStripeAmount($amount, $precision, $currency)
{
if(in_array($amount, ["BIF","CLP","DJF","GNF","JPY","KMF","KRW","MGA","PYG","RWF","UGX","VND","VUV","XAF","XOF","XPF"]))
if(in_array($currency->code, ["BIF","CLP","DJF","GNF","JPY","KMF","KRW","MGA","PYG","RWF","UGX","VND","VUV","XAF","XOF","XPF"]))
return $amount;
return $amount / pow(10, $precision);
@ -28,7 +28,7 @@ trait Utilities
public function convertToStripeAmount($amount, $precision, $currency)
{
if(in_array($amount, ["BIF","CLP","DJF","GNF","JPY","KMF","KRW","MGA","PYG","RWF","UGX","VND","VUV","XAF","XOF","XPF"]))
if(in_array($currency->code, ["BIF","CLP","DJF","GNF","JPY","KMF","KRW","MGA","PYG","RWF","UGX","VND","VUV","XAF","XOF","XPF"]))
return $amount;
return round(($amount * pow(10, $precision)),0);

View File

@ -52,7 +52,10 @@ class iDeal
'payment_method_types' => ['ideal'],
'customer' => $this->stripe->findOrCreateCustomer(),
'description' => $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')),
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::IDEAL,
],
], $this->stripe->stripe_connect_auth);
$data['pi_client_secret'] = $intent->client_secret;

View File

@ -25,25 +25,26 @@ use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Stripe\ACH;
use App\PaymentDrivers\Stripe\ACSS;
use App\PaymentDrivers\Stripe\Alipay;
use App\PaymentDrivers\Stripe\ApplePay;
use App\PaymentDrivers\Stripe\BECS;
use App\PaymentDrivers\Stripe\Bancontact;
use App\PaymentDrivers\Stripe\BrowserPay;
use App\PaymentDrivers\Stripe\Charge;
use App\PaymentDrivers\Stripe\Connect\Verify;
use App\PaymentDrivers\Stripe\CreditCard;
use App\PaymentDrivers\Stripe\ImportCustomers;
use App\PaymentDrivers\Stripe\SOFORT;
use App\PaymentDrivers\Stripe\SEPA;
use App\PaymentDrivers\Stripe\PRZELEWY24;
use App\PaymentDrivers\Stripe\GIROPAY;
use App\PaymentDrivers\Stripe\iDeal;
use App\PaymentDrivers\Stripe\EPS;
use App\PaymentDrivers\Stripe\Bancontact;
use App\PaymentDrivers\Stripe\BECS;
use App\PaymentDrivers\Stripe\ACSS;
use App\PaymentDrivers\Stripe\BrowserPay;
use App\PaymentDrivers\Stripe\FPX;
use App\PaymentDrivers\Stripe\GIROPAY;
use App\PaymentDrivers\Stripe\ImportCustomers;
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentWebhook;
use App\PaymentDrivers\Stripe\PRZELEWY24;
use App\PaymentDrivers\Stripe\SEPA;
use App\PaymentDrivers\Stripe\SOFORT;
use App\PaymentDrivers\Stripe\UpdatePaymentMethods;
use App\PaymentDrivers\Stripe\Utilities;
use App\PaymentDrivers\Stripe\iDeal;
use App\Utils\Traits\MakesHash;
use Exception;
use Illuminate\Http\RedirectResponse;
@ -472,10 +473,7 @@ class StripePaymentDriver extends BaseDriver
$response = null;
try {
// $response = $this->stripe
// ->refunds
// ->create(['charge' => $payment->transaction_reference, 'amount' => $this->convertToStripeAmount($amount, $this->client->currency()->precision, $this->client->currency())], $meta);
$response = \Stripe\Refund::create([
'charge' => $payment->transaction_reference,
'amount' => $this->convertToStripeAmount($amount, $this->client->currency()->precision, $this->client->currency())
@ -532,7 +530,15 @@ class StripePaymentDriver extends BaseDriver
// Allow app to catch up with webhook request.
sleep(2);
if ($request->type === 'charge.succeeded' || $request->type === 'payment_intent.succeeded') {
//payment_intent.succeeded - this will confirm or cancel the payment
if($request->type === 'payment_intent.succeeded'){
PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(10);
// PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id);
return response()->json([], 200);
}
if ($request->type === 'charge.succeeded') {
// if ($request->type === 'charge.succeeded' || $request->type === 'payment_intent.succeeded') {
foreach ($request->data as $transaction) {
@ -559,6 +565,7 @@ class StripePaymentDriver extends BaseDriver
$payment->save();
}
}
} elseif ($request->type === 'source.chargeable') {
$this->init();

View File

@ -174,15 +174,17 @@ class Statement
$item->tax_rate1 = 5;
}
$product = Product::first();
//$product = Product::first();
$item->cost = (float) $product->cost;
$item->product_key = $product->product_key;
$item->notes = $product->notes;
$item->custom_value1 = $product->custom_value1;
$item->custom_value2 = $product->custom_value2;
$item->custom_value3 = $product->custom_value3;
$item->custom_value4 = $product->custom_value4;
$product = new \stdClass;
$item->cost = (float) 10;
$item->product_key = 'test';
$item->notes = 'test notes';
$item->custom_value1 = 'custom value1';
$item->custom_value2 = 'custom value2';
$item->custom_value3 = 'custom value3';
$item->custom_value4 = 'custom value4';
$line_items[] = $item;
}

View File

@ -108,6 +108,7 @@ class CreditService
$this->updateBalance($adjustment)
->updatePaidToDate($adjustment)
->setStatus(Credit::STATUS_APPLIED)
->save();
//create a negative payment of total $this->credit->balance
@ -136,7 +137,6 @@ class CreditService
->client
->service()
->updatePaidToDate($adjustment)
->setStatus(Credit::STATUS_APPLIED)
->save();
event('eloquent.created: App\Models\Payment', $payment);

View File

@ -55,6 +55,16 @@ class UpdateInvoicePayment
if($paid_amount > $invoice->partial && $paid_amount > $invoice->balance)
$paid_amount = $invoice->balance;
/*Improve performance here - 26-01-2022 - also change the order of events for invoice first*/
$invoice->service() //caution what if we amount paid was less than partial - we wipe it!
->clearPartial()
->updateBalance($paid_amount * -1)
->updatePaidToDate($paid_amount)
->updateStatus()
->touchPdf()
->workFlow()
->save();
/* Updates the company ledger */
$this->payment
->ledger()
@ -77,19 +87,22 @@ class UpdateInvoicePayment
$this->payment->applied += $paid_amount;
$invoice->service() //caution what if we amount paid was less than partial - we wipe it!
->clearPartial()
->updateBalance($paid_amount * -1)
->updatePaidToDate($paid_amount)
->updateStatus()
->save();
// $invoice->service() //caution what if we amount paid was less than partial - we wipe it!
// ->clearPartial()
// ->updateBalance($paid_amount * -1)
// ->updatePaidToDate($paid_amount)
// ->updateStatus()
// ->save();
// $invoice->refresh();
// $invoice->service()
// ->touchPdf(true)
// ->workFlow()
// ->save();
$invoice->refresh();
$invoice->service()
->touchPdf(true)
->workFlow()
->save();
});

View File

@ -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.3.49',
'app_tag' => '5.3.49',
'app_version' => '5.3.51',
'app_tag' => '5.3.51',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

View File

@ -13,7 +13,7 @@
use Illuminate\Support\Facades\Route;
Route::group(['middleware' => ['throttle:10,1', 'api_secret_check']], function () {
Route::group(['middleware' => ['throttle:300,1', 'api_secret_check']], function () {
Route::post('api/v1/signup', 'AccountController@store')->name('signup.submit');
Route::post('api/v1/oauth_login', 'Auth\LoginController@oauthApiLogin');
});

View File

@ -28,7 +28,7 @@ Route::get('error', 'ClientPortal\ContactHashLoginController@errorPage')->name('
Route::get('client/payment/{contact_key}/{payment_id}', 'ClientPortal\InvitationController@paymentRouter')->middleware(['domain_db','contact_key_login']);
Route::get('client/ninja/{contact_key}/{company_key}', 'ClientPortal\NinjaPlanController@index')->name('client.ninja_contact_login')->middleware(['domain_db']);
Route::group(['middleware' => ['auth:contact', 'locale', 'check_client_existence','domain_db'], 'prefix' => 'client', 'as' => 'client.'], function () {
Route::group(['middleware' => ['auth:contact', 'locale', 'domain_db'], 'prefix' => 'client', 'as' => 'client.'], function () {
Route::get('dashboard', 'ClientPortal\DashboardController@index')->name('dashboard'); // name = (dashboard. index / create / show / update / destroy / edit
Route::get('plan', 'ClientPortal\NinjaPlanController@plan')->name('plan'); // name = (dashboard. index / create / show / update / destroy / edit