diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index cceb1853bbb4..70e1cbb639d6 100755 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -184,6 +184,11 @@ class AccountController extends \BaseController } } + $tokenBillingOptions = []; + for ($i=1; $i<=4; $i++) { + $tokenBillingOptions[$i] = trans("texts.token_billing_{$i}"); + } + $data = [ 'account' => $account, 'accountGateway' => $accountGateway, @@ -195,7 +200,8 @@ class AccountController extends \BaseController ->orderBy('name') ->get(), 'recommendedGateways' => $recommendedGatewayArray, - 'creditCardTypes' => $creditCards, + 'creditCardTypes' => $creditCards, + 'tokenBillingOptions' => $tokenBillingOptions, ]; return View::make('accounts.payments', $data); @@ -663,10 +669,22 @@ class AccountController extends \BaseController } } - if ($isMasked && count($account->account_gateways)) { + // check if a gateway is already configured + $currentGateway = false; + if (count($account->account_gateways)) { $currentGateway = $account->account_gateways[0]; + } + + // if the values haven't changed don't update the config + if ($isMasked && $currentGateway) { $currentGateway->accepted_credit_cards = $cardCount; $currentGateway->save(); + // if there's an existing config for this gateway update it + } elseif (!$isMasked && $currentGateway && $currentGateway->gateway_id == $gatewayId) { + $currentGateway->accepted_credit_cards = $cardCount; + $currentGateway->config = json_encode($config); + $currentGateway->save(); + // otherwise, create a new gateway config } else { $accountGateway->config = json_encode($config); $accountGateway->accepted_credit_cards = $cardCount; @@ -675,6 +693,11 @@ class AccountController extends \BaseController $account->account_gateways()->save($accountGateway); } + if (Input::get('token_billing_type_id')) { + $account->token_billing_type_id = Input::get('token_billing_type_id'); + $account->save(); + } + Session::flash('message', trans('texts.updated_settings')); } else { Session::flash('error', trans('validation.required', ['attribute' => 'gateway'])); diff --git a/app/controllers/ClientController.php b/app/controllers/ClientController.php index ea697d185320..a06b6c5bdc8e 100755 --- a/app/controllers/ClientController.php +++ b/app/controllers/ClientController.php @@ -106,6 +106,7 @@ class ClientController extends \BaseController 'credit' => $client->getTotalCredit(), 'title' => trans('texts.view_client'), 'hasRecurringInvoices' => Invoice::scope()->where('is_recurring', '=', true)->whereClientId($client->id)->count() > 0, + 'gatewayLink' => $client->getGatewayLink(), ); return View::make('clients.show', $data); diff --git a/app/controllers/InvoiceController.php b/app/controllers/InvoiceController.php index 11dd20b304d8..884e26efd819 100755 --- a/app/controllers/InvoiceController.php +++ b/app/controllers/InvoiceController.php @@ -177,6 +177,7 @@ class InvoiceController extends \BaseController 'invitation' => $invitation, 'invoiceLabels' => $client->account->getInvoiceLabels(), 'contact' => $contact, + 'hasToken' => $client->getGatewayToken() ); return View::make('invoices.view', $data); diff --git a/app/controllers/PaymentController.php b/app/controllers/PaymentController.php index 606ba6f8979e..ff4668c0b490 100755 --- a/app/controllers/PaymentController.php +++ b/app/controllers/PaymentController.php @@ -287,14 +287,16 @@ class PaymentController extends \BaseController public function show_payment($invitationKey) { + // Handle token billing + if (Input::get('use_token') == 'true') { + return self::do_payment($invitationKey, false, true); + } // For PayPal Express we redirect straight to their site $invitation = Invitation::with('invoice.client.account', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail(); $account = $invitation->invoice->client->account; - if ($account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) { if (Session::has('error')) { Session::reflash(); - return Redirect::to('view/'.$invitationKey); } else { return self::do_payment($invitationKey, false); @@ -321,6 +323,7 @@ class PaymentController extends \BaseController 'acceptedCreditCardTypes' => $acceptedCreditCardTypes, 'countries' => Country::remember(DEFAULT_QUERY_CACHE)->orderBy('name')->get(), 'currencyId' => $client->currency_id, + 'account' => $client->account ]; return View::make('payments.payment', $data); @@ -486,7 +489,7 @@ class PaymentController extends \BaseController } } - public function do_payment($invitationKey, $onSite = true) + public function do_payment($invitationKey, $onSite = true, $useToken = false) { $rules = array( 'first_name' => 'required', @@ -512,11 +515,12 @@ class PaymentController extends \BaseController $invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail(); $invoice = $invitation->invoice; - $accountGateway = $invoice->client->account->account_gateways[0]; + $client = $invoice->client; + $account = $client->account; + $accountGateway = $account->account_gateways[0]; $paymentLibrary = $accountGateway->gateway->paymentlibrary; if ($onSite) { - $client = $invoice->client; $client->address1 = trim(Input::get('address1')); $client->address2 = trim(Input::get('address2')); $client->city = trim(Input::get('city')); @@ -528,7 +532,32 @@ class PaymentController extends \BaseController try { if ($paymentLibrary->id == PAYMENT_LIBRARY_OMNIPAY) { $gateway = self::createGateway($accountGateway); - $details = self::getPaymentDetails($invoice, Input::all()); + $details = self::getPaymentDetails($invoice, $useToken ? false : Input::all()); + + if ($accountGateway->gateway_id == GATEWAY_STRIPE) { + if ($useToken) { + $details['cardReference'] = $client->getGatewayToken(); + } elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing')) { + $tokenResponse = $gateway->createCard($details)->send(); + $cardReference = $tokenResponse->getCardReference(); + $details['cardReference'] = $cardReference; + + $token = AccountGatewayToken::where('client_id', '=', $client->id) + ->where('account_gateway_id', '=', $accountGateway->id)->first(); + + if (!$token) { + $token = new AccountGatewayToken(); + $token->account_id = $account->id; + $token->contact_id = $invitation->contact_id; + $token->account_gateway_id = $accountGateway->id; + $token->client_id = $client->id; + } + + $token->token = $cardReference; + $token->save(); + } + } + $response = $gateway->purchase($details)->send(); $ref = $response->getTransactionReference(); @@ -541,7 +570,6 @@ class PaymentController extends \BaseController if ($response->isSuccessful()) { $payment = self::createPayment($invitation, $ref); - Session::flash('message', trans('texts.applied_payment')); return Redirect::to('view/'.$payment->invitation->invitation_key); @@ -693,12 +721,14 @@ class PaymentController extends \BaseController ->withInput(); } else { $this->paymentRepo->save($publicId, Input::all()); - + if ($publicId) { Session::flash('message', trans('texts.updated_payment')); + return Redirect::to('payments/'); } else { Session::flash('message', trans('texts.created_payment')); + return Redirect::to('clients/'.Input::get('client')); } } diff --git a/app/database/migrations/2015_02_17_131714_support_token_billing.php b/app/database/migrations/2015_02_17_131714_support_token_billing.php new file mode 100644 index 000000000000..0022d44cf2ef --- /dev/null +++ b/app/database/migrations/2015_02_17_131714_support_token_billing.php @@ -0,0 +1,56 @@ +smallInteger('token_billing_type_id')->default(TOKEN_BILLING_OPT_IN); + }); + + Schema::create('account_gateway_tokens', function($table) + { + $table->increments('id'); + $table->unsignedInteger('account_id'); + $table->unsignedInteger('contact_id'); + $table->unsignedInteger('account_gateway_id'); + $table->unsignedInteger('client_id'); + $table->string('token'); + + $table->timestamps(); + $table->softDeletes(); + + $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('contact_id')->references('id')->on('contacts')->onDelete('cascade'); + $table->foreign('account_gateway_id')->references('id')->on('account_gateways')->onDelete('cascade'); + $table->foreign('client_id')->references('id')->on('clients')->onDelete('cascade'); + }); + + DB::table('accounts')->update(['token_billing_type_id' => TOKEN_BILLING_OPT_IN]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('accounts', function($table) + { + $table->dropColumn('token_billing_type_id'); + }); + + Schema::drop('account_gateway_tokens'); + } + +} \ No newline at end of file diff --git a/app/lang/da/texts.php b/app/lang/da/texts.php index e1f9114c53e2..51b4440567c3 100644 --- a/app/lang/da/texts.php +++ b/app/lang/da/texts.php @@ -512,4 +512,17 @@ return array( 'reset_all' => 'Reset All', 'approve' => 'Approve', + 'token_billing_type_id' => 'Token Billing', + 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', + 'token_billing_1' => 'Disabled', + 'token_billing_2' => 'Opt-in - checkbox is shown but not selected', + 'token_billing_3' => 'Opt-out - checkbox is shown and selected', + 'token_billing_4' => 'Always', + 'token_billing_checkbox' => 'Store credit card details', + 'view_in_stripe' => 'View in Stripe', + 'use_card_on_file' => 'Use card on file', + 'edit_payment_details' => 'Edit payment details', + 'token_billing' => 'Save card details', + 'token_billing_secure' => 'All data is stored securely by :stripe_link', + ); diff --git a/app/lang/de/texts.php b/app/lang/de/texts.php index 2adb31ed7f4a..cc3db81c1bca 100644 --- a/app/lang/de/texts.php +++ b/app/lang/de/texts.php @@ -500,6 +500,19 @@ return array( 'payment_email' => 'Zahlungsmail', 'quote_email' => 'Angebotsmail', 'reset_all' => 'Alle zurücksetzen', - 'approve' => 'Approve', + + 'token_billing_type_id' => 'Token Billing', + 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', + 'token_billing_1' => 'Disabled', + 'token_billing_2' => 'Opt-in - checkbox is shown but not selected', + 'token_billing_3' => 'Opt-out - checkbox is shown and selected', + 'token_billing_4' => 'Always', + 'token_billing_checkbox' => 'Store credit card details', + 'view_in_stripe' => 'View in Stripe', + 'use_card_on_file' => 'Use card on file', + 'edit_payment_details' => 'Edit payment details', + 'token_billing' => 'Save card details', + 'token_billing_secure' => 'All data is stored securely by :stripe_link', + ); diff --git a/app/lang/en/texts.php b/app/lang/en/texts.php index d01ba177f7d9..4da38e1dcdef 100644 --- a/app/lang/en/texts.php +++ b/app/lang/en/texts.php @@ -510,4 +510,18 @@ return array( 'reset_all' => 'Reset All', 'approve' => 'Approve', + 'token_billing_type_id' => 'Token Billing', + 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', + 'token_billing_1' => 'Disabled', + 'token_billing_2' => 'Opt-in - checkbox is shown but not selected', + 'token_billing_3' => 'Opt-out - checkbox is shown and selected', + 'token_billing_4' => 'Always', + 'token_billing_checkbox' => 'Store credit card details', + 'view_in_stripe' => 'View in Stripe', + 'use_card_on_file' => 'Use card on file', + 'edit_payment_details' => 'Edit payment details', + 'token_billing' => 'Save card details', + 'token_billing_secure' => 'All data is stored securely by :stripe_link', + + ); diff --git a/app/lang/es/texts.php b/app/lang/es/texts.php index 80b1dacf658e..4d3133ee4c09 100644 --- a/app/lang/es/texts.php +++ b/app/lang/es/texts.php @@ -482,4 +482,17 @@ return array( 'reset_all' => 'Reset All', 'approve' => 'Approve', + 'token_billing_type_id' => 'Token Billing', + 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', + 'token_billing_1' => 'Disabled', + 'token_billing_2' => 'Opt-in - checkbox is shown but not selected', + 'token_billing_3' => 'Opt-out - checkbox is shown and selected', + 'token_billing_4' => 'Always', + 'token_billing_checkbox' => 'Store credit card details', + 'view_in_stripe' => 'View in Stripe', + 'use_card_on_file' => 'Use card on file', + 'edit_payment_details' => 'Edit payment details', + 'token_billing' => 'Save card details', + 'token_billing_secure' => 'All data is stored securely by :stripe_link', + ); \ No newline at end of file diff --git a/app/lang/fr/texts.php b/app/lang/fr/texts.php index 0e3304f18e8e..893deff828fd 100644 --- a/app/lang/fr/texts.php +++ b/app/lang/fr/texts.php @@ -502,5 +502,18 @@ return array( 'quote_email' => 'Quote Email', 'reset_all' => 'Reset All', 'approve' => 'Approve', + + 'token_billing_type_id' => 'Token Billing', + 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', + 'token_billing_1' => 'Disabled', + 'token_billing_2' => 'Opt-in - checkbox is shown but not selected', + 'token_billing_3' => 'Opt-out - checkbox is shown and selected', + 'token_billing_4' => 'Always', + 'token_billing_checkbox' => 'Store credit card details', + 'view_in_stripe' => 'View in Stripe', + 'use_card_on_file' => 'Use card on file', + 'edit_payment_details' => 'Edit payment details', + 'token_billing' => 'Save card details', + 'token_billing_secure' => 'All data is stored securely by :stripe_link', ); \ No newline at end of file diff --git a/app/lang/it/texts.php b/app/lang/it/texts.php index 4aae44b0e581..4d69a3d7eaa7 100644 --- a/app/lang/it/texts.php +++ b/app/lang/it/texts.php @@ -504,5 +504,18 @@ return array( 'quote_email' => 'Quote Email', 'reset_all' => 'Reset All', 'approve' => 'Approve', + + 'token_billing_type_id' => 'Token Billing', + 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', + 'token_billing_1' => 'Disabled', + 'token_billing_2' => 'Opt-in - checkbox is shown but not selected', + 'token_billing_3' => 'Opt-out - checkbox is shown and selected', + 'token_billing_4' => 'Always', + 'token_billing_checkbox' => 'Store credit card details', + 'view_in_stripe' => 'View in Stripe', + 'use_card_on_file' => 'Use card on file', + 'edit_payment_details' => 'Edit payment details', + 'token_billing' => 'Save card details', + 'token_billing_secure' => 'All data is stored securely by :stripe_link', ); diff --git a/app/lang/lt/texts.php b/app/lang/lt/texts.php index f17166132271..7ae31d36baef 100644 --- a/app/lang/lt/texts.php +++ b/app/lang/lt/texts.php @@ -512,6 +512,19 @@ return array( 'quote_email' => 'Quote Email', 'reset_all' => 'Reset All', 'approve' => 'Approve', + + 'token_billing_type_id' => 'Token Billing', + 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', + 'token_billing_1' => 'Disabled', + 'token_billing_2' => 'Opt-in - checkbox is shown but not selected', + 'token_billing_3' => 'Opt-out - checkbox is shown and selected', + 'token_billing_4' => 'Always', + 'token_billing_checkbox' => 'Store credit card details', + 'view_in_stripe' => 'View in Stripe', + 'use_card_on_file' => 'Use card on file', + 'edit_payment_details' => 'Edit payment details', + 'token_billing' => 'Save card details', + 'token_billing_secure' => 'All data is stored securely by :stripe_link', ); diff --git a/app/lang/nb_NO/texts.php b/app/lang/nb_NO/texts.php index 4874c2066838..e26530142b06 100644 --- a/app/lang/nb_NO/texts.php +++ b/app/lang/nb_NO/texts.php @@ -511,5 +511,18 @@ return array( 'reset_all' => 'Reset All', 'approve' => 'Approve', + 'token_billing_type_id' => 'Token Billing', + 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', + 'token_billing_1' => 'Disabled', + 'token_billing_2' => 'Opt-in - checkbox is shown but not selected', + 'token_billing_3' => 'Opt-out - checkbox is shown and selected', + 'token_billing_4' => 'Always', + 'token_billing_checkbox' => 'Store credit card details', + 'view_in_stripe' => 'View in Stripe', + 'use_card_on_file' => 'Use card on file', + 'edit_payment_details' => 'Edit payment details', + 'token_billing' => 'Save card details', + 'token_billing_secure' => 'All data is stored securely by :stripe_link', + ); \ No newline at end of file diff --git a/app/lang/nl/texts.php b/app/lang/nl/texts.php index 1f0dc0922191..b9189193a81f 100644 --- a/app/lang/nl/texts.php +++ b/app/lang/nl/texts.php @@ -505,6 +505,19 @@ return array( 'quote_email' => 'Quote Email', 'reset_all' => 'Reset All', 'approve' => 'Approve', - + + 'token_billing_type_id' => 'Token Billing', + 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', + 'token_billing_1' => 'Disabled', + 'token_billing_2' => 'Opt-in - checkbox is shown but not selected', + 'token_billing_3' => 'Opt-out - checkbox is shown and selected', + 'token_billing_4' => 'Always', + 'token_billing_checkbox' => 'Store credit card details', + 'view_in_stripe' => 'View in Stripe', + 'use_card_on_file' => 'Use card on file', + 'edit_payment_details' => 'Edit payment details', + 'token_billing' => 'Save card details', + 'token_billing_secure' => 'All data is stored securely by :stripe_link', + ); \ No newline at end of file diff --git a/app/lang/pt_BR/texts.php b/app/lang/pt_BR/texts.php index 8cf7074dd189..7ea1062f9725 100644 --- a/app/lang/pt_BR/texts.php +++ b/app/lang/pt_BR/texts.php @@ -492,6 +492,19 @@ return array( 'quote_email' => 'Quote Email', 'reset_all' => 'Reset All', 'approve' => 'Approve', + + 'token_billing_type_id' => 'Token Billing', + 'token_billing_help' => 'Enables you to store credit cards with your gateway, and charge them at a later date.', + 'token_billing_1' => 'Disabled', + 'token_billing_2' => 'Opt-in - checkbox is shown but not selected', + 'token_billing_3' => 'Opt-out - checkbox is shown and selected', + 'token_billing_4' => 'Always', + 'token_billing_checkbox' => 'Store credit card details', + 'view_in_stripe' => 'View in Stripe', + 'use_card_on_file' => 'Use card on file', + 'edit_payment_details' => 'Edit payment details', + 'token_billing' => 'Save card details', + 'token_billing_secure' => 'All data is stored securely by :stripe_link', ); diff --git a/app/models/Account.php b/app/models/Account.php index 40ad5e677a81..267cb76b9f99 100755 --- a/app/models/Account.php +++ b/app/models/Account.php @@ -334,4 +334,15 @@ class Account extends Eloquent return "
" . trans('texts.email_signature') . "
\$account