1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-05 18:52:44 +01:00

Merge pull request #4774 from turbo124/v5-develop

Payment notifications for autobilling
This commit is contained in:
David Bomba 2021-01-27 21:57:16 +11:00 committed by GitHub
commit d492d6c12f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 178 additions and 74 deletions

View File

@ -331,7 +331,7 @@ class CreateTestData extends Command
$this->info('Creating '.$this->count.' clients');
for ($x = 0; $x < $this->count * 200; $x++) {
for ($x = 0; $x < $this->count * 100; $x++) {
$z = $x + 1;
$this->info('Creating client # '.$z);

View File

@ -0,0 +1,78 @@
<?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 App\Filters;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
/**
* DocumentFilters.
*/
class DocumentFilters extends QueryFilters
{
public function client_id(int $client_id) :Builder
{
return $this->builder->where('client_id', $client_id);
}
/**
* Filter based on search text.
*
* @param string query filter
* @return Builder
* @deprecated
*/
public function filter(string $filter = '') : Builder
{
if (strlen($filter) == 0) {
return $this->builder;
}
return $this->builder;
}
/**
* Sorts the list based on $sort.
*
* @param string sort formatted as column|asc
* @return Builder
*/
public function sort(string $sort) : Builder
{
$sort_col = explode('|', $sort);
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
}
/**
* Returns the base query.
*
* @param int company_id
* @param User $user
* @return Builder
* @deprecated
*/
public function baseQuery(int $company_id, User $user) : Builder
{
}
/**
* Filters the query by the users company ID.
*
* @return Illuminate\Database\Query\Builder
*/
public function entityFilter()
{
return $this->builder->company();
}
}

View File

@ -65,7 +65,6 @@ class BaseController extends Controller
'company.task_statuses',
'company.expense_categories',
'company.documents',
//'company.users',
'company.users.company_user',
'company.clients.contacts.company',
'company.clients.gateway_tokens',
@ -105,6 +104,7 @@ class BaseController extends Controller
'user.company_user',
'token',
'company.activities',
'company.documents',
//'company.users.company_user',
'company.tax_rates',
'company.groups',
@ -184,7 +184,10 @@ class BaseController extends Controller
protected function refreshResponse($query)
{
$this->manager->parseIncludes($this->first_load);
if (auth()->user()->getCompany()->is_large)
$this->manager->parseIncludes($this->mini_load);
else
$this->manager->parseIncludes($this->first_load);
$this->serializer = request()->input('serializer') ?: EntityTransformer::API_SERIALIZER_ARRAY;
@ -197,9 +200,9 @@ class BaseController extends Controller
$transformer = new $this->entity_transformer($this->serializer);
$updated_at = request()->has('updated_at') ? request()->input('updated_at') : 0;
if (auth()->user()->getCompany()->is_large && ! request()->has('updated_at')) {
return response()->json(['message' => ctrans('texts.large_account_update_parameter'), 'errors' =>[]], 401);
}
// if (auth()->user()->getCompany()->is_large && ! request()->has('updated_at')) {
// return response()->json(['message' => ctrans('texts.large_account_update_parameter'), 'errors' =>[]], 401);
// }
$updated_at = date('Y-m-d H:i:s', $updated_at);

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use App\Filters\DocumentFilters;
use App\Http\Requests\Document\DestroyDocumentRequest;
use App\Http\Requests\Document\ShowDocumentRequest;
use App\Http\Requests\Document\StoreDocumentRequest;
@ -26,10 +27,6 @@ class DocumentController extends BaseController
*/
protected $document_repo;
/**
* DocumentController constructor.
* @param DocumentRepository $document_repo
*/
public function __construct(DocumentRepository $document_repo)
{
parent::__construct();
@ -40,13 +37,46 @@ class DocumentController extends BaseController
}
/**
* Display a listing of the resource.
*
* @return void
* @OA\Get(
* path="/api/v1/documents",
* operationId="getDocuments",
* tags={"documents"},
* summary="Gets a list of documents",
* description="Lists documents, search and filters allow fine grained lists to be generated.
Query parameters can be added to performed more fine grained filtering of the documents, these are handled by the DocumentsFilters class which defines the methods available",
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
* @OA\Parameter(ref="#/components/parameters/include"),
* @OA\Parameter(ref="#/components/parameters/index"),
* @OA\Response(
* response=200,
* description="A list of documents",
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
* @OA\JsonContent(ref="#/components/schemas/Document"),
* ),
* @OA\Response(
* response=422,
* description="Validation error",
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
* ),
* @OA\Response(
* response="default",
* description="Unexpected Error",
* @OA\JsonContent(ref="#/components/schemas/Error"),
* ),
* )
* @param DocumentsFilters $filters
* @return Response|mixed
*/
public function index()
public function index(DocumentFilters $filters)
{
//
$documents = Document::filter($filters);
return $this->listResponse($documents);
}
/**

View File

@ -80,6 +80,7 @@ class SendRecurring implements ShouldQueue
}
if ($invoice->client->getSetting('auto_bill_date') == 'on_send_date' && $this->recurring_invoice->auto_bill_enabled) {
nlog("attempting to autobill {$invoice->number}");
$invoice->service()->autoBill()->save();
}

View File

@ -57,12 +57,35 @@ class PaymentEmailEngine extends BaseEmailEngine
/* Use default translations if a custom message has not been set*/
if (iconv_strlen($body_template) == 0) {
$body_template = trans(
'texts.payment_message',
['amount' => $payment->amount, 'company' => $payment->company->present()->name()],
null,
$this->client->locale()
);
if ($payment->invoices()->exists())
{
$invoice_texts = ctrans('texts.invoice_number_short');
foreach ($this->payment->invoices as $invoice) {
$invoice_texts .= $invoice->number.',';
}
$invoice_texts = substr($invoice_texts, 0, -1);
$body_template = trans(
'texts.payment_message_extended',
['amount' => $payment->amount, 'company' => $payment->company->present()->name(), 'invoice' => $invoice_texts],
null,
$this->client->locale()
);
}
else
{
$body_template = trans(
'texts.payment_message',
['amount' => $payment->amount, 'company' => $payment->company->present()->name()],
null,
$this->client->locale()
);
}
}
if (is_array($this->template_data) && array_key_exists('subject', $this->template_data) && strlen($this->template_data['subject']) > 0) {

View File

@ -11,12 +11,14 @@
namespace App\Models;
use App\Models\Filterable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Storage;
class Document extends BaseModel
{
use SoftDeletes;
use Filterable;
const DOCUMENT_PREVIEW_SIZE = 300; // pixels

View File

@ -91,13 +91,13 @@ class CompanyPresenter extends EntityPresenter
}
}
public function getSpcQrCode($client_custom, $invoice_number, $balance)
public function getSpcQrCode($client_currency, $invoice_number, $balance)
{
$settings = $this->entity->settings;
return
"SPC\n0200\n1\nCH860021421411198240K\nK\n{$this->name}\n{$settings->address1}\n{$settings->postal_code} {$settings->city}\n\n\nCH\n\n\n\n\n\n\n\n{$balance}\n{$client_custom}\n\n\n\n\n\n\n\nNON\n\n{$invoice_number}\nEPD\n";
"SPC\n0200\n1\nCH860021421411198240K\nK\n{$this->name}\n{$settings->address1}\n{$settings->postal_code} {$settings->city}\n\n\nCH\n\n\n\n\n\n\n\n{$balance}\n{$client_currency}\n\n\n\n\n\n\n\nNON\n\n{$invoice_number}\nEPD\n";
}
}

View File

@ -157,7 +157,7 @@ class AuthorizeCreditCard
$payment_record = [];
$payment_record['amount'] = $amount;
$payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER;
$payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD;
$payment_record['transaction_reference'] = $response->getTransactionResponse()->getTransId();
$payment = $this->authorize->createPayment($payment_record);
@ -165,21 +165,6 @@ class AuthorizeCreditCard
return $payment;
}
private function createPaymentRecord($data, $amount) :?Payment
{
$response = $data['response'];
//create a payment record
$payment = $this->authorize->createPayment($data['response']);
$payment->gateway_type_id = GatewayType::CREDIT_CARD;
$payment->type_id = PaymentType::CREDIT_CARD_OTHER;
$payment->transaction_reference = $response->getTransactionResponse()->getTransId();
$payment->amount = $amount;
$payment->save();
return $payment;
}
private function processSuccessfulResponse($data, $request)
{
$payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$request->input('payment_hash')])->firstOrFail();

View File

@ -209,6 +209,8 @@ class BaseDriver extends AbstractPaymentDriver
$payment->currency_id = $this->client->getSetting('currency_id');
$payment->date = Carbon::now();
//$payment->gateway_type_id = $data['gateway_type_id'];
$client_contact = $this->getContact();
$client_contact_id = $client_contact ? $client_contact->id : null;

View File

@ -130,6 +130,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
'payment_type' => PaymentType::PAYPAL,
'amount' => $this->payment_hash->data->amount,
'transaction_reference' => $response->getTransactionReference(),
'gateway_type_id' => GatewayType::PAYPAL,
];
$payment = $this->createPayment($data, \App\Models\Payment::STATUS_COMPLETED);

View File

@ -157,6 +157,7 @@ class ACH
'payment_type' => PaymentType::ACH,
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->amount, $this->stripe->client->currency()->precision),
'transaction_reference' => $state['charge']->id,
'gateway_type_id' => GatewayType::BANK_TRANSFER,
];
$payment = $this->stripe->createPayment($data, Payment::STATUS_PENDING);

View File

@ -76,6 +76,8 @@ class Alipay
'payment_type' => PaymentType::ALIPAY,
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision),
'transaction_reference' => $source,
'gateway_type_id' => GatewayType::ALIPAY,
];
$payment = $this->stripe->createPayment($data, Payment::STATUS_PENDING);

View File

@ -172,23 +172,18 @@ class Charge
$data = [
'gateway_type_id' => $cgt->gateway_type_id,
'type_id' => $this->transformPaymentTypeToConstant($payment_method_type),
'payment_type' => $this->transformPaymentTypeToConstant($payment_method_type),
'transaction_reference' => $response->charges->data[0]->id,
'amount' => $amount,
];
$payment = $this->stripe->createPaymentRecord($data, $amount);
$payment = $this->stripe->createPayment($data);
$payment->meta = $cgt->meta;
$payment->save();
$payment_hash->payment_id = $payment->id;
$payment_hash->save();
$this->stripe->attachInvoices($payment, $payment_hash);
$payment->service()->updateInvoicePayment($payment_hash);
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));
return $payment;
}

View File

@ -114,6 +114,7 @@ class CreditCard
'payment_type' => PaymentType::parseCardType(strtolower($stripe_method->card->brand)),
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->server_response->amount, $this->stripe->client->currency()->precision),
'transaction_reference' => optional($this->stripe->payment_hash->data->payment_intent->charges->data[0])->id,
'gateway_type_id' => GatewayType::CREDIT_CARD,
];

View File

@ -82,6 +82,7 @@ class SOFORT
'payment_type' => PaymentType::SOFORT,
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->stripe_amount, $this->stripe->client->currency()->precision),
'transaction_reference' => $source,
'gateway_type_id' => GatewayType::SOFORT,
];
$payment = $this->stripe->createPayment($data, Payment::STATUS_PENDING);

View File

@ -175,7 +175,7 @@ class StripePaymentDriver extends BaseDriver
return $this->payment_method->paymentView($data);
}
public function processPaymentResponse($request) //We never have to worry about unsuccessful payments as failures are handled at the front end for this driver.
public function processPaymentResponse($request)
{
return $this->payment_method->paymentResponse($request);
}
@ -322,34 +322,11 @@ class StripePaymentDriver extends BaseDriver
public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash)
{
$this->setPaymentHash($payment_hash);
return (new Charge($this))->tokenBilling($cgt, $payment_hash);
}
/**
* Creates a payment record for the given
* data array.
*
* @param array $data An array of payment attributes
* @param float $amount The amount of the payment
* @return Payment The payment object
*/
public function createPaymentRecord($data, $amount): ?Payment
{
$payment = PaymentFactory::create($this->client->company_id, $this->client->user_id);
$payment->client_id = $this->client->id;
$payment->company_gateway_id = $this->company_gateway->id;
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->gateway_type_id = $data['gateway_type_id'];
$payment->type_id = $data['type_id'];
$payment->currency_id = $this->client->getSetting('currency_id');
$payment->date = Carbon::now();
$payment->transaction_reference = $data['transaction_reference'];
$payment->amount = $amount;
$payment->save();
return $payment->service()->applyNumber()->save();
}
/**
* Attach Stripe payment method to Stripe client.
*

View File

@ -276,7 +276,7 @@ class HtmlEngine
$data['$company.website'] = ['value' => $this->settings->website ?: '&nbsp;', 'label' => ctrans('texts.website')];
$data['$company.address'] = ['value' => $this->company->present()->address($this->settings) ?: '&nbsp;', 'label' => ctrans('texts.address')];
$data['$spc_qr_code'] = ['value' => $this->company->present()->getSpcQrCode($this->client->custom1, $this->entity->number, $this->entity->balance), 'label' => ''];
$data['$spc_qr_code'] = ['value' => $this->company->present()->getSpcQrCode($this->client->currency()->code, $this->entity->number, $this->entity->balance), 'label' => ''];
$logo = $this->company->present()->logo($this->settings);

View File

@ -3382,4 +3382,6 @@ return [
'user_detached' => 'User detached from company',
'create_webhook_failure' => 'Failed to create Webhook',
'number' => 'Number',
'payment_message_extended' => 'Thank you for your payment of :amount for :invoice',
];