From c14d34350fb1d8782052f171f7f4f0d21d0cf652 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 1 Feb 2021 16:30:28 +1100 Subject: [PATCH 1/5] Working on payment failure emails --- app/Jobs/Mail/PaymentFailureMailer.php | 5 ++++- app/PaymentDrivers/BaseDriver.php | 8 +++++--- app/PaymentDrivers/Stripe/Charge.php | 17 +++++++++++++++-- app/Utils/Traits/Notifications/UserNotifies.php | 1 + 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/app/Jobs/Mail/PaymentFailureMailer.php b/app/Jobs/Mail/PaymentFailureMailer.php index 5d82c83cd7dc..27543a98c1d4 100644 --- a/app/Jobs/Mail/PaymentFailureMailer.php +++ b/app/Jobs/Mail/PaymentFailureMailer.php @@ -69,6 +69,8 @@ class PaymentFailureMailer extends BaseMailerJob implements ShouldQueue */ public function handle() { + nlog("payment failure mailer "); + /*If we are migrating data we don't want to fire these notification*/ if ($this->company->is_disabled) { return true; @@ -81,11 +83,12 @@ class PaymentFailureMailer extends BaseMailerJob implements ShouldQueue $this->setMailDriver(); //iterate through company_users - $this->company->company_users->each(function ($company_user) { + $this->company->company_users->each(function ($company_user) { //determine if this user has the right permissions $methods = $this->findCompanyUserNotificationType($company_user, ['payment_failure']); + //if mail is a method type -fire mail!! if (($key = array_search('mail', $methods)) !== false) { unset($methods[$key]); diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index 1b83db02500a..f2c48d6760af 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -333,13 +333,15 @@ class BaseDriver extends AbstractPaymentDriver public function processInternallyFailedPayment($gateway, $e) { - if ($e instanceof Exception) { - $error = $e->getMessage(); - } if ($e instanceof CheckoutHttpException) { $error = $e->getBody(); } + else if ($e instanceof Exception) { + $error = $e->getMessage(); + } + else + $error = $e->getMessage(); $amount = optional($this->payment_hash->data)->value ?? optional($this->payment_hash->data)->amount; diff --git a/app/PaymentDrivers/Stripe/Charge.php b/app/PaymentDrivers/Stripe/Charge.php index f68c1a693c98..373773d3eef9 100644 --- a/app/PaymentDrivers/Stripe/Charge.php +++ b/app/PaymentDrivers/Stripe/Charge.php @@ -77,7 +77,7 @@ class Charge 'confirm' => true, 'description' => $description, ]); - +info("attempting token billing"); SystemLogger::dispatch($response, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (CardException $e) { // Since it's a decline, \Stripe\Exception\CardException will be caught @@ -89,6 +89,7 @@ class Charge 'param' => $e->getError()->param, 'message' => $e->getError()->message, ]; + $this->stripe->processInternallyFailedPayment($this->stripe, $e); SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (RateLimitException $e) { @@ -101,7 +102,9 @@ class Charge 'param' => '', 'message' => 'Too many requests made to the API too quickly', ]; - + + $this->stripe->processInternallyFailedPayment($this->stripe, $e); + SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (InvalidRequestException $e) { // Invalid parameters were supplied to Stripe's API @@ -114,6 +117,8 @@ class Charge 'message' => 'Invalid parameters were supplied to Stripe\'s API', ]; + $this->stripe->processInternallyFailedPayment($this->stripe, $e); + SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (AuthenticationException $e) { // Authentication with Stripe's API failed @@ -126,6 +131,8 @@ class Charge 'message' => 'Authentication with Stripe\'s API failed', ]; + $this->stripe->processInternallyFailedPayment($this->stripe, $e); + SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (ApiConnectionException $e) { // Network communication with Stripe failed @@ -138,6 +145,8 @@ class Charge 'message' => 'Network communication with Stripe failed', ]; + $this->stripe->processInternallyFailedPayment($this->stripe, $e); + SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (ApiErrorException $e) { $data = [ @@ -148,6 +157,8 @@ class Charge 'message' => 'API Error', ]; + $this->stripe->processInternallyFailedPayment($this->stripe, $e); + SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (Exception $e) { // Something else happened, completely unrelated to Stripe @@ -160,6 +171,8 @@ class Charge 'message' => $e->getMessage(), ]; + $this->stripe->processInternallyFailedPayment($this->stripe, $e); + SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } diff --git a/app/Utils/Traits/Notifications/UserNotifies.php b/app/Utils/Traits/Notifications/UserNotifies.php index d5a85cde92f4..96a955fc691d 100644 --- a/app/Utils/Traits/Notifications/UserNotifies.php +++ b/app/Utils/Traits/Notifications/UserNotifies.php @@ -25,6 +25,7 @@ trait UserNotifies $notifiable_methods = []; $notifications = $company_user->notifications; + //if a user owns this record or is assigned to it, they are attached the permission for notification. if ($invitation->{$entity_name}->user_id == $company_user->_user_id || $invitation->{$entity_name}->assigned_user_id == $company_user->user_id) { array_push($required_permissions, 'all_user_notifications'); } From 5bc8f4d1dce0c9eaacddc611ac8c87875bfcc5dc Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 1 Feb 2021 21:07:00 +1100 Subject: [PATCH 2/5] Fixes for texts --- resources/lang/en/texts.php | 445 ++++++++++++++++++------------------ 1 file changed, 225 insertions(+), 220 deletions(-) diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index a48e91d75b4d..df9560565a5a 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -1,6 +1,6 @@ 'Organization', 'name' => 'Name', 'website' => 'Website', @@ -3917,222 +3917,227 @@ return [ 'to_update_run' => 'To update run', 'registration_url' => 'Registration URL', 'show_product_cost' => 'Show Product Cost', - "complete" => "Complete", - "next" => "Next", - "next_step" => "Next step", - "notification_credit_sent_subject" => "Credit :invoice was sent to :client", - "notification_credit_viewed_subject" => "Credit :invoice was viewed by :client", - "notification_credit_sent" => "The following client :client was emailed Credit :invoice for :amount.", - "notification_credit_viewed" => "The following client :client viewed Credit :credit for :amount.", - "reset_password_text" => "Enter your email to reset your password.", - "password_reset" => "Password reset", - "account_login_text" => "Welcome back! Glad to see you.", - "request_cancellation" => "Request cancellation", - "delete_payment_method" => "Delete Payment Method", - "about_to_delete_payment_method" => "You are about to delete the payment method.", - "action_cant_be_reversed" => "Action can't be reversed", - "profile_updated_successfully" => "The profile has been updated successfully.", - "currency_ethiopian_birr" => "Ethiopian Birr", - "client_information_text" => "Use a permanent address where you can receive mail.", - "status_id" => "Invoice Status", - "email_already_register" => "This email is already linked to an account", - "locations" => "Locations", - "freq_indefinitely" => "Indefinitely", - "cycles_remaining" => "Cycles remaining", - "i_understand_delete" => "I understand, delete", - "download_files" => "Download Files", - "download_timeframe" => "Use this link to download your files, the link will expire in 1 hour.", - "new_signup" => "New Signup", - "new_signup_text" => "A new account has been created by :user - :email - from IP address: :ip", - "notification_payment_paid_subject" => "Payment was made by :client", - "notification_partial_payment_paid_subject" => "Partial payment was made by :client", - "notification_payment_paid" => "A payment of :amount was made by client :client towards :invoice", - "notification_partial_payment_paid" => "A partial payment of :amount was made by client :client towards :invoice", - "notification_bot" => "Notification Bot", - "invoice_number_placeholder" => "Invoice # :invoice", - "entity_number_placeholder" => ":entity # :entity_number", - "email_link_not_working" => "If button above isn't working for you, please click on the link", - "display_log" => "Display Log", - "send_fail_logs_to_our_server" => "Report errors in realtime", - "setup" => "Setup", - "quick_overview_statistics" => "Quick overview & statistics", - "update_your_personal_info" => "Update your personal information", - "name_website_logo" => "Name, website & logo", - "make_sure_use_full_link" => "Make sure you use full link to your site", - "personal_address" => "Personal address", - "enter_your_personal_address" => "Enter your personal address", - "enter_your_shipping_address" => "Enter your shipping address", - "list_of_invoices" => "List of invoices", - "with_selected" => "With selected", - "invoice_still_unpaid" => "This invoice is still not paid. Click the button to complete the payment", - "list_of_recurring_invoices" => "List of recurring invoices", - "details_of_recurring_invoice" => "Here are some details about recurring invoice", - "cancellation" => "Cancellation", - "about_cancellation" => "In case you want to stop the recurring invoice,\n please click the request the cancellation.", - "cancellation_warning" => "Warning! You are requesting a cancellation of this service.\n Your service may be cancelled with no further notification to you.", - "cancellation_pending" => "Cancellation pending, we'll be in touch!", - "list_of_payments" => "List of payments", - "payment_details" => "Details of the payment", - "list_of_payment_invoices" => "List of invoices affected by the payment", - "list_of_payment_methods" => "List of payment methods", - "payment_method_details" => "Details of payment method", - "permanently_remove_payment_method" => "Permanently remove this payment method.", - "warning_action_cannot_be_reversed" => "Warning! This action can't be reversed!", - "confirmation" => "Confirmation", - "list_of_quotes" => "Quotes", - "waiting_for_approval" => "Waiting for approval", - "quote_still_not_approved" => "This quote is still not approved", - "list_of_credits" => "Credits", - "required_extensions" => "Required extensions", - "php_version" => "PHP version", - "writable_env_file" => "Writable .env file", - "env_not_writable" => ".env file is not writable by the current user.", - "minumum_php_version" => "Minimum PHP version", - "satisfy_requirements" => "Make sure all requirements are satisfied.", - "oops_issues" => "Oops, something doesn't look right!", - "open_in_new_tab" => "Open in new tab", - "complete_your_payment" => "Complete payment", - "authorize_for_future_use" => "Authorize payment method for future use", - "page" => "Page", - "per_page" => "Per page", - "of" => "Of", - "view_credit" => "View Credit", - "to_view_entity_password" => "To view the :entity you need to enter password.", - "showing_x_of" => "Showing :first to :last out of :total results", - "no_results" => "No results found.", - "payment_failed_subject" => "Payment failed for Client :client", - "payment_failed_body" => "A payment made by client :client failed with message :message", - "register" => "Register", - "register_label" => "Create your account in seconds", - "password_confirmation" => "Confirm your password", - "verification" => "Verification", - "complete_your_bank_account_verification" => "Before using a bank account it must be verified.", - "checkout_com" => "Checkout.com", - "footer_label" => "Copyright © :year :company.", - "credit_card_invalid" => "Provided credit card number is not valid.", - "month_invalid" => "Provided month is not valid.", - "year_invalid" => "Provided year is not valid.", - "https_required" => "HTTPS is required, form will fail", - "if_you_need_help" => "If you need help you can post to our", - "update_password_on_confirm" => "After updating password, your account will be confirmed.", - "bank_account_not_linked" => "To pay with a bank account, first you have to add it as payment method.", - "application_settings_label" => "Let's store basic information about your Invoice Ninja!", - "recommended_in_production" => "Highly recommended in production", - "enable_only_for_development" => "Enable only for development", - "test_pdf" => "Test PDF", - "checkout_authorize_label" => "Checkout.com can be can saved as payment method for future use, once you complete your first transaction. Don't forget to check 'Store credit card details' during payment process.", - "sofort_authorize_label" => "Bank account (SOFORT) can be can saved as payment method for future use, once you complete your first transaction. Don't forget to check 'Store payment details' during payment process.", - "node_status" => "Node status", - "npm_status" => "NPM status", - "node_status_not_found" => "I could not find Node anywhere. Is it installed?", - "npm_status_not_found" => "I could not find NPM anywhere. Is it installed?", - "locked_invoice" => "This invoice is locked and unable to be modified", - "downloads" => "Downloads", - "resource" => "Resource", - "document_details" => "Details about the document", - "hash" => "Hash", - "resources" => "Resources", - "allowed_file_types" => "Allowed file types:", - "common_codes" => "Common codes and their meanings", - "payment_error_code_20087" => "20087: Bad Track Data (invalid CVV and/or expiry date)", - "download_selected" => "Download selected", - "to_pay_invoices" => "To pay invoices, you have to", - "add_payment_method_first" => "add payment method", - "no_items_selected" => "No items selected.", - "payment_due" => "Payment due", - "account_balance" => "Account balance", - "thanks" => "Thanks", - "minimum_required_payment" => "Minimum required payment is :amount", - "under_payments_disabled" => "Company doesn't support under payments.", - "over_payments_disabled" => "Company doesn't support over payments.", - "saved_at" => "Saved at :time", - "credit_payment" => "Credit applied to Invoice :invoice_number", - "credit_subject" => "New credit :number from :account", - "credit_message" => "To view your credit for :amount, click the link below.", - "payment_type_Crypto" => "Cryptocurrency", - "payment_type_Credit" => "Credit", - "store_for_future_use" => "Store for future use", - "pay_with_credit" => "Pay with credit", - "payment_method_saving_failed" => "Payment method can't be saved for future use.", - "pay_with" => "Pay with", - "n/a" => "N/A", - "by_clicking_next_you_accept_terms" => "By clicking 'Next step' you accept terms.", - "not_specified" => "Not specified", - "before_proceeding_with_payment_warning" => "Before proceeding with payment, you have to fill following fields", - "after_completing_go_back_to_previous_page" => "After completing, go back to previous page.", - "pay" => "Pay", - "instructions" => "Instructions", - "notification_invoice_reminder1_sent_subject" => "Reminder 1 for Invoice :invoice was sent to :client", - "notification_invoice_reminder2_sent_subject" => "Reminder 2 for Invoice :invoice was sent to :client", - "notification_invoice_reminder3_sent_subject" => "Reminder 3 for Invoice :invoice was sent to :client", - "notification_invoice_reminder_endless_sent_subject" => "Endless reminder for Invoice :invoice was sent to :client", - "assigned_user" => "Assigned User", - "setup_steps_notice" => "To proceed to next step, make sure you test each section.", - "setup_phantomjs_note" => "Note about Phantom JS. Read more.", - "minimum_payment" => "Minimum Payment", - "no_action_provided" => "No action provided. If you believe this is wrong, please contact the support.", - "no_payable_invoices_selected" => "No payable invoices selected. Make sure you are not trying to pay draft invoice or invoice with zero balance due.", - "required_payment_information" => "Required payment details", - "required_payment_information_more" => "To complete a payment we need more details about you.", - "required_client_info_save_label" => "We will save this, so you don't have to enter it next time.", - "notification_credit_bounced" => "We were unable to deliver Credit :invoice to :contact. \n :error", - "notification_credit_bounced_subject" => "Unable to deliver Credit :invoice", - "save_payment_method_details" => "Save payment method details", - "new_card" => "New card", - "new_bank_account" => "New bank account", - "company_limit_reached" => "Limit of 10 companies per account.", - "credits_applied_validation" => "Total credits applied cannot be MORE than total of invoices", - "credit_number_taken" => "Credit number already taken", - "credit_not_found" => "Credit not found", - "invoices_dont_match_client" => "Selected invoices are not from a single client", - "duplicate_credits_submitted" => "Duplicate credits submitted.", - "duplicate_invoices_submitted" => "Duplicate invoices submitted.", - "credit_with_no_invoice" => "You must have an invoice set when using a credit in a payment", - "client_id_required" => "Client id is required", - "expense_number_taken" => "Expense number already taken", - "invoice_number_taken" => "Invoice number already taken", - "payment_id_required" => "Payment `id` required.", - "unable_to_retrieve_payment" => "Unable to retrieve specified payment", - "invoice_not_related_to_payment" => "Invoice id :invoice is not related to this payment", - "credit_not_related_to_payment" => "Credit id :credit is not related to this payment", - "max_refundable_invoice" => "Attempting to refund more than allowed for invoice id :invoice, maximum refundable amount is :amount", - "refund_without_invoices" => "Attempting to refund a payment with invoices attached, please specify valid invoice/s to be refunded.", - "refund_without_credits" => "Attempting to refund a payment with credits attached, please specify valid credits/s to be refunded.", - "max_refundable_credit" => "Attempting to refund more than allowed for credit :credit, maximum refundable amount is :amount", - "project_client_do_not_match" => "Project client does not match entity client", - "quote_number_taken" => "Quote number already taken", - "recurring_invoice_number_taken" => "Recurring Invoice number :number already taken", - "user_not_associated_with_account" => "User not associated with this account", - "amounts_do_not_balance" => "Amounts do not balance correctly.", - "insufficient_applied_amount_remaining" => "Insufficient applied amount remaining to cover payment.", - "insufficient_credit_balance" => "Insufficient balance on credit.", - "one_or_more_invoices_paid" => "One or more of these invoices have been paid", - "invoice_cannot_be_refunded" => "Invoice id :number cannot be refunded", - "attempted_refund_failed" => "Attempting to refund :amount only :refundable_amount available for refund", - "user_not_associated_with_this_account" => "This user is unable to be attached to this company. Perhaps they have already registered a user on another account?", - "migration_completed" => "Migration completed", - "migration_completed_description" => "Your migration has completed, please review your data after logging in.", - "api_404" => "404 | Nothing to see here!", - "large_account_update_parameter" => "Cannot load a large account without a updated_at parameter", - "no_backup_exists" => "No backup exists for this activity", - "company_user_not_found" => "Company User record not found", - "no_credits_found" => "No credits found.", - "action_unavailable" => "The requested action :action is not available.", - "no_documents_found" => "No Documents Found", - "no_group_settings_found" => "No group settings found", - "access_denied" => "Insufficient privileges to access/modify this resource", - "invoice_cannot_be_marked_paid" => "Invoice cannot be marked as paid", - "invoice_license_or_environment" => "Invalid license, or invalid environment :environment", - "route_not_available" => "Route not available", - "invalid_design_object" => "Invalid custom design object", - "quote_not_found" => "Quote/s not found", - "quote_unapprovable" => "Unable to approve this quote as it has expired.", - "scheduler_has_run" => "Scheduler has run", - "scheduler_has_never_run" => "Scheduler has never run", - "self_update_not_available" => "Self update not available on this system.", - "user_detached" => "User detached from company", - "create_webhook_failure" => "Failed to create Webhook", - "payment_message_extended" => "Thank you for your payment of :amount for :invoice", - "online_payments_minimum_note" => "Note: Online payments are supported only if amount is bigger than $1 or currency equivalent.", - "payment_token_not_found" => "Payment token not found, please try again. If an issue still persist, try with another payment method", -]; + 'complete' => 'Complete', + 'next' => 'Next', + 'next_step' => 'Next step', + 'notification_credit_sent_subject' => 'Credit :invoice was sent to :client', + 'notification_credit_viewed_subject' => 'Credit :invoice was viewed by :client', + 'notification_credit_sent' => 'The following client :client was emailed Credit :invoice for :amount.', + 'notification_credit_viewed' => 'The following client :client viewed Credit :credit for :amount.', + 'reset_password_text' => 'Enter your email to reset your password.', + 'password_reset' => 'Password reset', + 'account_login_text' => 'Welcome back! Glad to see you.', + 'request_cancellation' => 'Request cancellation', + 'delete_payment_method' => 'Delete Payment Method', + 'about_to_delete_payment_method' => 'You are about to delete the payment method.', + 'action_cant_be_reversed' => 'Action can\'t be reversed', + 'profile_updated_successfully' => 'The profile has been updated successfully.', + 'currency_ethiopian_birr' => 'Ethiopian Birr', + 'client_information_text' => 'Use a permanent address where you can receive mail.', + 'status_id' => 'Invoice Status', + 'email_already_register' => 'This email is already linked to an account', + 'locations' => 'Locations', + 'freq_indefinitely' => 'Indefinitely', + 'cycles_remaining' => 'Cycles remaining', + 'i_understand_delete' => 'I understand, delete', + 'download_files' => 'Download Files', + 'download_timeframe' => 'Use this link to download your files, the link will expire in 1 hour.', + 'new_signup' => 'New Signup', + 'new_signup_text' => 'A new account has been created by :user - :email - from IP address: :ip', + 'notification_payment_paid_subject' => 'Payment was made by :client', + 'notification_partial_payment_paid_subject' => 'Partial payment was made by :client', + 'notification_payment_paid' => 'A payment of :amount was made by client :client towards :invoice', + 'notification_partial_payment_paid' => 'A partial payment of :amount was made by client :client towards :invoice', + 'notification_bot' => 'Notification Bot', + 'invoice_number_placeholder' => 'Invoice # :invoice', + 'entity_number_placeholder' => ':entity # :entity_number', + 'email_link_not_working' => 'If the button above isn\'t working for you, please click on the link', + 'display_log' => 'Display Log', + 'send_fail_logs_to_our_server' => 'Report errors in realtime', + 'setup' => 'Setup', + 'quick_overview_statistics' => 'Quick overview & statistics', + 'update_your_personal_info' => 'Update your personal information', + 'name_website_logo' => 'Name, website & logo', + 'make_sure_use_full_link' => 'Make sure you use full link to your site', + 'personal_address' => 'Personal address', + 'enter_your_personal_address' => 'Enter your personal address', + 'enter_your_shipping_address' => 'Enter your shipping address', + 'list_of_invoices' => 'List of invoices', + 'with_selected' => 'With selected', + 'invoice_still_unpaid' => 'This invoice is still not paid. Click the button to complete the payment', + 'list_of_recurring_invoices' => 'List of recurring invoices', + 'details_of_recurring_invoice' => 'Here are some details about recurring invoice', + 'cancellation' => 'Cancellation', + 'about_cancellation' => 'In case you want to stop the recurring invoice,\n please click the request the cancellation.', + 'cancellation_warning' => 'Warning! You are requesting a cancellation of this service.\n Your service may be cancelled with no further notification to you.', + 'cancellation_pending' => 'Cancellation pending, we\'ll be in touch!', + 'list_of_payments' => 'List of payments', + 'payment_details' => 'Details of the payment', + 'list_of_payment_invoices' => 'List of invoices affected by the payment', + 'list_of_payment_methods' => 'List of payment methods', + 'payment_method_details' => 'Details of payment method', + 'permanently_remove_payment_method' => 'Permanently remove this payment method.', + 'warning_action_cannot_be_reversed' => 'Warning! This action can not be reversed!', + 'confirmation' => 'Confirmation', + 'list_of_quotes' => 'Quotes', + 'waiting_for_approval' => 'Waiting for approval', + 'quote_still_not_approved' => 'This quote is still not approved', + 'list_of_credits' => 'Credits', + 'required_extensions' => 'Required extensions', + 'php_version' => 'PHP version', + 'writable_env_file' => 'Writable .env file', + 'env_not_writable' => '.env file is not writable by the current user.', + 'minumum_php_version' => 'Minimum PHP version', + 'satisfy_requirements' => 'Make sure all requirements are satisfied.', + 'oops_issues' => 'Oops, something does not look right!', + 'open_in_new_tab' => 'Open in new tab', + 'complete_your_payment' => 'Complete payment', + 'authorize_for_future_use' => 'Authorize payment method for future use', + 'page' => 'Page', + 'per_page' => 'Per page', + 'of' => 'Of', + 'view_credit' => 'View Credit', + 'to_view_entity_password' => 'To view the :entity you need to enter password.', + 'showing_x_of' => 'Showing :first to :last out of :total results', + 'no_results' => 'No results found.', + 'payment_failed_subject' => 'Payment failed for Client :client', + 'payment_failed_body' => 'A payment made by client :client failed with message :message', + 'register' => 'Register', + 'register_label' => 'Create your account in seconds', + 'password_confirmation' => 'Confirm your password', + 'verification' => 'Verification', + 'complete_your_bank_account_verification' => 'Before using a bank account it must be verified.', + 'checkout_com' => 'Checkout.com', + 'footer_label' => 'Copyright © :year :company.', + 'credit_card_invalid' => 'Provided credit card number is not valid.', + 'month_invalid' => 'Provided month is not valid.', + 'year_invalid' => 'Provided year is not valid.', + 'https_required' => 'HTTPS is required, form will fail', + 'if_you_need_help' => 'If you need help you can post to our', + 'update_password_on_confirm' => 'After updating password, your account will be confirmed.', + 'bank_account_not_linked' => 'To pay with a bank account, first you have to add it as payment method.', + 'application_settings_label' => 'Let\'s store basic information about your Invoice Ninja!', + 'recommended_in_production' => 'Highly recommended in production', + 'enable_only_for_development' => 'Enable only for development', + 'test_pdf' => 'Test PDF', + 'checkout_authorize_label' => 'Checkout.com can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store credit card details" during payment process.', + 'sofort_authorize_label' => 'Bank account (SOFORT) can be can saved as payment method for future use, once you complete your first transaction. Don\'t forget to check "Store payment details" during payment process.', + 'node_status' => 'Node status', + 'npm_status' => 'NPM status', + 'node_status_not_found' => 'I could not find Node anywhere. Is it installed?', + 'npm_status_not_found' => 'I could not find NPM anywhere. Is it installed?', + 'locked_invoice' => 'This invoice is locked and unable to be modified', + 'downloads' => 'Downloads', + 'resource' => 'Resource', + 'document_details' => 'Details about the document', + 'hash' => 'Hash', + 'resources' => 'Resources', + 'allowed_file_types' => 'Allowed file types:', + 'common_codes' => 'Common codes and their meanings', + 'payment_error_code_20087' => '20087: Bad Track Data (invalid CVV and/or expiry date)', + 'download_selected' => 'Download selected', + 'to_pay_invoices' => 'To pay invoices, you have to', + 'add_payment_method_first' => 'add payment method', + 'no_items_selected' => 'No items selected.', + 'payment_due' => 'Payment due', + 'account_balance' => 'Account balance', + 'thanks' => 'Thanks', + 'minimum_required_payment' => 'Minimum required payment is :amount', + 'under_payments_disabled' => 'Company doesn\'t support under payments.', + 'over_payments_disabled' => 'Company doesn\'t support over payments.', + 'saved_at' => 'Saved at :time', + 'credit_payment' => 'Credit applied to Invoice :invoice_number', + 'credit_subject' => 'New credit :number from :account', + 'credit_message' => 'To view your credit for :amount, click the link below.', + 'payment_type_Crypto' => 'Cryptocurrency', + 'payment_type_Credit' => 'Credit', + 'store_for_future_use' => 'Store for future use', + 'pay_with_credit' => 'Pay with credit', + 'payment_method_saving_failed' => 'Payment method can\'t be saved for future use.', + 'pay_with' => 'Pay with', + 'n/a' => 'N/A', + 'by_clicking_next_you_accept_terms' => 'By clicking "Next step" you accept terms.', + 'not_specified' => 'Not specified', + 'before_proceeding_with_payment_warning' => 'Before proceeding with payment, you have to fill following fields', + 'after_completing_go_back_to_previous_page' => 'After completing, go back to previous page.', + 'pay' => 'Pay', + 'instructions' => 'Instructions', + 'notification_invoice_reminder1_sent_subject' => 'Reminder 1 for Invoice :invoice was sent to :client', + 'notification_invoice_reminder2_sent_subject' => 'Reminder 2 for Invoice :invoice was sent to :client', + 'notification_invoice_reminder3_sent_subject' => 'Reminder 3 for Invoice :invoice was sent to :client', + 'notification_invoice_reminder_endless_sent_subject' => 'Endless reminder for Invoice :invoice was sent to :client', + 'assigned_user' => 'Assigned User', + 'setup_steps_notice' => 'To proceed to next step, make sure you test each section.', + 'setup_phantomjs_note' => 'Note about Phantom JS. Read more.', + 'minimum_payment' => 'Minimum Payment', + 'no_action_provided' => 'No action provided. If you believe this is wrong, please contact the support.', + 'no_payable_invoices_selected' => 'No payable invoices selected. Make sure you are not trying to pay draft invoice or invoice with zero balance due.', + 'required_payment_information' => 'Required payment details', + 'required_payment_information_more' => 'To complete a payment we need more details about you.', + 'required_client_info_save_label' => 'We will save this, so you don\'t have to enter it next time.', + 'notification_credit_bounced' => 'We were unable to deliver Credit :invoice to :contact. \n :error', + 'notification_credit_bounced_subject' => 'Unable to deliver Credit :invoice', + 'save_payment_method_details' => 'Save payment method details', + 'new_card' => 'New card', + 'new_bank_account' => 'New bank account', + 'company_limit_reached' => 'Limit of 10 companies per account.', + 'credits_applied_validation' => 'Total credits applied cannot be MORE than total of invoices', + 'credit_number_taken' => 'Credit number already taken', + 'credit_not_found' => 'Credit not found', + 'invoices_dont_match_client' => 'Selected invoices are not from a single client', + 'duplicate_credits_submitted' => 'Duplicate credits submitted.', + 'duplicate_invoices_submitted' => 'Duplicate invoices submitted.', + 'credit_with_no_invoice' => 'You must have an invoice set when using a credit in a payment', + 'client_id_required' => 'Client id is required', + 'expense_number_taken' => 'Expense number already taken', + 'invoice_number_taken' => 'Invoice number already taken', + 'payment_id_required' => 'Payment `id` required.', + 'unable_to_retrieve_payment' => 'Unable to retrieve specified payment', + 'invoice_not_related_to_payment' => 'Invoice id :invoice is not related to this payment', + 'credit_not_related_to_payment' => 'Credit id :credit is not related to this payment', + 'max_refundable_invoice' => 'Attempting to refund more than allowed for invoice id :invoice, maximum refundable amount is :amount', + 'refund_without_invoices' => 'Attempting to refund a payment with invoices attached, please specify valid invoice/s to be refunded.', + 'refund_without_credits' => 'Attempting to refund a payment with credits attached, please specify valid credits/s to be refunded.', + 'max_refundable_credit' => 'Attempting to refund more than allowed for credit :credit, maximum refundable amount is :amount', + 'project_client_do_not_match' => 'Project client does not match entity client', + 'quote_number_taken' => 'Quote number already taken', + 'recurring_invoice_number_taken' => 'Recurring Invoice number :number already taken', + 'user_not_associated_with_account' => 'User not associated with this account', + 'amounts_do_not_balance' => 'Amounts do not balance correctly.', + 'insufficient_applied_amount_remaining' => 'Insufficient applied amount remaining to cover payment.', + 'insufficient_credit_balance' => 'Insufficient balance on credit.', + 'one_or_more_invoices_paid' => 'One or more of these invoices have been paid', + 'invoice_cannot_be_refunded' => 'Invoice id :number cannot be refunded', + 'attempted_refund_failed' => 'Attempting to refund :amount only :refundable_amount available for refund', + 'user_not_associated_with_this_account' => 'This user is unable to be attached to this company. Perhaps they have already registered a user on another account?', + 'migration_completed' => 'Migration completed', + 'migration_completed_description' => 'Your migration has completed, please review your data after logging in.', + 'api_404' => '404 | Nothing to see here!', + 'large_account_update_parameter' => 'Cannot load a large account without a updated_at parameter', + 'no_backup_exists' => 'No backup exists for this activity', + 'company_user_not_found' => 'Company User record not found', + 'no_credits_found' => 'No credits found.', + 'action_unavailable' => 'The requested action :action is not available.', + 'no_documents_found' => 'No Documents Found', + 'no_group_settings_found' => 'No group settings found', + 'access_denied' => 'Insufficient privileges to access/modify this resource', + 'invoice_cannot_be_marked_paid' => 'Invoice cannot be marked as paid', + 'invoice_license_or_environment' => 'Invalid license, or invalid environment :environment', + 'route_not_available' => 'Route not available', + 'invalid_design_object' => 'Invalid custom design object', + 'quote_not_found' => 'Quote/s not found', + 'quote_unapprovable' => 'Unable to approve this quote as it has expired.', + 'scheduler_has_run' => 'Scheduler has run', + 'scheduler_has_never_run' => 'Scheduler has never run', + 'self_update_not_available' => 'Self update not available on this system.', + 'user_detached' => 'User detached from company', + 'create_webhook_failure' => 'Failed to create Webhook', + 'payment_message_extended' => 'Thank you for your payment of :amount for :invoice', + 'online_payments_minimum_note' => 'Note: Online payments are supported only if amount is bigger than $1 or currency equivalent.', + 'payment_token_not_found' => 'Payment token not found, please try again. If an issue still persist, try with another payment method', + +); + +return $LANG; + +?> From 2ab99e8132fa4f140a8ccae724dc8be19a6901d7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 1 Feb 2021 22:26:42 +1100 Subject: [PATCH 3/5] AutoBilling failure mailer --- app/Jobs/Mail/AutoBillingFailureMailer.php | 109 ++++++++++++++++++ app/Jobs/Mail/PaymentFailureMailer.php | 3 +- app/Mail/Admin/AutoBillingFailureObject.php | 105 +++++++++++++++++ app/Mail/Admin/PaymentFailureObject.php | 4 +- app/PaymentDrivers/BaseDriver.php | 5 +- .../Traits/Notifications/UserNotifies.php | 2 + 6 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 app/Jobs/Mail/AutoBillingFailureMailer.php create mode 100644 app/Mail/Admin/AutoBillingFailureObject.php diff --git a/app/Jobs/Mail/AutoBillingFailureMailer.php b/app/Jobs/Mail/AutoBillingFailureMailer.php new file mode 100644 index 000000000000..3ccd0c420005 --- /dev/null +++ b/app/Jobs/Mail/AutoBillingFailureMailer.php @@ -0,0 +1,109 @@ +client = $client; + + $this->error = $error; + + $this->company = $company; + + $this->payment_hash = $payment_hash; + + $this->company = $company; + + $this->settings = $client->getMergedSettings(); + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + + /*If we are migrating data we don't want to fire these notification*/ + if ($this->company->is_disabled) { + return true; + } + + //Set DB + MultiDB::setDb($this->company->db); + + //if we need to set an email driver do it now + $this->setMailDriver(); + + //iterate through company_users + $this->company->company_users->each(function ($company_user) { + + //determine if this user has the right permissions + $methods = $this->findCompanyUserNotificationType($company_user, ['payment_failure','all_notifications']); + + //if mail is a method type -fire mail!! + if (($key = array_search('mail', $methods)) !== false) { + unset($methods[$key]); + + $mail_obj = (new AutoBillingFailureObject($this->client, $this->error, $this->company, $this->payment_hash))->build(); + $mail_obj->from = [config('mail.from.address'), config('mail.from.name')]; + + //send email + try { + Mail::to($company_user->user->email) + ->send(new EntityNotificationMailer($mail_obj)); + } catch (\Exception $e) { + //$this->failed($e); + $this->logMailError($e->getMessage(), $this->client); + } + } + }); + } +} diff --git a/app/Jobs/Mail/PaymentFailureMailer.php b/app/Jobs/Mail/PaymentFailureMailer.php index 27543a98c1d4..e51ebe468719 100644 --- a/app/Jobs/Mail/PaymentFailureMailer.php +++ b/app/Jobs/Mail/PaymentFailureMailer.php @@ -69,7 +69,6 @@ class PaymentFailureMailer extends BaseMailerJob implements ShouldQueue */ public function handle() { - nlog("payment failure mailer "); /*If we are migrating data we don't want to fire these notification*/ if ($this->company->is_disabled) { @@ -86,7 +85,7 @@ class PaymentFailureMailer extends BaseMailerJob implements ShouldQueue $this->company->company_users->each(function ($company_user) { //determine if this user has the right permissions - $methods = $this->findCompanyUserNotificationType($company_user, ['payment_failure']); + $methods = $this->findCompanyUserNotificationType($company_user, ['payment_failure','all_notifications']); //if mail is a method type -fire mail!! diff --git a/app/Mail/Admin/AutoBillingFailureObject.php b/app/Mail/Admin/AutoBillingFailureObject.php new file mode 100644 index 000000000000..e6cfd14103b5 --- /dev/null +++ b/app/Mail/Admin/AutoBillingFailureObject.php @@ -0,0 +1,105 @@ +client = $client; + + $this->error = $error; + + $this->company = $company; + + $this->payment_hash = $payment_hash; + + $this->company = $company; + + } + + public function build() + { + + $this->invoice = Invoice::where('id', $this->decodePrimarykey($this->payment_hash->invoices()[0]->invoice_id))->first(); + + $mail_obj = new stdClass; + $mail_obj->amount = $this->getAmount(); + $mail_obj->subject = $this->getSubject(); + $mail_obj->data = $this->getData(); + $mail_obj->markdown = 'email.admin.generic'; + $mail_obj->tag = $this->company->company_key; + + return $mail_obj; + } + + private function getAmount() + { + return array_sum(array_column($this->payment_hash->invoices(), 'amount')) + $this->payment_hash->fee_total; + } + + private function getSubject() + { + + return + ctrans( + 'texts.auto_bill_failed', + ['invoice_number' => $this->invoice->number] + ); + } + + private function getData() + { + $signature = $this->client->getSetting('email_signature'); + + $data = [ + 'title' => ctrans( + 'texts.auto_bill_failed', + ['invoice_number' => $this->invoice->number] + ), + 'message' => $this->error, + 'signature' => $signature, + 'logo' => $this->company->present()->logo(), + 'settings' => $this->client->getMergedSettings(), + 'whitelabel' => $this->company->account->isPaid() ? true : false, + 'url' => config('ninja.app_url'), + 'button' => ctrans('texts.login'), + ]; + + return $data; + } +} diff --git a/app/Mail/Admin/PaymentFailureObject.php b/app/Mail/Admin/PaymentFailureObject.php index e0b58025edf9..b7e693d00bfe 100644 --- a/app/Mail/Admin/PaymentFailureObject.php +++ b/app/Mail/Admin/PaymentFailureObject.php @@ -54,7 +54,7 @@ class PaymentFailureObject return ctrans( 'texts.payment_failed_subject', - ['client' => $this->payment->client->present()->name()] + ['client' => $this->client->present()->name()] ); } @@ -78,6 +78,8 @@ class PaymentFailureObject 'logo' => $this->company->present()->logo(), 'settings' => $this->client->getMergedSettings(), 'whitelabel' => $this->company->account->isPaid() ? true : false, + 'url' => config('ninja.app_url'), + 'button' => ctrans('texts.login'), ]; return $data; diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index f2c48d6760af..11ff29606185 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -16,6 +16,7 @@ use App\Events\Payment\PaymentWasCreated; use App\Exceptions\PaymentFailed; use App\Factory\PaymentFactory; use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest; +use App\Jobs\Mail\AutoBillingFailureMailer; use App\Jobs\Mail\PaymentFailureMailer; use App\Jobs\Util\SystemLogger; use App\Models\Client; @@ -345,11 +346,11 @@ class BaseDriver extends AbstractPaymentDriver $amount = optional($this->payment_hash->data)->value ?? optional($this->payment_hash->data)->amount; - PaymentFailureMailer::dispatch( + AutoBillingFailureMailer::dispatch( $gateway->client, $error, $gateway->client->company, - $amount + $this->payment_hash ); SystemLogger::dispatch( diff --git a/app/Utils/Traits/Notifications/UserNotifies.php b/app/Utils/Traits/Notifications/UserNotifies.php index 96a955fc691d..e05689359ee1 100644 --- a/app/Utils/Traits/Notifications/UserNotifies.php +++ b/app/Utils/Traits/Notifications/UserNotifies.php @@ -66,6 +66,8 @@ trait UserNotifies public function findCompanyUserNotificationType($company_user, $required_permissions) :array { + nlog("find company user notification type"); + if ($company_user->company->is_disabled) { return []; } From 58970fab99bcd4f661e2e9a70d7ba1ef6b1d430f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 1 Feb 2021 23:41:11 +1100 Subject: [PATCH 4/5] payment failure emails --- app/PaymentDrivers/BaseDriver.php | 32 +++++++++++++++++-- app/PaymentDrivers/Stripe/Charge.php | 16 +++++----- .../Traits/Notifications/UserNotifies.php | 1 - 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index 11ff29606185..9b7dea308e3d 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -344,8 +344,6 @@ class BaseDriver extends AbstractPaymentDriver else $error = $e->getMessage(); - $amount = optional($this->payment_hash->data)->value ?? optional($this->payment_hash->data)->amount; - AutoBillingFailureMailer::dispatch( $gateway->client, $error, @@ -364,6 +362,36 @@ class BaseDriver extends AbstractPaymentDriver throw new PaymentFailed($error, $e->getCode()); } + public function tokenBillingFailed($gateway, $e) + { + $this->unWindGatewayFees($this->payment_hash); + + if ($e instanceof CheckoutHttpException) { + $error = $e->getBody(); + } + else if ($e instanceof Exception) { + $error = $e->getMessage(); + } + else + $error = $e->getMessage(); + + AutoBillingFailureMailer::dispatch( + $gateway->client, + $error, + $gateway->client->company, + $this->payment_hash + ); + + SystemLogger::dispatch( + $gateway->payment_hash, + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_ERROR, + $gateway::SYSTEM_LOG_TYPE, + $gateway->client, + ); + + } + /** * Wrapper method for checking if resource is good. * diff --git a/app/PaymentDrivers/Stripe/Charge.php b/app/PaymentDrivers/Stripe/Charge.php index 373773d3eef9..44d6642f850a 100644 --- a/app/PaymentDrivers/Stripe/Charge.php +++ b/app/PaymentDrivers/Stripe/Charge.php @@ -77,7 +77,7 @@ class Charge 'confirm' => true, 'description' => $description, ]); -info("attempting token billing"); + SystemLogger::dispatch($response, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (CardException $e) { // Since it's a decline, \Stripe\Exception\CardException will be caught @@ -89,7 +89,7 @@ info("attempting token billing"); 'param' => $e->getError()->param, 'message' => $e->getError()->message, ]; - $this->stripe->processInternallyFailedPayment($this->stripe, $e); + $this->stripe->tokenBillingFailed($this->stripe, $e); SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (RateLimitException $e) { @@ -103,7 +103,7 @@ info("attempting token billing"); 'message' => 'Too many requests made to the API too quickly', ]; - $this->stripe->processInternallyFailedPayment($this->stripe, $e); + $this->stripe->tokenBillingFailed($this->stripe, $e); SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (InvalidRequestException $e) { @@ -117,7 +117,7 @@ info("attempting token billing"); 'message' => 'Invalid parameters were supplied to Stripe\'s API', ]; - $this->stripe->processInternallyFailedPayment($this->stripe, $e); + $this->stripe->tokenBillingFailed($this->stripe, $e); SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (AuthenticationException $e) { @@ -131,7 +131,7 @@ info("attempting token billing"); 'message' => 'Authentication with Stripe\'s API failed', ]; - $this->stripe->processInternallyFailedPayment($this->stripe, $e); + $this->stripe->tokenBillingFailed($this->stripe, $e); SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (ApiConnectionException $e) { @@ -145,7 +145,7 @@ info("attempting token billing"); 'message' => 'Network communication with Stripe failed', ]; - $this->stripe->processInternallyFailedPayment($this->stripe, $e); + $this->stripe->tokenBillingFailed($this->stripe, $e); SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (ApiErrorException $e) { @@ -157,7 +157,7 @@ info("attempting token billing"); 'message' => 'API Error', ]; - $this->stripe->processInternallyFailedPayment($this->stripe, $e); + $this->stripe->tokenBillingFailed($this->stripe, $e); SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } catch (Exception $e) { @@ -171,7 +171,7 @@ info("attempting token billing"); 'message' => $e->getMessage(), ]; - $this->stripe->processInternallyFailedPayment($this->stripe, $e); + $this->stripe->tokenBillingFailed($this->stripe, $e); SystemLogger::dispatch($data, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_STRIPE, $this->stripe->client); } diff --git a/app/Utils/Traits/Notifications/UserNotifies.php b/app/Utils/Traits/Notifications/UserNotifies.php index e05689359ee1..8b38541c800d 100644 --- a/app/Utils/Traits/Notifications/UserNotifies.php +++ b/app/Utils/Traits/Notifications/UserNotifies.php @@ -66,7 +66,6 @@ trait UserNotifies public function findCompanyUserNotificationType($company_user, $required_permissions) :array { - nlog("find company user notification type"); if ($company_user->company->is_disabled) { return []; From 0ae2c6db270815d3215b16b244855174c0e2bd74 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 2 Feb 2021 07:14:38 +1100 Subject: [PATCH 5/5] Fix for pausing recurring invoices --- app/Services/Recurring/RecurringService.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Services/Recurring/RecurringService.php b/app/Services/Recurring/RecurringService.php index a84c229514f3..9942efcc69e5 100644 --- a/app/Services/Recurring/RecurringService.php +++ b/app/Services/Recurring/RecurringService.php @@ -33,7 +33,8 @@ class RecurringService */ public function stop() { - $this->status_id = RecurringInvoice::STATUS_PAUSED; + if($this->recurring_entity->status_id < RecurringInvoice::STATUS_PAUSED) + $this->recurring_entity->status_id = RecurringInvoice::STATUS_PAUSED; return $this; }