mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Working on buy now buttons
This commit is contained in:
parent
e68e213237
commit
401851e212
@ -2,7 +2,12 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
## [2.6] - 2016-07-08
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added 'Buy Now' buttons
|
||||||
|
|
||||||
|
## [2.6] - 2016-07-12
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Configuration for first day of the week #950
|
- Configuration for first day of the week #950
|
||||||
|
@ -25,6 +25,7 @@ use App\Models\Document;
|
|||||||
use App\Models\Gateway;
|
use App\Models\Gateway;
|
||||||
use App\Models\InvoiceDesign;
|
use App\Models\InvoiceDesign;
|
||||||
use App\Models\TaxRate;
|
use App\Models\TaxRate;
|
||||||
|
use App\Models\Product;
|
||||||
use App\Models\PaymentTerm;
|
use App\Models\PaymentTerm;
|
||||||
use App\Ninja\Repositories\AccountRepository;
|
use App\Ninja\Repositories\AccountRepository;
|
||||||
use App\Ninja\Repositories\ReferralRepository;
|
use App\Ninja\Repositories\ReferralRepository;
|
||||||
@ -714,6 +715,14 @@ class AccountController extends BaseController
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$types = [GATEWAY_TYPE_CREDIT_CARD, GATEWAY_TYPE_BANK_TRANSFER, GATEWAY_TYPE_PAYPAL, GATEWAY_TYPE_BITCOIN, GATEWAY_TYPE_DWOLLA];
|
||||||
|
$options = [];
|
||||||
|
foreach ($types as $type) {
|
||||||
|
if ($account->getGatewayByType($type)) {
|
||||||
|
$options[$type] = trans("texts.{$type}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'client_view_css' => $css,
|
'client_view_css' => $css,
|
||||||
'enable_portal_password' => $account->enable_portal_password,
|
'enable_portal_password' => $account->enable_portal_password,
|
||||||
@ -721,6 +730,8 @@ class AccountController extends BaseController
|
|||||||
'title' => trans('texts.client_portal'),
|
'title' => trans('texts.client_portal'),
|
||||||
'section' => ACCOUNT_CLIENT_PORTAL,
|
'section' => ACCOUNT_CLIENT_PORTAL,
|
||||||
'account' => $account,
|
'account' => $account,
|
||||||
|
'products' => Product::scope()->orderBy('product_key')->get(),
|
||||||
|
'gateway_types' => $options,
|
||||||
];
|
];
|
||||||
|
|
||||||
return View::make('accounts.client_portal', $data);
|
return View::make('accounts.client_portal', $data);
|
||||||
|
@ -4,14 +4,20 @@ use Session;
|
|||||||
use Input;
|
use Input;
|
||||||
use Utils;
|
use Utils;
|
||||||
use View;
|
use View;
|
||||||
|
use Auth;
|
||||||
|
use URL;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Validator;
|
||||||
use App\Models\Invitation;
|
use App\Models\Invitation;
|
||||||
use App\Models\Account;
|
use App\Models\Account;
|
||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
|
use App\Models\Product;
|
||||||
use App\Models\PaymentMethod;
|
use App\Models\PaymentMethod;
|
||||||
use App\Services\PaymentService;
|
use App\Services\PaymentService;
|
||||||
use App\Ninja\Mailers\UserMailer;
|
use App\Ninja\Mailers\UserMailer;
|
||||||
use App\Http\Requests\CreateOnlinePaymentRequest;
|
use App\Http\Requests\CreateOnlinePaymentRequest;
|
||||||
|
use App\Ninja\Repositories\ClientRepository;
|
||||||
|
use App\Services\InvoiceService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class OnlinePaymentController
|
* Class OnlinePaymentController
|
||||||
@ -203,4 +209,57 @@ class OnlinePaymentController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function handleBuyNow(ClientRepository $clientRepo, InvoiceService $invoiceService, $gatewayType = false)
|
||||||
|
{
|
||||||
|
$account = Account::whereAccountKey(Input::get('account_key'))->first();
|
||||||
|
$redirectUrl = Input::get('redirect_url', URL::previous());
|
||||||
|
|
||||||
|
if ( ! $account) {
|
||||||
|
return redirect()->to("{$redirectUrl}/?error=invalid account");
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth::onceUsingId($account->users[0]->id);
|
||||||
|
$product = Product::scope(Input::get('product_id'))->first();
|
||||||
|
|
||||||
|
if ( ! $product) {
|
||||||
|
return redirect()->to("{$redirectUrl}/?error=invalid product");
|
||||||
|
}
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'first_name' => 'string|max:100',
|
||||||
|
'last_name' => 'string|max:100',
|
||||||
|
'email' => 'email|string|max:100',
|
||||||
|
];
|
||||||
|
|
||||||
|
$validator = Validator::make(Input::all(), $rules);
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return redirect()->to("{$redirectUrl}/?error=" . $validator->errors()->first());
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'contact' => Input::all()
|
||||||
|
];
|
||||||
|
$client = $clientRepo->save($data);
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'client_id' => $client->public_id,
|
||||||
|
'invoice_items' => [[
|
||||||
|
'product_key' => $product->product_key,
|
||||||
|
'notes' => $product->notes,
|
||||||
|
'cost' => $product->cost,
|
||||||
|
'qty' => 1,
|
||||||
|
'tax_rate1' => $product->default_tax_rate ? $product->default_tax_rate->rate : 0,
|
||||||
|
'tax_name1' => $product->default_tax_rate ? $product->default_tax_rate->name : '',
|
||||||
|
]]
|
||||||
|
];
|
||||||
|
$invoice = $invoiceService->save($data);
|
||||||
|
$invitation = $invoice->invitations[0];
|
||||||
|
$link = $invitation->getLink();
|
||||||
|
|
||||||
|
if ($gatewayType) {
|
||||||
|
return redirect()->to($invitation->getLink('payment') . "/{$gatewayType}");
|
||||||
|
} else {
|
||||||
|
return redirect()->to($invitation->getLink());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ class VerifyCsrfToken extends BaseVerifier
|
|||||||
'hook/email_bounced',
|
'hook/email_bounced',
|
||||||
'reseller_stats',
|
'reseller_stats',
|
||||||
'payment_hook/*',
|
'payment_hook/*',
|
||||||
|
'buy_now/*',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,7 +43,6 @@ class VerifyCsrfToken extends BaseVerifier
|
|||||||
public function handle($request, Closure $next)
|
public function handle($request, Closure $next)
|
||||||
{
|
{
|
||||||
foreach ($this->openRoutes as $route) {
|
foreach ($this->openRoutes as $route) {
|
||||||
|
|
||||||
if ($request->is($route)) {
|
if ($request->is($route)) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,7 @@ Route::post('signup/submit', 'AccountController@submitSignup');
|
|||||||
|
|
||||||
Route::get('/auth/{provider}', 'Auth\AuthController@authLogin');
|
Route::get('/auth/{provider}', 'Auth\AuthController@authLogin');
|
||||||
Route::get('/auth_unlink', 'Auth\AuthController@authUnlink');
|
Route::get('/auth_unlink', 'Auth\AuthController@authUnlink');
|
||||||
|
Route::match(['GET', 'POST'], '/buy_now/{gateway_type?}', 'OnlinePaymentController@handleBuyNow');
|
||||||
|
|
||||||
Route::post('/hook/email_bounced', 'AppController@emailBounced');
|
Route::post('/hook/email_bounced', 'AppController@emailBounced');
|
||||||
Route::post('/hook/email_opened', 'AppController@emailOpened');
|
Route::post('/hook/email_opened', 'AppController@emailOpened');
|
||||||
|
@ -227,7 +227,7 @@ class BasePaymentDriver
|
|||||||
$gateway = $this->gateway();
|
$gateway = $this->gateway();
|
||||||
|
|
||||||
if ($input) {
|
if ($input) {
|
||||||
$this->updateAddress();
|
$this->updateClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
// load or create token
|
// load or create token
|
||||||
@ -280,8 +280,13 @@ class BasePaymentDriver
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateAddress()
|
private function updateClient()
|
||||||
{
|
{
|
||||||
|
if ( ! $this->contact()->email && $this->input['email']) {
|
||||||
|
$this->contact()->email = $this->input['email'];
|
||||||
|
$this->contact()->save();
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! $this->isGatewayType(GATEWAY_TYPE_CREDIT_CARD)) {
|
if ( ! $this->isGatewayType(GATEWAY_TYPE_CREDIT_CARD)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -12,21 +12,6 @@ use App\Ninja\Datatables\PaymentDatatable;
|
|||||||
|
|
||||||
class PaymentService extends BaseService
|
class PaymentService extends BaseService
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var DatatableService
|
|
||||||
*/
|
|
||||||
protected $datatableService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var PaymentRepository
|
|
||||||
*/
|
|
||||||
protected $paymentRepo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var AccountRepository
|
|
||||||
*/
|
|
||||||
protected $accountRepo;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PaymentService constructor.
|
* PaymentService constructor.
|
||||||
*
|
*
|
||||||
@ -157,5 +142,4 @@ class PaymentService extends BaseService
|
|||||||
return parent::bulk($ids, $action);
|
return parent::bulk($ids, $action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2027,7 +2027,14 @@ $LANG = array(
|
|||||||
'restored_expense_category' => 'Successfully restored expense category',
|
'restored_expense_category' => 'Successfully restored expense category',
|
||||||
'apply_taxes' => 'Apply taxes',
|
'apply_taxes' => 'Apply taxes',
|
||||||
'min_to_max_users' => ':min to :max users',
|
'min_to_max_users' => ':min to :max users',
|
||||||
'max_users_reached' => 'The maximum number of users has been reached.'
|
'max_users_reached' => 'The maximum number of users has been reached.',
|
||||||
|
'buy_now_buttons' => 'Buy Now Buttons',
|
||||||
|
'landing_page' => 'Landing Page',
|
||||||
|
'payment_type' => 'Payment Type',
|
||||||
|
'form' => 'Form',
|
||||||
|
'link' => 'Link',
|
||||||
|
'fields' => 'Fields',
|
||||||
|
'buy_now_buttons_warning' => 'Note: client and invoice records are created even if the transaction isn\'t completed.',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3,7 +3,16 @@
|
|||||||
@section('head')
|
@section('head')
|
||||||
@parent
|
@parent
|
||||||
|
|
||||||
<link href='https://fonts.googleapis.com/css?family=Roboto+Mono' rel='stylesheet' type='text/css'>
|
@include('money_script')
|
||||||
|
|
||||||
|
<link href='https://fonts.googleapis.com/css?family=Roboto+Mono' rel='stylesheet' type='text/css'>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.checkbox-inline input[type="checkbox"] {
|
||||||
|
margin-left:-20px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@stop
|
@stop
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
@ -69,6 +78,65 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">{!! trans('texts.buy_now_buttons') !!}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="col-md-10 col-md-offset-1">
|
||||||
|
|
||||||
|
{!! Former::select('product')
|
||||||
|
->onchange('updateBuyNowButtons()')
|
||||||
|
->addOption('', '')
|
||||||
|
->inlineHelp('buy_now_buttons_warning')
|
||||||
|
->addGroupClass('product-select') !!}
|
||||||
|
|
||||||
|
{!! Former::inline_checkboxes('client_fields')
|
||||||
|
->onchange('updateBuyNowButtons()')
|
||||||
|
->checkboxes([
|
||||||
|
trans('texts.first_name') => ['value' => 'first_name', 'name' => 'first_name'],
|
||||||
|
trans('texts.last_name') => ['value' => 'last_name', 'name' => 'last_name'],
|
||||||
|
trans('texts.email') => ['value' => 'email', 'name' => 'email'],
|
||||||
|
]) !!}
|
||||||
|
|
||||||
|
{!! Former::inline_radios('landing_page')
|
||||||
|
->onchange('showPaymentTypes();updateBuyNowButtons();')
|
||||||
|
->radios([
|
||||||
|
trans('texts.invoice') => ['value' => 'invoice', 'name' => 'landing_page_type'],
|
||||||
|
trans('texts.payment') => ['value' => 'payment', 'name' => 'landing_page_type'],
|
||||||
|
])->check('invoice') !!}
|
||||||
|
|
||||||
|
<div id="paymentTypesDiv" style="display:none">
|
||||||
|
{!! Former::select('payment_type')
|
||||||
|
->onchange('updateBuyNowButtons()')
|
||||||
|
->options($gateway_types) !!}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p> </p>
|
||||||
|
|
||||||
|
<div role="tabpanel">
|
||||||
|
<ul class="nav nav-tabs" role="tablist" style="border: none">
|
||||||
|
<li role="presentation" class="active">
|
||||||
|
<a href="#form" aria-controls="form" role="tab" data-toggle="tab">{{ trans('texts.form') }}</a>
|
||||||
|
</li>
|
||||||
|
<li role="presentation">
|
||||||
|
<a href="#link" aria-controls="link" role="tab" data-toggle="tab">{{ trans('texts.link') }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div role="tabpanel" class="tab-pane active" id="form">
|
||||||
|
<textarea id="formTextarea" class="form-control" rows="4" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
<div role="tabpanel" class="tab-pane" id="link">
|
||||||
|
<textarea id="linkTextarea" class="form-control" rows="4" readonly></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (Utils::hasFeature(FEATURE_CLIENT_PORTAL_CSS))
|
@if (Utils::hasFeature(FEATURE_CLIENT_PORTAL_CSS))
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
@ -95,12 +163,74 @@
|
|||||||
</center>
|
</center>
|
||||||
|
|
||||||
{!! Former::close() !!}
|
{!! Former::close() !!}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
var products = {!! $products !!};
|
||||||
|
console.log(products);
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
var $productSelect = $('select#product');
|
||||||
|
for (var i=0; i<products.length; i++) {
|
||||||
|
var product = products[i];
|
||||||
|
|
||||||
|
$productSelect.append(new Option(formatMoney(product.cost) + ' - ' + product.product_key, product.public_id));
|
||||||
|
}
|
||||||
|
$productSelect.combobox();
|
||||||
|
|
||||||
|
fixCheckboxes();
|
||||||
|
updateBuyNowButtons();
|
||||||
|
})
|
||||||
|
|
||||||
$('#enable_portal_password').change(fixCheckboxes);
|
$('#enable_portal_password').change(fixCheckboxes);
|
||||||
function fixCheckboxes(){
|
|
||||||
|
function fixCheckboxes() {
|
||||||
var checked = $('#enable_portal_password').is(':checked');
|
var checked = $('#enable_portal_password').is(':checked');
|
||||||
$('#send_portal_password').prop('disabled', !checked);
|
$('#send_portal_password').prop('disabled', !checked);
|
||||||
}
|
}
|
||||||
fixCheckboxes();
|
|
||||||
|
function showPaymentTypes() {
|
||||||
|
var val = $('input[name=landing_page_type]:checked').val()
|
||||||
|
if (val == '{{ ENTITY_PAYMENT }}') {
|
||||||
|
$('#paymentTypesDiv').fadeIn();
|
||||||
|
} else {
|
||||||
|
$('#paymentTypesDiv').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBuyNowButtons() {
|
||||||
|
var productId = $('#product').val();
|
||||||
|
var landingPage = $('input[name=landing_page_type]:checked').val()
|
||||||
|
var paymentType = landingPage == 'payment' ? '/' + $('#payment_type').val() : '';
|
||||||
|
|
||||||
|
var form = '';
|
||||||
|
var link = '';
|
||||||
|
|
||||||
|
if (productId) {
|
||||||
|
var link = '{{ url('/buy_now') }}' + paymentType +
|
||||||
|
'?account_key={{ $account->account_key }}' +
|
||||||
|
'&product_id=' + productId;
|
||||||
|
|
||||||
|
var form = '<form action="{{ url('/buy_now') }}' + paymentType + '" method="post" target="_top">' + "\n" +
|
||||||
|
'<input type="hidden" name="account_key" value="{{ $account->account_key }}"/>' + "\n" +
|
||||||
|
'<input type="hidden" name="product_id" value="' + productId + '"/>' + "\n";
|
||||||
|
|
||||||
|
@foreach (['first_name', 'last_name', 'email'] as $field)
|
||||||
|
if ($('input#{{ $field }}').is(':checked')) {
|
||||||
|
form += '<input type="{{ $field == 'email' ? 'email' : 'text' }}" name="{{ $field }}" placeholder="{{ trans("texts.{$field}") }}" required/>' + "\n";
|
||||||
|
link += '&{{ $field }}=';
|
||||||
|
}
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
form += '<input type="submit" value="Buy Now" name="submit"/>' + "\n" + '</form>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#formTextarea').text(form);
|
||||||
|
$('#linkTextarea').text(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@stop
|
@stop
|
||||||
|
@ -77,16 +77,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (isset($paymentTitle) || ! empty($contact->email))
|
<div class="row" style="display:{{ isset($paymentTitle) || empty($contact->email) ? 'block' : 'none' }}">
|
||||||
<div class="row" style="display:{{ isset($paymentTitle) ? 'block' : 'none' }}">
|
<div class="col-md-12">
|
||||||
<div class="col-md-12">
|
{!! Former::text('email')
|
||||||
{!! Former::text('email')
|
->placeholder(trans('texts.email'))
|
||||||
->placeholder(trans('texts.email'))
|
->autocomplete('email')
|
||||||
->autocomplete('email')
|
->label('') !!}
|
||||||
->label('') !!}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
</div>
|
||||||
|
|
||||||
<p> <br/> </p>
|
<p> <br/> </p>
|
||||||
|
|
||||||
@ -257,5 +255,5 @@
|
|||||||
<p> </p>
|
<p> </p>
|
||||||
|
|
||||||
{!! Former::close() !!}
|
{!! Former::close() !!}
|
||||||
|
|
||||||
@stop
|
@stop
|
||||||
|
Loading…
x
Reference in New Issue
Block a user