From 077b0f39ff7849d3148d46c9d6b3bec0c946f996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 10 Oct 2024 18:28:58 +0200 Subject: [PATCH 01/15] setup peppol --- .../Controllers/EInvoicePeppolController.php | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 app/Http/Controllers/EInvoicePeppolController.php diff --git a/app/Http/Controllers/EInvoicePeppolController.php b/app/Http/Controllers/EInvoicePeppolController.php new file mode 100644 index 0000000000..c3ec58f7b0 --- /dev/null +++ b/app/Http/Controllers/EInvoicePeppolController.php @@ -0,0 +1,40 @@ +user(); + + $legal_entity_response = $storecove->createLegalEntity($request->validated(), $user->company()); + + $add_identifier_response = $storecove->addIdentifier( + legal_entity_id: $legal_entity_response['id'], + identifier: $user->company()->settings->vat_number, + scheme: "{$request->country}:VAT" + ); + + $user->company->legal_entity_id = $legal_entity_response['id']; + $user->company->save(); + + return response()->noContent(); + } +} From 314872a4e49d2444e6a25775fe2394cbcc4563d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 10 Oct 2024 18:29:35 +0200 Subject: [PATCH 02/15] request for setting up peppol --- .../EInvoice/Peppol/CreateRequest.php | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 app/Http/Requests/EInvoice/Peppol/CreateRequest.php diff --git a/app/Http/Requests/EInvoice/Peppol/CreateRequest.php b/app/Http/Requests/EInvoice/Peppol/CreateRequest.php new file mode 100644 index 0000000000..3327d0c93d --- /dev/null +++ b/app/Http/Requests/EInvoice/Peppol/CreateRequest.php @@ -0,0 +1,54 @@ +user(); + + return $user->account->isPaid(); + } + + /** + * @return array|string> + */ + public function rules(): array + { + return [ + 'party_name' => ['required', 'string'], + 'line1' => ['required', 'string'], + 'line2' => ['nullable', 'string'], + 'city' => ['required', 'string'], + 'country' => ['required', 'string'], + 'zip' => ['required', 'string'], + 'county' => ['required', 'string'], + ]; + } + + public function prepareForValidation(): void + { + $country = Country::findOrFail($this->country); + + $this->merge([ + 'country' => $country->iso_3166_2, + ]); + } +} From 0371ccd2deec4c49f7556103fe5be9451136fc30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 10 Oct 2024 18:29:43 +0200 Subject: [PATCH 03/15] fixes for unreachable legal_entity_id --- app/Services/EDocument/Gateway/Storecove/Storecove.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/EDocument/Gateway/Storecove/Storecove.php b/app/Services/EDocument/Gateway/Storecove/Storecove.php index b9d4744055..753d5c0fbd 100644 --- a/app/Services/EDocument/Gateway/Storecove/Storecove.php +++ b/app/Services/EDocument/Gateway/Storecove/Storecove.php @@ -51,7 +51,7 @@ class Storecove "identifier" => "1200109963131" ]; - private ?int $legal_entity_id; + private ?int $legal_entity_id = null; public StorecoveRouter $router; From f728ebb7978b51db949f1603ef2fd063b2f1cc93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 10 Oct 2024 18:29:50 +0200 Subject: [PATCH 04/15] expose legal_entity_id on transformer --- app/Transformers/CompanyTransformer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 7c28215870..4dd3cf7b2d 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -222,6 +222,7 @@ public function transform(Company $company) 'e_invoice' => $company->e_invoice ?: new \stdClass(), 'has_quickbooks_token' => $company->quickbooks ? true : false, 'is_quickbooks_token_active' => $company->quickbooks?->accessTokenKey ?? false, + 'legal_entity_id' => $company->legal_entity_id ?? null, ]; } From fa13efe5da8f348da40deb3e0c55fd852f3ed6c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 10 Oct 2024 18:29:55 +0200 Subject: [PATCH 05/15] update routes --- routes/api.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/routes/api.php b/routes/api.php index 9f251a11e8..e05d28b3a1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -10,6 +10,7 @@ | is assigned the "api" middleware group. Enjoy building your API! | */ +use App\Http\Controllers\EInvoicePeppolController; use App\Http\Controllers\SubscriptionStepsController; use Illuminate\Support\Facades\Route; use App\Http\Controllers\BaseController; @@ -228,6 +229,8 @@ Route::post('documents/bulk', [DocumentController::class, 'bulk'])->name('documents.bulk'); Route::post('einvoice/validateEntity', [EInvoiceController::class, 'validateEntity'])->name('einvoice.validateEntity'); + Route::post('einvoice/peppol/setup', [EInvoicePeppolController::class, 'setup'])->name('einvoice.peppol.setup'); + Route::post('emails', [EmailController::class, 'send'])->name('email.send')->middleware('user_verified'); Route::post('emails/clientHistory/{client}', [EmailHistoryController::class, 'clientHistory'])->name('email.clientHistory'); Route::post('emails/entityHistory', [EmailHistoryController::class, 'entityHistory'])->name('email.entityHistory'); From b0aa22c86dd9e6ed390ce5f1b0a0bf3590bea158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Thu, 10 Oct 2024 18:29:59 +0200 Subject: [PATCH 06/15] add translations --- lang/en/texts.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lang/en/texts.php b/lang/en/texts.php index 5ab54ab89e..60c1573754 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5359,7 +5359,17 @@ 'updated_records' => 'Updated Records', 'vat_not_registered' => 'Seller not VAT registered', 'small_company_info' => 'No disclosure of sales tax in accordance with ยง 19 UStG', - 'log_duration_words' => 'Log duration in words' + 'log_duration_words' => 'Log duration in words', + 'peppol_onboarding' => 'Looks like it\'s your first time using PEPPOL.', + 'get_started' => 'Get Started', + 'configure_peppol' => 'Configure PEPPOL', + 'step' => 'Step', + 'peppol_whitelabel_warning' => 'In order to use PEPPOL, you need to have white-label license.', + 'peppol_plan_warning' => 'In order to use PEPPOL, you need to be on paid plan.', + 'peppol_credits_info' => 'Text how they need a credits to operate with PEPPOL.', + 'buy_credits' => 'Buy Credits', + 'peppol_successfully_configured' => 'PEPPOL successsfully configured.', + ); return $lang; From aae830f60c971de0bae543b331c5974cf5de7b2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 11 Oct 2024 17:55:41 +0200 Subject: [PATCH 07/15] include einvoice token on submission --- app/Services/EDocument/Jobs/SendEDocument.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Services/EDocument/Jobs/SendEDocument.php b/app/Services/EDocument/Jobs/SendEDocument.php index 9426d5cfdc..3cf81a1e9e 100644 --- a/app/Services/EDocument/Jobs/SendEDocument.php +++ b/app/Services/EDocument/Jobs/SendEDocument.php @@ -65,6 +65,9 @@ public function handle() 'document' => base64_encode($xml), 'tenant_id' => $model->company->company_key, 'identifiers' => $identifiers, + 'e_invoicing_token' => $model->company->e_invoicing_token, + + // include whitelabel key. ]; $r = Http::withHeaders($this->getHeaders()) From d45e4ad1b3cdc9c7af4849e1bd6e79b780532640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 11 Oct 2024 17:55:46 +0200 Subject: [PATCH 08/15] e_invoicing_token field --- app/Transformers/CompanyTransformer.php | 1 + .../2024_10_11_153311_add_e_invoicing_token.php | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 database/migrations/2024_10_11_153311_add_e_invoicing_token.php diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 4dd3cf7b2d..28e705076e 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -223,6 +223,7 @@ public function transform(Company $company) 'has_quickbooks_token' => $company->quickbooks ? true : false, 'is_quickbooks_token_active' => $company->quickbooks?->accessTokenKey ?? false, 'legal_entity_id' => $company->legal_entity_id ?? null, + 'e_invoicing_token' => $company->e_invoicing_token ?? null, ]; } diff --git a/database/migrations/2024_10_11_153311_add_e_invoicing_token.php b/database/migrations/2024_10_11_153311_add_e_invoicing_token.php new file mode 100644 index 0000000000..141e74a42e --- /dev/null +++ b/database/migrations/2024_10_11_153311_add_e_invoicing_token.php @@ -0,0 +1,15 @@ +string('e_invoicing_token')->nullable(); + }); + } +}; From 2441669f8d4dd1a979b3953d8250736d9795687a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 21 Oct 2024 17:38:10 +0200 Subject: [PATCH 09/15] connect & disconnect --- .../Controllers/EInvoicePeppolController.php | 58 +++++++++++++++---- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/EInvoicePeppolController.php b/app/Http/Controllers/EInvoicePeppolController.php index c3ec58f7b0..e87070b48a 100644 --- a/app/Http/Controllers/EInvoicePeppolController.php +++ b/app/Http/Controllers/EInvoicePeppolController.php @@ -12,6 +12,7 @@ namespace App\Http\Controllers; use App\Http\Requests\EInvoice\Peppol\CreateRequest; +use App\Http\Requests\EInvoice\Peppol\DisconnectRequest; use App\Services\EDocument\Gateway\Storecove\Storecove; use Illuminate\Http\Response; @@ -20,21 +21,56 @@ class EInvoicePeppolController extends BaseController public function setup(CreateRequest $request, Storecove $storecove): Response { /** - * @var \App\Models\User + * @var \App\Models\Company */ - $user = auth()->user(); + $company = auth()->user()->company(); - $legal_entity_response = $storecove->createLegalEntity($request->validated(), $user->company()); + $data = [ + ...$request->validated(), + 'country' => $request->country()->iso_3166_2, + ]; + + $legal_entity_response = $storecove->createLegalEntity($data, $company); $add_identifier_response = $storecove->addIdentifier( legal_entity_id: $legal_entity_response['id'], - identifier: $user->company()->settings->vat_number, - scheme: "{$request->country}:VAT" - ); - - $user->company->legal_entity_id = $legal_entity_response['id']; - $user->company->save(); - - return response()->noContent(); + identifier: $company->settings->vat_number, + scheme: $request->receiverIdentifier(), + ); + + if ($add_identifier_response) { + $company->legal_entity_id = $legal_entity_response['id']; + $company->save(); + + return response()->noContent(); + } + + // @todo: Improve with proper error. + + return response()->noContent(status: 422); + } + + public function disconnect(DisconnectRequest $request, Storecove $storecove): Response + { + /** + * @var \App\Models\Company + */ + $company = auth()->user()->company(); + + $response = $storecove->deleteIdentifier( + legal_entity_id: $company->legal_entity_id, + ); + + if ($response) { + $company->legal_entity_id = null; + $company->save(); + + return response()->noContent(); + + } + + // @todo: Improve with proper error. + + return response()->noContent(status: 422); } } From a2cd8c2a219b19457680d283ab0bb3552517c302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 21 Oct 2024 17:38:18 +0200 Subject: [PATCH 10/15] update create request --- .../EInvoice/Peppol/CreateRequest.php | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/app/Http/Requests/EInvoice/Peppol/CreateRequest.php b/app/Http/Requests/EInvoice/Peppol/CreateRequest.php index 3327d0c93d..0cbb6a7da4 100644 --- a/app/Http/Requests/EInvoice/Peppol/CreateRequest.php +++ b/app/Http/Requests/EInvoice/Peppol/CreateRequest.php @@ -13,6 +13,9 @@ namespace App\Http\Requests\EInvoice\Peppol; use App\Models\Country; +use App\Rules\EInvoice\Peppol\SupportsReceiverIdentifier; +use App\Services\EDocument\Standards\Peppol\ReceiverIdentifier; +use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Foundation\Http\FormRequest; class CreateRequest extends FormRequest @@ -24,7 +27,12 @@ public function authorize(): bool */ $user = auth()->user(); - return $user->account->isPaid(); + if (app()->isLocal()) { + return true; + } + + return $user->account->isPaid() && + $user->company()->legal_entity_id === null; } /** @@ -37,18 +45,28 @@ public function rules(): array 'line1' => ['required', 'string'], 'line2' => ['nullable', 'string'], 'city' => ['required', 'string'], - 'country' => ['required', 'string'], + 'country' => ['required', 'integer', 'exists:countries,id', new SupportsReceiverIdentifier()], 'zip' => ['required', 'string'], 'county' => ['required', 'string'], ]; } - public function prepareForValidation(): void + protected function failedAuthorization(): void + { + throw new AuthorizationException( + message: ctrans('texts.peppol_not_paid_message'), + ); + } + + public function country(): Country + { + return Country::find($this->country); + } + + public function receiverIdentifier(): string { - $country = Country::findOrFail($this->country); + $identifier = new ReceiverIdentifier($this->country()->iso_3166_2); - $this->merge([ - 'country' => $country->iso_3166_2, - ]); + return $identifier->get(); } } From b1cbace972241056df6d7dc2bf13f72258b00a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 21 Oct 2024 17:38:24 +0200 Subject: [PATCH 11/15] disconnect request --- .../EInvoice/Peppol/DisconnectRequest.php | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 app/Http/Requests/EInvoice/Peppol/DisconnectRequest.php diff --git a/app/Http/Requests/EInvoice/Peppol/DisconnectRequest.php b/app/Http/Requests/EInvoice/Peppol/DisconnectRequest.php new file mode 100644 index 0000000000..7485a00029 --- /dev/null +++ b/app/Http/Requests/EInvoice/Peppol/DisconnectRequest.php @@ -0,0 +1,52 @@ +user(); + + if (app()->isLocal()) { + return true; + } + + return $user->account->isPaid() && + $user->company()->legal_entity_id !== null; + } + + /** + * @return array|string> + */ + public function rules(): array + { + return []; + } + + protected function failedAuthorization(): void + { + throw new AuthorizationException( + message: ctrans('texts.peppol_not_paid_message'), + ); + } +} From 4be182d6214f7d050802e7d53dd1a066c60ea620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 21 Oct 2024 17:38:53 +0200 Subject: [PATCH 12/15] checks for receiver identifier --- .../Peppol/SupportsReceiverIdentifier.php | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 app/Rules/EInvoice/Peppol/SupportsReceiverIdentifier.php diff --git a/app/Rules/EInvoice/Peppol/SupportsReceiverIdentifier.php b/app/Rules/EInvoice/Peppol/SupportsReceiverIdentifier.php new file mode 100644 index 0000000000..d8577ece97 --- /dev/null +++ b/app/Rules/EInvoice/Peppol/SupportsReceiverIdentifier.php @@ -0,0 +1,31 @@ +iso_3166_2); + + if ($checker->get() === null) { + $fail(ctrans('texts.peppol_country_not_supported')); + } + } +} From 2521c61d0f59153367342c5278716869a7805574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 21 Oct 2024 17:39:02 +0200 Subject: [PATCH 13/15] receiver identifier service --- .../Standards/Peppol/ReceiverIdentifier.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 app/Services/EDocument/Standards/Peppol/ReceiverIdentifier.php diff --git a/app/Services/EDocument/Standards/Peppol/ReceiverIdentifier.php b/app/Services/EDocument/Standards/Peppol/ReceiverIdentifier.php new file mode 100644 index 0000000000..50ab09ef55 --- /dev/null +++ b/app/Services/EDocument/Standards/Peppol/ReceiverIdentifier.php @@ -0,0 +1,34 @@ + 'DE:VAT', + + // @todo: Check with Dave what other countries we support. + ]; + + public function __construct( + public string $country, + ) { + } + + public function get(): ?string + { + return $this->mappings[$this->country] ?? null; + } +} From 00173c17f1d3c339945bd86c190517a669d07402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 21 Oct 2024 17:39:08 +0200 Subject: [PATCH 14/15] update translations --- lang/en/texts.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lang/en/texts.php b/lang/en/texts.php index 60c1573754..86b695bf74 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5369,7 +5369,11 @@ 'peppol_credits_info' => 'Text how they need a credits to operate with PEPPOL.', 'buy_credits' => 'Buy Credits', 'peppol_successfully_configured' => 'PEPPOL successsfully configured.', - + 'peppol_not_paid_message' => 'PEPPOL is only available to paid plans. Please upgrade your plan to get access to PEPPOL.', + 'peppol_country_not_supported' => 'PEPPOL is not available for this country.', + 'peppol_disconnect' => 'Disconnect PEPPOL', + 'peppol_disconnect_short' => 'Disconnect from PEPPOL.', + 'peppol_disconnect_long' => 'PEPPOL will be disconnected from your account and...', ); return $lang; From 56264b1868b1dde3b5481dcdb961c397d2a6281d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Mon, 21 Oct 2024 17:39:14 +0200 Subject: [PATCH 15/15] update routes --- routes/api.php | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/api.php b/routes/api.php index e05d28b3a1..d347d4e7e8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -230,6 +230,7 @@ Route::post('einvoice/validateEntity', [EInvoiceController::class, 'validateEntity'])->name('einvoice.validateEntity'); Route::post('einvoice/peppol/setup', [EInvoicePeppolController::class, 'setup'])->name('einvoice.peppol.setup'); + Route::post('einvoice/peppol/disconnect', [EInvoicePeppolController::class, 'disconnect'])->name('einvoice.peppol.disconnect'); Route::post('emails', [EmailController::class, 'send'])->name('email.send')->middleware('user_verified'); Route::post('emails/clientHistory/{client}', [EmailHistoryController::class, 'clientHistory'])->name('email.clientHistory');