diff --git a/app/Http/Controllers/AccountGatewayController.php b/app/Http/Controllers/AccountGatewayController.php index d481e1b029d8..86c76ecf2760 100644 --- a/app/Http/Controllers/AccountGatewayController.php +++ b/app/Http/Controllers/AccountGatewayController.php @@ -43,7 +43,7 @@ class AccountGatewayController extends BaseController { $accountGateway = AccountGateway::scope($publicId)->firstOrFail(); $config = $accountGateway->getConfig(); - + foreach ($config as $field => $value) { $config->$field = str_repeat('*', strlen($value)); } @@ -84,6 +84,7 @@ class AccountGatewayController extends BaseController $data['selectGateways'] = Gateway::where('payment_library_id', '=', 1) ->where('id', '!=', GATEWAY_PAYPAL_EXPRESS) ->where('id', '!=', GATEWAY_BITPAY) + ->where('id', '!=', GATEWAY_GOCARDLESS) ->where('id', '!=', GATEWAY_DWOLLA) ->orderBy('name')->get(); $data['hiddenFields'] = Gateway::$hiddenFields; @@ -104,6 +105,9 @@ class AccountGatewayController extends BaseController if ($type == PAYMENT_TYPE_BITCOIN) { $paymentTypes[$type] .= ' - BitPay'; } + if ($type == PAYMENT_TYPE_DIRECT_DEBIT) { + $paymentTypes[$type] .= ' - GoCardless'; + } } } @@ -173,6 +177,8 @@ class AccountGatewayController extends BaseController $gatewayId = GATEWAY_PAYPAL_EXPRESS; } elseif ($paymentType == PAYMENT_TYPE_BITCOIN) { $gatewayId = GATEWAY_BITPAY; + } elseif ($paymentType == PAYMENT_TYPE_DIRECT_DEBIT) { + $gatewayId = GATEWAY_GOCARDLESS; } elseif ($paymentType == PAYMENT_TYPE_DWOLLA) { $gatewayId = GATEWAY_DWOLLA; } diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index 83714effe168..1d165d0de9cb 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -91,7 +91,7 @@ class InvoiceController extends BaseController ->withTrashed() ->firstOrFail(); $entityType = $invoice->getEntityType(); - + $contactIds = DB::table('invitations') ->join('contacts', 'contacts.id', '=', 'invitations.contact_id') ->where('invitations.invoice_id', '=', $invoice->id) @@ -170,7 +170,7 @@ class InvoiceController extends BaseController 'actions' => $actions, 'lastSent' => $lastSent); $data = array_merge($data, self::getViewModel()); - + if ($clone) { $data['formIsChanged'] = true; } @@ -206,14 +206,14 @@ class InvoiceController extends BaseController $account = Auth::user()->account; $entityType = $isRecurring ? ENTITY_RECURRING_INVOICE : ENTITY_INVOICE; $clientId = null; - + if ($clientPublicId) { $clientId = Client::getPrivateId($clientPublicId); } - + $invoice = $account->createInvoice($entityType, $clientId); $invoice->public_id = 0; - + $data = [ 'clients' => Client::scope()->with('contacts', 'country')->orderBy('name')->get(), 'entityType' => $invoice->getEntityType(), @@ -223,7 +223,7 @@ class InvoiceController extends BaseController 'title' => trans('texts.new_invoice'), ]; $data = array_merge($data, self::getViewModel()); - + return View::make('invoices.edit', $data); } @@ -244,7 +244,7 @@ class InvoiceController extends BaseController $recurringHelp .= $line; } } - + $recurringDueDateHelp = ''; foreach (preg_split("/((\r?\n)|(\r\n?))/", trans('texts.recurring_due_date_help')) as $line) { $parts = explode("=>", $line); @@ -260,20 +260,20 @@ class InvoiceController extends BaseController $recurringDueDates = array( trans('texts.use_client_terms') => array('value' => '', 'class' => 'monthly weekly'), ); - + $ends = array('th','st','nd','rd','th','th','th','th','th','th'); for($i = 1; $i < 31; $i++){ if ($i >= 11 && $i <= 13) $ordinal = $i. 'th'; else $ordinal = $i . $ends[$i % 10]; - + $dayStr = str_pad($i, 2, '0', STR_PAD_LEFT); $str = trans('texts.day_of_month', array('ordinal'=>$ordinal)); - + $recurringDueDates[$str] = array('value' => "1998-01-$dayStr", 'data-num' => $i, 'class' => 'monthly'); } $recurringDueDates[trans('texts.last_day_of_month')] = array('value' => "1998-01-31", 'data-num' => 31, 'class' => 'monthly'); - - + + $daysOfWeek = array( trans('texts.sunday'), trans('texts.monday'), @@ -286,13 +286,13 @@ class InvoiceController extends BaseController foreach(array('1st','2nd','3rd','4th') as $i=>$ordinal){ foreach($daysOfWeek as $j=>$dayOfWeek){ $str = trans('texts.day_of_week_after', array('ordinal' => $ordinal, 'day' => $dayOfWeek)); - + $day = $i * 7 + $j + 1; $dayStr = str_pad($day, 2, '0', STR_PAD_LEFT); $recurringDueDates[$str] = array('value' => "1998-02-$dayStr", 'data-num' => $day, 'class' => 'weekly'); } } - + return [ 'data' => Input::old('data'), 'account' => Auth::user()->account->load('country'), @@ -352,7 +352,7 @@ class InvoiceController extends BaseController if ($action == 'email') { return $this->emailInvoice($invoice, Input::get('pdfupload')); } - + return redirect()->to($invoice->getRoute()); } @@ -379,7 +379,7 @@ class InvoiceController extends BaseController } elseif ($action == 'email') { return $this->emailInvoice($invoice, Input::get('pdfupload')); } - + return redirect()->to($invoice->getRoute()); } diff --git a/app/Http/Controllers/PaymentController.php b/app/Http/Controllers/PaymentController.php index 681956dd2a03..8d769c126053 100644 --- a/app/Http/Controllers/PaymentController.php +++ b/app/Http/Controllers/PaymentController.php @@ -125,6 +125,7 @@ class PaymentController extends BaseController public function show_payment($invitationKey, $paymentType = false) { + $invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail(); $invoice = $invitation->invoice; $client = $invoice->client; @@ -137,6 +138,7 @@ class PaymentController extends BaseController $paymentType = Session::get($invitation->id . 'payment_type') ?: $account->account_gateways[0]->getPaymentType(); } + if ($paymentType == PAYMENT_TYPE_TOKEN) { $useToken = true; $paymentType = PAYMENT_TYPE_CREDIT_CARD; @@ -145,11 +147,13 @@ class PaymentController extends BaseController $accountGateway = $invoice->client->account->getGatewayByType($paymentType); $gateway = $accountGateway->gateway; + $acceptedCreditCardTypes = $accountGateway->getCreditcardTypes(); + // Handle offsite payments - if ($useToken || $paymentType != PAYMENT_TYPE_CREDIT_CARD - || $gateway->id == GATEWAY_EWAY + if ($useToken || $paymentType != PAYMENT_TYPE_CREDIT_CARD + || $gateway->id == GATEWAY_EWAY || $gateway->id == GATEWAY_TWO_CHECKOUT || $gateway->id == GATEWAY_PAYFAST || $gateway->id == GATEWAY_MOLLIE) { @@ -353,6 +357,7 @@ class PaymentController extends BaseController $account = $client->account; $accountGateway = $account->getGatewayByType(Session::get($invitation->id . 'payment_type')); + $rules = [ 'first_name' => 'required', 'last_name' => 'required', @@ -434,16 +439,19 @@ class PaymentController extends BaseController $response = $gateway->purchase($details)->send(); + if ($accountGateway->gateway_id == GATEWAY_EWAY) { $ref = $response->getData()['AccessCode']; } elseif ($accountGateway->gateway_id == GATEWAY_TWO_CHECKOUT) { $ref = $response->getData()['cart_order_id']; } elseif ($accountGateway->gateway_id == GATEWAY_PAYFAST) { $ref = $response->getData()['m_payment_id']; + } elseif ($accountGateway->gateway_id == GATEWAY_GOCARDLESS) { + $ref = $response->getData()['signature']; } else { $ref = $response->getTransactionReference(); } - + if (!$ref) { $this->error('No-Ref', $response->getMessage(), $accountGateway); @@ -466,6 +474,7 @@ class PaymentController extends BaseController return Redirect::to('view/'.$payment->invitation->invitation_key); } elseif ($response->isRedirect()) { + $invitation->transaction_reference = $ref; $invitation->save(); Session::put('transaction_reference', $ref); @@ -515,7 +524,6 @@ class PaymentController extends BaseController $this->error('No-Payment-Type', false, false); return Redirect::to($invitation->getLink()); } - $accountGateway = $account->getGatewayByType($paymentType); $gateway = $this->paymentService->createGateway($accountGateway); @@ -535,7 +543,9 @@ class PaymentController extends BaseController && !$accountGateway->isGateway(GATEWAY_TWO_CHECKOUT) && !$accountGateway->isGateway(GATEWAY_CHECKOUT_COM)) { $details = $this->paymentService->getPaymentDetails($invitation, $accountGateway); + $response = $this->paymentService->completePurchase($gateway, $accountGateway, $details, $token); + $ref = $response->getTransactionReference() ?: $token; if ($response->isCancelled()) { @@ -554,6 +564,7 @@ class PaymentController extends BaseController return Redirect::to($invitation->getLink()); } } catch (\Exception $e) { + $this->error('Offsite-uncaught', false, $accountGateway, $e); return Redirect::to($invitation->getLink()); } diff --git a/app/Http/routes.php b/app/Http/routes.php index 4091add33ec2..d2b7d42a4bf5 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -87,7 +87,7 @@ Route::group(['middleware' => 'auth'], function() { Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible'); Route::get('hide_message', 'HomeController@hideMessage'); Route::get('force_inline_pdf', 'UserController@forcePDFJS'); - + Route::get('api/users', array('as'=>'api.users', 'uses'=>'UserController@getDatatable')); Route::resource('users', 'UserController'); Route::post('users/bulk', 'UserController@bulk'); @@ -118,12 +118,12 @@ Route::group(['middleware' => 'auth'], function() { Route::post('settings/cancel_account', 'AccountController@cancelAccount'); Route::get('settings/{section?}', 'AccountController@showSection'); Route::post('settings/{section?}', 'AccountController@doSection'); - + Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData')); Route::post('user/setTheme', 'UserController@setTheme'); Route::post('remove_logo', 'AccountController@removeLogo'); Route::post('account/go_pro', 'AccountController@enableProPlan'); - + Route::post('/export', 'ExportController@doExport'); Route::post('/import', 'ImportController@doImport'); Route::post('/import_csv', 'ImportController@doImportCSV'); @@ -143,10 +143,10 @@ Route::group(['middleware' => 'auth'], function() { Route::post('tasks/bulk', 'TaskController@bulk'); Route::get('api/recurring_invoices/{client_id?}', array('as'=>'api.recurring_invoices', 'uses'=>'InvoiceController@getRecurringDatatable')); - + Route::get('invoices/invoice_history/{invoice_id}', 'InvoiceController@invoiceHistory'); Route::get('quotes/quote_history/{invoice_id}', 'InvoiceController@invoiceHistory'); - + Route::resource('invoices', 'InvoiceController'); Route::get('api/invoices/{client_id?}', array('as'=>'api.invoices', 'uses'=>'InvoiceController@getDatatable')); Route::get('invoices/create/{client_id?}', 'InvoiceController@create'); @@ -417,6 +417,7 @@ if (!defined('CONTACT_EMAIL')) { define('GATEWAY_PAYPAL_EXPRESS', 17); define('GATEWAY_PAYPAL_PRO', 18); define('GATEWAY_STRIPE', 23); + define('GATEWAY_GOCARDLESS', 6); define('GATEWAY_TWO_CHECKOUT', 27); define('GATEWAY_BEANSTREAM', 29); define('GATEWAY_PSIGATE', 30); @@ -484,6 +485,7 @@ if (!defined('CONTACT_EMAIL')) { define('PAYMENT_TYPE_PAYPAL', 'PAYMENT_TYPE_PAYPAL'); define('PAYMENT_TYPE_CREDIT_CARD', 'PAYMENT_TYPE_CREDIT_CARD'); + define('PAYMENT_TYPE_DIRECT_DEBIT', 'PAYMENT_TYPE_DIRECT_DEBIT'); define('PAYMENT_TYPE_BITCOIN', 'PAYMENT_TYPE_BITCOIN'); define('PAYMENT_TYPE_DWOLLA', 'PAYMENT_TYPE_DWOLLA'); define('PAYMENT_TYPE_TOKEN', 'PAYMENT_TYPE_TOKEN'); @@ -593,4 +595,4 @@ if (Auth::check() && Auth::user()->id === 1) { Auth::loginUsingId(1); } -*/ \ No newline at end of file +*/ diff --git a/app/Models/Gateway.php b/app/Models/Gateway.php index da556dfb0e35..06621518403b 100644 --- a/app/Models/Gateway.php +++ b/app/Models/Gateway.php @@ -12,7 +12,8 @@ class Gateway extends Eloquent PAYMENT_TYPE_CREDIT_CARD, PAYMENT_TYPE_PAYPAL, PAYMENT_TYPE_BITCOIN, - PAYMENT_TYPE_DWOLLA + PAYMENT_TYPE_DIRECT_DEBIT, + PAYMENT_TYPE_DWOLLA, ]; public static $hiddenFields = [ @@ -94,6 +95,8 @@ class Gateway extends Eloquent return PAYMENT_TYPE_BITCOIN; } else if ($gatewayId == GATEWAY_DWOLLA) { return PAYMENT_TYPE_DWOLLA; + }else if ($gatewayId == GATEWAY_GOCARDLESS) { + return PAYMENT_TYPE_DIRECT_DEBIT; } else { return PAYMENT_TYPE_CREDIT_CARD; } diff --git a/app/Services/PaymentService.php b/app/Services/PaymentService.php index 7a5147b0459e..85b20ef81245 100644 --- a/app/Services/PaymentService.php +++ b/app/Services/PaymentService.php @@ -213,7 +213,7 @@ class PaymentService extends BaseService $invoice = $invitation->invoice; // sync pro accounts - if ($invoice->account->account_key == NINJA_ACCOUNT_KEY + if ($invoice->account->account_key == NINJA_ACCOUNT_KEY && $invoice->amount == PRO_PLAN_PRICE) { $account = Account::with('users')->find($invoice->client->public_id); if ($account->pro_plan_paid && $account->pro_plan_paid != '0000-00-00') { @@ -237,7 +237,7 @@ class PaymentService extends BaseService $payment->contact_id = $invitation->contact_id; $payment->transaction_reference = $ref; $payment->payment_date = date_create()->format('Y-m-d'); - + if ($payerId) { $payment->payer_id = $payerId; } @@ -254,6 +254,7 @@ class PaymentService extends BaseService $response = $gateway->fetchTransaction($details)->send(); return $gateway->fetchTransaction($details)->send(); } else { + return $gateway->completePurchase($details)->send(); } } diff --git a/resources/lang/en/texts.php b/resources/lang/en/texts.php index 6fd77cd1cb59..9c4ac9d97f85 100644 --- a/resources/lang/en/texts.php +++ b/resources/lang/en/texts.php @@ -593,6 +593,7 @@ return array( 'payment_type_credit_card' => 'Credit Card', 'payment_type_paypal' => 'PayPal', 'payment_type_bitcoin' => 'Bitcoin', + 'payment_type_direct_debit' => 'Direct Debit', 'knowledge_base' => 'Knowledge Base', 'partial' => 'Partial', 'partial_remaining' => ':partial of :balance', @@ -895,7 +896,7 @@ return array( 'default_invoice_footer' => 'Default Invoice Footer', 'quote_footer' => 'Quote Footer', 'free' => 'Free', - + 'quote_is_approved' => 'This quote is approved', 'apply_credit' => 'Apply Credit', 'system_settings' => 'System Settings', @@ -976,7 +977,7 @@ return array( 'custom_account_fields_helps' => 'Add a label and value to the company details section of the PDF.', 'custom_invoice_fields_helps' => 'Add a text input to the invoice create/edit page and display the label and value on the PDF.', 'custom_invoice_charges_helps' => 'Add a text input to the invoice create/edit page and include the charge in the invoice subtotals.', - + 'token_expired' => 'Validation token was expired. Please try again.', 'invoice_link' => 'Invoice Link', 'button_confirmation_message' => 'Click to confirm your email address.', @@ -996,7 +997,7 @@ return array( 'white_label_custom_css' => ':link for $'.WHITE_LABEL_PRICE.' to enable custom styling and help support our project.', 'white_label_purchase_link' => 'Purchase a white label license', - + // recurring due dates 'recurring_due_dates' => 'Recurring Invoice Due Dates', 'recurring_due_date_help' => '
Automatically sets a due date for the invoice.
@@ -1037,5 +1038,5 @@ return array( 'invoice_message_button' => 'To view your invoice for :amount, click the button below.', 'quote_message_button' => 'To view your quote for :amount, click the button below.', 'payment_message_button' => 'Thank you for your payment of :amount.', - + ); \ No newline at end of file diff --git a/resources/views/accounts/account_gateway.blade.php b/resources/views/accounts/account_gateway.blade.php index 411658e76f64..eda78b3c000c 100644 --- a/resources/views/accounts/account_gateway.blade.php +++ b/resources/views/accounts/account_gateway.blade.php @@ -1,11 +1,11 @@ @extends('header') -@section('content') - @parent +@section('content') + @parent @include('accounts.nav', ['selected' => ACCOUNT_PAYMENTS]) - {!! Former::open($url)->method($method)->rule()->addClass('warn-on-exit') !!} + {!! Former::open($url)->method($method)->rule()->addClass('warn-on-exit') !!} {!! Former::populate($account) !!} @@ -14,7 +14,7 @@