Merge branch 'develop' of github.com:hillelcoren/invoice-ninja into develop
23
app/Events/PaymentCompleted.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PaymentCompleted extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $payment;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($payment)
|
||||
{
|
||||
$this->payment = $payment;
|
||||
}
|
||||
|
||||
}
|
23
app/Events/PaymentFailed.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PaymentFailed extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $payment;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($payment)
|
||||
{
|
||||
$this->payment = $payment;
|
||||
}
|
||||
|
||||
}
|
25
app/Events/PaymentWasRefunded.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PaymentWasRefunded extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
|
||||
public $payment;
|
||||
public $refundAmount;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($payment, $refundAmount)
|
||||
{
|
||||
$this->payment = $payment;
|
||||
$this->refundAmount = $refundAmount;
|
||||
}
|
||||
|
||||
}
|
22
app/Events/PaymentWasVoided.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php namespace App\Events;
|
||||
|
||||
use App\Events\Event;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class PaymentWasVoided extends Event {
|
||||
|
||||
use SerializesModels;
|
||||
public $payment;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($payment)
|
||||
{
|
||||
$this->payment = $payment;
|
||||
}
|
||||
|
||||
}
|
@ -182,14 +182,7 @@ class AccountController extends BaseController
|
||||
|
||||
if ($account->company->payment) {
|
||||
$payment = $account->company->payment;
|
||||
|
||||
$gateway = $this->paymentService->createGateway($payment->account_gateway);
|
||||
$refund = $gateway->refund(array(
|
||||
'transactionReference' => $payment->transaction_reference,
|
||||
'amount' => $payment->amount
|
||||
));
|
||||
$refund->send();
|
||||
$payment->delete();
|
||||
$this->paymentService->refund($payment);
|
||||
Session::flash('message', trans('texts.plan_refunded'));
|
||||
\Log::info("Refunded Plan Payment: {$account->name} - {$user->email}");
|
||||
} else {
|
||||
|
@ -86,6 +86,7 @@ class AccountGatewayController extends BaseController
|
||||
->where('id', '!=', GATEWAY_BITPAY)
|
||||
->where('id', '!=', GATEWAY_GOCARDLESS)
|
||||
->where('id', '!=', GATEWAY_DWOLLA)
|
||||
->where('id', '!=', GATEWAY_STRIPE)
|
||||
->orderBy('name')->get();
|
||||
$data['hiddenFields'] = Gateway::$hiddenFields;
|
||||
|
||||
@ -104,7 +105,31 @@ class AccountGatewayController extends BaseController
|
||||
$paymentTypes = [];
|
||||
foreach (Gateway::$paymentTypes as $type) {
|
||||
if ($accountGateway || !$account->getGatewayByType($type)) {
|
||||
$paymentTypes[$type] = trans('texts.'.strtolower($type));
|
||||
if ($type == PAYMENT_TYPE_CREDIT_CARD && $account->getGatewayByType(PAYMENT_TYPE_STRIPE)) {
|
||||
// Stripe is already handling credit card payments
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type == PAYMENT_TYPE_STRIPE && $account->getGatewayByType(PAYMENT_TYPE_CREDIT_CARD)) {
|
||||
// Another gateway is already handling credit card payments
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($type == PAYMENT_TYPE_DIRECT_DEBIT && $stripeGateway = $account->getGatewayByType(PAYMENT_TYPE_STRIPE)) {
|
||||
if (!empty($stripeGateway->getAchEnabled())) {
|
||||
// Stripe is already handling ACH payments
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($type == PAYMENT_TYPE_PAYPAL && $braintreeGateway = $account->getGatewayConfig(GATEWAY_BRAINTREE)) {
|
||||
if (!empty($braintreeGateway->getPayPalEnabled())) {
|
||||
// PayPal is already enabled
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$paymentTypes[$type] = $type == PAYMENT_TYPE_CREDIT_CARD ? trans('texts.other_providers'): trans('texts.'.strtolower($type));
|
||||
|
||||
if ($type == PAYMENT_TYPE_BITCOIN) {
|
||||
$paymentTypes[$type] .= ' - BitPay';
|
||||
@ -185,6 +210,8 @@ class AccountGatewayController extends BaseController
|
||||
$gatewayId = GATEWAY_GOCARDLESS;
|
||||
} elseif ($paymentType == PAYMENT_TYPE_DWOLLA) {
|
||||
$gatewayId = GATEWAY_DWOLLA;
|
||||
} elseif ($paymentType == PAYMENT_TYPE_STRIPE) {
|
||||
$gatewayId = GATEWAY_STRIPE;
|
||||
}
|
||||
|
||||
if (!$gatewayId) {
|
||||
@ -204,6 +231,7 @@ class AccountGatewayController extends BaseController
|
||||
// do nothing - we're unable to acceptance test with StripeJS
|
||||
} else {
|
||||
$rules['publishable_key'] = 'required';
|
||||
$rules['enable_ach'] = 'boolean';
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,6 +287,35 @@ class AccountGatewayController extends BaseController
|
||||
$config->publishableKey = $oldConfig->publishableKey;
|
||||
}
|
||||
|
||||
$plaidClientId = Input::get('plaid_client_id');
|
||||
if ($plaidClientId = str_replace('*', '', $plaidClientId)) {
|
||||
$config->plaidClientId = $plaidClientId;
|
||||
} elseif ($oldConfig && property_exists($oldConfig, 'plaidClientId')) {
|
||||
$config->plaidClientId = $oldConfig->plaidClientId;
|
||||
}
|
||||
|
||||
$plaidSecret = Input::get('plaid_secret');
|
||||
if ($plaidSecret = str_replace('*', '', $plaidSecret)) {
|
||||
$config->plaidSecret = $plaidSecret;
|
||||
} elseif ($oldConfig && property_exists($oldConfig, 'plaidSecret')) {
|
||||
$config->plaidSecret = $oldConfig->plaidSecret;
|
||||
}
|
||||
|
||||
$plaidPublicKey = Input::get('plaid_public_key');
|
||||
if ($plaidPublicKey = str_replace('*', '', $plaidPublicKey)) {
|
||||
$config->plaidPublicKey = $plaidPublicKey;
|
||||
} elseif ($oldConfig && property_exists($oldConfig, 'plaidPublicKey')) {
|
||||
$config->plaidPublicKey = $oldConfig->plaidPublicKey;
|
||||
}
|
||||
|
||||
if ($gatewayId == GATEWAY_STRIPE) {
|
||||
$config->enableAch = boolval(Input::get('enable_ach'));
|
||||
}
|
||||
|
||||
if ($gatewayId == GATEWAY_BRAINTREE) {
|
||||
$config->enablePayPal = boolval(Input::get('enable_paypal'));
|
||||
}
|
||||
|
||||
$cardCount = 0;
|
||||
if ($creditcards) {
|
||||
foreach ($creditcards as $card => $value) {
|
||||
|
@ -33,8 +33,7 @@ class AuthController extends Controller {
|
||||
$client = $invoice->client;
|
||||
$account = $client->account;
|
||||
|
||||
$data['hideLogo'] = $account->hasFeature(FEATURE_WHITE_LABEL);
|
||||
$data['clientViewCSS'] = $account->clientViewCSS();
|
||||
$data['account'] = $account;
|
||||
$data['clientFontUrl'] = $account->getFontsUrl();
|
||||
}
|
||||
}
|
||||
|
@ -49,9 +49,7 @@ class PasswordController extends Controller {
|
||||
$invoice = $invitation->invoice;
|
||||
$client = $invoice->client;
|
||||
$account = $client->account;
|
||||
|
||||
$data['hideLogo'] = $account->hasFeature(FEATURE_WHITE_LABEL);
|
||||
$data['clientViewCSS'] = $account->clientViewCSS();
|
||||
$data['account'] = $account;
|
||||
$data['clientFontUrl'] = $account->getFontsUrl();
|
||||
}
|
||||
}
|
||||
@ -117,8 +115,7 @@ class PasswordController extends Controller {
|
||||
$client = $invoice->client;
|
||||
$account = $client->account;
|
||||
|
||||
$data['hideLogo'] = $account->hasFeature(FEATURE_WHITE_LABEL);
|
||||
$data['clientViewCSS'] = $account->clientViewCSS();
|
||||
$data['account'] = $account;
|
||||
$data['clientFontUrl'] = $account->getFontsUrl();
|
||||
}
|
||||
}
|
||||
|
@ -135,7 +135,8 @@ class ClientController extends BaseController
|
||||
'hasRecurringInvoices' => Invoice::scope()->where('is_recurring', '=', true)->whereClientId($client->id)->count() > 0,
|
||||
'hasQuotes' => Invoice::scope()->where('is_quote', '=', true)->whereClientId($client->id)->count() > 0,
|
||||
'hasTasks' => Task::scope()->whereClientId($client->id)->count() > 0,
|
||||
'gatewayLink' => $client->getGatewayLink(),
|
||||
'gatewayLink' => $client->getGatewayLink($accountGateway),
|
||||
'gateway' => $accountGateway
|
||||
);
|
||||
|
||||
return View::make('clients.show', $data);
|
||||
|
@ -15,14 +15,17 @@ use Cache;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Invitation;
|
||||
use App\Models\Client;
|
||||
use App\Models\Account;
|
||||
use App\Models\PaymentType;
|
||||
use App\Models\License;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Affiliate;
|
||||
use App\Models\PaymentMethod;
|
||||
use App\Ninja\Repositories\PaymentRepository;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
use App\Ninja\Mailers\ContactMailer;
|
||||
use App\Ninja\Mailers\UserMailer;
|
||||
use App\Services\PaymentService;
|
||||
|
||||
use App\Http\Requests\PaymentRequest;
|
||||
@ -33,7 +36,7 @@ class PaymentController extends BaseController
|
||||
{
|
||||
protected $entityType = ENTITY_PAYMENT;
|
||||
|
||||
public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService)
|
||||
public function __construct(PaymentRepository $paymentRepo, InvoiceRepository $invoiceRepo, AccountRepository $accountRepo, ContactMailer $contactMailer, PaymentService $paymentService, UserMailer $userMailer)
|
||||
{
|
||||
// parent::__construct();
|
||||
|
||||
@ -42,6 +45,7 @@ class PaymentController extends BaseController
|
||||
$this->accountRepo = $accountRepo;
|
||||
$this->contactMailer = $contactMailer;
|
||||
$this->paymentService = $paymentService;
|
||||
$this->userMailer = $userMailer;
|
||||
}
|
||||
|
||||
public function index()
|
||||
@ -56,8 +60,10 @@ class PaymentController extends BaseController
|
||||
'client',
|
||||
'transaction_reference',
|
||||
'method',
|
||||
'source',
|
||||
'payment_amount',
|
||||
'payment_date',
|
||||
'status',
|
||||
''
|
||||
]),
|
||||
));
|
||||
@ -128,7 +134,7 @@ class PaymentController extends BaseController
|
||||
];
|
||||
}
|
||||
|
||||
public function show_payment($invitationKey, $paymentType = false)
|
||||
public function show_payment($invitationKey, $paymentType = false, $sourceId = false)
|
||||
{
|
||||
|
||||
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
|
||||
@ -144,54 +150,84 @@ class PaymentController extends BaseController
|
||||
$account->account_gateways[0]->getPaymentType();
|
||||
}
|
||||
|
||||
$data = array();
|
||||
|
||||
if ($paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL) {
|
||||
if ($paymentType == PAYMENT_TYPE_TOKEN) {
|
||||
$useToken = true;
|
||||
$paymentType = PAYMENT_TYPE_CREDIT_CARD;
|
||||
$accountGateway = $invoice->client->account->getTokenGateway();
|
||||
$paymentType = $accountGateway->getPaymentType();
|
||||
} else {
|
||||
$accountGateway = $invoice->client->account->getGatewayByType($paymentType);
|
||||
}
|
||||
|
||||
Session::put($invitation->id . 'payment_type', $paymentType);
|
||||
|
||||
$accountGateway = $invoice->client->account->getGatewayByType($paymentType);
|
||||
$gateway = $accountGateway->gateway;
|
||||
|
||||
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
|
||||
|
||||
|
||||
// Handle offsite payments
|
||||
if ($useToken || $paymentType != PAYMENT_TYPE_CREDIT_CARD
|
||||
$isOffsite = ($paymentType != PAYMENT_TYPE_CREDIT_CARD && $accountGateway->getPaymentType() != PAYMENT_TYPE_STRIPE)
|
||||
|| $gateway->id == GATEWAY_EWAY
|
||||
|| $gateway->id == GATEWAY_TWO_CHECKOUT
|
||||
|| $gateway->id == GATEWAY_PAYFAST
|
||||
|| $gateway->id == GATEWAY_MOLLIE) {
|
||||
|| $gateway->id == GATEWAY_MOLLIE;
|
||||
|
||||
// Handle offsite payments
|
||||
if ($useToken || $isOffsite) {
|
||||
if (Session::has('error')) {
|
||||
Session::reflash();
|
||||
return Redirect::to('view/'.$invitationKey);
|
||||
return Redirect::to('view/' . $invitationKey);
|
||||
} else {
|
||||
return self::do_payment($invitationKey, false, $useToken);
|
||||
return self::do_payment($invitationKey, false, $useToken, $sourceId);
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
$data += [
|
||||
'accountGateway' => $accountGateway,
|
||||
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
|
||||
'gateway' => $gateway,
|
||||
'showAddress' => $accountGateway->show_address,
|
||||
];
|
||||
|
||||
if ($paymentType == PAYMENT_TYPE_STRIPE_ACH) {
|
||||
$data['currencies'] = Cache::get('currencies');
|
||||
}
|
||||
|
||||
if ($gateway->id == GATEWAY_BRAINTREE) {
|
||||
$data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account);
|
||||
}
|
||||
|
||||
} else {
|
||||
if ($deviceData = Input::get('details')) {
|
||||
Session::put($invitation->id . 'device_data', $deviceData);
|
||||
}
|
||||
|
||||
Session::put($invitation->id . 'payment_type', PAYMENT_TYPE_BRAINTREE_PAYPAL);
|
||||
$paypalDetails = json_decode(Input::get('details'));
|
||||
if (!$sourceId || !$paypalDetails) {
|
||||
return Redirect::to('view/'.$invitationKey);
|
||||
}
|
||||
$data['paypalDetails'] = $paypalDetails;
|
||||
}
|
||||
|
||||
$data += [
|
||||
'showBreadcrumbs' => false,
|
||||
'url' => 'payment/'.$invitationKey,
|
||||
'amount' => $invoice->getRequestedAmount(),
|
||||
'invoiceNumber' => $invoice->invoice_number,
|
||||
'client' => $client,
|
||||
'contact' => $invitation->contact,
|
||||
'gateway' => $gateway,
|
||||
'accountGateway' => $accountGateway,
|
||||
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
|
||||
'paymentType' => $paymentType,
|
||||
'countries' => Cache::get('countries'),
|
||||
'currencyId' => $client->getCurrencyId(),
|
||||
'currencyCode' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD'),
|
||||
'account' => $client->account,
|
||||
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
|
||||
'hideHeader' => $account->isNinjaAccount(),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'showAddress' => $accountGateway->show_address,
|
||||
'sourceId' => $sourceId,
|
||||
'clientFontUrl' => $client->account->getFontsUrl(),
|
||||
];
|
||||
|
||||
return View::make('payments.payment', $data);
|
||||
return View::make('payments.add_paymentmethod', $data);
|
||||
}
|
||||
|
||||
public function show_license_payment()
|
||||
@ -222,7 +258,7 @@ class PaymentController extends BaseController
|
||||
|
||||
$account = $this->accountRepo->getNinjaAccount();
|
||||
$account->load('account_gateways.gateway');
|
||||
$accountGateway = $account->getGatewayByType(PAYMENT_TYPE_CREDIT_CARD);
|
||||
$accountGateway = $account->getGatewayByType(PAYMENT_TYPE_STRIPE_CREDIT_CARD);
|
||||
$gateway = $accountGateway->gateway;
|
||||
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
|
||||
|
||||
@ -247,7 +283,7 @@ class PaymentController extends BaseController
|
||||
'showAddress' => true,
|
||||
];
|
||||
|
||||
return View::make('payments.payment', $data);
|
||||
return View::make('payments.add_paymentmethod', $data);
|
||||
}
|
||||
|
||||
public function do_license_payment()
|
||||
@ -278,7 +314,7 @@ class PaymentController extends BaseController
|
||||
|
||||
$account = $this->accountRepo->getNinjaAccount();
|
||||
$account->load('account_gateways.gateway');
|
||||
$accountGateway = $account->getGatewayByType(PAYMENT_TYPE_CREDIT_CARD);
|
||||
$accountGateway = $account->getGatewayByType(PAYMENT_TYPE_STRIPE_CREDIT_CARD);
|
||||
|
||||
try {
|
||||
$affiliate = Affiliate::find(Session::get('affiliate_id'));
|
||||
@ -363,21 +399,13 @@ class PaymentController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
public function do_payment($invitationKey, $onSite = true, $useToken = false)
|
||||
{
|
||||
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
|
||||
$invoice = $invitation->invoice;
|
||||
$client = $invoice->client;
|
||||
$account = $client->account;
|
||||
$accountGateway = $account->getGatewayByType(Session::get($invitation->id . 'payment_type'));
|
||||
|
||||
|
||||
public static function processPaymentClientDetails($client, $accountGateway, $paymentType, $onSite = true){
|
||||
$rules = [
|
||||
'first_name' => 'required',
|
||||
'last_name' => 'required',
|
||||
];
|
||||
|
||||
if ( ! Input::get('stripeToken')) {
|
||||
if ( ! Input::get('stripeToken') && ! Input::get('payment_method_nonce') && !(Input::get('plaidPublicToken') && Input::get('plaidAccountId'))) {
|
||||
$rules = array_merge(
|
||||
$rules,
|
||||
[
|
||||
@ -389,7 +417,9 @@ class PaymentController extends BaseController
|
||||
);
|
||||
}
|
||||
|
||||
if ($accountGateway->show_address) {
|
||||
$requireAddress = $accountGateway->show_address && $paymentType != PAYMENT_TYPE_STRIPE_ACH && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL;
|
||||
|
||||
if ($requireAddress) {
|
||||
$rules = array_merge($rules, [
|
||||
'address1' => 'required',
|
||||
'city' => 'required',
|
||||
@ -408,7 +438,7 @@ class PaymentController extends BaseController
|
||||
->withInput(Request::except('cvv'));
|
||||
}
|
||||
|
||||
if ($accountGateway->update_address) {
|
||||
if ($requireAddress && $accountGateway->update_address) {
|
||||
$client->address1 = trim(Input::get('address1'));
|
||||
$client->address2 = trim(Input::get('address2'));
|
||||
$client->city = trim(Input::get('city'));
|
||||
@ -419,6 +449,37 @@ class PaymentController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function do_payment($invitationKey, $onSite = true, $useToken = false, $sourceId = false)
|
||||
{
|
||||
$invitation = Invitation::with('invoice.invoice_items', 'invoice.client.currency', 'invoice.client.account.currency', 'invoice.client.account.account_gateways.gateway')->where('invitation_key', '=', $invitationKey)->firstOrFail();
|
||||
$invoice = $invitation->invoice;
|
||||
$client = $invoice->client;
|
||||
$account = $client->account;
|
||||
$paymentType = Session::get($invitation->id . 'payment_type');
|
||||
$accountGateway = $account->getGatewayByType($paymentType);
|
||||
$paymentMethod = null;
|
||||
|
||||
|
||||
|
||||
if ($useToken) {
|
||||
if(!$sourceId) {
|
||||
Session::flash('error', trans('texts.no_payment_method_specified'));
|
||||
return Redirect::to('payment/' . $invitationKey)->withInput(Request::except('cvv'));
|
||||
} else {
|
||||
$customerReference = $client->getGatewayToken($accountGateway, $accountGatewayToken/* return parameter*/);
|
||||
$paymentMethod = PaymentMethod::scope($sourceId, $account->id, $accountGatewayToken->id)->firstOrFail();
|
||||
$sourceReference = $paymentMethod->source_reference;
|
||||
}
|
||||
}
|
||||
|
||||
$result = static::processPaymentClientDetails($client, $accountGateway, $paymentType, $onSite);
|
||||
if ($result !== true) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
try {
|
||||
// For offsite payments send the client's details on file
|
||||
// If we're using a token then we don't need to send any other data
|
||||
@ -433,27 +494,65 @@ class PaymentController extends BaseController
|
||||
|
||||
// check if we're creating/using a billing token
|
||||
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
|
||||
if ($token = Input::get('stripeToken')) {
|
||||
$details['token'] = $token;
|
||||
unset($details['card']);
|
||||
if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && !Input::get('authorize_ach')) {
|
||||
Session::flash('error', trans('texts.ach_authorization_required'));
|
||||
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
|
||||
}
|
||||
|
||||
if ($useToken) {
|
||||
$details['customerReference'] = $client->getGatewayToken();
|
||||
} elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing')) {
|
||||
$token = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $invitation->contact_id);
|
||||
$details['customerReference'] = $customerReference;
|
||||
unset($details['token']);
|
||||
$details['cardReference'] = $sourceReference;
|
||||
} elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing') || $paymentType == PAYMENT_TYPE_STRIPE_ACH) {
|
||||
$token = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $invitation->contact_id, $customerReference/* return parameter */, $paymentMethod/* return parameter */);
|
||||
if ($token) {
|
||||
$details['customerReference'] = $token;
|
||||
$details['token'] = $token;
|
||||
$details['customerReference'] = $customerReference;
|
||||
|
||||
if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && empty(Input::get('plaidPublicToken')) ) {
|
||||
// The user needs to complete verification
|
||||
Session::flash('message', trans('texts.bank_account_verification_next_steps'));
|
||||
return Redirect::to('/client/paymentmethods');
|
||||
}
|
||||
} else {
|
||||
$this->error('Token-No-Ref', $this->paymentService->lastError, $accountGateway);
|
||||
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
|
||||
}
|
||||
}
|
||||
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
|
||||
$deviceData = Input::get('device_data');
|
||||
if (!$deviceData) {
|
||||
$deviceData = Session::get($invitation->id . 'device_data');
|
||||
}
|
||||
|
||||
if ($token = Input::get('payment_method_nonce')) {
|
||||
$details['token'] = $token;
|
||||
unset($details['card']);
|
||||
}
|
||||
|
||||
if ($useToken) {
|
||||
$details['customerId'] = $customerReference;
|
||||
$details['paymentMethodToken'] = $sourceReference;
|
||||
unset($details['token']);
|
||||
} elseif ($account->token_billing_type_id == TOKEN_BILLING_ALWAYS || Input::get('token_billing')) {
|
||||
$token = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $invitation->contact_id, $customerReference/* return parameter */, $paymentMethod/* return parameter */);
|
||||
if ($token) {
|
||||
$details['paymentMethodToken'] = $token;
|
||||
$details['customerId'] = $customerReference;
|
||||
unset($details['token']);
|
||||
} else {
|
||||
$this->error('Token-No-Ref', $this->paymentService->lastError, $accountGateway);
|
||||
return Redirect::to('payment/'.$invitationKey)->withInput(Request::except('cvv'));
|
||||
}
|
||||
}
|
||||
|
||||
if($deviceData) {
|
||||
$details['deviceData'] = $deviceData;
|
||||
}
|
||||
}
|
||||
|
||||
$response = $gateway->purchase($details)->send();
|
||||
|
||||
|
||||
if ($accountGateway->gateway_id == GATEWAY_EWAY) {
|
||||
$ref = $response->getData()['AccessCode'];
|
||||
} elseif ($accountGateway->gateway_id == GATEWAY_TWO_CHECKOUT) {
|
||||
@ -480,7 +579,7 @@ class PaymentController extends BaseController
|
||||
}
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $ref);
|
||||
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $ref, null, $details, $paymentMethod, $response);
|
||||
Session::flash('message', trans('texts.applied_payment'));
|
||||
|
||||
if ($account->account_key == NINJA_ACCOUNT_KEY) {
|
||||
@ -577,7 +676,7 @@ class PaymentController extends BaseController
|
||||
if ($response->isCancelled()) {
|
||||
// do nothing
|
||||
} elseif ($response->isSuccessful()) {
|
||||
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $ref, $payerId);
|
||||
$payment = $this->paymentService->createPayment($invitation, $accountGateway, $ref, $payerId, $details, null, $purchaseResponse);
|
||||
Session::flash('message', trans('texts.applied_payment'));
|
||||
} else {
|
||||
$this->error('offsite', $response->getMessage(), $accountGateway);
|
||||
@ -624,11 +723,12 @@ class PaymentController extends BaseController
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$amount = Input::get('amount');
|
||||
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
||||
$count = $this->paymentService->bulk($ids, $action);
|
||||
$count = $this->paymentService->bulk($ids, $action, array('amount'=>$amount));
|
||||
|
||||
if ($count > 0) {
|
||||
$message = Utils::pluralize($action.'d_payment', $count);
|
||||
$message = Utils::pluralize($action=='refund'?'refunded_payment':$action.'d_payment', $count);
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
@ -646,4 +746,136 @@ class PaymentController extends BaseController
|
||||
Session::flash('error', $message);
|
||||
Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true);
|
||||
}
|
||||
|
||||
public function getBankInfo($routingNumber) {
|
||||
if (strlen($routingNumber) != 9 || !preg_match('/\d{9}/', $routingNumber)) {
|
||||
return response()->json([
|
||||
'message' => 'Invalid routing number',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$data = PaymentMethod::lookupBankData($routingNumber);
|
||||
|
||||
if (is_string($data)) {
|
||||
return response()->json([
|
||||
'message' => $data,
|
||||
], 500);
|
||||
} elseif (!empty($data)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Bank not found',
|
||||
], 404);
|
||||
}
|
||||
|
||||
public function handlePaymentWebhook($accountKey, $gatewayId)
|
||||
{
|
||||
$gatewayId = intval($gatewayId);
|
||||
|
||||
$account = Account::where('accounts.account_key', '=', $accountKey)->first();
|
||||
|
||||
if (!$account) {
|
||||
return response()->json([
|
||||
'message' => 'Unknown account',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$accountGateway = $account->getGatewayConfig(intval($gatewayId));
|
||||
|
||||
if (!$accountGateway) {
|
||||
return response()->json([
|
||||
'message' => 'Unknown gateway',
|
||||
], 404);
|
||||
}
|
||||
|
||||
switch($gatewayId) {
|
||||
case GATEWAY_STRIPE:
|
||||
return $this->handleStripeWebhook($accountGateway);
|
||||
default:
|
||||
return response()->json([
|
||||
'message' => 'Unsupported gateway',
|
||||
], 404);
|
||||
}
|
||||
}
|
||||
|
||||
protected function handleStripeWebhook($accountGateway) {
|
||||
$eventId = Input::get('id');
|
||||
$eventType= Input::get('type');
|
||||
$accountId = $accountGateway->account_id;
|
||||
|
||||
if (!$eventId) {
|
||||
return response()->json(['message' => 'Missing event id'], 400);
|
||||
}
|
||||
|
||||
if (!$eventType) {
|
||||
return response()->json(['message' => 'Missing event type'], 400);
|
||||
}
|
||||
|
||||
$supportedEvents = array(
|
||||
'charge.failed',
|
||||
'charge.succeeded',
|
||||
'customer.source.updated',
|
||||
'customer.source.deleted',
|
||||
);
|
||||
|
||||
if (!in_array($eventType, $supportedEvents)) {
|
||||
return array('message' => 'Ignoring event');
|
||||
}
|
||||
|
||||
// Fetch the event directly from Stripe for security
|
||||
$eventDetails = $this->paymentService->makeStripeCall($accountGateway, 'GET', 'events/'.$eventId);
|
||||
|
||||
if (is_string($eventDetails) || !$eventDetails) {
|
||||
return response()->json([
|
||||
'message' => $eventDetails ? $eventDetails : 'Could not get event details.',
|
||||
], 500);
|
||||
}
|
||||
|
||||
if ($eventType != $eventDetails['type']) {
|
||||
return response()->json(['message' => 'Event type mismatch'], 400);
|
||||
}
|
||||
|
||||
if (!$eventDetails['pending_webhooks']) {
|
||||
return response()->json(['message' => 'This is not a pending event'], 400);
|
||||
}
|
||||
|
||||
|
||||
if ($eventType == 'charge.failed' || $eventType == 'charge.succeeded') {
|
||||
$charge = $eventDetails['data']['object'];
|
||||
$transactionRef = $charge['id'];
|
||||
|
||||
$payment = Payment::scope(false, $accountId)->where('transaction_reference', '=', $transactionRef)->first();
|
||||
|
||||
if (!$payment) {
|
||||
return array('message' => 'Unknown payment');
|
||||
}
|
||||
|
||||
if ($eventType == 'charge.failed') {
|
||||
if (!$payment->isFailed()) {
|
||||
$payment->markFailed($charge['failure_message']);
|
||||
$this->userMailer->sendNotification($payment->user, $payment->invoice, 'payment_failed', $payment);
|
||||
}
|
||||
} elseif ($eventType == 'charge.succeeded') {
|
||||
$payment->markComplete();
|
||||
}
|
||||
} elseif($eventType == 'customer.source.updated' || $eventType == 'customer.source.deleted') {
|
||||
$source = $eventDetails['data']['object'];
|
||||
$sourceRef = $source['id'];
|
||||
|
||||
$paymentMethod = PaymentMethod::scope(false, $accountId)->where('source_reference', '=', $sourceRef)->first();
|
||||
|
||||
if (!$paymentMethod) {
|
||||
return array('message' => 'Unknown payment method');
|
||||
}
|
||||
|
||||
if ($eventType == 'customer.source.deleted') {
|
||||
$paymentMethod->delete();
|
||||
} elseif ($eventType == 'customer.source.updated') {
|
||||
$this->paymentService->convertPaymentMethodFromStripe($source, null, $paymentMethod)->save();
|
||||
}
|
||||
}
|
||||
|
||||
return array('message' => 'Processed successfully');
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,13 @@ use Request;
|
||||
use Response;
|
||||
use Session;
|
||||
use Datatable;
|
||||
use Validator;
|
||||
use Cache;
|
||||
use Redirect;
|
||||
use App\Models\Gateway;
|
||||
use App\Models\Invitation;
|
||||
use App\Models\Document;
|
||||
use App\ModelsPaymentMethod;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Ninja\Repositories\PaymentRepository;
|
||||
use App\Ninja\Repositories\ActivityRepository;
|
||||
@ -92,15 +96,22 @@ class PublicClientController extends BaseController
|
||||
'phone',
|
||||
]);
|
||||
|
||||
$data = array();
|
||||
$paymentTypes = $this->getPaymentTypes($client, $invitation);
|
||||
$paymentURL = '';
|
||||
if (count($paymentTypes)) {
|
||||
if (count($paymentTypes) == 1) {
|
||||
$paymentURL = $paymentTypes[0]['url'];
|
||||
if (!$account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) {
|
||||
$paymentURL = URL::to($paymentURL);
|
||||
}
|
||||
}
|
||||
|
||||
if ($braintreeGateway = $account->getGatewayConfig(GATEWAY_BRAINTREE)){
|
||||
if($braintreeGateway->getPayPalEnabled()) {
|
||||
$data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account);
|
||||
}
|
||||
}
|
||||
|
||||
$showApprove = $invoice->quote_invoice_id ? false : true;
|
||||
if ($invoice->due_date) {
|
||||
$showApprove = time() < strtotime($invoice->due_date);
|
||||
@ -122,15 +133,10 @@ class PublicClientController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
$data = array(
|
||||
$data += array(
|
||||
'account' => $account,
|
||||
'showApprove' => $showApprove,
|
||||
'showBreadcrumbs' => false,
|
||||
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
|
||||
'hideHeader' => $account->isNinjaAccount() || !$account->enable_client_portal,
|
||||
'hideDashboard' => !$account->enable_client_portal_dashboard,
|
||||
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'invoice' => $invoice->hidePrivateFields(),
|
||||
'invitation' => $invitation,
|
||||
@ -161,23 +167,84 @@ class PublicClientController extends BaseController
|
||||
$paymentTypes = [];
|
||||
$account = $client->account;
|
||||
|
||||
if ($client->getGatewayToken()) {
|
||||
$paymentMethods = $this->paymentService->getClientPaymentMethods($client);
|
||||
|
||||
if ($paymentMethods) {
|
||||
foreach ($paymentMethods as $paymentMethod) {
|
||||
if ($paymentMethod->payment_type_id != PAYMENT_TYPE_ACH || $paymentMethod->status == PAYMENT_METHOD_STATUS_VERIFIED) {
|
||||
$code = htmlentities(str_replace(' ', '', strtolower($paymentMethod->payment_type->name)));
|
||||
|
||||
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH) {
|
||||
if($paymentMethod->bank_data) {
|
||||
$html = '<div>' . htmlentities($paymentMethod->bank_data->name) . '</div>';
|
||||
}
|
||||
} elseif ($paymentMethod->payment_type_id == PAYMENT_TYPE_ID_PAYPAL) {
|
||||
$html = '<img height="22" src="'.URL::to('/images/credit_cards/paypal.png').'" alt="'.trans("texts.card_".$code).'">';
|
||||
} else {
|
||||
$html = '<img height="22" src="'.URL::to('/images/credit_cards/'.$code.'.png').'" alt="'.trans("texts.card_".$code).'">';
|
||||
}
|
||||
|
||||
$url = URL::to("/payment/{$invitation->invitation_key}/token/".$paymentMethod->public_id);
|
||||
|
||||
if ($paymentMethod->payment_type_id == PAYMENT_TYPE_ID_PAYPAL) {
|
||||
$html .= ' <span>'.$paymentMethod->email.'</span>';
|
||||
$url .= '#braintree_paypal';
|
||||
} elseif ($paymentMethod->payment_type_id != PAYMENT_TYPE_ACH) {
|
||||
$html .= '<div class="pull-right" style="text-align:right">'.trans('texts.card_expiration', array('expires' => Utils::fromSqlDate($paymentMethod->expiration, false)->format('m/y'))).'<br>';
|
||||
$html .= '•••'.$paymentMethod->last4.'</div>';
|
||||
} else {
|
||||
$html .= '<div style="text-align:right">';
|
||||
$html .= '•••'.$paymentMethod->last4.'</div>';
|
||||
}
|
||||
|
||||
$paymentTypes[] = [
|
||||
'url' => URL::to("payment/{$invitation->invitation_key}/token"), 'label' => trans('texts.use_card_on_file')
|
||||
'url' => $url,
|
||||
'label' => $html,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach(Gateway::$paymentTypes as $type) {
|
||||
if ($account->getGatewayByType($type)) {
|
||||
if ($gateway = $account->getGatewayByType($type)) {
|
||||
$types = array($type);
|
||||
|
||||
if ($type == PAYMENT_TYPE_STRIPE) {
|
||||
$types = array(PAYMENT_TYPE_STRIPE_CREDIT_CARD);
|
||||
if ($gateway->getAchEnabled()) {
|
||||
$types[] = PAYMENT_TYPE_STRIPE_ACH;
|
||||
}
|
||||
}
|
||||
|
||||
foreach($types as $type) {
|
||||
$typeLink = strtolower(str_replace('PAYMENT_TYPE_', '', $type));
|
||||
$url = URL::to("/payment/{$invitation->invitation_key}/{$typeLink}");
|
||||
|
||||
// PayPal doesn't allow being run in an iframe so we need to open in new tab
|
||||
if ($type === PAYMENT_TYPE_PAYPAL && $account->iframe_url) {
|
||||
$url = 'javascript:window.open("'.$url.'", "_blank")';
|
||||
$url = 'javascript:window.open("' . $url . '", "_blank")';
|
||||
}
|
||||
|
||||
if ($type == PAYMENT_TYPE_STRIPE_CREDIT_CARD) {
|
||||
$label = trans('texts.' . strtolower(PAYMENT_TYPE_CREDIT_CARD));
|
||||
} elseif ($type == PAYMENT_TYPE_STRIPE_ACH) {
|
||||
$label = trans('texts.' . strtolower(PAYMENT_TYPE_DIRECT_DEBIT));
|
||||
} else {
|
||||
$label = trans('texts.' . strtolower($type));
|
||||
}
|
||||
|
||||
$paymentTypes[] = [
|
||||
'url' => $url, 'label' => trans('texts.'.strtolower($type))
|
||||
'url' => $url, 'label' => $label
|
||||
];
|
||||
|
||||
if($gateway->getPayPalEnabled()) {
|
||||
$paymentTypes[] = [
|
||||
'label' => trans('texts.paypal'),
|
||||
'url' => $url = URL::to("/payment/{$invitation->invitation_key}/braintree_paypal"),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,12 +291,17 @@ class PublicClientController extends BaseController
|
||||
'color' => $color,
|
||||
'account' => $account,
|
||||
'client' => $client,
|
||||
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
|
||||
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'gateway' => $account->getTokenGateway(),
|
||||
'paymentMethods' => $this->paymentService->getClientPaymentMethods($client),
|
||||
];
|
||||
|
||||
if ($braintreeGateway = $account->getGatewayConfig(GATEWAY_BRAINTREE)){
|
||||
if($braintreeGateway->getPayPalEnabled()) {
|
||||
$data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->view('invited.dashboard', $data);
|
||||
}
|
||||
|
||||
@ -252,6 +324,9 @@ class PublicClientController extends BaseController
|
||||
'invoice' => trans('texts.invoice') . ' ' . $model->invoice,
|
||||
'contact' => Utils::getClientDisplayName($model),
|
||||
'payment' => trans('texts.payment') . ($model->payment ? ' ' . $model->payment : ''),
|
||||
'credit' => $model->payment_amount ? Utils::formatMoney($model->credit, $model->currency_id, $model->country_id) : '',
|
||||
'payment_amount' => $model->payment_amount ? Utils::formatMoney($model->payment_amount, $model->currency_id, $model->country_id) : null,
|
||||
'adjustment' => $model->adjustment ? Utils::formatMoney($model->adjustment, $model->currency_id, $model->country_id) : null,
|
||||
];
|
||||
|
||||
return trans("texts.activity_{$model->activity_type_id}", $data);
|
||||
@ -261,6 +336,33 @@ class PublicClientController extends BaseController
|
||||
->make();
|
||||
}
|
||||
|
||||
public function recurringInvoiceIndex()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$account = $invitation->account;
|
||||
|
||||
if (!$account->enable_client_portal) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'account' => $account,
|
||||
'client' => $invitation->invoice->client,
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'title' => trans('texts.recurring_invoices'),
|
||||
'entityType' => ENTITY_RECURRING_INVOICE,
|
||||
'columns' => Utils::trans(['frequency', 'start_date', 'end_date', 'invoice_total', 'auto_bill']),
|
||||
];
|
||||
|
||||
return response()->view('public_list', $data);
|
||||
}
|
||||
|
||||
public function invoiceIndex()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
@ -277,10 +379,8 @@ class PublicClientController extends BaseController
|
||||
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
|
||||
'hideDashboard' => !$account->enable_client_portal_dashboard,
|
||||
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'account' => $account,
|
||||
'client' => $invitation->invoice->client,
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'title' => trans('texts.invoices'),
|
||||
'entityType' => ENTITY_INVOICE,
|
||||
@ -299,6 +399,15 @@ class PublicClientController extends BaseController
|
||||
return $this->invoiceRepo->getClientDatatable($invitation->contact_id, ENTITY_INVOICE, Input::get('sSearch'));
|
||||
}
|
||||
|
||||
public function recurringInvoiceDatatable()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->invoiceRepo->getClientRecurringDatatable($invitation->contact_id);
|
||||
}
|
||||
|
||||
|
||||
public function paymentIndex()
|
||||
{
|
||||
@ -314,14 +423,11 @@ class PublicClientController extends BaseController
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
|
||||
'hideDashboard' => !$account->enable_client_portal_dashboard,
|
||||
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'account' => $account,
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'entityType' => ENTITY_PAYMENT,
|
||||
'title' => trans('texts.payments'),
|
||||
'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date'])
|
||||
'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'source', 'payment_amount', 'payment_date', 'status'])
|
||||
];
|
||||
|
||||
return response()->view('public_list', $data);
|
||||
@ -337,12 +443,60 @@ class PublicClientController extends BaseController
|
||||
return Datatable::query($payments)
|
||||
->addColumn('invoice_number', function ($model) { return $model->invitation_key ? link_to('/view/'.$model->invitation_key, $model->invoice_number)->toHtml() : $model->invoice_number; })
|
||||
->addColumn('transaction_reference', function ($model) { return $model->transaction_reference ? $model->transaction_reference : '<i>Manual entry</i>'; })
|
||||
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); })
|
||||
->addColumn('payment_type', function ($model) { return ($model->payment_type && !$model->last4) ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); })
|
||||
->addColumn('payment_source', function ($model) {
|
||||
$code = str_replace(' ', '', strtolower($model->payment_type));
|
||||
$card_type = trans("texts.card_" . $code);
|
||||
if ($model->payment_type_id != PAYMENT_TYPE_ACH) {
|
||||
if($model->last4) {
|
||||
$expiration = trans('texts.card_expiration', array('expires' => Utils::fromSqlDate($model->expiration, false)->format('m/y')));
|
||||
return '<img height="22" src="' . URL::to('/images/credit_cards/' . $code . '.png') . '" alt="' . htmlentities($card_type) . '"> •••' . $model->last4 . ' ' . $expiration;
|
||||
} elseif ($model->email) {
|
||||
return $model->email;
|
||||
}
|
||||
} elseif ($model->last4) {
|
||||
$bankData = PaymentMethod::lookupBankData($model->routing_number);
|
||||
if (is_object($bankData)) {
|
||||
return $bankData->name.' •••' . $model->last4;
|
||||
} elseif($model->last4) {
|
||||
return '<img height="22" src="' . URL::to('/images/credit_cards/ach.png') . '" alt="' . htmlentities($card_type) . '"> •••' . $model->last4;
|
||||
}
|
||||
}
|
||||
})
|
||||
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); })
|
||||
->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); })
|
||||
->addColumn('status', function ($model) { return $this->getPaymentStatusLabel($model); })
|
||||
->orderColumns( 'invoice_number', 'transaction_reference', 'payment_type', 'amount', 'payment_date')
|
||||
->make();
|
||||
}
|
||||
|
||||
private function getPaymentStatusLabel($model)
|
||||
{
|
||||
$label = trans("texts.status_" . strtolower($model->payment_status_name));
|
||||
$class = 'default';
|
||||
switch ($model->payment_status_id) {
|
||||
case PAYMENT_STATUS_PENDING:
|
||||
$class = 'info';
|
||||
break;
|
||||
case PAYMENT_STATUS_COMPLETED:
|
||||
$class = 'success';
|
||||
break;
|
||||
case PAYMENT_STATUS_FAILED:
|
||||
$class = 'danger';
|
||||
break;
|
||||
case PAYMENT_STATUS_PARTIALLY_REFUNDED:
|
||||
$label = trans('texts.status_partially_refunded_amount', [
|
||||
'amount' => Utils::formatMoney($model->refunded, $model->currency_id, $model->country_id),
|
||||
]);
|
||||
$class = 'primary';
|
||||
break;
|
||||
case PAYMENT_STATUS_REFUNDED:
|
||||
$class = 'default';
|
||||
break;
|
||||
}
|
||||
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
|
||||
}
|
||||
|
||||
public function quoteIndex()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
@ -358,10 +512,7 @@ class PublicClientController extends BaseController
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
|
||||
'hideDashboard' => !$account->enable_client_portal_dashboard,
|
||||
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'account' => $account,
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'title' => trans('texts.quotes'),
|
||||
'entityType' => ENTITY_QUOTE,
|
||||
@ -396,10 +547,7 @@ class PublicClientController extends BaseController
|
||||
$color = $account->primary_color ? $account->primary_color : '#0b4d78';
|
||||
$data = [
|
||||
'color' => $color,
|
||||
'hideLogo' => $account->hasFeature(FEATURE_WHITE_LABEL),
|
||||
'hideDashboard' => !$account->enable_client_portal_dashboard,
|
||||
'showDocuments' => $account->hasFeature(FEATURE_DOCUMENTS),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'account' => $account,
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'title' => trans('texts.documents'),
|
||||
'entityType' => ENTITY_DOCUMENT,
|
||||
@ -593,4 +741,241 @@ class PublicClientController extends BaseController
|
||||
return DocumentController::getDownloadResponse($document);
|
||||
}
|
||||
|
||||
public function paymentMethods()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$client = $invitation->invoice->client;
|
||||
$account = $client->account;
|
||||
$paymentMethods = $this->paymentService->getClientPaymentMethods($client);
|
||||
|
||||
$data = array(
|
||||
'account' => $account,
|
||||
'color' => $account->primary_color ? $account->primary_color : '#0b4d78',
|
||||
'client' => $client,
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'paymentMethods' => $paymentMethods,
|
||||
'gateway' => $account->getTokenGateway(),
|
||||
'title' => trans('texts.payment_methods')
|
||||
);
|
||||
|
||||
if ($braintreeGateway = $account->getGatewayConfig(GATEWAY_BRAINTREE)){
|
||||
if($braintreeGateway->getPayPalEnabled()) {
|
||||
$data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->view('payments.paymentmethods', $data);
|
||||
}
|
||||
|
||||
public function verifyPaymentMethod()
|
||||
{
|
||||
$publicId = Input::get('source_id');
|
||||
$amount1 = Input::get('verification1');
|
||||
$amount2 = Input::get('verification2');
|
||||
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$client = $invitation->invoice->client;
|
||||
$result = $this->paymentService->verifyClientPaymentMethod($client, $publicId, $amount1, $amount2);
|
||||
|
||||
if (is_string($result)) {
|
||||
Session::flash('error', $result);
|
||||
} else {
|
||||
Session::flash('message', trans('texts.payment_method_verified'));
|
||||
}
|
||||
|
||||
return redirect()->to($client->account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
|
||||
}
|
||||
|
||||
public function removePaymentMethod($publicId)
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$client = $invitation->invoice->client;
|
||||
$result = $this->paymentService->removeClientPaymentMethod($client, $publicId);
|
||||
|
||||
if (is_string($result)) {
|
||||
Session::flash('error', $result);
|
||||
} else {
|
||||
Session::flash('message', trans('texts.payment_method_removed'));
|
||||
}
|
||||
|
||||
return redirect()->to($client->account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
|
||||
}
|
||||
|
||||
public function addPaymentMethod($paymentType, $token=false)
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$invoice = $invitation->invoice;
|
||||
$client = $invitation->invoice->client;
|
||||
$account = $client->account;
|
||||
|
||||
$typeLink = $paymentType;
|
||||
$paymentType = 'PAYMENT_TYPE_' . strtoupper($paymentType);
|
||||
$accountGateway = $invoice->client->account->getTokenGateway();
|
||||
$gateway = $accountGateway->gateway;
|
||||
|
||||
if ($token && $paymentType == PAYMENT_TYPE_BRAINTREE_PAYPAL) {
|
||||
$sourceReference = $this->paymentService->createToken($this->paymentService->createGateway($accountGateway), array('token'=>$token), $accountGateway, $client, $invitation->contact_id);
|
||||
|
||||
if(empty($sourceReference)) {
|
||||
$this->paymentMethodError('Token-No-Ref', $this->paymentService->lastError, $accountGateway);
|
||||
} else {
|
||||
Session::flash('message', trans('texts.payment_method_added'));
|
||||
}
|
||||
return redirect()->to($account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
|
||||
}
|
||||
|
||||
$acceptedCreditCardTypes = $accountGateway->getCreditcardTypes();
|
||||
|
||||
|
||||
$data = [
|
||||
'showBreadcrumbs' => false,
|
||||
'client' => $client,
|
||||
'contact' => $invitation->contact,
|
||||
'gateway' => $gateway,
|
||||
'accountGateway' => $accountGateway,
|
||||
'acceptedCreditCardTypes' => $acceptedCreditCardTypes,
|
||||
'paymentType' => $paymentType,
|
||||
'countries' => Cache::get('countries'),
|
||||
'currencyId' => $client->getCurrencyId(),
|
||||
'currencyCode' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD'),
|
||||
'account' => $account,
|
||||
'url' => URL::to('client/paymentmethods/add/'.$typeLink),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'showAddress' => $accountGateway->show_address,
|
||||
'paymentTitle' => trans('texts.add_payment_method'),
|
||||
];
|
||||
|
||||
if ($paymentType == PAYMENT_TYPE_STRIPE_ACH) {
|
||||
|
||||
$data['currencies'] = Cache::get('currencies');
|
||||
}
|
||||
|
||||
if ($gateway->id == GATEWAY_BRAINTREE) {
|
||||
$data['braintreeClientToken'] = $this->paymentService->getBraintreeClientToken($account);
|
||||
}
|
||||
|
||||
return View::make('payments.add_paymentmethod', $data);
|
||||
}
|
||||
|
||||
public function postAddPaymentMethod($paymentType)
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$typeLink = $paymentType;
|
||||
$paymentType = 'PAYMENT_TYPE_' . strtoupper($paymentType);
|
||||
$client = $invitation->invoice->client;
|
||||
$account = $client->account;
|
||||
|
||||
$accountGateway = $account->getGatewayByType($paymentType);
|
||||
$sourceToken = $accountGateway->gateway_id == GATEWAY_STRIPE ? Input::get('stripeToken'):Input::get('payment_method_nonce');
|
||||
|
||||
$result = PaymentController::processPaymentClientDetails($client, $accountGateway, $paymentType);
|
||||
if ($result !== true) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ($sourceToken) {
|
||||
$details = array('token' => $sourceToken);
|
||||
} elseif (Input::get('plaidPublicToken')) {
|
||||
$usingPlaid = true;
|
||||
$details = array('plaidPublicToken' => Input::get('plaidPublicToken'), 'plaidAccountId' => Input::get('plaidAccountId'));
|
||||
}
|
||||
|
||||
if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && !Input::get('authorize_ach')) {
|
||||
Session::flash('error', trans('texts.ach_authorization_required'));
|
||||
return Redirect::to('client/paymentmethods/add/' . $typeLink)->withInput(Request::except('cvv'));
|
||||
}
|
||||
|
||||
if (!empty($details)) {
|
||||
$gateway = $this->paymentService->createGateway($accountGateway);
|
||||
$sourceReference = $this->paymentService->createToken($gateway, $details, $accountGateway, $client, $invitation->contact_id);
|
||||
} else {
|
||||
return Redirect::to('client/paymentmethods/add/' . $typeLink)->withInput(Request::except('cvv'));
|
||||
}
|
||||
|
||||
if(empty($sourceReference)) {
|
||||
$this->paymentMethodError('Token-No-Ref', $this->paymentService->lastError, $accountGateway);
|
||||
return Redirect::to('client/paymentmethods/add/' . $typeLink)->withInput(Request::except('cvv'));
|
||||
} else if ($paymentType == PAYMENT_TYPE_STRIPE_ACH && empty($usingPlaid) ) {
|
||||
// The user needs to complete verification
|
||||
Session::flash('message', trans('texts.bank_account_verification_next_steps'));
|
||||
return Redirect::to($account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
|
||||
} else {
|
||||
Session::flash('message', trans('texts.payment_method_added'));
|
||||
return redirect()->to($account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
|
||||
}
|
||||
}
|
||||
|
||||
public function setDefaultPaymentMethod(){
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$validator = Validator::make(Input::all(), array('source' => 'required'));
|
||||
$client = $invitation->invoice->client;
|
||||
if ($validator->fails()) {
|
||||
return Redirect::to($client->account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
|
||||
}
|
||||
|
||||
$result = $this->paymentService->setClientDefaultPaymentMethod($client, Input::get('source'));
|
||||
|
||||
if (is_string($result)) {
|
||||
Session::flash('error', $result);
|
||||
} else {
|
||||
Session::flash('message', trans('texts.payment_method_set_as_default'));
|
||||
}
|
||||
|
||||
return redirect()->to($client->account->enable_client_portal?'/client/dashboard':'/client/paymentmethods/');
|
||||
}
|
||||
|
||||
private function paymentMethodError($type, $error, $accountGateway = false, $exception = false)
|
||||
{
|
||||
$message = '';
|
||||
if ($accountGateway && $accountGateway->gateway) {
|
||||
$message = $accountGateway->gateway->name . ': ';
|
||||
}
|
||||
$message .= $error ?: trans('texts.payment_method_error');
|
||||
|
||||
Session::flash('error', $message);
|
||||
Utils::logError("Payment Method Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true);
|
||||
}
|
||||
|
||||
public function setAutoBill(){
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
return $this->returnError();
|
||||
}
|
||||
|
||||
$validator = Validator::make(Input::all(), array('public_id' => 'required'));
|
||||
$client = $invitation->invoice->client;
|
||||
|
||||
if ($validator->fails()) {
|
||||
return Redirect::to('client/invoices/recurring');
|
||||
}
|
||||
|
||||
$publicId = Input::get('public_id');
|
||||
$enable = Input::get('enable');
|
||||
$invoice = $client->invoices->where('public_id', intval($publicId))->first();
|
||||
|
||||
if ($invoice && $invoice->is_recurring && ($invoice->auto_bill == AUTO_BILL_OPT_IN || $invoice->auto_bill == AUTO_BILL_OPT_OUT)) {
|
||||
$invoice->client_enable_auto_bill = $enable ? true : false;
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
return Redirect::to('client/invoices/recurring');
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ class VerifyCsrfToken extends BaseVerifier {
|
||||
'hook/email_opened',
|
||||
'hook/email_bounced',
|
||||
'reseller_stats',
|
||||
'paymenthook/*',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -41,11 +41,19 @@ Route::group(['middleware' => 'auth:client'], function() {
|
||||
Route::get('download/{invitation_key}', 'PublicClientController@download');
|
||||
Route::get('view', 'HomeController@viewLogo');
|
||||
Route::get('approve/{invitation_key}', 'QuoteController@approve');
|
||||
Route::get('payment/{invitation_key}/{payment_type?}', 'PaymentController@show_payment');
|
||||
Route::get('payment/{invitation_key}/{payment_type?}/{source_id?}', 'PaymentController@show_payment');
|
||||
Route::post('payment/{invitation_key}', 'PaymentController@do_payment');
|
||||
Route::match(['GET', 'POST'], 'complete', 'PaymentController@offsite_payment');
|
||||
Route::get('client/paymentmethods', 'PublicClientController@paymentMethods');
|
||||
Route::post('client/paymentmethods/verify', 'PublicClientController@verifyPaymentMethod');
|
||||
Route::get('client/paymentmethods/add/{payment_type}/{source_id?}', 'PublicClientController@addPaymentMethod');
|
||||
Route::post('client/paymentmethods/add/{payment_type}', 'PublicClientController@postAddPaymentMethod');
|
||||
Route::post('client/paymentmethods/default', 'PublicClientController@setDefaultPaymentMethod');
|
||||
Route::post('client/paymentmethods/{source_id}/remove', 'PublicClientController@removePaymentMethod');
|
||||
Route::get('client/quotes', 'PublicClientController@quoteIndex');
|
||||
Route::get('client/invoices', 'PublicClientController@invoiceIndex');
|
||||
Route::get('client/invoices/recurring', 'PublicClientController@recurringInvoiceIndex');
|
||||
Route::post('client/invoices/auto_bill', 'PublicClientController@setAutoBill');
|
||||
Route::get('client/documents', 'PublicClientController@documentIndex');
|
||||
Route::get('client/payments', 'PublicClientController@paymentIndex');
|
||||
Route::get('client/dashboard', 'PublicClientController@dashboard');
|
||||
@ -55,12 +63,15 @@ Route::group(['middleware' => 'auth:client'], function() {
|
||||
|
||||
Route::get('api/client.quotes', array('as'=>'api.client.quotes', 'uses'=>'PublicClientController@quoteDatatable'));
|
||||
Route::get('api/client.invoices', array('as'=>'api.client.invoices', 'uses'=>'PublicClientController@invoiceDatatable'));
|
||||
Route::get('api/client.recurring_invoices', array('as'=>'api.client.recurring_invoices', 'uses'=>'PublicClientController@recurringInvoiceDatatable'));
|
||||
Route::get('api/client.documents', array('as'=>'api.client.documents', 'uses'=>'PublicClientController@documentDatatable'));
|
||||
Route::get('api/client.payments', array('as'=>'api.client.payments', 'uses'=>'PublicClientController@paymentDatatable'));
|
||||
Route::get('api/client.activity', array('as'=>'api.client.activity', 'uses'=>'PublicClientController@activityDatatable'));
|
||||
});
|
||||
|
||||
|
||||
Route::get('bank/{routing_number}', 'PaymentController@getBankInfo');
|
||||
Route::post('paymenthook/{accountKey}/{gatewayId}', 'PaymentController@handlePaymentWebhook');
|
||||
Route::get('license', 'PaymentController@show_license_payment');
|
||||
Route::post('license', 'PaymentController@do_license_payment');
|
||||
Route::get('claim_license', 'PaymentController@claim_license');
|
||||
@ -396,6 +407,9 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
//define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
|
||||
define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
|
||||
define('ACTIVITY_TYPE_DELETE_PAYMENT', 13);
|
||||
define('ACTIVITY_TYPE_VOIDED_PAYMENT', 39);
|
||||
define('ACTIVITY_TYPE_REFUNDED_PAYMENT', 40);
|
||||
define('ACTIVITY_TYPE_FAILED_PAYMENT', 41);
|
||||
|
||||
define('ACTIVITY_TYPE_CREATE_CREDIT', 14);
|
||||
//define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
|
||||
@ -473,7 +487,13 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('INVOICE_STATUS_PARTIAL', 5);
|
||||
define('INVOICE_STATUS_PAID', 6);
|
||||
|
||||
define('PAYMENT_TYPE_CREDIT', 1);
|
||||
define('PAYMENT_STATUS_PENDING', 1);
|
||||
define('PAYMENT_STATUS_VOIDED', 2);
|
||||
define('PAYMENT_STATUS_FAILED', 3);
|
||||
define('PAYMENT_STATUS_COMPLETED', 4);
|
||||
define('PAYMENT_STATUS_PARTIALLY_REFUNDED', 5);
|
||||
define('PAYMENT_STATUS_REFUNDED', 6);
|
||||
|
||||
define('CUSTOM_DESIGN', 11);
|
||||
|
||||
define('FREQUENCY_WEEKLY', 1);
|
||||
@ -537,6 +557,8 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('GATEWAY_DWOLLA', 43);
|
||||
define('GATEWAY_CHECKOUT_COM', 47);
|
||||
define('GATEWAY_CYBERSOURCE', 49);
|
||||
define('GATEWAY_WEPAY', 60);
|
||||
define('GATEWAY_BRAINTREE', 61);
|
||||
|
||||
define('EVENT_CREATE_CLIENT', 1);
|
||||
define('EVENT_CREATE_INVOICE', 2);
|
||||
@ -608,7 +630,34 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('TOKEN_BILLING_OPT_OUT', 3);
|
||||
define('TOKEN_BILLING_ALWAYS', 4);
|
||||
|
||||
define('PAYMENT_TYPE_CREDIT', 1);
|
||||
define('PAYMENT_TYPE_ACH', 5);
|
||||
define('PAYMENT_TYPE_VISA', 6);
|
||||
define('PAYMENT_TYPE_MASTERCARD', 7);
|
||||
define('PAYMENT_TYPE_AMERICAN_EXPRESS', 8);
|
||||
define('PAYMENT_TYPE_DISCOVER', 9);
|
||||
define('PAYMENT_TYPE_DINERS', 10);
|
||||
define('PAYMENT_TYPE_EUROCARD', 11);
|
||||
define('PAYMENT_TYPE_NOVA', 12);
|
||||
define('PAYMENT_TYPE_CREDIT_CARD_OTHER', 13);
|
||||
define('PAYMENT_TYPE_ID_PAYPAL', 14);
|
||||
define('PAYMENT_TYPE_CARTE_BLANCHE', 17);
|
||||
define('PAYMENT_TYPE_UNIONPAY', 18);
|
||||
define('PAYMENT_TYPE_JCB', 19);
|
||||
define('PAYMENT_TYPE_LASER', 20);
|
||||
define('PAYMENT_TYPE_MAESTRO', 21);
|
||||
define('PAYMENT_TYPE_SOLO', 22);
|
||||
define('PAYMENT_TYPE_SWITCH', 23);
|
||||
|
||||
define('PAYMENT_METHOD_STATUS_NEW', 'new');
|
||||
define('PAYMENT_METHOD_STATUS_VERIFICATION_FAILED', 'verification_failed');
|
||||
define('PAYMENT_METHOD_STATUS_VERIFIED', 'verified');
|
||||
|
||||
define('PAYMENT_TYPE_PAYPAL', 'PAYMENT_TYPE_PAYPAL');
|
||||
define('PAYMENT_TYPE_STRIPE', 'PAYMENT_TYPE_STRIPE');
|
||||
define('PAYMENT_TYPE_STRIPE_CREDIT_CARD', 'PAYMENT_TYPE_STRIPE_CREDIT_CARD');
|
||||
define('PAYMENT_TYPE_STRIPE_ACH', 'PAYMENT_TYPE_STRIPE_ACH');
|
||||
define('PAYMENT_TYPE_BRAINTREE_PAYPAL', 'PAYMENT_TYPE_BRAINTREE_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');
|
||||
@ -652,6 +701,11 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('RESELLER_REVENUE_SHARE', 'A');
|
||||
define('RESELLER_LIMITED_USERS', 'B');
|
||||
|
||||
define('AUTO_BILL_OFF', 0);
|
||||
define('AUTO_BILL_OPT_IN', 1);
|
||||
define('AUTO_BILL_OPT_OUT', 2);
|
||||
define('AUTO_BILL_ALWAYS', 3);
|
||||
|
||||
// These must be lowercase
|
||||
define('PLAN_FREE', 'free');
|
||||
define('PLAN_PRO', 'pro');
|
||||
|
@ -22,8 +22,11 @@ use App\Events\QuoteInvitationWasViewed;
|
||||
use App\Events\QuoteInvitationWasApproved;
|
||||
use App\Events\PaymentWasCreated;
|
||||
use App\Events\PaymentWasDeleted;
|
||||
use App\Events\PaymentWasRefunded;
|
||||
use App\Events\PaymentWasVoided;
|
||||
use App\Events\PaymentWasArchived;
|
||||
use App\Events\PaymentWasRestored;
|
||||
use App\Events\PaymentFailed;
|
||||
use App\Events\CreditWasCreated;
|
||||
use App\Events\CreditWasDeleted;
|
||||
use App\Events\CreditWasArchived;
|
||||
@ -309,6 +312,42 @@ class ActivityListener
|
||||
);
|
||||
}
|
||||
|
||||
public function refundedPayment(PaymentWasRefunded $event)
|
||||
{
|
||||
$payment = $event->payment;
|
||||
|
||||
$this->activityRepo->create(
|
||||
$payment,
|
||||
ACTIVITY_TYPE_REFUNDED_PAYMENT,
|
||||
$event->refundAmount,
|
||||
$event->refundAmount * -1
|
||||
);
|
||||
}
|
||||
|
||||
public function voidedPayment(PaymentWasVoided $event)
|
||||
{
|
||||
$payment = $event->payment;
|
||||
|
||||
$this->activityRepo->create(
|
||||
$payment,
|
||||
ACTIVITY_TYPE_VOIDED_PAYMENT,
|
||||
$payment->amount,
|
||||
$payment->amount * -1
|
||||
);
|
||||
}
|
||||
|
||||
public function failedPayment(PaymentFailed $event)
|
||||
{
|
||||
$payment = $event->payment;
|
||||
|
||||
$this->activityRepo->create(
|
||||
$payment,
|
||||
ACTIVITY_TYPE_FAILED_PAYMENT,
|
||||
$payment->amount,
|
||||
$payment->amount * -1
|
||||
);
|
||||
}
|
||||
|
||||
public function archivedPayment(PaymentWasArchived $event)
|
||||
{
|
||||
if ($event->payment->is_deleted) {
|
||||
|
@ -1,8 +1,10 @@
|
||||
<?php namespace App\Listeners;
|
||||
|
||||
use App\Events\PaymentFailed;
|
||||
use Carbon;
|
||||
use App\Models\Credit;
|
||||
use App\Events\PaymentWasDeleted;
|
||||
use App\Events\PaymentWasRefunded;
|
||||
use App\Ninja\Repositories\CreditRepository;
|
||||
|
||||
class CreditListener
|
||||
@ -26,7 +28,24 @@ class CreditListener
|
||||
$credit = Credit::createNew();
|
||||
$credit->client_id = $payment->client_id;
|
||||
$credit->credit_date = Carbon::now()->toDateTimeString();
|
||||
$credit->balance = $credit->amount = $payment->amount;
|
||||
$credit->balance = $credit->amount = $payment->amount - $payment->refunded;
|
||||
$credit->private_notes = $payment->transaction_reference;
|
||||
$credit->save();
|
||||
}
|
||||
|
||||
public function refundedPayment(PaymentWasRefunded $event)
|
||||
{
|
||||
$payment = $event->payment;
|
||||
|
||||
// if the payment was from a credit we need to refund the credit
|
||||
if ($payment->payment_type_id != PAYMENT_TYPE_CREDIT) {
|
||||
return;
|
||||
}
|
||||
|
||||
$credit = Credit::createNew();
|
||||
$credit->client_id = $payment->client_id;
|
||||
$credit->credit_date = Carbon::now()->toDateTimeString();
|
||||
$credit->balance = $credit->amount = $event->refundAmount;
|
||||
$credit->private_notes = $payment->transaction_reference;
|
||||
$credit->save();
|
||||
}
|
||||
|
@ -7,7 +7,10 @@ use App\Events\InvoiceWasUpdated;
|
||||
use App\Events\InvoiceWasCreated;
|
||||
use App\Events\PaymentWasCreated;
|
||||
use App\Events\PaymentWasDeleted;
|
||||
use App\Events\PaymentWasRefunded;
|
||||
use App\Events\PaymentWasRestored;
|
||||
use App\Events\PaymentWasVoided;
|
||||
use App\Events\PaymentFailed;
|
||||
use App\Events\InvoiceInvitationWasViewed;
|
||||
|
||||
class InvoiceListener
|
||||
@ -55,6 +58,26 @@ class InvoiceListener
|
||||
}
|
||||
|
||||
public function deletedPayment(PaymentWasDeleted $event)
|
||||
{
|
||||
$payment = $event->payment;
|
||||
$invoice = $payment->invoice;
|
||||
$adjustment = $payment->amount - $payment->refunded;
|
||||
|
||||
$invoice->updateBalances($adjustment);
|
||||
$invoice->updatePaidStatus();
|
||||
}
|
||||
|
||||
public function refundedPayment(PaymentWasRefunded $event)
|
||||
{
|
||||
$payment = $event->payment;
|
||||
$invoice = $payment->invoice;
|
||||
$adjustment = $event->refundAmount;
|
||||
|
||||
$invoice->updateBalances($adjustment);
|
||||
$invoice->updatePaidStatus();
|
||||
}
|
||||
|
||||
public function voidedPayment(PaymentWasVoided $event)
|
||||
{
|
||||
$payment = $event->payment;
|
||||
$invoice = $payment->invoice;
|
||||
@ -64,6 +87,16 @@ class InvoiceListener
|
||||
$invoice->updatePaidStatus();
|
||||
}
|
||||
|
||||
public function failedPayment(PaymentFailed $event)
|
||||
{
|
||||
$payment = $event->payment;
|
||||
$invoice = $payment->invoice;
|
||||
$adjustment = $payment->amount - $payment->refunded;
|
||||
|
||||
$invoice->updateBalances($adjustment);
|
||||
$invoice->updatePaidStatus();
|
||||
}
|
||||
|
||||
public function restoredPayment(PaymentWasRestored $event)
|
||||
{
|
||||
if ( ! $event->fromDeleted) {
|
||||
@ -72,7 +105,7 @@ class InvoiceListener
|
||||
|
||||
$payment = $event->payment;
|
||||
$invoice = $payment->invoice;
|
||||
$adjustment = $payment->amount * -1;
|
||||
$adjustment = ($payment->amount - $payment->refunded) * -1;
|
||||
|
||||
$invoice->updateBalances($adjustment);
|
||||
$invoice->updatePaidStatus();
|
||||
|
@ -381,6 +381,19 @@ class Account extends Eloquent
|
||||
|
||||
public function getGatewayByType($type = PAYMENT_TYPE_ANY)
|
||||
{
|
||||
if ($type == PAYMENT_TYPE_STRIPE_ACH || $type == PAYMENT_TYPE_STRIPE_CREDIT_CARD) {
|
||||
$type = PAYMENT_TYPE_STRIPE;
|
||||
}
|
||||
|
||||
if ($type == PAYMENT_TYPE_BRAINTREE_PAYPAL) {
|
||||
$gateway = $this->getGatewayConfig(GATEWAY_BRAINTREE);
|
||||
|
||||
if (!$gateway || !$gateway->getPayPalEnabled()){
|
||||
return false;
|
||||
}
|
||||
return $gateway;
|
||||
}
|
||||
|
||||
foreach ($this->account_gateways as $gateway) {
|
||||
if (!$type || $type == PAYMENT_TYPE_ANY) {
|
||||
return $gateway;
|
||||
@ -1217,9 +1230,9 @@ class Account extends Eloquent
|
||||
return false;
|
||||
}
|
||||
|
||||
public function showTokenCheckbox()
|
||||
public function showTokenCheckbox(&$storage_gateway = null)
|
||||
{
|
||||
if (!$this->isGatewayConfigured(GATEWAY_STRIPE)) {
|
||||
if (!($storage_gateway = $this->getTokenGatewayId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1227,6 +1240,25 @@ class Account extends Eloquent
|
||||
|| $this->token_billing_type_id == TOKEN_BILLING_OPT_OUT;
|
||||
}
|
||||
|
||||
public function getTokenGatewayId() {
|
||||
if ($this->isGatewayConfigured(GATEWAY_STRIPE)) {
|
||||
return GATEWAY_STRIPE;
|
||||
} elseif ($this->isGatewayConfigured(GATEWAY_BRAINTREE)) {
|
||||
return GATEWAY_BRAINTREE;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getTokenGateway() {
|
||||
$gatewayId = $this->getTokenGatewayId();
|
||||
if (!$gatewayId) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $this->getGatewayConfig($gatewayId);
|
||||
}
|
||||
|
||||
public function selectTokenCheckbox()
|
||||
{
|
||||
return $this->token_billing_type_id == TOKEN_BILLING_OPT_OUT;
|
||||
|
@ -71,5 +71,58 @@ class AccountGateway extends EntityModel
|
||||
|
||||
return $this->getConfigField('publishableKey');
|
||||
}
|
||||
|
||||
public function getAchEnabled()
|
||||
{
|
||||
return !empty($this->getConfigField('enableAch'));
|
||||
}
|
||||
|
||||
public function getPayPAlEnabled()
|
||||
{
|
||||
return !empty($this->getConfigField('enablePayPal'));
|
||||
}
|
||||
|
||||
public function getPlaidSecret()
|
||||
{
|
||||
if ( ! $this->isGateway(GATEWAY_STRIPE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->getConfigField('plaidSecret');
|
||||
}
|
||||
|
||||
public function getPlaidClientId()
|
||||
{
|
||||
if ( ! $this->isGateway(GATEWAY_STRIPE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->getConfigField('plaidClientId');
|
||||
}
|
||||
|
||||
public function getPlaidPublicKey()
|
||||
{
|
||||
if ( ! $this->isGateway(GATEWAY_STRIPE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->getConfigField('plaidPublicKey');
|
||||
}
|
||||
|
||||
public function getPlaidEnabled()
|
||||
{
|
||||
return !empty($this->getPlaidClientId()) && $this->getAchEnabled();
|
||||
}
|
||||
|
||||
public function getPlaidEnvironment()
|
||||
{
|
||||
if (!$this->getPlaidClientId()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stripe_key = $this->getPublishableStripeKey();
|
||||
|
||||
return substr(trim($stripe_key), 0, 8) == 'pk_test_' ? 'tartan' : 'production';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,4 +8,18 @@ class AccountGatewayToken extends Eloquent
|
||||
use SoftDeletes;
|
||||
protected $dates = ['deleted_at'];
|
||||
public $timestamps = true;
|
||||
|
||||
protected $casts = [
|
||||
'uses_local_payment_methods' => 'boolean',
|
||||
];
|
||||
|
||||
public function payment_methods()
|
||||
{
|
||||
return $this->hasMany('App\Models\PaymentMethod');
|
||||
}
|
||||
|
||||
public function default_payment_method()
|
||||
{
|
||||
return $this->hasOne('App\Models\PaymentMethod', 'id', 'default_payment_method_id');
|
||||
}
|
||||
}
|
@ -70,6 +70,8 @@ class Activity extends Eloquent
|
||||
'quote' => $invoice ? link_to($invoice->getRoute(), $invoice->getDisplayName()) : null,
|
||||
'contact' => $contactId ? $client->getDisplayName() : $user->getDisplayName(),
|
||||
'payment' => $payment ? $payment->transaction_reference : null,
|
||||
'payment_amount' => $payment ? $account->formatMoney($payment->amount, $payment) : null,
|
||||
'adjustment' => $this->adjustment ? $account->formatMoney($this->adjustment, $this) : asdf,
|
||||
'credit' => $credit ? $account->formatMoney($credit->amount, $client) : null,
|
||||
];
|
||||
|
||||
|
@ -261,7 +261,7 @@ class Client extends EntityModel
|
||||
}
|
||||
|
||||
|
||||
public function getGatewayToken()
|
||||
public function getGatewayToken(&$accountGateway = null, &$token = null)
|
||||
{
|
||||
$account = $this->account;
|
||||
|
||||
@ -273,7 +273,9 @@ class Client extends EntityModel
|
||||
return false;
|
||||
}
|
||||
|
||||
$accountGateway = $account->getGatewayConfig(GATEWAY_STRIPE);
|
||||
if (!$accountGateway){
|
||||
$accountGateway = $account->getTokenGateway();
|
||||
}
|
||||
|
||||
if (!$accountGateway) {
|
||||
return false;
|
||||
@ -285,10 +287,22 @@ class Client extends EntityModel
|
||||
return $token ? $token->token : false;
|
||||
}
|
||||
|
||||
public function getGatewayLink()
|
||||
public function getGatewayLink(&$accountGateway = null)
|
||||
{
|
||||
$token = $this->getGatewayToken();
|
||||
return $token ? "https://dashboard.stripe.com/customers/{$token}" : false;
|
||||
$token = $this->getGatewayToken($accountGateway);
|
||||
if (!$token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
|
||||
return "https://dashboard.stripe.com/customers/{$token}";
|
||||
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
|
||||
$merchantId = $accountGateway->getConfig()->merchantId;
|
||||
$testMode = $accountGateway->getConfig()->testMode;
|
||||
return $testMode ? "https://sandbox.braintreegateway.com/merchants/{$merchantId}/customers/{$token}" : "https://www.braintreegateway.com/merchants/{$merchantId}/customers/{$token}";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getAmount()
|
||||
@ -319,6 +333,10 @@ class Client extends EntityModel
|
||||
$this->last_login = Carbon::now()->toDateTimeString();
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function hasAutoBillConfigurableInvoices(){
|
||||
return $this->invoices()->whereIn('auto_bill', [AUTO_BILL_OPT_IN, AUTO_BILL_OPT_OUT])->count() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
Client::creating(function ($client) {
|
||||
|
@ -9,6 +9,7 @@ class Gateway extends Eloquent
|
||||
public $timestamps = true;
|
||||
|
||||
public static $paymentTypes = [
|
||||
PAYMENT_TYPE_STRIPE,
|
||||
PAYMENT_TYPE_CREDIT_CARD,
|
||||
PAYMENT_TYPE_PAYPAL,
|
||||
PAYMENT_TYPE_BITCOIN,
|
||||
@ -97,8 +98,10 @@ class Gateway extends Eloquent
|
||||
return PAYMENT_TYPE_BITCOIN;
|
||||
} else if ($gatewayId == GATEWAY_DWOLLA) {
|
||||
return PAYMENT_TYPE_DWOLLA;
|
||||
}else if ($gatewayId == GATEWAY_GOCARDLESS) {
|
||||
} else if ($gatewayId == GATEWAY_GOCARDLESS) {
|
||||
return PAYMENT_TYPE_DIRECT_DEBIT;
|
||||
} else if ($gatewayId == GATEWAY_STRIPE) {
|
||||
return PAYMENT_TYPE_STRIPE;
|
||||
} else {
|
||||
return PAYMENT_TYPE_CREDIT_CARD;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ class Invoice extends EntityModel implements BalanceAffecting
|
||||
protected $casts = [
|
||||
'is_recurring' => 'boolean',
|
||||
'has_tasks' => 'boolean',
|
||||
'auto_bill' => 'boolean',
|
||||
'client_enable_auto_bill' => 'boolean',
|
||||
'has_expenses' => 'boolean',
|
||||
];
|
||||
|
||||
|
@ -1,7 +1,14 @@
|
||||
<?php namespace App\Models;
|
||||
|
||||
use Event;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Events\PaymentWasCreated;
|
||||
use App\Events\PaymentWasRefunded;
|
||||
use App\Events\PaymentWasVoided;
|
||||
use App\Events\PaymentCompleted;
|
||||
use App\Events\PaymentVoided;
|
||||
use App\Events\PaymentFailed;
|
||||
use App\Models\PaymentMethod;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
|
||||
class Payment extends EntityModel
|
||||
@ -52,6 +59,16 @@ class Payment extends EntityModel
|
||||
return $this->belongsTo('App\Models\PaymentType');
|
||||
}
|
||||
|
||||
public function payment_method()
|
||||
{
|
||||
return $this->belongsTo('App\Models\PaymentMethod');
|
||||
}
|
||||
|
||||
public function payment_status()
|
||||
{
|
||||
return $this->belongsTo('App\Models\PaymentStatus');
|
||||
}
|
||||
|
||||
public function getRoute()
|
||||
{
|
||||
return "/payments/{$this->public_id}/edit";
|
||||
@ -69,10 +86,99 @@ class Payment extends EntityModel
|
||||
return trim("payment {$this->transaction_reference}");
|
||||
}
|
||||
|
||||
public function isPending()
|
||||
{
|
||||
return $this->payment_status_id = PAYMENT_STATUS_PENDING;
|
||||
}
|
||||
|
||||
public function isFailed()
|
||||
{
|
||||
return $this->payment_status_id = PAYMENT_STATUS_FAILED;
|
||||
}
|
||||
|
||||
public function isCompleted()
|
||||
{
|
||||
return $this->payment_status_id == PAYMENT_STATUS_COMPLETED;
|
||||
}
|
||||
|
||||
public function isPartiallyRefunded()
|
||||
{
|
||||
return $this->payment_status_id == PAYMENT_STATUS_PARTIALLY_REFUNDED;
|
||||
}
|
||||
|
||||
public function isRefunded()
|
||||
{
|
||||
return $this->payment_status_id == PAYMENT_STATUS_REFUNDED;
|
||||
}
|
||||
|
||||
public function isVoided()
|
||||
{
|
||||
return $this->payment_status_id == PAYMENT_STATUS_VOIDED;
|
||||
}
|
||||
|
||||
public function recordRefund($amount = null)
|
||||
{
|
||||
if (!$this->isRefunded() && !$this->isVoided()) {
|
||||
if (!$amount) {
|
||||
$amount = $this->amount;
|
||||
}
|
||||
|
||||
$new_refund = min($this->amount, $this->refunded + $amount);
|
||||
$refund_change = $new_refund - $this->refunded;
|
||||
|
||||
if ($refund_change) {
|
||||
$this->refunded = $new_refund;
|
||||
$this->payment_status_id = $this->refunded == $this->amount ? PAYMENT_STATUS_REFUNDED : PAYMENT_STATUS_PARTIALLY_REFUNDED;
|
||||
$this->save();
|
||||
|
||||
Event::fire(new PaymentWasRefunded($this, $refund_change));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function markVoided()
|
||||
{
|
||||
if (!$this->isVoided() && !$this->isPartiallyRefunded() && !$this->isRefunded()) {
|
||||
$this->refunded = $this->amount;
|
||||
$this->payment_status_id = PAYMENT_STATUS_VOIDED;
|
||||
$this->save();
|
||||
|
||||
Event::fire(new PaymentWasVoided($this));
|
||||
}
|
||||
}
|
||||
|
||||
public function markComplete()
|
||||
{
|
||||
$this->payment_status_id = PAYMENT_STATUS_COMPLETED;
|
||||
$this->save();
|
||||
Event::fire(new PaymentCompleted($this));
|
||||
}
|
||||
|
||||
public function markFailed($failureMessage)
|
||||
{
|
||||
$this->payment_status_id = PAYMENT_STATUS_FAILED;
|
||||
$this->gateway_error = $failureMessage;
|
||||
$this->save();
|
||||
Event::fire(new PaymentFailed($this));
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return ENTITY_PAYMENT;
|
||||
}
|
||||
|
||||
public function getBankData()
|
||||
{
|
||||
if (!$this->routing_number) {
|
||||
return null;
|
||||
}
|
||||
return PaymentMethod::lookupBankData($this->routing_number);
|
||||
}
|
||||
|
||||
public function getLast4Attribute($value)
|
||||
{
|
||||
return $value ? str_pad($value, 4, '0', STR_PAD_LEFT) : null;
|
||||
}
|
||||
}
|
||||
|
||||
Payment::creating(function ($payment) {
|
||||
|
162
app/Models/PaymentMethod.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?php namespace App\Models;
|
||||
|
||||
use Cache;
|
||||
use Eloquent;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class PaymentMethod extends EntityModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $dates = ['deleted_at'];
|
||||
public $timestamps = true;
|
||||
protected $hidden = ['id'];
|
||||
|
||||
public static function createNew($accountGatewayToken = null)
|
||||
{
|
||||
$entity = new PaymentMethod();
|
||||
|
||||
$entity->account_id = $accountGatewayToken->account_id;
|
||||
$entity->account_gateway_token_id = $accountGatewayToken->id;
|
||||
|
||||
$lastEntity = static::scope(false, $entity->account_id);
|
||||
|
||||
$lastEntity = $lastEntity->orderBy('public_id', 'DESC')
|
||||
->first();
|
||||
|
||||
if ($lastEntity) {
|
||||
$entity->public_id = $lastEntity->public_id + 1;
|
||||
} else {
|
||||
$entity->public_id = 1;
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Account');
|
||||
}
|
||||
|
||||
public function contact()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Contact');
|
||||
}
|
||||
|
||||
public function account_gateway_token()
|
||||
{
|
||||
return $this->belongsTo('App\Models\AccountGatewayToken');
|
||||
}
|
||||
|
||||
public function payment_type()
|
||||
{
|
||||
return $this->belongsTo('App\Models\PaymentType');
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Currency');
|
||||
}
|
||||
|
||||
public function payments()
|
||||
{
|
||||
return $this->hasMany('App\Models\Payments');
|
||||
}
|
||||
|
||||
public function getBankData()
|
||||
{
|
||||
if (!$this->routing_number) {
|
||||
return null;
|
||||
}
|
||||
return static::lookupBankData($this->routing_number);
|
||||
}
|
||||
|
||||
public function getLast4Attribute($value)
|
||||
{
|
||||
return $value ? str_pad($value, 4, '0', STR_PAD_LEFT) : null;
|
||||
}
|
||||
|
||||
public function scopeScope($query, $publicId = false, $accountId = false, $accountGatewayTokenId = false)
|
||||
{
|
||||
$query = parent::scopeScope($query, $publicId, $accountId);
|
||||
|
||||
if ($accountGatewayTokenId) {
|
||||
$query->where($this->getTable() . '.account_gateway_token_id', '=', $accountGatewayTokenId);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public static function lookupBankData($routingNumber) {
|
||||
$cached = Cache::get('bankData:'.$routingNumber);
|
||||
|
||||
if ($cached != null) {
|
||||
return $cached == false ? null : $cached;
|
||||
}
|
||||
|
||||
$dataPath = base_path('vendor/gatepay/FedACHdir/FedACHdir.txt');
|
||||
|
||||
if (!file_exists($dataPath) || !$size = filesize($dataPath)) {
|
||||
return 'Invalid data file';
|
||||
}
|
||||
|
||||
$lineSize = 157;
|
||||
$numLines = $size/$lineSize;
|
||||
|
||||
if ($numLines % 1 != 0) {
|
||||
// The number of lines should be an integer
|
||||
return 'Invalid data file';
|
||||
}
|
||||
|
||||
// Format: http://www.sco.ca.gov/Files-21C/Bank_Master_Interface_Information_Package.pdf
|
||||
$file = fopen($dataPath, 'r');
|
||||
|
||||
// Binary search
|
||||
$low = 0;
|
||||
$high = $numLines - 1;
|
||||
while ($low <= $high) {
|
||||
$mid = floor(($low + $high) / 2);
|
||||
|
||||
fseek($file, $mid * $lineSize);
|
||||
$thisNumber = fread($file, 9);
|
||||
|
||||
if ($thisNumber > $routingNumber) {
|
||||
$high = $mid - 1;
|
||||
} else if ($thisNumber < $routingNumber) {
|
||||
$low = $mid + 1;
|
||||
} else {
|
||||
$data = new \stdClass();
|
||||
$data->routing_number = $thisNumber;
|
||||
|
||||
fseek($file, 26, SEEK_CUR);
|
||||
|
||||
$data->name = trim(fread($file, 36));
|
||||
$data->address = trim(fread($file, 36));
|
||||
$data->city = trim(fread($file, 20));
|
||||
$data->state = fread($file, 2);
|
||||
$data->zip = fread($file, 5).'-'.fread($file, 4);
|
||||
$data->phone = fread($file, 10);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data)) {
|
||||
Cache::put('bankData:'.$routingNumber, $data, 5);
|
||||
return $data;
|
||||
} else {
|
||||
Cache::put('bankData:'.$routingNumber, false, 5);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PaymentMethod::deleting(function($paymentMethod) {
|
||||
$accountGatewayToken = $paymentMethod->account_gateway_token;
|
||||
if ($accountGatewayToken->default_payment_method_id == $paymentMethod->id) {
|
||||
$newDefault = $accountGatewayToken->payment_methods->first(function($i, $paymentMethdod) use ($accountGatewayToken){
|
||||
return $paymentMethdod->id != $accountGatewayToken->default_payment_method_id;
|
||||
});
|
||||
$accountGatewayToken->default_payment_method_id = $newDefault ? $newDefault->id : null;
|
||||
$accountGatewayToken->save();
|
||||
}
|
||||
});
|
8
app/Models/PaymentStatus.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php namespace App\Models;
|
||||
|
||||
use Eloquent;
|
||||
|
||||
class PaymentStatus extends Eloquent
|
||||
{
|
||||
public $timestamps = false;
|
||||
}
|
@ -57,6 +57,7 @@ class UserMailer extends Mailer
|
||||
];
|
||||
|
||||
if ($payment) {
|
||||
$data['payment'] = $payment;
|
||||
$data['paymentAmount'] = $account->formatMoney($payment->amount, $client);
|
||||
}
|
||||
|
||||
|
@ -97,6 +97,7 @@ class ActivityRepository
|
||||
'contacts.last_name as last_name',
|
||||
'contacts.email as email',
|
||||
'payments.transaction_reference as payment',
|
||||
'payments.amount as payment_amount',
|
||||
'credits.amount as credit'
|
||||
);
|
||||
}
|
||||
|
@ -147,6 +147,53 @@ class InvoiceRepository extends BaseRepository
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function getClientRecurringDatatable($contactId)
|
||||
{
|
||||
$query = DB::table('invitations')
|
||||
->join('accounts', 'accounts.id', '=', 'invitations.account_id')
|
||||
->join('invoices', 'invoices.id', '=', 'invitations.invoice_id')
|
||||
->join('clients', 'clients.id', '=', 'invoices.client_id')
|
||||
->join('frequencies', 'frequencies.id', '=', 'invoices.frequency_id')
|
||||
->where('invitations.contact_id', '=', $contactId)
|
||||
->where('invitations.deleted_at', '=', null)
|
||||
->where('invoices.is_quote', '=', false)
|
||||
->where('invoices.is_deleted', '=', false)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('invoices.is_recurring', '=', true)
|
||||
->whereIn('invoices.auto_bill', [AUTO_BILL_OPT_IN, AUTO_BILL_OPT_OUT])
|
||||
//->where('invoices.start_date', '>=', date('Y-m-d H:i:s'))
|
||||
->select(
|
||||
DB::raw('COALESCE(clients.currency_id, accounts.currency_id) currency_id'),
|
||||
DB::raw('COALESCE(clients.country_id, accounts.country_id) country_id'),
|
||||
'invitations.invitation_key',
|
||||
'invoices.invoice_number',
|
||||
'invoices.due_date',
|
||||
'clients.public_id as client_public_id',
|
||||
'clients.name as client_name',
|
||||
'invoices.public_id',
|
||||
'invoices.amount',
|
||||
'invoices.start_date',
|
||||
'invoices.end_date',
|
||||
'invoices.client_enable_auto_bill',
|
||||
'frequencies.name as frequency'
|
||||
);
|
||||
|
||||
$table = \Datatable::query($query)
|
||||
->addColumn('frequency', function ($model) { return $model->frequency; })
|
||||
->addColumn('start_date', function ($model) { return Utils::fromSqlDate($model->start_date); })
|
||||
->addColumn('end_date', function ($model) { return Utils::fromSqlDate($model->end_date); })
|
||||
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); })
|
||||
->addColumn('client_enable_auto_bill', function ($model) {
|
||||
if ($model->client_enable_auto_bill) {
|
||||
return trans('texts.enabled') . ' <a href="javascript:setAutoBill('.$model->public_id.',false)">('.trans('texts.disable').')</a>';
|
||||
} else {
|
||||
return trans('texts.disabled') . ' <a href="javascript:setAutoBill('.$model->public_id.',true)">('.trans('texts.enable').')</a>';
|
||||
}
|
||||
});
|
||||
|
||||
return $table->make();
|
||||
}
|
||||
|
||||
public function getClientDatatable($contactId, $entityType, $search)
|
||||
{
|
||||
$query = DB::table('invitations')
|
||||
@ -276,7 +323,12 @@ class InvoiceRepository extends BaseRepository
|
||||
$invoice->frequency_id = $data['frequency_id'] ? $data['frequency_id'] : 0;
|
||||
$invoice->start_date = Utils::toSqlDate($data['start_date']);
|
||||
$invoice->end_date = Utils::toSqlDate($data['end_date']);
|
||||
$invoice->auto_bill = isset($data['auto_bill']) && $data['auto_bill'] ? true : false;
|
||||
$invoice->client_enable_auto_bill = isset($data['client_enable_auto_bill']) && $data['client_enable_auto_bill'] ? true : false;
|
||||
$invoice->auto_bill = isset($data['auto_bill']) ? intval($data['auto_bill']) : 0;
|
||||
|
||||
if ($invoice->auto_bill < AUTO_BILL_OFF || $invoice->auto_bill > AUTO_BILL_ALWAYS ) {
|
||||
$invoice->auto_bill = AUTO_BILL_OFF;
|
||||
}
|
||||
|
||||
if (isset($data['recurring_due_date'])) {
|
||||
$invoice->due_date = $data['recurring_due_date'];
|
||||
@ -763,7 +815,7 @@ class InvoiceRepository extends BaseRepository
|
||||
$recurInvoice->last_sent_date = date('Y-m-d');
|
||||
$recurInvoice->save();
|
||||
|
||||
if ($recurInvoice->auto_bill) {
|
||||
if ($recurInvoice->auto_bill == AUTO_BILL_ALWAYS || ($recurInvoice->auto_bill != AUTO_BILL_OFF && $recurInvoice->client_enable_auto_bill)) {
|
||||
if ($this->paymentService->autoBillInvoice($invoice)) {
|
||||
// update the invoice reference to match its actual state
|
||||
// this is to ensure a 'payment received' email is sent
|
||||
|
@ -22,6 +22,7 @@ class PaymentRepository extends BaseRepository
|
||||
->join('clients', 'clients.id', '=', 'payments.client_id')
|
||||
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->join('payment_statuses', 'payment_statuses.id', '=', 'payments.payment_status_id')
|
||||
->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id')
|
||||
->leftJoin('account_gateways', 'account_gateways.id', '=', 'payments.account_gateway_id')
|
||||
->leftJoin('gateways', 'gateways.id', '=', 'account_gateways.gateway_id')
|
||||
@ -39,6 +40,8 @@ class PaymentRepository extends BaseRepository
|
||||
'clients.user_id as client_user_id',
|
||||
'payments.amount',
|
||||
'payments.payment_date',
|
||||
'payments.payment_status_id',
|
||||
'payments.payment_type_id',
|
||||
'invoices.public_id as invoice_public_id',
|
||||
'invoices.user_id as invoice_user_id',
|
||||
'invoices.invoice_number',
|
||||
@ -50,8 +53,15 @@ class PaymentRepository extends BaseRepository
|
||||
'payments.deleted_at',
|
||||
'payments.is_deleted',
|
||||
'payments.user_id',
|
||||
'payments.refunded',
|
||||
'payments.expiration',
|
||||
'payments.last4',
|
||||
'payments.email',
|
||||
'payments.routing_number',
|
||||
'invoices.is_deleted as invoice_is_deleted',
|
||||
'gateways.name as gateway_name'
|
||||
'gateways.name as gateway_name',
|
||||
'gateways.id as gateway_id',
|
||||
'payment_statuses.name as payment_status_name'
|
||||
);
|
||||
|
||||
if (!\Session::get('show_trash:payment')) {
|
||||
@ -85,6 +95,7 @@ class PaymentRepository extends BaseRepository
|
||||
->join('clients', 'clients.id', '=', 'payments.client_id')
|
||||
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->join('payment_statuses', 'payment_statuses.id', '=', 'payments.payment_status_id')
|
||||
->leftJoin('invitations', function ($join) {
|
||||
$join->on('invitations.invoice_id', '=', 'invoices.id')
|
||||
->on('invitations.contact_id', '=', 'contacts.id');
|
||||
@ -105,13 +116,21 @@ class PaymentRepository extends BaseRepository
|
||||
'clients.public_id as client_public_id',
|
||||
'payments.amount',
|
||||
'payments.payment_date',
|
||||
'payments.payment_type_id',
|
||||
'invoices.public_id as invoice_public_id',
|
||||
'invoices.invoice_number',
|
||||
'contacts.first_name',
|
||||
'contacts.last_name',
|
||||
'contacts.email',
|
||||
'payment_types.name as payment_type',
|
||||
'payments.account_gateway_id'
|
||||
'payments.account_gateway_id',
|
||||
'payments.refunded',
|
||||
'payments.expiration',
|
||||
'payments.last4',
|
||||
'payments.email',
|
||||
'payments.routing_number',
|
||||
'payments.payment_status_id',
|
||||
'payment_statuses.name as payment_status_name'
|
||||
);
|
||||
|
||||
if ($filter) {
|
||||
@ -199,6 +218,4 @@ class PaymentRepository extends BaseRepository
|
||||
|
||||
parent::restore($payment);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -108,6 +108,19 @@ class EventServiceProvider extends ServiceProvider {
|
||||
'App\Listeners\InvoiceListener@deletedPayment',
|
||||
'App\Listeners\CreditListener@deletedPayment',
|
||||
],
|
||||
'App\Events\PaymentWasRefunded' => [
|
||||
'App\Listeners\ActivityListener@refundedPayment',
|
||||
'App\Listeners\InvoiceListener@refundedPayment',
|
||||
'App\Listeners\CreditListener@refundedPayment',
|
||||
],
|
||||
'App\Events\PaymentWasVoided' => [
|
||||
'App\Listeners\ActivityListener@voidedPayment',
|
||||
'App\Listeners\InvoiceListener@voidedPayment',
|
||||
],
|
||||
'App\Events\PaymentFailed' => [
|
||||
'App\Listeners\ActivityListener@failedPayment',
|
||||
'App\Listeners\InvoiceListener@failedPayment',
|
||||
],
|
||||
'App\Events\PaymentWasRestored' => [
|
||||
'App\Listeners\ActivityListener@restoredPayment',
|
||||
'App\Listeners\InvoiceListener@restoredPayment',
|
||||
|
@ -44,7 +44,9 @@ class ActivityService extends BaseService
|
||||
'quote' => $model->invoice ? link_to('/quotes/' . $model->invoice_public_id, $model->invoice)->toHtml() : null,
|
||||
'contact' => $model->contact_id ? link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model))->toHtml() : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email),
|
||||
'payment' => $model->payment ?: '',
|
||||
'credit' => Utils::formatMoney($model->credit, $model->currency_id, $model->country_id)
|
||||
'credit' => $model->payment_amount ? Utils::formatMoney($model->credit, $model->currency_id, $model->country_id) : '',
|
||||
'payment_amount' => $model->payment_amount ? Utils::formatMoney($model->payment_amount, $model->currency_id, $model->country_id) : null,
|
||||
'adjustment' => $model->adjustment ? Utils::formatMoney($model->adjustment, $model->currency_id, $model->country_id) : null
|
||||
];
|
||||
|
||||
return trans("texts.activity_{$model->activity_type_id}", $data);
|
||||
|
@ -30,12 +30,12 @@ class BaseService
|
||||
return count($entities);
|
||||
}
|
||||
|
||||
public function createDatatable($entityType, $query, $showCheckbox = true, $hideClient = false)
|
||||
public function createDatatable($entityType, $query, $showCheckbox = true, $hideClient = false, $orderColumns = [])
|
||||
{
|
||||
$columns = $this->getDatatableColumns($entityType, !$showCheckbox);
|
||||
$actions = $this->getDatatableActions($entityType);
|
||||
|
||||
return $this->datatableService->createDatatable($entityType, $query, $columns, $actions, $showCheckbox);
|
||||
return $this->datatableService->createDatatable($entityType, $query, $columns, $actions, $showCheckbox, $orderColumns);
|
||||
}
|
||||
|
||||
protected function getDatatableColumns($entityType, $hideClient)
|
||||
|
@ -7,10 +7,10 @@ use Auth;
|
||||
|
||||
class DatatableService
|
||||
{
|
||||
public function createDatatable($entityType, $query, $columns, $actions = null, $showCheckbox = true)
|
||||
public function createDatatable($entityType, $query, $columns, $actions = null, $showCheckbox = true, $orderColumns = [])
|
||||
{
|
||||
$table = Datatable::query($query);
|
||||
$orderColumns = [];
|
||||
$calculateOrderColumns = empty($orderColumns);
|
||||
|
||||
if ($actions && $showCheckbox) {
|
||||
$table->addColumn('checkbox', function ($model) {
|
||||
@ -31,9 +31,11 @@ class DatatableService
|
||||
|
||||
if ($visible) {
|
||||
$table->addColumn($field, $value);
|
||||
if ($calculateOrderColumns) {
|
||||
$orderColumns[] = $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($actions) {
|
||||
$this->createDropdown($entityType, $table, $actions);
|
||||
|
@ -5,14 +5,18 @@ use Auth;
|
||||
use URL;
|
||||
use DateTime;
|
||||
use Event;
|
||||
use Cache;
|
||||
use Omnipay;
|
||||
use Session;
|
||||
use CreditCard;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentMethod;
|
||||
use App\Models\Account;
|
||||
use App\Models\Country;
|
||||
use App\Models\Client;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\AccountGateway;
|
||||
use App\Http\Controllers\PaymentController;
|
||||
use App\Models\AccountGatewayToken;
|
||||
use App\Ninja\Repositories\PaymentRepository;
|
||||
use App\Ninja\Repositories\AccountRepository;
|
||||
@ -24,6 +28,11 @@ class PaymentService extends BaseService
|
||||
public $lastError;
|
||||
protected $datatableService;
|
||||
|
||||
protected static $refundableGateways = array(
|
||||
GATEWAY_STRIPE,
|
||||
GATEWAY_BRAINTREE
|
||||
);
|
||||
|
||||
public function __construct(PaymentRepository $paymentRepo, AccountRepository $accountRepo, DatatableService $datatableService)
|
||||
{
|
||||
$this->datatableService = $datatableService;
|
||||
@ -39,7 +48,7 @@ class PaymentService extends BaseService
|
||||
public function createGateway($accountGateway)
|
||||
{
|
||||
$gateway = Omnipay::create($accountGateway->gateway->provider);
|
||||
$gateway->initialize((array) $accountGateway->getConfig());
|
||||
$gateway->initialize((array)$accountGateway->getConfig());
|
||||
|
||||
if ($accountGateway->isGateway(GATEWAY_DWOLLA)) {
|
||||
if ($gateway->getSandbox() && isset($_ENV['DWOLLA_SANDBOX_KEY']) && isset($_ENV['DWOLLA_SANSBOX_SECRET'])) {
|
||||
@ -58,7 +67,7 @@ class PaymentService extends BaseService
|
||||
{
|
||||
$invoice = $invitation->invoice;
|
||||
$account = $invoice->account;
|
||||
$key = $invoice->account_id.'-'.$invoice->invoice_number;
|
||||
$key = $invoice->account_id . '-' . $invoice->invoice_number;
|
||||
$currencyCode = $invoice->client->currency ? $invoice->client->currency->code : ($invoice->account->currency ? $invoice->account->currency->code : 'USD');
|
||||
|
||||
if ($input) {
|
||||
@ -70,7 +79,7 @@ class PaymentService extends BaseService
|
||||
$data = $this->createDataForClient($invitation);
|
||||
}
|
||||
|
||||
$card = new CreditCard($data);
|
||||
$card = !empty($data['number']) ? new CreditCard($data) : null;
|
||||
$data = [
|
||||
'amount' => $invoice->getRequestedAmount(),
|
||||
'card' => $card,
|
||||
@ -86,6 +95,17 @@ class PaymentService extends BaseService
|
||||
$data['ButtonSource'] = 'InvoiceNinja_SP';
|
||||
};
|
||||
|
||||
if ($input && $accountGateway->isGateway(GATEWAY_STRIPE)) {
|
||||
if (!empty($input['stripeToken'])) {
|
||||
$data['token'] = $input['stripeToken'];
|
||||
unset($data['card']);
|
||||
} elseif (!empty($input['plaidPublicToken'])) {
|
||||
$data['plaidPublicToken'] = $input['plaidPublicToken'];
|
||||
$data['plaidAccountId'] = $input['plaidAccountId'];
|
||||
unset($data['card']);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@ -155,12 +175,210 @@ class PaymentService extends BaseService
|
||||
];
|
||||
}
|
||||
|
||||
public function createToken($gateway, $details, $accountGateway, $client, $contactId)
|
||||
public function getClientPaymentMethods($client)
|
||||
{
|
||||
$tokenResponse = $gateway->createCard($details)->send();
|
||||
$cardReference = $tokenResponse->getCustomerReference();
|
||||
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
|
||||
if (!$token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($cardReference) {
|
||||
if (!$accountGatewayToken->uses_local_payment_methods && $accountGateway->gateway_id == GATEWAY_STRIPE) {
|
||||
// Migrate Stripe data
|
||||
$gateway = $this->createGateway($accountGateway);
|
||||
$response = $gateway->fetchCustomer(array('customerReference' => $token))->send();
|
||||
if (!$response->isSuccessful()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $response->getData();
|
||||
$sources = isset($data['sources']) ? $data['sources']['data'] : $data['cards']['data'];
|
||||
|
||||
// Load
|
||||
$accountGatewayToken->payment_methods();
|
||||
foreach ($sources as $source) {
|
||||
$paymentMethod = $this->convertPaymentMethodFromStripe($source, $accountGatewayToken);
|
||||
if ($paymentMethod) {
|
||||
$paymentMethod->save();
|
||||
}
|
||||
|
||||
if ($data['default_source'] == $source['id']) {
|
||||
$accountGatewayToken->default_payment_method_id = $paymentMethod->id;
|
||||
}
|
||||
}
|
||||
|
||||
$accountGatewayToken->uses_local_payment_methods = true;
|
||||
$accountGatewayToken->save();
|
||||
}
|
||||
|
||||
return $accountGatewayToken->payment_methods;
|
||||
}
|
||||
|
||||
public function verifyClientPaymentMethod($client, $publicId, $amount1, $amount2)
|
||||
{
|
||||
$token = $client->getGatewayToken($accountGateway);
|
||||
if ($accountGateway->gateway_id != GATEWAY_STRIPE) {
|
||||
return 'Unsupported gateway';
|
||||
}
|
||||
|
||||
$paymentMethod = PaymentMethod::scope($publicId, $client->account_id, $accountGatewayToken->id)->firstOrFail();
|
||||
|
||||
// Omnipay doesn't support verifying payment methods
|
||||
// Also, it doesn't want to urlencode without putting numbers inside the brackets
|
||||
$result = $this->makeStripeCall(
|
||||
$accountGateway,
|
||||
'POST',
|
||||
'customers/' . $token . '/sources/' . $paymentMethod->source_reference . '/verify',
|
||||
'amounts[]=' . intval($amount1) . '&amounts[]=' . intval($amount2)
|
||||
);
|
||||
|
||||
if (!is_string($result)) {
|
||||
$paymentMethod->status = PAYMENT_METHOD_STATUS_VERIFIED;
|
||||
$paymentMethod->save();
|
||||
|
||||
if (!$paymentMethod->account_gateway_token->default_payment_method_id) {
|
||||
$paymentMethod->account_gateway_token->default_payment_method_id = $paymentMethod->id;
|
||||
$paymentMethod->account_gateway_token->save();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function removeClientPaymentMethod($client, $publicId)
|
||||
{
|
||||
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
|
||||
if (!$token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$paymentMethod = PaymentMethod::scope($publicId, $client->account_id, $accountGatewayToken->id)->firstOrFail();
|
||||
|
||||
$gateway = $this->createGateway($accountGateway);
|
||||
|
||||
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
|
||||
$response = $gateway->deleteCard(array('customerReference' => $token, 'cardReference' => $paymentMethod->source_reference))->send();
|
||||
if (!$response->isSuccessful()) {
|
||||
return $response->getMessage();
|
||||
}
|
||||
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
|
||||
$response = $gateway->deletePaymentMethod(array('token' => $paymentMethod->source_reference))->send();
|
||||
|
||||
if (!$response->isSuccessful()) {
|
||||
return $response->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
$paymentMethod->delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setClientDefaultPaymentMethod($client, $publicId)
|
||||
{
|
||||
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
|
||||
if (!$token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$paymentMethod = PaymentMethod::scope($publicId, $client->account_id, $accountGatewayToken->id)->firstOrFail();
|
||||
$paymentMethod->account_gateway_token->default_payment_method_id = $paymentMethod->id;
|
||||
$paymentMethod->account_gateway_token->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function createToken($gateway, $details, $accountGateway, $client, $contactId, &$customerReference = null, &$paymentMethod = null)
|
||||
{
|
||||
$customerReference = $client->getGatewayToken($accountGateway, $accountGatewayToken/* return paramenter */);
|
||||
|
||||
if ($customerReference) {
|
||||
$details['customerReference'] = $customerReference;
|
||||
|
||||
if ($accountGateway->gateway->id == GATEWAY_STRIPE) {
|
||||
$customerResponse = $gateway->fetchCustomer(array('customerReference' => $customerReference))->send();
|
||||
|
||||
if (!$customerResponse->isSuccessful()) {
|
||||
$customerReference = null; // The customer might not exist anymore
|
||||
}
|
||||
} elseif ($accountGateway->gateway->id == GATEWAY_BRAINTREE) {
|
||||
$customer = $gateway->findCustomer($customerReference)->send()->getData();
|
||||
|
||||
if (!($customer instanceof \Braintree\Customer)) {
|
||||
$customerReference = null; // The customer might not exist anymore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($accountGateway->gateway->id == GATEWAY_STRIPE) {
|
||||
if (!empty($details['plaidPublicToken'])) {
|
||||
$plaidResult = $this->getPlaidToken($accountGateway, $details['plaidPublicToken'], $details['plaidAccountId']);
|
||||
|
||||
if (is_string($plaidResult)) {
|
||||
$this->lastError = $plaidResult;
|
||||
return;
|
||||
} elseif (!$plaidResult) {
|
||||
$this->lastError = 'No token received from Plaid';
|
||||
return;
|
||||
}
|
||||
|
||||
unset($details['plaidPublicToken']);
|
||||
unset($details['plaidAccountId']);
|
||||
$details['token'] = $plaidResult['stripe_bank_account_token'];
|
||||
}
|
||||
|
||||
$tokenResponse = $gateway->createCard($details)->send();
|
||||
|
||||
if ($tokenResponse->isSuccessful()) {
|
||||
$sourceReference = $tokenResponse->getCardReference();
|
||||
if (!$customerReference) {
|
||||
$customerReference = $tokenResponse->getCustomerReference();
|
||||
}
|
||||
|
||||
if (!$sourceReference) {
|
||||
$responseData = $tokenResponse->getData();
|
||||
if (!empty($responseData['object']) && ($responseData['object'] == 'bank_account' || $responseData['object'] == 'card')) {
|
||||
$sourceReference = $responseData['id'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($customerReference == $sourceReference) {
|
||||
// This customer was just created; find the card
|
||||
$data = $tokenResponse->getData();
|
||||
if (!empty($data['default_source'])) {
|
||||
$sourceReference = $data['default_source'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$data = $tokenResponse->getData();
|
||||
if ($data && $data['error'] && $data['error']['type'] == 'invalid_request_error') {
|
||||
$this->lastError = $data['error']['message'];
|
||||
return;
|
||||
}
|
||||
}
|
||||
} elseif ($accountGateway->gateway->id == GATEWAY_BRAINTREE) {
|
||||
if (!$customerReference) {
|
||||
$tokenResponse = $gateway->createCustomer(array('customerData' => array()))->send();
|
||||
if ($tokenResponse->isSuccessful()) {
|
||||
$customerReference = $tokenResponse->getCustomerData()->id;
|
||||
} else {
|
||||
$this->lastError = $tokenResponse->getData()->message;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($customerReference) {
|
||||
$details['customerId'] = $customerReference;
|
||||
|
||||
$tokenResponse = $gateway->createPaymentMethod($details)->send();
|
||||
if ($tokenResponse->isSuccessful()) {
|
||||
$sourceReference = $tokenResponse->getData()->paymentMethod->token;
|
||||
} else {
|
||||
$this->lastError = $tokenResponse->getData()->message;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($customerReference) {
|
||||
$token = AccountGatewayToken::where('client_id', '=', $client->id)
|
||||
->where('account_gateway_id', '=', $accountGateway->id)->first();
|
||||
|
||||
@ -172,13 +390,120 @@ class PaymentService extends BaseService
|
||||
$token->client_id = $client->id;
|
||||
}
|
||||
|
||||
$token->token = $cardReference;
|
||||
$token->token = $customerReference;
|
||||
$token->save();
|
||||
|
||||
$paymentMethod = $this->createPaymentMethodFromGatewayResponse($tokenResponse, $accountGateway, $accountGatewayToken, $contactId);
|
||||
|
||||
} else {
|
||||
$this->lastError = $tokenResponse->getMessage();
|
||||
}
|
||||
|
||||
return $cardReference;
|
||||
return $sourceReference;
|
||||
}
|
||||
|
||||
public function convertPaymentMethodFromStripe($source, $accountGatewayToken = null, $paymentMethod = null) {
|
||||
// Creating a new one or updating an existing one
|
||||
if (!$paymentMethod) {
|
||||
$paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod();
|
||||
}
|
||||
|
||||
$paymentMethod->last4 = $source['last4'];
|
||||
$paymentMethod->source_reference = $source['id'];
|
||||
|
||||
if ($source['object'] == 'bank_account') {
|
||||
$paymentMethod->routing_number = $source['routing_number'];
|
||||
$paymentMethod->payment_type_id = PAYMENT_TYPE_ACH;
|
||||
$paymentMethod->status = $source['status'];
|
||||
$currency = Cache::get('currencies')->where('code', strtoupper($source['currency']))->first();
|
||||
if ($currency) {
|
||||
$paymentMethod->currency_id = $currency->id;
|
||||
$paymentMethod->setRelation('currency', $currency);
|
||||
}
|
||||
} elseif ($source['object'] == 'card') {
|
||||
$paymentMethod->expiration = $source['exp_year'] . '-' . $source['exp_month'] . '-00';
|
||||
$paymentMethod->payment_type_id = $this->parseCardType($source['brand']);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
$paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id));
|
||||
|
||||
return $paymentMethod;
|
||||
}
|
||||
|
||||
public function convertPaymentMethodFromBraintree($source, $accountGatewayToken = null, $paymentMethod = null) {
|
||||
// Creating a new one or updating an existing one
|
||||
if (!$paymentMethod) {
|
||||
$paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod();
|
||||
}
|
||||
|
||||
if ($source instanceof \Braintree\CreditCard) {
|
||||
$paymentMethod->payment_type_id = $this->parseCardType($source->cardType);
|
||||
$paymentMethod->last4 = $source->last4;
|
||||
$paymentMethod->expiration = $source->expirationYear . '-' . $source->expirationMonth . '-00';
|
||||
} elseif ($source instanceof \Braintree\PayPalAccount) {
|
||||
$paymentMethod->email = $source->email;
|
||||
$paymentMethod->payment_type_id = PAYMENT_TYPE_ID_PAYPAL;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
$paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id));
|
||||
|
||||
$paymentMethod->source_reference = $source->token;
|
||||
|
||||
return $paymentMethod;
|
||||
}
|
||||
|
||||
public function createPaymentMethodFromGatewayResponse($gatewayResponse, $accountGateway, $accountGatewayToken = null, $contactId = null) {
|
||||
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
|
||||
$data = $gatewayResponse->getData();
|
||||
if(!empty($data['object']) && ($data['object'] == 'card' || $data['object'] == 'bank_account')) {
|
||||
$source = $data;
|
||||
} else {
|
||||
$source = !empty($data['source']) ? $data['source'] : $data['card'];
|
||||
}
|
||||
|
||||
if ($source) {
|
||||
$paymentMethod = $this->convertPaymentMethodFromStripe($source, $accountGatewayToken);
|
||||
}
|
||||
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
|
||||
$data = $gatewayResponse->getData();
|
||||
|
||||
if (!empty($data->transaction)) {
|
||||
$transaction = $data->transaction;
|
||||
|
||||
$paymentMethod = $accountGatewayToken ? PaymentMethod::createNew($accountGatewayToken) : new PaymentMethod();
|
||||
if ($transaction->paymentInstrumentType == 'credit_card') {
|
||||
$card = $transaction->creditCardDetails;
|
||||
$paymentMethod->last4 = $card->last4;
|
||||
$paymentMethod->expiration = $card->expirationYear . '-' . $card->expirationMonth . '-00';
|
||||
$paymentMethod->payment_type_id = $this->parseCardType($card->cardType);
|
||||
} elseif ($transaction->paymentInstrumentType == 'paypal_account') {
|
||||
$paymentMethod->payment_type_id = PAYMENT_TYPE_ID_PAYPAL;
|
||||
$paymentMethod->email = $transaction->paypalDetails->payerEmail;
|
||||
}
|
||||
$paymentMethod->setRelation('payment_type', Cache::get('paymentTypes')->find($paymentMethod->payment_type_id));
|
||||
} elseif (!empty($data->paymentMethod)) {
|
||||
$paymentMethod = $this->convertPaymentMethodFromBraintree($data->paymentMethod, $accountGatewayToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!empty($paymentMethod) && $accountGatewayToken && $contactId) {
|
||||
$paymentMethod->account_gateway_token_id = $accountGatewayToken->id;
|
||||
$paymentMethod->account_id = $accountGatewayToken->account_id;
|
||||
$paymentMethod->contact_id = $contactId;
|
||||
$paymentMethod->save();
|
||||
|
||||
if (!$paymentMethod->account_gateway_token->default_payment_method_id) {
|
||||
$paymentMethod->account_gateway_token->default_payment_method_id = $paymentMethod->id;
|
||||
$paymentMethod->account_gateway_token->save();
|
||||
}
|
||||
}
|
||||
|
||||
return $paymentMethod;
|
||||
}
|
||||
|
||||
public function getCheckoutComToken($invitation)
|
||||
@ -205,7 +530,19 @@ class PaymentService extends BaseService
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function createPayment($invitation, $accountGateway, $ref, $payerId = null)
|
||||
public function getBraintreeClientToken($account)
|
||||
{
|
||||
$token = false;
|
||||
|
||||
$accountGateway = $account->getGatewayConfig(GATEWAY_BRAINTREE);
|
||||
$gateway = $this->createGateway($accountGateway);
|
||||
|
||||
$token = $gateway->clientToken()->send()->getToken();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function createPayment($invitation, $accountGateway, $ref, $payerId = null, $paymentDetails = null, $paymentMethod = null, $purchaseResponse = null)
|
||||
{
|
||||
$invoice = $invitation->invoice;
|
||||
|
||||
@ -219,10 +556,57 @@ class PaymentService extends BaseService
|
||||
$payment->transaction_reference = $ref;
|
||||
$payment->payment_date = date_create()->format('Y-m-d');
|
||||
|
||||
if (!empty($paymentDetails['card'])) {
|
||||
$card = $paymentDetails['card'];
|
||||
$payment->last4 = substr($card->number, -4);
|
||||
$year = $card->expiryYear;
|
||||
if (strlen($year) == 2) {
|
||||
$year = '20' . $year;
|
||||
}
|
||||
|
||||
$payment->expiration = $year . '-' . $card->expiryMonth . '-00';
|
||||
$payment->payment_type_id = $this->detectCardType($card->number);
|
||||
}
|
||||
|
||||
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
|
||||
$data = $purchaseResponse->getData();
|
||||
$source = !empty($data['source'])?$data['source']:$data['card'];
|
||||
|
||||
$payment->payment_status_id = $data['status'] == 'succeeded' ? PAYMENT_STATUS_COMPLETED : PAYMENT_STATUS_PENDING;
|
||||
|
||||
if ($source) {
|
||||
$payment->last4 = $source['last4'];
|
||||
|
||||
if ($source['object'] == 'bank_account') {
|
||||
$payment->routing_number = $source['routing_number'];
|
||||
$payment->payment_type_id = PAYMENT_TYPE_ACH;
|
||||
}
|
||||
else{
|
||||
$payment->expiration = $source['exp_year'] . '-' . $source['exp_month'] . '-00';
|
||||
$payment->payment_type_id = $this->parseCardType($source['brand']);
|
||||
}
|
||||
}
|
||||
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
|
||||
$transaction = $purchaseResponse->getData()->transaction;
|
||||
if ($transaction->paymentInstrumentType == 'credit_card') {
|
||||
$card = $transaction->creditCardDetails;
|
||||
$payment->last4 = $card->last4;
|
||||
$payment->expiration = $card->expirationYear . '-' . $card->expirationMonth . '-00';
|
||||
$payment->payment_type_id = $this->parseCardType($card->cardType);
|
||||
} elseif ($transaction->paymentInstrumentType == 'paypal_account') {
|
||||
$payment->payment_type_id = PAYMENT_TYPE_ID_PAYPAL;
|
||||
$payment->email = $transaction->paypalDetails->payerEmail;
|
||||
}
|
||||
}
|
||||
|
||||
if ($payerId) {
|
||||
$payment->payer_id = $payerId;
|
||||
}
|
||||
|
||||
if ($paymentMethod) {
|
||||
$payment->payment_method_id = $paymentMethod->id;
|
||||
}
|
||||
|
||||
$payment->save();
|
||||
|
||||
// enable pro plan for hosted users
|
||||
@ -281,6 +665,47 @@ class PaymentService extends BaseService
|
||||
return $payment;
|
||||
}
|
||||
|
||||
private function parseCardType($cardName) {
|
||||
$cardTypes = array(
|
||||
'Visa' => PAYMENT_TYPE_VISA,
|
||||
'American Express' => PAYMENT_TYPE_AMERICAN_EXPRESS,
|
||||
'MasterCard' => PAYMENT_TYPE_MASTERCARD,
|
||||
'Discover' => PAYMENT_TYPE_DISCOVER,
|
||||
'JCB' => PAYMENT_TYPE_JCB,
|
||||
'Diners Club' => PAYMENT_TYPE_DINERS,
|
||||
'Carte Blanche' => PAYMENT_TYPE_CARTE_BLANCHE,
|
||||
'China UnionPay' => PAYMENT_TYPE_UNIONPAY,
|
||||
'Laser' => PAYMENT_TYPE_LASER,
|
||||
'Maestro' => PAYMENT_TYPE_MAESTRO,
|
||||
'Solo' => PAYMENT_TYPE_SOLO,
|
||||
'Switch' => PAYMENT_TYPE_SWITCH
|
||||
);
|
||||
|
||||
if (!empty($cardTypes[$cardName])) {
|
||||
return $cardTypes[$cardName];
|
||||
} else {
|
||||
return PAYMENT_TYPE_CREDIT_CARD_OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
private function detectCardType($number)
|
||||
{
|
||||
if (preg_match('/^3[47][0-9]{13}$/',$number)) {
|
||||
return PAYMENT_TYPE_AMERICAN_EXPRESS;
|
||||
} elseif (preg_match('/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/',$number)) {
|
||||
return PAYMENT_TYPE_DINERS;
|
||||
} elseif (preg_match('/^6(?:011|5[0-9][0-9])[0-9]{12}$/',$number)) {
|
||||
return PAYMENT_TYPE_DISCOVER;
|
||||
} elseif (preg_match('/^(?:2131|1800|35\d{3})\d{11}$/',$number)) {
|
||||
return PAYMENT_TYPE_JCB;
|
||||
} elseif (preg_match('/^5[1-5][0-9]{14}$/',$number)) {
|
||||
return PAYMENT_TYPE_MASTERCARD;
|
||||
} elseif (preg_match('/^4[0-9]{12}(?:[0-9]{3})?$/',$number)) {
|
||||
return PAYMENT_TYPE_VISA;
|
||||
}
|
||||
return PAYMENT_TYPE_CREDIT_CARD_OTHER;
|
||||
}
|
||||
|
||||
public function completePurchase($gateway, $accountGateway, $details, $token)
|
||||
{
|
||||
if ($accountGateway->isGateway(GATEWAY_MOLLIE)) {
|
||||
@ -296,12 +721,15 @@ class PaymentService extends BaseService
|
||||
public function autoBillInvoice($invoice)
|
||||
{
|
||||
$client = $invoice->client;
|
||||
$account = $invoice->account;
|
||||
$invitation = $invoice->invitations->first();
|
||||
$accountGateway = $account->getGatewayConfig(GATEWAY_STRIPE);
|
||||
$token = $client->getGatewayToken();
|
||||
|
||||
if (!$invitation || !$accountGateway || !$token) {
|
||||
// Make sure we've migrated in data from Stripe
|
||||
$this->getClientPaymentMethods($client);
|
||||
|
||||
$invitation = $invoice->invitations->first();
|
||||
$token = $client->getGatewayToken($accountGateway/* return parameter */, $accountGatewayToken/* return parameter */);
|
||||
$defaultPaymentMethod = $accountGatewayToken->default_payment_method;
|
||||
|
||||
if (!$invitation || !$token || !$defaultPaymentMethod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -310,12 +738,18 @@ class PaymentService extends BaseService
|
||||
$details = $this->getPaymentDetails($invitation, $accountGateway);
|
||||
$details['customerReference'] = $token;
|
||||
|
||||
if ($accountGateway->gateway_id == GATEWAY_STRIPE) {
|
||||
$details['cardReference'] = $defaultPaymentMethod->source_reference;
|
||||
} elseif ($accountGateway->gateway_id == GATEWAY_BRAINTREE) {
|
||||
$details['paymentMethodToken'] = $defaultPaymentMethod->source_reference;
|
||||
}
|
||||
|
||||
// submit purchase/get response
|
||||
$response = $gateway->purchase($details)->send();
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
$ref = $response->getTransactionReference();
|
||||
return $this->createPayment($invitation, $accountGateway, $ref);
|
||||
return $this->createPayment($invitation, $accountGateway, $ref, null, $details, $defaultPaymentMethod, $response);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -329,7 +763,8 @@ class PaymentService extends BaseService
|
||||
$query->where('payments.user_id', '=', Auth::user()->id);
|
||||
}
|
||||
|
||||
return $this->createDatatable(ENTITY_PAYMENT, $query, !$clientPublicId);
|
||||
return $this->createDatatable(ENTITY_PAYMENT, $query, !$clientPublicId, false,
|
||||
['invoice_number', 'transaction_reference', 'payment_type', 'amount', 'payment_date']);
|
||||
}
|
||||
|
||||
protected function getDatatableColumns($entityType, $hideClient)
|
||||
@ -365,7 +800,29 @@ class PaymentService extends BaseService
|
||||
[
|
||||
'payment_type',
|
||||
function ($model) {
|
||||
return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? $model->gateway_name : '');
|
||||
return ($model->payment_type && !$model->last4) ? $model->payment_type : ($model->account_gateway_id ? $model->gateway_name : '');
|
||||
}
|
||||
],
|
||||
[
|
||||
'source',
|
||||
function ($model) {
|
||||
$code = str_replace(' ', '', strtolower($model->payment_type));
|
||||
$card_type = trans("texts.card_" . $code);
|
||||
if ($model->payment_type_id != PAYMENT_TYPE_ACH) {
|
||||
if($model->last4) {
|
||||
$expiration = trans('texts.card_expiration', array('expires' => Utils::fromSqlDate($model->expiration, false)->format('m/y')));
|
||||
return '<img height="22" src="' . URL::to('/images/credit_cards/' . $code . '.png') . '" alt="' . htmlentities($card_type) . '"> •••' . $model->last4 . ' ' . $expiration;
|
||||
} elseif ($model->email) {
|
||||
return $model->email;
|
||||
}
|
||||
} elseif ($model->last4) {
|
||||
$bankData = PaymentMethod::lookupBankData($model->routing_number);
|
||||
if (is_object($bankData)) {
|
||||
return $bankData->name.' •••' . $model->last4;
|
||||
} elseif($model->last4) {
|
||||
return '<img height="22" src="' . URL::to('/images/credit_cards/ach.png') . '" alt="' . htmlentities($card_type) . '"> •••' . $model->last4;
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
@ -379,6 +836,12 @@ class PaymentService extends BaseService
|
||||
function ($model) {
|
||||
return Utils::dateToString($model->payment_date);
|
||||
}
|
||||
],
|
||||
[
|
||||
'payment_status_name',
|
||||
function ($model) use ($entityType) {
|
||||
return self::getStatusLabel($entityType, $model);
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
@ -394,9 +857,226 @@ class PaymentService extends BaseService
|
||||
function ($model) {
|
||||
return Auth::user()->can('editByOwner', [ENTITY_PAYMENT, $model->user_id]);
|
||||
}
|
||||
],
|
||||
[
|
||||
trans('texts.refund_payment'),
|
||||
function ($model) {
|
||||
$max_refund = number_format($model->amount - $model->refunded, 2);
|
||||
$formatted = Utils::formatMoney($max_refund, $model->currency_id, $model->country_id);
|
||||
$symbol = Utils::getFromCache($model->currency_id, 'currencies')->symbol;
|
||||
return "javascript:showRefundModal({$model->public_id}, '{$max_refund}', '{$formatted}', '{$symbol}')";
|
||||
},
|
||||
function ($model) {
|
||||
return Auth::user()->can('editByOwner', [ENTITY_PAYMENT, $model->user_id]) && $model->payment_status_id >= PAYMENT_STATUS_COMPLETED &&
|
||||
$model->refunded < $model->amount &&
|
||||
(
|
||||
($model->transaction_reference && in_array($model->gateway_id , static::$refundableGateways))
|
||||
|| $model->payment_type_id == PAYMENT_TYPE_CREDIT
|
||||
);
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function bulk($ids, $action, $params = array())
|
||||
{
|
||||
if ($action == 'refund') {
|
||||
if ( ! $ids ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$payments = $this->getRepo()->findByPublicIdsWithTrashed($ids);
|
||||
$successful = 0;
|
||||
|
||||
foreach ($payments as $payment) {
|
||||
if(Auth::user()->can('edit', $payment)){
|
||||
$amount = !empty($params['amount']) ? floatval($params['amount']) : null;
|
||||
if ($this->refund($payment, $amount)) {
|
||||
$successful++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $successful;
|
||||
} else {
|
||||
return parent::bulk($ids, $action);
|
||||
}
|
||||
}
|
||||
|
||||
private function getStatusLabel($entityType, $model)
|
||||
{
|
||||
$label = trans("texts.status_" . strtolower($model->payment_status_name));
|
||||
$class = 'default';
|
||||
switch ($model->payment_status_id) {
|
||||
case PAYMENT_STATUS_PENDING:
|
||||
$class = 'info';
|
||||
break;
|
||||
case PAYMENT_STATUS_COMPLETED:
|
||||
$class = 'success';
|
||||
break;
|
||||
case PAYMENT_STATUS_FAILED:
|
||||
$class = 'danger';
|
||||
break;
|
||||
case PAYMENT_STATUS_PARTIALLY_REFUNDED:
|
||||
$label = trans('texts.status_partially_refunded_amount', [
|
||||
'amount' => Utils::formatMoney($model->refunded, $model->currency_id, $model->country_id),
|
||||
]);
|
||||
$class = 'primary';
|
||||
break;
|
||||
case PAYMENT_STATUS_VOIDED:
|
||||
case PAYMENT_STATUS_REFUNDED:
|
||||
$class = 'default';
|
||||
break;
|
||||
}
|
||||
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
|
||||
}
|
||||
|
||||
public function refund($payment, $amount = null) {
|
||||
if (!$amount) {
|
||||
$amount = $payment->amount;
|
||||
}
|
||||
|
||||
$amount = min($amount, $payment->amount - $payment->refunded);
|
||||
|
||||
$accountGateway = $payment->account_gateway;
|
||||
|
||||
if (!$accountGateway) {
|
||||
$accountGateway = AccountGateway::withTrashed()->find($payment->account_gateway_id);
|
||||
}
|
||||
|
||||
if (!$amount || !$accountGateway) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($payment->payment_type_id != PAYMENT_TYPE_CREDIT) {
|
||||
$gateway = $this->createGateway($accountGateway);
|
||||
$refund = $gateway->refund(array(
|
||||
'transactionReference' => $payment->transaction_reference,
|
||||
'amount' => $amount,
|
||||
));
|
||||
$response = $refund->send();
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
$payment->recordRefund($amount);
|
||||
} else {
|
||||
$data = $response->getData();
|
||||
|
||||
if ($data instanceof \Braintree\Result\Error) {
|
||||
$error = $data->errors->deepAll()[0];
|
||||
if ($error && $error->code == 91506) {
|
||||
if ($amount == $payment->amount) {
|
||||
// This is an unsettled transaction; try to void it
|
||||
$void = $gateway->void(array(
|
||||
'transactionReference' => $payment->transaction_reference,
|
||||
));
|
||||
$response = $void->send();
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
$payment->markVoided();
|
||||
}
|
||||
} else {
|
||||
$this->error('Unknown', 'Partial refund not allowed for unsettled transactions.', $accountGateway);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$response->isSuccessful()) {
|
||||
$this->error('Unknown', $response->getMessage(), $accountGateway);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$payment->recordRefund($amount);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private function error($type, $error, $accountGateway = false, $exception = false)
|
||||
{
|
||||
$message = '';
|
||||
if ($accountGateway && $accountGateway->gateway) {
|
||||
$message = $accountGateway->gateway->name . ': ';
|
||||
}
|
||||
$message .= $error ?: trans('texts.payment_error');
|
||||
|
||||
Session::flash('error', $message);
|
||||
Utils::logError("Payment Error [{$type}]: " . ($exception ? Utils::getErrorString($exception) : $message), 'PHP', true);
|
||||
}
|
||||
|
||||
public function makeStripeCall($accountGateway, $method, $url, $body = null) {
|
||||
$apiKey = $accountGateway->getConfig()->apiKey;
|
||||
|
||||
if (!$apiKey) {
|
||||
return 'No API key set';
|
||||
}
|
||||
|
||||
try{
|
||||
$options = [
|
||||
'headers' => ['content-type' => 'application/x-www-form-urlencoded'],
|
||||
'auth' => [$accountGateway->getConfig()->apiKey,''],
|
||||
];
|
||||
|
||||
if ($body) {
|
||||
$options['body'] = $body;
|
||||
}
|
||||
|
||||
$response = (new \GuzzleHttp\Client(['base_uri'=>'https://api.stripe.com/v1/']))->request(
|
||||
$method,
|
||||
$url,
|
||||
$options
|
||||
);
|
||||
return json_decode($response->getBody(), true);
|
||||
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
|
||||
$response = $e->getResponse();
|
||||
$body = json_decode($response->getBody(), true);
|
||||
|
||||
if ($body && $body['error'] && $body['error']['type'] == 'invalid_request_error') {
|
||||
return $body['error']['message'];
|
||||
}
|
||||
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private function getPlaidToken($accountGateway, $publicToken, $accountId) {
|
||||
$clientId = $accountGateway->getPlaidClientId();
|
||||
$secret = $accountGateway->getPlaidSecret();
|
||||
|
||||
if (!$clientId) {
|
||||
return 'No client ID set';
|
||||
}
|
||||
|
||||
if (!$secret) {
|
||||
return 'No secret set';
|
||||
}
|
||||
|
||||
try{
|
||||
$subdomain = $accountGateway->getPlaidEnvironment() == 'production' ? 'api' : 'tartan';
|
||||
$response = (new \GuzzleHttp\Client(['base_uri'=>"https://{$subdomain}.plaid.com"]))->request(
|
||||
'POST',
|
||||
'exchange_token',
|
||||
[
|
||||
'allow_redirects' => false,
|
||||
'headers' => ['content-type' => 'application/x-www-form-urlencoded'],
|
||||
'body' => http_build_query(array(
|
||||
'client_id' => $clientId,
|
||||
'secret' => $secret,
|
||||
'public_token' => $publicToken,
|
||||
'account_id' => $accountId,
|
||||
))
|
||||
]
|
||||
);
|
||||
return json_decode($response->getBody(), true);
|
||||
} catch (\GuzzleHttp\Exception\BadResponseException $e) {
|
||||
$response = $e->getResponse();
|
||||
$body = json_decode($response->getBody(), true);
|
||||
|
||||
if ($body && !empty($body['message'])) {
|
||||
return $body['message'];
|
||||
}
|
||||
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
"omnipay/mollie": "dev-master#22956c1a62a9662afa5f5d119723b413770ac525",
|
||||
"omnipay/2checkout": "dev-master#e9c079c2dde0d7ba461903b3b7bd5caf6dee1248",
|
||||
"omnipay/gocardless": "dev-master",
|
||||
"omnipay/stripe": "2.3.0",
|
||||
"omnipay/stripe": "dev-master",
|
||||
"laravel/framework": "5.2.*",
|
||||
"laravelcollective/html": "5.2.*",
|
||||
"laravelcollective/bus": "5.2.*",
|
||||
@ -72,7 +72,9 @@
|
||||
"asgrim/ofxparser": "^1.1",
|
||||
"league/flysystem-aws-s3-v3": "~1.0",
|
||||
"league/flysystem-rackspace": "~1.0",
|
||||
"barracudanetworks/archivestream-php": "^1.0"
|
||||
"barracudanetworks/archivestream-php": "^1.0",
|
||||
"omnipay/braintree": "~2.0@dev",
|
||||
"gatepay/FedACHdir": "dev-master@dev"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0",
|
||||
@ -121,5 +123,23 @@
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "dist"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "gatepay/FedACHdir",
|
||||
"version": "dev-master",
|
||||
"dist": {
|
||||
"url": "https://github.com/gatepay/FedACHdir/archive/master.zip",
|
||||
"type": "zip"
|
||||
},
|
||||
"source": {
|
||||
"url": "git@github.com:gatepay/FedACHdir.git",
|
||||
"type": "git",
|
||||
"reference": "origin/master"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
146
composer.lock
generated
@ -4,8 +4,8 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "cf82d2ddb25cb1a7d6b4867bcc8692b8",
|
||||
"content-hash": "481a95753b873249aebceb99e7426421",
|
||||
"hash": "7139e4aedb2ac151079c50ee5c17f93c",
|
||||
"content-hash": "a314d6c0a16785dd2395a7fd73cdc76d",
|
||||
"packages": [
|
||||
{
|
||||
"name": "agmscode/omnipay-agms",
|
||||
@ -127,7 +127,7 @@
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/formers/former/zipball/78ae8c65b1f8134e2db1c9491c251c03638823ca",
|
||||
"url": "https://api.github.com/repos/formers/former/zipball/37f6876a5d211427b5c445cd64f0eb637f42f685",
|
||||
"reference": "d97f907741323b390f43954a90a227921ecc6b96",
|
||||
"shasum": ""
|
||||
},
|
||||
@ -558,6 +558,53 @@
|
||||
],
|
||||
"time": "2016-03-03 14:38:04"
|
||||
},
|
||||
{
|
||||
"name": "braintree/braintree_php",
|
||||
"version": "3.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/braintree/braintree_php.git",
|
||||
"reference": "2ab7e41d8d31286fa64bae7279a8e785a40b1be4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/braintree/braintree_php/zipball/2ab7e41d8d31286fa64bae7279a8e785a40b1be4",
|
||||
"reference": "2ab7e41d8d31286fa64bae7279a8e785a40b1be4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-hash": "*",
|
||||
"ext-openssl": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "3.7.*"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"Braintree": "lib/"
|
||||
},
|
||||
"psr-4": {
|
||||
"Braintree\\": "lib/Braintree"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Braintree",
|
||||
"homepage": "http://www.braintreepayments.com"
|
||||
}
|
||||
],
|
||||
"description": "Braintree PHP Client Library",
|
||||
"time": "2016-04-12 20:39:36"
|
||||
},
|
||||
{
|
||||
"name": "cardgate/omnipay-cardgate",
|
||||
"version": "v2.0.0",
|
||||
@ -699,7 +746,7 @@
|
||||
"laravel"
|
||||
],
|
||||
"abandoned": "OpenSkill/Datatable",
|
||||
"time": "2015-04-29 07:00:36"
|
||||
"time": "2015-11-23 21:33:41"
|
||||
},
|
||||
{
|
||||
"name": "classpreloader/classpreloader",
|
||||
@ -1964,6 +2011,17 @@
|
||||
],
|
||||
"time": "2015-01-16 08:41:13"
|
||||
},
|
||||
{
|
||||
"name": "gatepay/FedACHdir",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:gatepay/FedACHdir.git",
|
||||
"reference": "origin/master"
|
||||
},
|
||||
"type": "library",
|
||||
"time": "2016-04-29 12:01:22"
|
||||
},
|
||||
{
|
||||
"name": "guzzle/guzzle",
|
||||
"version": "v3.8.1",
|
||||
@ -2338,7 +2396,7 @@
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Intervention/image/zipball/e368d262887dbb2fdfaf710880571ede51e9c0e6",
|
||||
"url": "https://api.github.com/repos/Intervention/image/zipball/22088b04728a039bd1fc32f7e79a89a118b78698",
|
||||
"reference": "e368d262887dbb2fdfaf710880571ede51e9c0e6",
|
||||
"shasum": ""
|
||||
},
|
||||
@ -4277,6 +4335,69 @@
|
||||
],
|
||||
"time": "2016-03-10 03:16:04"
|
||||
},
|
||||
{
|
||||
"name": "omnipay/braintree",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/omnipay-braintree.git",
|
||||
"reference": "e4b4027c6a9e6443833490d0d51fd530f0a19f62"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/omnipay-braintree/zipball/e4b4027c6a9e6443833490d0d51fd530f0a19f62",
|
||||
"reference": "e4b4027c6a9e6443833490d0d51fd530f0a19f62",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"braintree/braintree_php": "^2.39|^3.0",
|
||||
"omnipay/common": "~2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"omnipay/tests": "~2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Omnipay\\Braintree\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Barry vd. Heuvel",
|
||||
"email": "barryvdh@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Kayla Daniels",
|
||||
"email": "kayladnls@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Omnipay Contributors",
|
||||
"homepage": "https://github.com/thephpleague/omnipay-braintree/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Braintree gateway for Omnipay payment processing library",
|
||||
"homepage": "https://github.com/thephpleague/omnipay-braintree",
|
||||
"keywords": [
|
||||
"braintree",
|
||||
"gateway",
|
||||
"merchant",
|
||||
"omnipay",
|
||||
"pay",
|
||||
"payment",
|
||||
"purchase"
|
||||
],
|
||||
"time": "2016-02-25 20:54:09"
|
||||
},
|
||||
{
|
||||
"name": "omnipay/buckaroo",
|
||||
"version": "v2.0.1",
|
||||
@ -5661,16 +5782,16 @@
|
||||
},
|
||||
{
|
||||
"name": "omnipay/stripe",
|
||||
"version": "v2.3.0",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/omnipay-stripe.git",
|
||||
"reference": "54b816a5e95e34c988d71fb805b0232cfd7c1ce5"
|
||||
"reference": "0ea7a647ee01e29c152814e11c2ea6307e5b0db9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/omnipay-stripe/zipball/54b816a5e95e34c988d71fb805b0232cfd7c1ce5",
|
||||
"reference": "54b816a5e95e34c988d71fb805b0232cfd7c1ce5",
|
||||
"url": "https://api.github.com/repos/thephpleague/omnipay-stripe/zipball/0ea7a647ee01e29c152814e11c2ea6307e5b0db9",
|
||||
"reference": "0ea7a647ee01e29c152814e11c2ea6307e5b0db9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@ -5714,7 +5835,7 @@
|
||||
"payment",
|
||||
"stripe"
|
||||
],
|
||||
"time": "2015-11-10 16:17:35"
|
||||
"time": "2016-04-26 08:34:50"
|
||||
},
|
||||
{
|
||||
"name": "omnipay/targetpay",
|
||||
@ -9875,6 +9996,7 @@
|
||||
"omnipay/mollie": 20,
|
||||
"omnipay/2checkout": 20,
|
||||
"omnipay/gocardless": 20,
|
||||
"omnipay/stripe": 20,
|
||||
"anahkiasen/former": 20,
|
||||
"chumper/datatable": 20,
|
||||
"intervention/image": 20,
|
||||
@ -9894,7 +10016,9 @@
|
||||
"meebio/omnipay-secure-trading": 20,
|
||||
"labs7in0/omnipay-wechat": 20,
|
||||
"laracasts/presenter": 20,
|
||||
"jlapp/swaggervel": 20
|
||||
"jlapp/swaggervel": 20,
|
||||
"omnipay/braintree": 20,
|
||||
"gatepay/fedachdir": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
|
145
database/migrations/2016_04_23_182223_payments_changes.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class PaymentsChanges extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::dropIfExists('payment_statuses');
|
||||
|
||||
Schema::create('payment_statuses', function($table)
|
||||
{
|
||||
$table->increments('id');
|
||||
$table->string('name');
|
||||
});
|
||||
|
||||
(new \PaymentStatusSeeder())->run();
|
||||
|
||||
Schema::dropIfExists('payment_methods');
|
||||
|
||||
Schema::create('payment_methods', function($table)
|
||||
{
|
||||
$table->increments('id');
|
||||
$table->unsignedInteger('account_id');
|
||||
$table->unsignedInteger('contact_id')->nullable();
|
||||
$table->unsignedInteger('account_gateway_token_id');
|
||||
$table->unsignedInteger('payment_type_id');
|
||||
$table->string('source_reference');
|
||||
|
||||
$table->unsignedInteger('routing_number')->nullable();
|
||||
$table->smallInteger('last4')->unsigned()->nullable();
|
||||
$table->date('expiration')->nullable();
|
||||
$table->string('email')->nullable();
|
||||
$table->unsignedInteger('currency_id')->nullable();
|
||||
$table->string('status')->nullable();
|
||||
|
||||
$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_token_id')->references('id')->on('account_gateway_tokens');
|
||||
$table->foreign('payment_type_id')->references('id')->on('payment_types');
|
||||
$table->foreign('currency_id')->references('id')->on('currencies');
|
||||
|
||||
$table->unsignedInteger('public_id')->index();
|
||||
$table->unique( array('account_id','public_id') );
|
||||
});
|
||||
|
||||
Schema::table('payments', function($table)
|
||||
{
|
||||
$table->decimal('refunded', 13, 2);
|
||||
$table->unsignedInteger('payment_status_id')->default(PAYMENT_STATUS_COMPLETED);
|
||||
$table->foreign('payment_status_id')->references('id')->on('payment_statuses');
|
||||
|
||||
$table->unsignedInteger('routing_number')->nullable();
|
||||
$table->smallInteger('last4')->unsigned()->nullable();
|
||||
$table->date('expiration')->nullable();
|
||||
$table->text('gateway_error')->nullable();
|
||||
$table->string('email')->nullable();
|
||||
|
||||
$table->unsignedInteger('payment_method_id')->nullable();
|
||||
$table->foreign('payment_method_id')->references('id')->on('payment_methods');
|
||||
});
|
||||
|
||||
Schema::table('invoices', function($table)
|
||||
{
|
||||
$table->boolean('client_enable_auto_bill')->default(false);
|
||||
});
|
||||
|
||||
\DB::table('invoices')
|
||||
->where('auto_bill', '=', 1)
|
||||
->update(array('client_enable_auto_bill' => 1, 'auto_bill' => AUTO_BILL_OPT_OUT));
|
||||
|
||||
|
||||
Schema::table('account_gateway_tokens', function($table)
|
||||
{
|
||||
$table->unsignedInteger('default_payment_method_id')->nullable();
|
||||
$table->foreign('default_payment_method_id')->references('id')->on('payment_methods');
|
||||
|
||||
$table->boolean('uses_local_payment_methods')->defalut(true);
|
||||
});
|
||||
|
||||
\DB::table('account_gateway_tokens')->update(array('uses_local_payment_methods' => false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('payments', function($table)
|
||||
{
|
||||
$table->dropColumn('refunded');
|
||||
$table->dropForeign('payments_payment_status_id_foreign');
|
||||
$table->dropColumn('payment_status_id');
|
||||
|
||||
$table->dropColumn('routing_number');
|
||||
$table->dropColumn('last4');
|
||||
$table->dropColumn('expiration');
|
||||
$table->dropColumn('gateway_error');
|
||||
$table->dropColumn('email');
|
||||
|
||||
$table->dropForeign('payments_payment_method_id_foreign');
|
||||
$table->dropColumn('payment_method_id');
|
||||
});
|
||||
|
||||
\DB::table('invoices')
|
||||
->where(function($query){
|
||||
$query->where('auto_bill', '=', AUTO_BILL_ALWAYS);
|
||||
$query->orwhere(function($query){
|
||||
$query->where('auto_bill', '!=', AUTO_BILL_OFF);
|
||||
$query->where('client_enable_auto_bill', '=', 1);
|
||||
});
|
||||
})
|
||||
->update(array('auto_bill' => 1));
|
||||
|
||||
\DB::table('invoices')
|
||||
->where('auto_bill', '!=', 1)
|
||||
->update(array('auto_bill' => 0));
|
||||
|
||||
Schema::table('invoices', function ($table) {
|
||||
$table->dropColumn('client_enable_auto_bill');
|
||||
});
|
||||
|
||||
Schema::dropIfExists('payment_statuses');
|
||||
|
||||
Schema::table('account_gateway_tokens', function($table)
|
||||
{
|
||||
$table->dropForeign('account_gateway_tokens_default_payment_method_id_foreign');
|
||||
$table->dropColumn('default_payment_method_id');
|
||||
$table->dropColumn('uses_local_payment_methods');
|
||||
});
|
||||
|
||||
Schema::dropIfExists('payment_methods');
|
||||
}
|
||||
}
|
@ -20,23 +20,6 @@ class ConstantsSeeder extends Seeder
|
||||
|
||||
public function run()
|
||||
{
|
||||
PaymentType::create(array('name' => 'Apply Credit'));
|
||||
PaymentType::create(array('name' => 'Bank Transfer'));
|
||||
PaymentType::create(array('name' => 'Cash'));
|
||||
PaymentType::create(array('name' => 'Debit'));
|
||||
PaymentType::create(array('name' => 'ACH'));
|
||||
PaymentType::create(array('name' => 'Visa Card'));
|
||||
PaymentType::create(array('name' => 'MasterCard'));
|
||||
PaymentType::create(array('name' => 'American Express'));
|
||||
PaymentType::create(array('name' => 'Discover Card'));
|
||||
PaymentType::create(array('name' => 'Diners Card'));
|
||||
PaymentType::create(array('name' => 'EuroCard'));
|
||||
PaymentType::create(array('name' => 'Nova'));
|
||||
PaymentType::create(array('name' => 'Credit Card Other'));
|
||||
PaymentType::create(array('name' => 'PayPal'));
|
||||
PaymentType::create(array('name' => 'Google Wallet'));
|
||||
PaymentType::create(array('name' => 'Check'));
|
||||
|
||||
Theme::create(array('name' => 'amelia'));
|
||||
Theme::create(array('name' => 'cerulean'));
|
||||
Theme::create(array('name' => 'cosmo'));
|
||||
|
@ -19,10 +19,12 @@ class DatabaseSeeder extends Seeder
|
||||
$this->call('FontsSeeder');
|
||||
$this->call('BanksSeeder');
|
||||
$this->call('InvoiceStatusSeeder');
|
||||
$this->call('PaymentStatusSeeder');
|
||||
$this->call('CurrenciesSeeder');
|
||||
$this->call('DateFormatsSeeder');
|
||||
$this->call('InvoiceDesignsSeeder');
|
||||
$this->call('PaymentTermsSeeder');
|
||||
$this->call('PaymentTypesSeeder');
|
||||
$this->call('LanguageSeeder');
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ class PaymentLibrariesSeeder extends Seeder
|
||||
['name' => 'SecPay', 'provider' => 'SecPay', 'payment_library_id' => 1],
|
||||
['name' => 'WeChat Express', 'provider' => 'WeChat_Express', 'payment_library_id' => 1],
|
||||
['name' => 'WePay', 'provider' => 'WePay', 'payment_library_id' => 1],
|
||||
['name' => 'Braintree', 'provider' => 'Braintree', 'payment_library_id' => 1],
|
||||
];
|
||||
|
||||
foreach ($gateways as $gateway) {
|
||||
|
37
database/seeds/PaymentStatusSeeder.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use App\Models\PaymentStatus;
|
||||
|
||||
class PaymentStatusSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
Eloquent::unguard();
|
||||
|
||||
$this->createPaymentStatuses();
|
||||
|
||||
Eloquent::reguard();
|
||||
}
|
||||
|
||||
private function createPaymentStatuses()
|
||||
{
|
||||
$statuses = [
|
||||
['id' => '1', 'name' => 'Pending'],
|
||||
['id' => '2', 'name' => 'Voided'],
|
||||
['id' => '3', 'name' => 'Failed'],
|
||||
['id' => '4', 'name' => 'Completed'],
|
||||
['id' => '5', 'name' => 'Partially Refunded'],
|
||||
['id' => '6', 'name' => 'Refunded'],
|
||||
];
|
||||
|
||||
foreach ($statuses as $status) {
|
||||
$record = PaymentStatus::find($status['id']);
|
||||
if ($record) {
|
||||
$record->name = $status['name'];
|
||||
$record->save();
|
||||
} else {
|
||||
PaymentStatus::create($status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
database/seeds/PaymentTypesSeeder.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use App\Models\PaymentType;
|
||||
|
||||
class PaymentTypesSeeder extends Seeder
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
Eloquent::unguard();
|
||||
|
||||
$paymentTypes = [
|
||||
array('name' => 'Apply Credit'),
|
||||
array('name' => 'Bank Transfer'),
|
||||
array('name' => 'Cash'),
|
||||
array('name' => 'Debit'),
|
||||
array('name' => 'ACH'),
|
||||
array('name' => 'Visa Card'),
|
||||
array('name' => 'MasterCard'),
|
||||
array('name' => 'American Express'),
|
||||
array('name' => 'Discover Card'),
|
||||
array('name' => 'Diners Card'),
|
||||
array('name' => 'EuroCard'),
|
||||
array('name' => 'Nova'),
|
||||
array('name' => 'Credit Card Other'),
|
||||
array('name' => 'PayPal'),
|
||||
array('name' => 'Google Wallet'),
|
||||
array('name' => 'Check'),
|
||||
array('name' => 'Carte Blanche'),
|
||||
array('name' => 'UnionPay'),
|
||||
array('name' => 'JCB'),
|
||||
array('name' => 'Laser'),
|
||||
array('name' => 'Maestro'),
|
||||
array('name' => 'Solo'),
|
||||
array('name' => 'Switch'),
|
||||
];
|
||||
|
||||
foreach ($paymentTypes as $paymentType) {
|
||||
if (!DB::table('payment_types')->where('name', '=', $paymentType['name'])->get()) {
|
||||
PaymentType::create($paymentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -15,10 +15,12 @@ class UpdateSeeder extends Seeder
|
||||
$this->call('FontsSeeder');
|
||||
$this->call('BanksSeeder');
|
||||
$this->call('InvoiceStatusSeeder');
|
||||
$this->call('PaymentStatusSeeder');
|
||||
$this->call('CurrenciesSeeder');
|
||||
$this->call('DateFormatsSeeder');
|
||||
$this->call('InvoiceDesignsSeeder');
|
||||
$this->call('PaymentTermsSeeder');
|
||||
$this->call('PaymentTypesSeeder');
|
||||
$this->call('LanguageSeeder');
|
||||
}
|
||||
}
|
||||
|
4
public/css/built.css
vendored
4
public/css/built.public.css
vendored
BIN
public/images/credit_cards/ach.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
public/images/credit_cards/americanexpress.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
public/images/credit_cards/carteblanche.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/images/credit_cards/creditcardother.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/images/credit_cards/dinerscard.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
public/images/credit_cards/discovercard.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
public/images/credit_cards/jcb.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
public/images/credit_cards/laser.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
public/images/credit_cards/maestro.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
public/images/credit_cards/mastercard.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
public/images/credit_cards/paypal.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
public/images/credit_cards/solo.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
public/images/credit_cards/switch.png
Normal file
After Width: | Height: | Size: 938 B |
BIN
public/images/credit_cards/unionpay.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
public/images/credit_cards/visacard.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
12
public/images/plaid-logo.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 30 30" enable-background="new 0 0 30 30" xml:space="preserve">
|
||||
<path id="XMLID_14_" fill="#275A76" d="M15.1,3.2l-3.2-3.3L2.5,2.3L0,11.6l3.2,3.3l-3.3,3.2l2.4,9.3l9.3,2.6l3.3-3.2l3.2,3.3
|
||||
l9.3-2.4l2.6-9.3l-3.2-3.3l3.3-3.2l-2.4-9.3L18.4,0L15.1,3.2z M13.2,5l-3.1,3.1l-3.9-4l4.9-1.3L13.2,5z M15,16.9l3.1,3.1l-3.1,3.1
|
||||
L11.8,20L15,16.9z M10,18.1l-3.1-3.1l3.1-3.1l3.1,3.1L10,18.1z M16.9,15l3.1-3.1l3.1,3.1L20,18.2L16.9,15z M15,13.1L11.9,10l3.1-3.1
|
||||
l3.1,3.1L15,13.1z M2.9,10.9L4.3,6l3.9,4l-3.1,3.1L2.9,10.9z M5,16.8l3.1,3.1l-4,3.9l-1.3-4.9L5,16.8z M10.9,27.1L6,25.7l4-3.9
|
||||
l3.1,3.1L10.9,27.1z M16.8,25l3.1-3.1l3.9,4l-4.9,1.3L16.8,25z M27.1,19.1L25.7,24l-3.9-4l3.1-3.1L27.1,19.1z M25,13.2l-3.1-3.1
|
||||
l4-3.9l1.3,4.9L25,13.2z M24,4.3l-4,3.9l-3.1-3.1l2.2-2.2L24,4.3z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
12
public/images/plaid-logowhite.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 30 30" enable-background="new 0 0 30 30" xml:space="preserve">
|
||||
<path id="XMLID_14_" fill="#FFFFFF" d="M15.1,3.2l-3.2-3.3L2.5,2.3L0,11.6l3.2,3.3l-3.3,3.2l2.4,9.3l9.3,2.6l3.3-3.2l3.2,3.3
|
||||
l9.3-2.4l2.6-9.3l-3.2-3.3l3.3-3.2l-2.4-9.3L18.4,0L15.1,3.2z M13.2,5l-3.1,3.1l-3.9-4l4.9-1.3L13.2,5z M15,16.9l3.1,3.1l-3.1,3.1
|
||||
L11.8,20L15,16.9z M10,18.1l-3.1-3.1l3.1-3.1l3.1,3.1L10,18.1z M16.9,15l3.1-3.1l3.1,3.1L20,18.2L16.9,15z M15,13.1L11.9,10l3.1-3.1
|
||||
l3.1,3.1L15,13.1z M2.9,10.9L4.3,6l3.9,4l-3.1,3.1L2.9,10.9z M5,16.8l3.1,3.1l-4,3.9l-1.3-4.9L5,16.8z M10.9,27.1L6,25.7l4-3.9
|
||||
l3.1,3.1L10.9,27.1z M16.8,25l3.1-3.1l3.9,4l-4.9,1.3L16.8,25z M27.1,19.1L25.7,24l-3.9-4l3.1-3.1L27.1,19.1z M25,13.2l-3.1-3.1
|
||||
l4-3.9l1.3,4.9L25,13.2z M24,4.3l-4,3.9l-3.1-3.1l2.2-2.2L24,4.3z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -17,7 +17,6 @@
|
||||
| loading any of our classes later on. It feels nice to relax.
|
||||
|
|
||||
*/
|
||||
|
||||
require __DIR__.'/../bootstrap/autoload.php';
|
||||
|
||||
/*
|
||||
|
@ -516,11 +516,11 @@ return array(
|
||||
'token_billing_3' => 'Fravalg - checkboks er vist og valgt',
|
||||
'token_billing_4' => 'Altid',
|
||||
'token_billing_checkbox' => 'Opbevar kreditkort oplysninger',
|
||||
'view_in_stripe' => 'Vis i Stripe ',
|
||||
'view_in_gateway' => 'Vis i :gateway',
|
||||
'use_card_on_file' => 'Brug allerede gemt kort',
|
||||
'edit_payment_details' => 'Redigér betalings detaljer',
|
||||
'token_billing' => 'Gem kort detaljer',
|
||||
'token_billing_secure' => 'Kort data er opbevaret sikkert hos :stripe_link',
|
||||
'token_billing_secure' => 'Kort data er opbevaret sikkert hos :link',
|
||||
|
||||
'support' => 'Kundeservice',
|
||||
'contact_information' => 'Kontakt information',
|
||||
|
@ -516,11 +516,11 @@ return array(
|
||||
'token_billing_3' => 'Opt-out - Kontrollkästchen wird angezeigt und ist ausgewählt',
|
||||
'token_billing_4' => 'Immer',
|
||||
'token_billing_checkbox' => 'Kreditkarteninformationen speichern',
|
||||
'view_in_stripe' => 'Auf Stripe anzeigen',
|
||||
'view_in_gateway' => 'Auf :gateway anzeigen',
|
||||
'use_card_on_file' => 'Verwende gespeicherte Kreditkarte',
|
||||
'edit_payment_details' => 'Zahlungsdetails bearbeiten',
|
||||
'token_billing' => 'Kreditkarte merken',
|
||||
'token_billing_secure' => 'Die Daten werden sicher von :stripe_link gespeichert.',
|
||||
'token_billing_secure' => 'Die Daten werden sicher von :link gespeichert.',
|
||||
|
||||
'support' => 'Support',
|
||||
'contact_information' => 'Kontakt-Informationen',
|
||||
|
@ -444,11 +444,11 @@ $LANG = array(
|
||||
'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',
|
||||
'view_in_gateway' => 'View in :gateway',
|
||||
'use_card_on_file' => 'Use card on file',
|
||||
'edit_payment_details' => 'Edit payment details',
|
||||
'token_billing' => 'Save card details',
|
||||
'token_billing_secure' => 'The data is stored securely by :stripe_link',
|
||||
'token_billing_secure' => 'The data is stored securely by :link',
|
||||
'support' => 'Support',
|
||||
'contact_information' => 'Contact Information',
|
||||
'256_encryption' => '256-Bit Encryption',
|
||||
@ -1182,6 +1182,117 @@ $LANG = array(
|
||||
'enterprise_plan_features' => 'The Enterprise plan adds support for multiple users and file attachments.',
|
||||
'return_to_app' => 'Return to app',
|
||||
|
||||
|
||||
// Payment updates
|
||||
'refund_payment' => 'Refund Payment',
|
||||
'refund_max' => 'Max:',
|
||||
'refund' => 'Refund',
|
||||
'are_you_sure_refund' => 'Refund selected payments?',
|
||||
'status_pending' => 'Pending',
|
||||
'status_completed' => 'Completed',
|
||||
'status_failed' => 'Failed',
|
||||
'status_partially_refunded' => 'Partially Refunded',
|
||||
'status_partially_refunded_amount' => ':amount Refunded',
|
||||
'status_refunded' => 'Refunded',
|
||||
'status_voided' => 'Cancelled',
|
||||
'refunded_payment' => 'Refunded Payment',
|
||||
'activity_39' => ':user cancelled a :payment_amount payment (:payment)',
|
||||
'activity_40' => ':user refunded :adjustment of a :payment_amount payment (:payment)',
|
||||
'card_expiration' => 'Exp: :expires',
|
||||
|
||||
'card_creditcardother' => 'Unknown',
|
||||
'card_americanexpress' => 'American Express',
|
||||
'card_carteblanche' => 'Carte Blanche',
|
||||
'card_unionpay' => 'UnionPay',
|
||||
'card_diners' => 'Diners Club',
|
||||
'card_discover' => 'Discover',
|
||||
'card_jcb' => 'JCB',
|
||||
'card_laser' => 'Laser',
|
||||
'card_maestro' => 'Maestro',
|
||||
'card_mastercard' => 'MasterCard',
|
||||
'card_solo' => 'Solo',
|
||||
'card_switch' => 'Switch',
|
||||
'card_visacard' => 'Visa',
|
||||
'card_ach' => 'ACH',
|
||||
|
||||
'payment_type_stripe' => 'Stripe',
|
||||
'ach' => 'ACH',
|
||||
'enable_ach' => 'Enable ACH',
|
||||
'stripe_ach_help' => 'ACH support must also be enabled at Stripe.',
|
||||
'stripe_ach_disabled' => 'Another gateway is already configured for direct debit.',
|
||||
|
||||
'plaid' => 'Plaid',
|
||||
'client_id' => 'Client Id',
|
||||
'secret' => 'Secret',
|
||||
'public_key' => 'Public Key',
|
||||
'plaid_optional' => '(optional)',
|
||||
'plaid_environment_help' => 'When a Stripe test key is given, Plaid\'s development environement (tartan) will be used.',
|
||||
'other_providers' => 'Other Providers',
|
||||
'country_not_supported' => 'That country is not supported.',
|
||||
'invalid_routing_number' => 'The routing number is not valid.',
|
||||
'invalid_account_number' => 'The account number is not valid.',
|
||||
'account_number_mismatch' => 'The account numbers do not match.',
|
||||
'missing_account_holder_type' => 'Please select an individual or company account.',
|
||||
'missing_account_holder_name' => 'Please enter the account holder\'s name.',
|
||||
'routing_number' => 'Routing Number',
|
||||
'confirm_account_number' => 'Confirm Account Number',
|
||||
'individual_account' => 'Individual Account',
|
||||
'company_account' => 'Company Account',
|
||||
'account_holder_name' => 'Account Holder Name',
|
||||
'add_account' => 'Add Account',
|
||||
'payment_methods' => 'Payment Methods',
|
||||
'complete_verification' => 'Complete Verification',
|
||||
'verification_amount1' => 'Amount 1',
|
||||
'verification_amount2' => 'Amount 2',
|
||||
'payment_method_verified' => 'Verification completed successfully',
|
||||
'verification_failed' => 'Verification Failed',
|
||||
'remove_payment_method' => 'Remove Payment Method',
|
||||
'confirm_remove_payment_method' => 'Are you sure you want to remove this payment method?',
|
||||
'remove' => 'Remove',
|
||||
'payment_method_removed' => 'Removed payment method.',
|
||||
'bank_account_verification_help' => 'We have made two deposits into your account with the description "VERIFICATION". These deposits will take 1-2 business days to appear on your statement. Please enter the amounts below.',
|
||||
'bank_account_verification_next_steps' => 'We have made two deposits into your account with the description "VERIFICATION". These deposits will take 1-2 business days to appear on your statement.
|
||||
Once you have the amounts, come back to this payment methods page and click "Complete Verification" next to the account.',
|
||||
'unknown_bank' => 'Unknown Bank',
|
||||
'ach_verification_delay_help' => 'You will be able to use the account after completing verification. Verification usually takes 1-2 business days.',
|
||||
'add_credit_card' => 'Add Credit Card',
|
||||
'payment_method_added' => 'Added payment method.',
|
||||
'use_for_auto_bill' => 'Use For Autobill',
|
||||
'used_for_auto_bill' => 'Autobill Payment Method',
|
||||
'payment_method_set_as_default' => 'Set Autobill payment method.',
|
||||
'activity_41' => ':payment_amount payment (:payment) failed',
|
||||
'webhook_url' => 'Webhook URL',
|
||||
'stripe_webhook_help' => 'You must :link.',
|
||||
'stripe_webhook_help_link_text' => 'add this URL as an endpoint at Stripe',
|
||||
'payment_method_error' => 'There was an error adding your payment methd. Please try again later.',
|
||||
'notification_invoice_payment_failed_subject' => 'Payment failed for Invoice :invoice',
|
||||
'notification_invoice_payment_failed' => 'A payment made by client :client towards Invoice :invoice failed. The payment has been marked as failed and :amount has been added to the client\'s balance.',
|
||||
'link_with_plaid' => 'Link Account Instantly with Plaid',
|
||||
'link_manually' => 'Link Manually',
|
||||
'secured_by_plaid' => 'Secured by Plaid',
|
||||
'plaid_linked_status' => 'Your bank account at :bank',
|
||||
'add_payment_method' => 'Add Payment Method',
|
||||
'account_holder_type' => 'Account Holder Type',
|
||||
'ach_authorization' => 'I authorize :company to electronically debit my account and, if necessary, electronically credit my account to correct erroneous debits.',
|
||||
'ach_authorization_required' => 'You must consent to ACH transactions.',
|
||||
'off' => 'Off',
|
||||
'opt_in' => 'Opt-in',
|
||||
'opt_out' => 'Opt-out',
|
||||
'always' => 'Always',
|
||||
'opted_out' => 'Opted out',
|
||||
'opted_in' => 'Opted in',
|
||||
'manage_auto_bill' => 'Manage Auto-bill',
|
||||
'enabled' => 'Enabled',
|
||||
'paypal' => 'PayPal',
|
||||
'braintree_enable_paypal' => 'Enable PayPal payments through BrainTree',
|
||||
'braintree_paypal_disabled_help' => 'The PayPal gateway is processing PayPal payments',
|
||||
'braintree_paypal_help' => 'You must also :link.',
|
||||
'braintree_paypal_help_link_text' => 'link PayPal to your BrainTree account',
|
||||
'token_billing_braintree_paypal' => 'Save payment details',
|
||||
'add_paypal_account' => 'Add PayPal Account',
|
||||
|
||||
|
||||
'no_payment_method_specified' => 'No payment method specified',
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
@ -489,11 +489,11 @@ return array(
|
||||
'token_billing_3' => 'Opt-out - el checkbox es mostrado y seleccionado',
|
||||
'token_billing_4' => 'Siempre',
|
||||
'token_billing_checkbox' => 'Almacenar detalles de la tarjeta de crédito',
|
||||
'view_in_stripe' => 'Ver en Stripe',
|
||||
'view_in_gateway' => 'Ver en :gateway',
|
||||
'use_card_on_file' => 'Usar la tarjeta en el archivo',
|
||||
'edit_payment_details' => 'Editar detalles del pago',
|
||||
'token_billing' => 'Guardar detalles de la tarjeta',
|
||||
'token_billing_secure' => 'La información es almacenada de manera segura por :stripe_link',
|
||||
'token_billing_secure' => 'La información es almacenada de manera segura por :link',
|
||||
|
||||
'support' => 'Soporte',
|
||||
'contact_information' => 'Información de Contacto',
|
||||
|
@ -509,11 +509,11 @@ return array(
|
||||
'token_billing_3' => 'La casilla Opt-Out se muestra y se selecciona',
|
||||
'token_billing_4' => 'Siempre',
|
||||
'token_billing_checkbox' => 'Almacenar datos de la tarjeta de crédito',
|
||||
'view_in_stripe' => 'Ver en Stripe',
|
||||
'view_in_gateway' => 'Ver en :gateway',
|
||||
'use_card_on_file' => 'Usar tarjeta en fichero', //??
|
||||
'edit_payment_details' => 'Editar detalles de pago',
|
||||
'token_billing' => 'Guardar datos de la tarjeta',
|
||||
'token_billing_secure' => 'Los datos serán almacenados de forma segura por :stripe_link',
|
||||
'token_billing_secure' => 'Los datos serán almacenados de forma segura por :link',
|
||||
|
||||
'support' => 'Soporte',
|
||||
'contact_information' => 'Información de Contacto',
|
||||
|
@ -509,11 +509,11 @@ return array(
|
||||
'token_billing_3' => 'Opt-out - Case à cocher affichée et sélectionné',
|
||||
'token_billing_4' => 'Toujours',
|
||||
'token_billing_checkbox' => 'Enregistrer les informations de paiement',
|
||||
'view_in_stripe' => 'Voir sur Stripe',
|
||||
'view_in_gateway' => 'Voir sur :gateway',
|
||||
'use_card_on_file' => 'Use card on file',
|
||||
'edit_payment_details' => 'Editer les détails de pauement',
|
||||
'token_billing' => 'Enregister les détails de paiement',
|
||||
'token_billing_secure' => 'Les données sont enregistrées de manière sécurisée par :stripe_link',
|
||||
'token_billing_secure' => 'Les données sont enregistrées de manière sécurisée par :link',
|
||||
|
||||
'support' => 'Support',
|
||||
'contact_information' => 'Information de contact',
|
||||
|
@ -510,11 +510,11 @@ return array(
|
||||
'token_billing_3' => 'Désengagement - case à cocher affichée avec sélection',
|
||||
'token_billing_4' => 'Toujours',
|
||||
'token_billing_checkbox' => 'Mémoriser les informations de carte de crédit',
|
||||
'view_in_stripe' => 'Visualiser dans Stripe',
|
||||
'view_in_gateway' => 'Visualiser dans :gateway',
|
||||
'use_card_on_file' => 'Mémoriser les informations de la carte pour usage ultérieur',
|
||||
'edit_payment_details' => 'Éditer les informations de paiement',
|
||||
'token_billing' => 'Sauvegarder les informations de carte de crédit',
|
||||
'token_billing_secure' => 'Les données sont mémorisées de façon sécuritaire avec :stripe_link',
|
||||
'token_billing_secure' => 'Les données sont mémorisées de façon sécuritaire avec :link',
|
||||
|
||||
'support' => 'Support',
|
||||
'contact_information' => 'Contact',
|
||||
|
@ -512,11 +512,11 @@ return array(
|
||||
'token_billing_3' => 'Opt-out - checkbox is shown and selected',
|
||||
'token_billing_4' => 'Sempre',
|
||||
'token_billing_checkbox' => 'Salva dettagli carta di credito',
|
||||
'view_in_stripe' => 'Vedi transazione in Stripe',
|
||||
'view_in_gateway' => 'Vedi transazione in :gateway',
|
||||
'use_card_on_file' => 'Carta di credito salvata',
|
||||
'edit_payment_details' => 'Modifica dettagli pagamento',
|
||||
'token_billing' => 'Salva carta di credito',
|
||||
'token_billing_secure' => 'I dati sono memorizzati su piattaforma sicura mediante :stripe_link',
|
||||
'token_billing_secure' => 'I dati sono memorizzati su piattaforma sicura mediante :link',
|
||||
|
||||
'support' => 'Support',
|
||||
'contact_information' => 'Informazioni di contatto',
|
||||
|
@ -445,11 +445,11 @@ $LANG = array(
|
||||
'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',
|
||||
'view_in_gateway' => 'View in :gateway',
|
||||
'use_card_on_file' => 'Use card on file',
|
||||
'edit_payment_details' => 'Edit payment details',
|
||||
'token_billing' => 'Save card details',
|
||||
'token_billing_secure' => 'The data is stored securely by :stripe_link',
|
||||
'token_billing_secure' => 'The data is stored securely by :link',
|
||||
'support' => 'Support',
|
||||
'contact_information' => 'Contact Information',
|
||||
'256_encryption' => '256-Bit Encryption',
|
||||
|
@ -520,11 +520,11 @@ return array(
|
||||
'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',
|
||||
'view_in_gateway' => 'View in :gateway',
|
||||
'use_card_on_file' => 'Use card on file',
|
||||
'edit_payment_details' => 'Edit payment details',
|
||||
'token_billing' => 'Save card details',
|
||||
'token_billing_secure' => 'The data is stored securely by :stripe_link',
|
||||
'token_billing_secure' => 'The data is stored securely by :link',
|
||||
|
||||
'support' => 'Support',
|
||||
'contact_information' => 'Contact information',
|
||||
|
@ -516,11 +516,11 @@ return array(
|
||||
'token_billing_3' => 'Aktivert - valgboks er vist og valgt',
|
||||
'token_billing_4' => 'Alltid',
|
||||
'token_billing_checkbox' => 'Lagre bankkort detaljer',
|
||||
'view_in_stripe' => 'Vis i Stripe',
|
||||
'view_in_gateway' => 'Vis i :gateway',
|
||||
'use_card_on_file' => 'Bruk lagret kort',
|
||||
'edit_payment_details' => 'Rediger betalings detaljer',
|
||||
'token_billing' => 'Lagre kort detaljer',
|
||||
'token_billing_secure' => 'Dataene er trygt lagret av :stripe_link',
|
||||
'token_billing_secure' => 'Dataene er trygt lagret av :link',
|
||||
|
||||
'support' => 'Brukerstøtte',
|
||||
'contact_information' => 'Kontaktinformasjon',
|
||||
|
@ -512,11 +512,11 @@ return array(
|
||||
'token_billing_3' => 'Opt-out - checkbox is getoond en geselecteerd',
|
||||
'token_billing_4' => 'Altijd',
|
||||
'token_billing_checkbox' => 'Sla carditcard gegevens op',
|
||||
'view_in_stripe' => 'In Stripe bekijken',
|
||||
'view_in_gateway' => 'In :gateway bekijken',
|
||||
'use_card_on_file' => 'Gebruik opgeslagen kaart',
|
||||
'edit_payment_details' => 'Betalingsdetails aanpassen',
|
||||
'token_billing' => 'Kaartgegevens opslaan',
|
||||
'token_billing_secure' => 'Kaartgegevens worden veilig opgeslagen door :stripe_link',
|
||||
'token_billing_secure' => 'Kaartgegevens worden veilig opgeslagen door :link',
|
||||
|
||||
'support' => 'Ondersteuning',
|
||||
'contact_information' => 'Contact informatie',
|
||||
|
@ -514,7 +514,7 @@ return array(
|
||||
'use_card_on_file' => 'Usar cartão no arquivo',
|
||||
'edit_payment_details' => 'Editar detalhes do pagamento',
|
||||
'token_billing' => 'Salvar detalhes do cartão',
|
||||
'token_billing_secure' => 'Dados armazenados com seguração por :stripe_link',
|
||||
'token_billing_secure' => 'Dados armazenados com seguração por :link',
|
||||
|
||||
'support' => 'Suporte',
|
||||
'contact_information' => 'Informações de Contato',
|
||||
|
@ -515,11 +515,11 @@ return array(
|
||||
'token_billing_3' => 'Opt-out - Checkbox visas och är förvald',
|
||||
'token_billing_4' => 'alltid',
|
||||
'token_billing_checkbox' => 'Spara betalkortsinformation',
|
||||
'view_in_stripe' => 'Visa i Stripe',
|
||||
'view_in_gateway' => 'Visa i :gateway',
|
||||
'use_card_on_file' => 'Använd kort på fil',
|
||||
'edit_payment_details' => 'Ändra betalningsdetaljer',
|
||||
'token_billing' => 'Spara kortinformation',
|
||||
'token_billing_secure' => 'Data sparas säkert med :stripe_link',
|
||||
'token_billing_secure' => 'Data sparas säkert med :link',
|
||||
|
||||
'support' => 'Support',
|
||||
'contact_information' => 'Kontaktinformation',
|
||||
|
@ -22,6 +22,11 @@
|
||||
{!! Former::populateField('show_address', intval($accountGateway->show_address)) !!}
|
||||
{!! Former::populateField('update_address', intval($accountGateway->update_address)) !!}
|
||||
{!! Former::populateField('publishable_key', $accountGateway->getPublishableStripeKey() ? str_repeat('*', strlen($accountGateway->getPublishableStripeKey())) : '') !!}
|
||||
{!! Former::populateField('enable_ach', $accountGateway->getAchEnabled() ? '1' : null) !!}
|
||||
{!! Former::populateField('enable_paypal', $accountGateway->getPayPalEnabled() ? '1' : null) !!}
|
||||
{!! Former::populateField('plaid_client_id', $accountGateway->getPlaidClientId() ? str_repeat('*', strlen($accountGateway->getPlaidClientId())) : '') !!}
|
||||
{!! Former::populateField('plaid_secret', $accountGateway->getPlaidSecret() ? str_repeat('*', strlen($accountGateway->getPlaidSecret())) : '') !!}
|
||||
{!! Former::populateField('plaid_public_key', $accountGateway->getPlaidPublicKey() ? str_repeat('*', strlen($accountGateway->getPlaidPublicKey())) : '') !!}
|
||||
|
||||
@if ($config)
|
||||
@foreach ($accountGateway->fields as $field => $junk)
|
||||
@ -45,7 +50,7 @@
|
||||
|
||||
{!! Former::select('gateway_id')
|
||||
->dataClass('gateway-dropdown')
|
||||
->addGroupClass('gateway-option')
|
||||
->addGroupClass('gateway-option gateway-choice')
|
||||
->fromQuery($selectGateways, 'name', 'id')
|
||||
->onchange('setFieldsShown()') !!}
|
||||
|
||||
@ -84,11 +89,44 @@
|
||||
|
||||
@if ($gateway->id == GATEWAY_STRIPE)
|
||||
{!! Former::text('publishable_key') !!}
|
||||
@endif
|
||||
|
||||
@if ($gateway->id == GATEWAY_STRIPE || $gateway->id == GATEWAY_BRAINTREE)
|
||||
{!! Former::select('token_billing_type_id')
|
||||
->options($tokenBillingOptions)
|
||||
->help(trans('texts.token_billing_help')) !!}
|
||||
@endif
|
||||
|
||||
@if ($gateway->id == GATEWAY_STRIPE)
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-4 col-sm-4">{{ trans('texts.webhook_url') }}</label>
|
||||
<div class="col-lg-8 col-sm-8 help-block">
|
||||
<input type="text" class="form-control" onfocus="$(this).select()" readonly value="{{ URL::to('/paymenthook/'.$account->account_key.'/'.GATEWAY_STRIPE) }}">
|
||||
<div class="help-block"><strong>{!! trans('texts.stripe_webhook_help', [
|
||||
'link'=>'<a href="https://dashboard.stripe.com/account/webhooks" target="_blank">'.trans('texts.stripe_webhook_help_link_text').'</a>'
|
||||
]) !!}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($gateway->id == GATEWAY_BRAINTREE)
|
||||
@if ($account->getGatewayByType(PAYMENT_TYPE_PAYPAL))
|
||||
{!! Former::checkbox('enable_paypal')
|
||||
->label(trans('texts.paypal'))
|
||||
->text(trans('texts.braintree_enable_paypal'))
|
||||
->value(null)
|
||||
->disabled(true)
|
||||
->help(trans('texts.braintree_paypal_disabled_help')) !!}
|
||||
@else
|
||||
{!! Former::checkbox('enable_paypal')
|
||||
->label(trans('texts.paypal'))
|
||||
->help(trans('texts.braintree_paypal_help', [
|
||||
'link'=>'<a href="https://articles.braintreepayments.com/guides/paypal/setup-guide" target="_blank">'.
|
||||
trans('texts.braintree_paypal_help_link_text').'</a>'
|
||||
]))
|
||||
->text(trans('texts.braintree_enable_paypal')) !!}
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@endforeach
|
||||
@ -108,6 +146,33 @@
|
||||
->class('creditcard-types')
|
||||
->addGroupClass('gateway-option')
|
||||
!!}
|
||||
<div class="stripe-ach">
|
||||
@if ($account->getGatewayByType(PAYMENT_TYPE_DIRECT_DEBIT))
|
||||
{!! Former::checkbox('enable_ach')
|
||||
->label(trans('texts.ach'))
|
||||
->text(trans('texts.enable_ach'))
|
||||
->value(null)
|
||||
->disabled(true)
|
||||
->help(trans('texts.stripe_ach_disabled')) !!}
|
||||
@else
|
||||
{!! Former::checkbox('enable_ach')
|
||||
->label(trans('texts.ach'))
|
||||
->text(trans('texts.enable_ach'))
|
||||
->help(trans('texts.stripe_ach_help')) !!}
|
||||
<div class="stripe-ach-options">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-8 col-sm-offset-4">
|
||||
<h4>{{trans('texts.plaid')}}</h4>
|
||||
<div class="help-block">{{trans('texts.plaid_optional')}}</div>
|
||||
</div>
|
||||
</div>
|
||||
{!! Former::text('plaid_client_id')->label(trans('texts.client_id')) !!}
|
||||
{!! Former::text('plaid_secret')->label(trans('texts.secret')) !!}
|
||||
{!! Former::text('plaid_public_key')->label(trans('texts.public_key'))
|
||||
->help(trans('texts.plaid_environment_help')) !!}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -126,9 +191,11 @@
|
||||
var val = $('#payment_type_id').val();
|
||||
if (val == 'PAYMENT_TYPE_CREDIT_CARD') {
|
||||
$('.gateway-option').show();
|
||||
$('.stripe-ach').hide();
|
||||
setFieldsShown();
|
||||
} else {
|
||||
$('.gateway-option').hide();
|
||||
$('.stripe-ach').hide();
|
||||
|
||||
if (val == 'PAYMENT_TYPE_PAYPAL') {
|
||||
setFieldsShown({{ GATEWAY_PAYPAL_EXPRESS }});
|
||||
@ -136,6 +203,10 @@
|
||||
setFieldsShown({{ GATEWAY_DWOLLA }});
|
||||
} else if (val == 'PAYMENT_TYPE_DIRECT_DEBIT') {
|
||||
setFieldsShown({{ GATEWAY_GOCARDLESS }});
|
||||
} else if (val == 'PAYMENT_TYPE_STRIPE') {
|
||||
$('.gateway-option:not(.gateway-choice)').show();
|
||||
$('.stripe-ach').show();
|
||||
setFieldsShown({{ GATEWAY_STRIPE }});
|
||||
} else {
|
||||
setFieldsShown({{ GATEWAY_BITPAY }});
|
||||
}
|
||||
@ -169,14 +240,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
function enablePlaidSettings() {
|
||||
var visible = $('#enable_ach').is(':checked');
|
||||
$('.stripe-ach-options').toggle(visible);
|
||||
}
|
||||
|
||||
$(function() {
|
||||
setPaymentType();
|
||||
enablePlaidSettings();
|
||||
@if ($accountGateway)
|
||||
$('.payment-type-option').hide();
|
||||
@endif
|
||||
|
||||
$('#show_address').change(enableUpdateAddress);
|
||||
enableUpdateAddress();
|
||||
|
||||
$('#enable_ach').change(enablePlaidSettings)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
@ -69,7 +69,7 @@
|
||||
{{ Former::populateField('remember', 'true') }}
|
||||
|
||||
<div class="modal-header">
|
||||
@if (!isset($hideLogo) || !$hideLogo)
|
||||
@if (!isset($account) || !$account->hasFeature(FEATURE_WHITE_LABEL))
|
||||
<a href="{{ NINJA_WEB_URL }}" target="_blank">
|
||||
<img src="{{ asset('images/icon-login.png') }}" />
|
||||
<h4>Invoice Ninja | {{ trans('texts.account_login') }}</h4>
|
||||
|
@ -54,7 +54,7 @@
|
||||
|
||||
{!! Former::open('client/recover_password')->addClass('form-signin') !!}
|
||||
<div class="modal-header">
|
||||
@if (!isset($hideLogo) || !$hideLogo)
|
||||
@if (!isset($account) || !$account->hasFeature(FEATURE_WHITE_LABEL))
|
||||
<a href="{{ NINJA_WEB_URL }}" target="_blank">
|
||||
<img src="{{ asset('images/icon-login.png') }}" />
|
||||
<h4>Invoice Ninja | {{ trans('texts.password_recovery') }}</h4>
|
||||
|
@ -58,7 +58,7 @@
|
||||
)) !!}
|
||||
|
||||
<div class="modal-header">
|
||||
@if (!isset($hideLogo) || !$hideLogo)
|
||||
@if (!isset($account) || !$account->hasFeature(FEATURE_WHITE_LABEL))
|
||||
<a href="{{ NINJA_WEB_URL }}" target="_blank">
|
||||
<img src="{{ asset('images/icon-login.png') }}" />
|
||||
<h4>Invoice Ninja | {{ trans('texts.set_password') }}</h4>
|
||||
|
@ -39,7 +39,7 @@
|
||||
</div>
|
||||
|
||||
@if ($gatewayLink)
|
||||
{!! Button::normal(trans('texts.view_in_stripe'))->asLinkTo($gatewayLink)->withAttributes(['target' => '_blank']) !!}
|
||||
{!! Button::normal(trans('texts.view_in_gateway', ['gateway'=>$gateway->gateway->name]))->asLinkTo($gatewayLink)->withAttributes(['target' => '_blank']) !!}
|
||||
@endif
|
||||
|
||||
@if ($client->trashed())
|
||||
@ -290,8 +290,10 @@
|
||||
trans('texts.invoice'),
|
||||
trans('texts.transaction_reference'),
|
||||
trans('texts.method'),
|
||||
trans('texts.source'),
|
||||
trans('texts.payment_amount'),
|
||||
trans('texts.payment_date'))
|
||||
trans('texts.payment_date'),
|
||||
trans('texts.status'))
|
||||
->setUrl(url('api/payments/' . $client->public_id))
|
||||
->setCustomValues('entityType', 'payments')
|
||||
->setOptions('sPaginationType', 'bootstrap')
|
||||
|
26
resources/views/emails/invoice_payment_failed_html.blade.php
Normal file
@ -0,0 +1,26 @@
|
||||
@extends('emails.master_user')
|
||||
|
||||
@section('markup')
|
||||
@if ($account->enable_email_markup)
|
||||
@include('emails.partials.user_view_action')
|
||||
@endif
|
||||
@stop
|
||||
|
||||
@section('body')
|
||||
<div>
|
||||
{{ trans('texts.email_salutation', ['name' => $userName]) }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ trans("texts.notification_invoice_payment_failed", ['amount' => $paymentAmount, 'client' => $clientName, 'invoice' => $invoiceNumber]) }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ $payment->gateway_error }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ trans('texts.email_signature') }} <br/>
|
||||
{{ trans('texts.email_from') }}
|
||||
</div>
|
||||
@stop
|
@ -0,0 +1,8 @@
|
||||
{!! trans('texts.email_salutation', ['name' => $userName]) !!}
|
||||
|
||||
{!! trans("texts.notification_invoice_payment_failed", ['amount' => $paymentAmount, 'client' => $clientName, 'invoice' => $invoiceNumber]) !!}
|
||||
|
||||
{!! $payment->gateway_error !!}
|
||||
|
||||
{!! trans('texts.email_signature') !!}
|
||||
{!! trans('texts.email_from') !!}
|
@ -126,7 +126,6 @@
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-md-4-left">
|
||||
<div class="well">
|
||||
@ -162,7 +161,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!empty($account->getTokenGatewayId()))
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<h3>{{ trans('texts.payment_methods') }}</h3>
|
||||
@include('payments.paymentmethods_list')
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div style="min-height: 550px">
|
||||
{!! Datatable::table()
|
||||
->addColumn(
|
||||
|
@ -164,12 +164,33 @@
|
||||
->label(trans("texts.{$entityType}_number_short"))
|
||||
->data_bind("value: invoice_number, valueUpdate: 'afterkeydown'") !!}
|
||||
</span>
|
||||
@if($account->getTokenGatewayId())
|
||||
<span data-bind="visible: is_recurring()" style="display: none">
|
||||
{!! Former::checkbox('auto_bill')
|
||||
->label(trans('texts.auto_bill'))
|
||||
->text(trans('texts.enable_with_stripe'))
|
||||
->data_bind("checked: auto_bill, valueUpdate: 'afterkeydown'") !!}
|
||||
<div data-bind="visible: !(auto_bill() == 1 && client_enable_auto_bill()) && !(auto_bill() == 2 && !client_enable_auto_bill())" style="display: none">
|
||||
{!! Former::select('auto_bill')
|
||||
->data_bind("value: auto_bill, valueUpdate: 'afterkeydown', event:{change:function(){if(auto_bill()==1)client_enable_auto_bill(0);if(auto_bill()==2)client_enable_auto_bill(1)}}")
|
||||
->options([
|
||||
0 => trans('texts.off'),
|
||||
1 => trans('texts.opt_in'),
|
||||
2 => trans('texts.opt_out'),
|
||||
3 => trans('texts.always'),
|
||||
]) !!}
|
||||
</div>
|
||||
<input type="hidden" name="client_enable_auto_bill" data-bind="attr: { value: client_enable_auto_bill() }" />
|
||||
<div class="form-group" data-bind="visible: auto_bill() == 1 && client_enable_auto_bill()">
|
||||
<div class="col-sm-4 control-label">{{trans('texts.auto_bill')}}</div>
|
||||
<div class="col-sm-8" style="padding-top:10px;padding-bottom:9px">
|
||||
{{trans('texts.opted_in')}} - <a href="#" data-bind="click:function(){client_enable_auto_bill(false)}">({{trans('texts.disable')}})</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" data-bind="visible: auto_bill() == 2 && !client_enable_auto_bill()">
|
||||
<div class="col-sm-4 control-label">{{trans('texts.auto_bill')}}</div>
|
||||
<div class="col-sm-8" style="padding-top:10px;padding-bottom:9px">
|
||||
{{trans('texts.opted_out')}} - <a href="#" data-bind="click:function(){client_enable_auto_bill(true)}">({{trans('texts.enable')}})</a>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
@endif
|
||||
{!! Former::text('po_number')->label(trans('texts.po_number_short'))->data_bind("value: po_number, valueUpdate: 'afterkeydown'") !!}
|
||||
{!! Former::text('discount')->data_bind("value: discount, valueUpdate: 'afterkeydown'")
|
||||
->addGroupClass('discount-group')->type('number')->min('0')->step('any')->append(
|
||||
|
@ -188,7 +188,8 @@ function InvoiceModel(data) {
|
||||
self.tax_rate2 = ko.observable();
|
||||
self.is_recurring = ko.observable(0);
|
||||
self.is_quote = ko.observable({{ $entityType == ENTITY_QUOTE ? '1' : '0' }});
|
||||
self.auto_bill = ko.observable();
|
||||
self.auto_bill = ko.observable(0);
|
||||
self.client_enable_auto_bill = ko.observable(false);
|
||||
self.invoice_status_id = ko.observable(0);
|
||||
self.invoice_items = ko.observableArray();
|
||||
self.documents = ko.observableArray();
|
||||
|
@ -14,7 +14,52 @@
|
||||
body {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.dropdown-menu li a{
|
||||
overflow:hidden;
|
||||
margin-top:5px;
|
||||
margin-bottom:5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@if (!empty($braintreeClientToken))
|
||||
<script type="text/javascript" src="https://js.braintreegateway.com/js/braintree-2.23.0.min.js"></script>
|
||||
<script type="text/javascript" >
|
||||
$(function() {
|
||||
var paypalLink = $('.dropdown-menu a[href$="/braintree_paypal"]'),
|
||||
paypalUrl = paypalLink.attr('href'),
|
||||
checkout;
|
||||
paypalLink.parent().attr('id', 'paypal-container');
|
||||
braintree.setup("{{ $braintreeClientToken }}", "custom", {
|
||||
onReady: function (integration) {
|
||||
checkout = integration;
|
||||
$('.dropdown-menu a[href$="#braintree_paypal"]').each(function(){
|
||||
var el=$(this);
|
||||
el.attr('href', el.attr('href').replace('#braintree_paypal','?device_data='+encodeURIComponent(integration.deviceData)))
|
||||
})
|
||||
},
|
||||
paypal: {
|
||||
container: "paypal-container",
|
||||
singleUse: false,
|
||||
enableShippingAddress: false,
|
||||
enableBillingAddress: false,
|
||||
headless: true,
|
||||
locale: "{{$invoice->client->language?$invoice->client->language->locale:$invoice->account->language->locale}}"
|
||||
},
|
||||
dataCollector: {
|
||||
paypal: true
|
||||
},
|
||||
onPaymentMethodReceived: function (obj) {
|
||||
window.location.href = paypalUrl + '/' + encodeURIComponent(obj.nonce) + "?details=" + encodeURIComponent(JSON.stringify(obj.details))
|
||||
}
|
||||
});
|
||||
paypalLink.click(function(e){
|
||||
e.preventDefault();
|
||||
checkout.paypal.initAuthFlow();
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@endif
|
||||
@stop
|
||||
|
||||
@section('content')
|
||||
|
@ -54,6 +54,40 @@
|
||||
->setOptions('aaSorting', [[isset($sortCol) ? $sortCol : '1', 'desc']])
|
||||
->render('datatable') !!}
|
||||
|
||||
@if ($entityType == ENTITY_PAYMENT)
|
||||
<div class="modal fade" id="paymentRefundModal" tabindex="-1" role="dialog" aria-labelledby="paymentRefundModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" style="min-width:150px">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title" id="paymentRefundModalLabel">{{ trans('texts.refund_payment') }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<label for="refundAmount" class="col-sm-offset-2 col-sm-2 control-label">{{ trans('texts.amount') }}</label>
|
||||
<div class="col-sm-4">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" id="refundCurrencySymbol"></span>
|
||||
<input type="number" class="form-control" id="refundAmount" name="amount" step="0.01" min="0.01" placeholder="{{ trans('texts.amount') }}">
|
||||
</div>
|
||||
<div class="help-block">{{ trans('texts.refund_max') }} <span id="refundMax"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer" style="margin-top: 0px">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.cancel') }}</button>
|
||||
<button type="button" class="btn btn-primary" id="completeRefundButton">{{ trans('texts.refund') }}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{!! Former::close() !!}
|
||||
|
||||
<script type="text/javascript">
|
||||
@ -103,6 +137,22 @@
|
||||
submitForm('invoice');
|
||||
}
|
||||
|
||||
@if ($entityType == ENTITY_PAYMENT)
|
||||
var paymentId = null;
|
||||
function showRefundModal(id, amount, formatted, symbol){
|
||||
paymentId = id;
|
||||
$('#refundCurrencySymbol').text(symbol);
|
||||
$('#refundMax').text(formatted);
|
||||
$('#refundAmount').val(amount).attr('max', amount);
|
||||
$('#paymentRefundModal').modal('show');
|
||||
}
|
||||
|
||||
function handleRefundClicked(){
|
||||
$('#public_id').val(paymentId);
|
||||
submitForm('refund');
|
||||
}
|
||||
@endif
|
||||
|
||||
function setTrashVisible() {
|
||||
var checked = $('#trashed').is(':checked');
|
||||
var url = '{{ URL::to('view_archive/' . $entityType) }}' + (checked ? '/true' : '/false');
|
||||
@ -157,6 +207,10 @@
|
||||
actionListHandler();
|
||||
}
|
||||
|
||||
@if ($entityType == ENTITY_PAYMENT)
|
||||
$('#completeRefundButton').click(handleRefundClicked)
|
||||
@endif
|
||||
|
||||
$('.archive, .invoice').prop('disabled', true);
|
||||
$('.archive:not(.dropdown-toggle)').click(function() {
|
||||
submitForm('archive');
|
||||
|
@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{App::getLocale()}}">
|
||||
<head>
|
||||
@if (isset($hideLogo) && $hideLogo)
|
||||
@if (isset($account) && $account instanceof \App\Models\Account && $account->hasFeature(FEATURE_WHITE_LABEL))
|
||||
<title>{{ trans('texts.client_portal') }}</title>
|
||||
@else
|
||||
<title>{{ isset($title) ? ($title . ' | Invoice Ninja') : ('Invoice Ninja | ' . trans('texts.app_title')) }}</title>
|
||||
|
696
resources/views/payments/add_paymentmethod.blade.php
Normal file
@ -0,0 +1,696 @@
|
||||
@extends('public.header')
|
||||
|
||||
@section('head')
|
||||
@parent
|
||||
@if (!empty($braintreeClientToken))
|
||||
<script type="text/javascript" src="https://js.braintreegateway.com/js/braintree-2.23.0.min.js"></script>
|
||||
<script type="text/javascript" >
|
||||
$(function() {
|
||||
braintree.setup("{{ $braintreeClientToken }}", "custom", {
|
||||
id: "payment-form",
|
||||
hostedFields: {
|
||||
number: {
|
||||
selector: "#card_number",
|
||||
placeholder: "{{ trans('texts.card_number') }}"
|
||||
},
|
||||
cvv: {
|
||||
selector: "#cvv",
|
||||
placeholder: "{{ trans('texts.cvv') }}"
|
||||
},
|
||||
expirationMonth: {
|
||||
selector: "#expiration_month",
|
||||
placeholder: "{{ trans('texts.expiration_month') }}"
|
||||
},
|
||||
expirationYear: {
|
||||
selector: "#expiration_year",
|
||||
placeholder: "{{ trans('texts.expiration_year') }}"
|
||||
},
|
||||
styles: {
|
||||
'input': {
|
||||
'font-family': {!! json_encode(Utils::getFromCache($account->getBodyFontId(), 'fonts')['css_stack']) !!},
|
||||
'font-weight': "{{ Utils::getFromCache($account->getBodyFontId(), 'fonts')['css_weight'] }}",
|
||||
'font-size': '16px'
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: function(e) {
|
||||
var $form = $('.payment-form');
|
||||
$form.find('button').prop('disabled', false);
|
||||
// Show the errors on the form
|
||||
if (e.details && e.details.invalidFieldKeys.length) {
|
||||
var invalidField = e.details.invalidFieldKeys[0];
|
||||
|
||||
if (invalidField == 'number') {
|
||||
$('#js-error-message').html('{{ trans('texts.invalid_card_number') }}').fadeIn();
|
||||
}
|
||||
else if (invalidField == 'expirationDate' || invalidField == 'expirationYear' || invalidField == 'expirationMonth') {
|
||||
$('#js-error-message').html('{{ trans('texts.invalid_expiry') }}').fadeIn();
|
||||
}
|
||||
else if (invalidField == 'cvv') {
|
||||
$('#js-error-message').html('{{ trans('texts.invalid_cvv') }}').fadeIn();
|
||||
}
|
||||
}
|
||||
else {
|
||||
$('#js-error-message').html(e.message).fadeIn();
|
||||
}
|
||||
}
|
||||
});
|
||||
$('.payment-form').submit(function(event) {
|
||||
var $form = $(this);
|
||||
|
||||
// Disable the submit button to prevent repeated clicks
|
||||
$form.find('button').prop('disabled', true);
|
||||
$('#js-error-message').hide();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@elseif (isset($accountGateway) && $accountGateway->getPublishableStripeKey())
|
||||
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
|
||||
<script type="text/javascript">
|
||||
Stripe.setPublishableKey('{{ $accountGateway->getPublishableStripeKey() }}');
|
||||
$(function() {
|
||||
$('.payment-form').submit(function(event) {
|
||||
if($('[name=plaidAccountId]').length)return;
|
||||
|
||||
var $form = $(this);
|
||||
|
||||
var data = {
|
||||
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
|
||||
account_holder_name: $('#account_holder_name').val(),
|
||||
account_holder_type: $('[name=account_holder_type]:checked').val(),
|
||||
currency: $("#currency").val(),
|
||||
country: $("#country").val(),
|
||||
routing_number: $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''),
|
||||
account_number: $('#account_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')
|
||||
@else
|
||||
name: $('#first_name').val() + ' ' + $('#last_name').val(),
|
||||
address_line1: $('#address1').val(),
|
||||
address_line2: $('#address2').val(),
|
||||
address_city: $('#city').val(),
|
||||
address_state: $('#state').val(),
|
||||
address_zip: $('#postal_code').val(),
|
||||
address_country: $("#country_id option:selected").text(),
|
||||
number: $('#card_number').val(),
|
||||
cvc: $('#cvv').val(),
|
||||
exp_month: $('#expiration_month').val(),
|
||||
exp_year: $('#expiration_year').val()
|
||||
@endif
|
||||
};
|
||||
|
||||
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
|
||||
// Validate the account details
|
||||
if (!data.account_holder_type) {
|
||||
$('#js-error-message').html('{{ trans('texts.missing_account_holder_type') }}').fadeIn();
|
||||
return false;
|
||||
}
|
||||
if (!data.account_holder_name) {
|
||||
$('#js-error-message').html('{{ trans('texts.missing_account_holder_name') }}').fadeIn();
|
||||
return false;
|
||||
}
|
||||
if (!data.routing_number || !Stripe.bankAccount.validateRoutingNumber(data.routing_number, data.country)) {
|
||||
$('#js-error-message').html('{{ trans('texts.invalid_routing_number') }}').fadeIn();
|
||||
return false;
|
||||
}
|
||||
if (data.account_number != $('#confirm_account_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')) {
|
||||
$('#js-error-message').html('{{ trans('texts.account_number_mismatch') }}').fadeIn();
|
||||
return false;
|
||||
}
|
||||
if (!data.account_number || !Stripe.bankAccount.validateAccountNumber(data.account_number, data.country)) {
|
||||
$('#js-error-message').html('{{ trans('texts.invalid_account_number') }}').fadeIn();
|
||||
return false;
|
||||
}
|
||||
@else
|
||||
// Validate the card details
|
||||
if (!Stripe.card.validateCardNumber(data.number)) {
|
||||
$('#js-error-message').html('{{ trans('texts.invalid_card_number') }}').fadeIn();
|
||||
return false;
|
||||
}
|
||||
if (!Stripe.card.validateExpiry(data.exp_month, data.exp_year)) {
|
||||
$('#js-error-message').html('{{ trans('texts.invalid_expiry') }}').fadeIn();
|
||||
return false;
|
||||
}
|
||||
if (!Stripe.card.validateCVC(data.cvc)) {
|
||||
$('#js-error-message').html('{{ trans('texts.invalid_cvv') }}').fadeIn();
|
||||
return false;
|
||||
}
|
||||
@endif
|
||||
|
||||
// Disable the submit button to prevent repeated clicks
|
||||
$form.find('button').prop('disabled', true);
|
||||
$('#js-error-message').hide();
|
||||
|
||||
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
|
||||
Stripe.bankAccount.createToken(data, stripeResponseHandler);
|
||||
@else
|
||||
Stripe.card.createToken(data, stripeResponseHandler);
|
||||
@endif
|
||||
|
||||
// Prevent the form from submitting with the default action
|
||||
return false;
|
||||
});
|
||||
|
||||
@if($accountGateway->getPlaidEnabled())
|
||||
var plaidHandler = Plaid.create({
|
||||
selectAccount: true,
|
||||
env: '{{ $accountGateway->getPlaidEnvironment() }}',
|
||||
clientName: {!! json_encode($account->getDisplayName()) !!},
|
||||
key: '{{ $accountGateway->getPlaidPublicKey() }}',
|
||||
product: 'auth',
|
||||
onSuccess: plaidSuccessHandler,
|
||||
onExit : function(){$('#secured_by_plaid').hide()}
|
||||
});
|
||||
|
||||
$('#plaid_link_button').click(function(){plaidHandler.open();$('#secured_by_plaid').fadeIn()});
|
||||
$('#plaid_unlink').click(function(e){
|
||||
e.preventDefault();
|
||||
$('#manual_container').fadeIn();
|
||||
$('#plaid_linked').hide();
|
||||
$('#plaid_link_button').show();
|
||||
$('#pay_now_button').hide();
|
||||
$('#add_account_button').show();
|
||||
$('[name=plaidPublicToken]').remove();
|
||||
$('[name=plaidAccountId]').remove();
|
||||
$('[name=account_holder_type],#account_holder_name').attr('required','required');
|
||||
})
|
||||
@endif
|
||||
});
|
||||
|
||||
function stripeResponseHandler(status, response) {
|
||||
var $form = $('.payment-form');
|
||||
|
||||
if (response.error) {
|
||||
// Show the errors on the form
|
||||
var error = response.error.message;
|
||||
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
|
||||
if(response.error.param == 'bank_account[country]') {
|
||||
error = "{{trans('texts.country_not_supported')}}";
|
||||
}
|
||||
@endif
|
||||
$form.find('button').prop('disabled', false);
|
||||
$('#js-error-message').html(error).fadeIn();
|
||||
} else {
|
||||
// response contains id and card, which contains additional card details
|
||||
var token = response.id;
|
||||
// Insert the token into the form so it gets submitted to the server
|
||||
$form.append($('<input type="hidden" name="stripeToken"/>').val(token));
|
||||
// and submit
|
||||
$form.get(0).submit();
|
||||
}
|
||||
};
|
||||
|
||||
function plaidSuccessHandler(public_token, metadata) {
|
||||
$('#secured_by_plaid').hide()
|
||||
var $form = $('.payment-form');
|
||||
|
||||
$form.append($('<input type="hidden" name="plaidPublicToken"/>').val(public_token));
|
||||
$form.append($('<input type="hidden" name="plaidAccountId"/>').val(metadata.account_id));
|
||||
$('#plaid_linked_status').text('{{ trans('texts.plaid_linked_status') }}'.replace(':bank', metadata.institution.name));
|
||||
$('#manual_container').fadeOut();
|
||||
$('#plaid_linked').show();
|
||||
$('#plaid_link_button').hide();
|
||||
$('[name=account_holder_type],#account_holder_name').removeAttr('required');
|
||||
|
||||
|
||||
var payNowBtn = $('#pay_now_button');
|
||||
if(payNowBtn.length) {
|
||||
payNowBtn.show();
|
||||
$('#add_account_button').hide();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@else
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$('.payment-form').submit(function(event) {
|
||||
var $form = $(this);
|
||||
|
||||
// Disable the submit button to prevent repeated clicks
|
||||
$form.find('button').prop('disabled', true);
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endif
|
||||
|
||||
@stop
|
||||
|
||||
@section('content')
|
||||
|
||||
@include('payments.payment_css')
|
||||
|
||||
|
||||
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
|
||||
{!! Former::open($url)
|
||||
->autocomplete('on')
|
||||
->addClass('payment-form')
|
||||
->id('payment-form')
|
||||
->rules(array(
|
||||
'first_name' => 'required',
|
||||
'last_name' => 'required',
|
||||
'account_number' => 'required',
|
||||
'routing_number' => 'required',
|
||||
'account_holder_name' => 'required',
|
||||
'account_holder_type' => 'required',
|
||||
'authorize_ach' => 'required',
|
||||
)) !!}
|
||||
@else
|
||||
{!! Former::vertical_open($url)
|
||||
->autocomplete('on')
|
||||
->addClass('payment-form')
|
||||
->id('payment-form')
|
||||
->rules(array(
|
||||
'first_name' => 'required',
|
||||
'last_name' => 'required',
|
||||
'card_number' => 'required',
|
||||
'expiration_month' => 'required',
|
||||
'expiration_year' => 'required',
|
||||
'cvv' => 'required',
|
||||
'address1' => 'required',
|
||||
'city' => 'required',
|
||||
'state' => 'required',
|
||||
'postal_code' => 'required',
|
||||
'country_id' => 'required',
|
||||
'phone' => 'required',
|
||||
'email' => 'required|email'
|
||||
)) !!}
|
||||
@endif
|
||||
|
||||
@if ($client)
|
||||
{{ Former::populate($client) }}
|
||||
{{ Former::populateField('first_name', $contact->first_name) }}
|
||||
{{ Former::populateField('last_name', $contact->last_name) }}
|
||||
{{ Former::populateField('email', $contact->email) }}
|
||||
@if (!$client->country_id && $client->account->country_id)
|
||||
{{ Former::populateField('country_id', $client->account->country_id) }}
|
||||
{{ Former::populateField('country', $client->account->country->iso_3166_2) }}
|
||||
@endif
|
||||
@if (!$client->currency_id && $client->account->currency_id)
|
||||
{{ Former::populateField('currency_id', $client->account->currency_id) }}
|
||||
{{ Former::populateField('currency', $client->account->currency->code) }}
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if (Utils::isNinjaDev())
|
||||
{{ Former::populateField('first_name', 'Test') }}
|
||||
{{ Former::populateField('last_name', 'Test') }}
|
||||
{{ Former::populateField('address1', '350 5th Ave') }}
|
||||
{{ Former::populateField('city', 'New York') }}
|
||||
{{ Former::populateField('state', 'NY') }}
|
||||
{{ Former::populateField('postal_code', '10118') }}
|
||||
{{ Former::populateField('country_id', 840) }}
|
||||
@endif
|
||||
|
||||
|
||||
<div class="container">
|
||||
<p> </p>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<header>
|
||||
@if ($client && isset($invoiceNumber))
|
||||
<h2>{{ $client->getDisplayName() }}</h2>
|
||||
<h3>{{ trans('texts.invoice') . ' ' . $invoiceNumber }}<span>| {{ trans('texts.amount_due') }}: <em>{{ $account->formatMoney($amount, $client, true) }}</em></span></h3>
|
||||
@elseif ($paymentTitle)
|
||||
<h2>{{ $paymentTitle }}
|
||||
@if(isset($paymentSubtitle))
|
||||
<br/><small>{{ $paymentSubtitle }}</small>
|
||||
@endif
|
||||
</h2>
|
||||
@endif
|
||||
</header>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
@if (Request::secure() || Utils::isNinjaDev())
|
||||
<div class="secure">
|
||||
<h3>{{ trans('texts.secure_payment') }}</h3>
|
||||
<div>{{ trans('texts.256_encryption') }}</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p> <br/> </p>
|
||||
<div>
|
||||
<div id="paypal-container"></div>
|
||||
@if($paymentType != PAYMENT_TYPE_STRIPE_ACH && $paymentType != PAYMENT_TYPE_BRAINTREE_PAYPAL)
|
||||
<h3>{{ trans('texts.contact_information') }}</h3>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('first_name')
|
||||
->placeholder(trans('texts.first_name'))
|
||||
->autocomplete('given-name')
|
||||
->label('') !!}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('last_name')
|
||||
->placeholder(trans('texts.last_name'))
|
||||
->autocomplete('family-name')
|
||||
->label('') !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="display:{{ isset($paymentTitle) ? 'block' : 'none' }}">
|
||||
<div class="col-md-12">
|
||||
{!! Former::text('email')
|
||||
->placeholder(trans('texts.email'))
|
||||
->autocomplete('email')
|
||||
->label('') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p> <br/> </p>
|
||||
|
||||
@if (!empty($showAddress))
|
||||
<h3>{{ trans('texts.billing_address') }} <span class="help">{{ trans('texts.payment_footer1') }}</span></h3>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('address1')
|
||||
->autocomplete('address-line1')
|
||||
->placeholder(trans('texts.address1'))
|
||||
->label('') !!}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('address2')
|
||||
->autocomplete('address-line2')
|
||||
->placeholder(trans('texts.address2'))
|
||||
->label('') !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('city')
|
||||
->autocomplete('address-level2')
|
||||
->placeholder(trans('texts.city'))
|
||||
->label('') !!}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('state')
|
||||
->autocomplete('address-level1')
|
||||
->placeholder(trans('texts.state'))
|
||||
->label('') !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('postal_code')
|
||||
->autocomplete('postal-code')
|
||||
->placeholder(trans('texts.postal_code'))
|
||||
->label('') !!}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{!! Former::select('country_id')
|
||||
->placeholder(trans('texts.country_id'))
|
||||
->fromQuery($countries, 'name', 'id')
|
||||
->addGroupClass('country-select')
|
||||
->label('') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p> <br/> </p>
|
||||
@endif
|
||||
|
||||
<h3>{{ trans('texts.billing_method') }}</h3>
|
||||
@endif
|
||||
|
||||
|
||||
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
|
||||
@if($accountGateway->getPlaidEnabled())
|
||||
<div id="plaid_container">
|
||||
<a class="btn btn-default btn-lg" id="plaid_link_button">
|
||||
<img src="{{ URL::to('images/plaid-logo.svg') }}">
|
||||
<img src="{{ URL::to('images/plaid-logowhite.svg') }}" class="hoverimg">
|
||||
{{ trans('texts.link_with_plaid') }}
|
||||
</a>
|
||||
<div id="plaid_linked">
|
||||
<div id="plaid_linked_status"></div>
|
||||
<a href="#" id="plaid_unlink">{{ trans('texts.unlink') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div id="manual_container">
|
||||
@if($accountGateway->getPlaidEnabled())
|
||||
<div id="plaid_or"><span>{{ trans('texts.or') }}</span></div>
|
||||
<h4>{{ trans('texts.link_manually') }}</h4>
|
||||
@endif
|
||||
<p>{{ trans('texts.ach_verification_delay_help') }}</p>
|
||||
{!! Former::radios('account_holder_type')->radios(array(
|
||||
trans('texts.individual_account') => array('value' => 'individual'),
|
||||
trans('texts.company_account') => array('value' => 'company'),
|
||||
))->inline()->label(trans('texts.account_holder_type')); !!}
|
||||
{!! Former::text('account_holder_name')
|
||||
->label(trans('texts.account_holder_name')) !!}
|
||||
{!! Former::select('country')
|
||||
->label(trans('texts.country_id'))
|
||||
->fromQuery($countries, 'name', 'iso_3166_2')
|
||||
->addGroupClass('country-select') !!}
|
||||
{!! Former::select('currency')
|
||||
->label(trans('texts.currency_id'))
|
||||
->fromQuery($currencies, 'name', 'code')
|
||||
->addGroupClass('currency-select') !!}
|
||||
{!! Former::text('')
|
||||
->id('routing_number')
|
||||
->label(trans('texts.routing_number')) !!}
|
||||
<div class="form-group" style="margin-top:-15px">
|
||||
<div class="col-md-8 col-md-offset-4">
|
||||
<div id="bank_name"></div>
|
||||
</div>
|
||||
</div>
|
||||
{!! Former::text('')
|
||||
->id('account_number')
|
||||
->label(trans('texts.account_number')) !!}
|
||||
{!! Former::text('')
|
||||
->id('confirm_account_number')
|
||||
->label(trans('texts.confirm_account_number')) !!}
|
||||
{!! Former::checkbox('authorize_ach')
|
||||
->text(trans('texts.ach_authorization', ['company'=>$account->getDisplayName()]))
|
||||
->label(' ') !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8 col-md-offset-4">
|
||||
{!! Button::success(strtoupper(trans('texts.add_account')))
|
||||
->submit()
|
||||
->withAttributes(['id'=>'add_account_button'])
|
||||
->large() !!}
|
||||
@if($accountGateway->getPlaidEnabled() && !empty($amount))
|
||||
{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) ))
|
||||
->submit()
|
||||
->withAttributes(['style'=>'display:none', 'id'=>'pay_now_button'])
|
||||
->large() !!}
|
||||
@endif
|
||||
</div>
|
||||
@elseif($paymentType == PAYMENT_TYPE_BRAINTREE_PAYPAL)
|
||||
<h3>{{ trans('texts.paypal') }}</h3>
|
||||
<div>{{$paypalDetails->firstName}} {{$paypalDetails->lastName}}</div>
|
||||
<div>{{$paypalDetails->email}}</div>
|
||||
<input type="hidden" name="payment_method_nonce" value="{{$sourceId}}">
|
||||
<input type="hidden" name="first_name" value="{{$paypalDetails->firstName}}">
|
||||
<input type="hidden" name="last_name" value="{{$paypalDetails->lastName}}">
|
||||
<p> </p>
|
||||
@if (isset($amount) && $client && $account->showTokenCheckbox())
|
||||
<input id="token_billing" type="checkbox" name="token_billing" {{ $account->selectTokenCheckbox() ? 'CHECKED' : '' }} value="1" style="margin-left:0px; vertical-align:top">
|
||||
<label for="token_billing" class="checkbox" style="display: inline;">{{ trans('texts.token_billing_braintree_paypal') }}</label>
|
||||
<span class="help-block" style="font-size:15px">
|
||||
{!! trans('texts.token_billing_secure', ['link' => link_to('https://www.braintreepayments.com/', 'Braintree', ['target' => '_blank'])]) !!}
|
||||
</span>
|
||||
@endif
|
||||
<p> </p>
|
||||
<center>
|
||||
@if(isset($amount))
|
||||
{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) ))
|
||||
->submit()
|
||||
->large() !!}
|
||||
@else
|
||||
{!! Button::success(strtoupper(trans('texts.add_credit_card') ))
|
||||
->submit()
|
||||
->large() !!}
|
||||
@endif
|
||||
</center>
|
||||
@else
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
@if (!empty($braintreeClientToken))
|
||||
<div id="card_number" class="braintree-hosted form-control"></div>
|
||||
@else
|
||||
{!! Former::text($accountGateway->getPublishableStripeKey() ? '' : 'card_number')
|
||||
->id('card_number')
|
||||
->placeholder(trans('texts.card_number'))
|
||||
->autocomplete('cc-number')
|
||||
->label('') !!}
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
@if (!empty($braintreeClientToken))
|
||||
<div id="cvv" class="braintree-hosted form-control"></div>
|
||||
@else
|
||||
{!! Former::text($accountGateway->getPublishableStripeKey() ? '' : 'cvv')
|
||||
->id('cvv')
|
||||
->placeholder(trans('texts.cvv'))
|
||||
->autocomplete('off')
|
||||
->label('') !!}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@if (!empty($braintreeClientToken))
|
||||
<div id="expiration_month" class="braintree-hosted form-control"></div>
|
||||
@else
|
||||
{!! Former::select($accountGateway->getPublishableStripeKey() ? '' : 'expiration_month')
|
||||
->id('expiration_month')
|
||||
->autocomplete('cc-exp-month')
|
||||
->placeholder(trans('texts.expiration_month'))
|
||||
->addOption('01 - January', '1')
|
||||
->addOption('02 - February', '2')
|
||||
->addOption('03 - March', '3')
|
||||
->addOption('04 - April', '4')
|
||||
->addOption('05 - May', '5')
|
||||
->addOption('06 - June', '6')
|
||||
->addOption('07 - July', '7')
|
||||
->addOption('08 - August', '8')
|
||||
->addOption('09 - September', '9')
|
||||
->addOption('10 - October', '10')
|
||||
->addOption('11 - November', '11')
|
||||
->addOption('12 - December', '12')->label('')
|
||||
!!}
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
@if (!empty($braintreeClientToken))
|
||||
<div id="expiration_year" class="braintree-hosted form-control"></div>
|
||||
@else
|
||||
{!! Former::select($accountGateway->getPublishableStripeKey() ? '' : 'expiration_year')
|
||||
->id('expiration_year')
|
||||
->autocomplete('cc-exp-year')
|
||||
->placeholder(trans('texts.expiration_year'))
|
||||
->addOption('2016', '2016')
|
||||
->addOption('2017', '2017')
|
||||
->addOption('2018', '2018')
|
||||
->addOption('2019', '2019')
|
||||
->addOption('2020', '2020')
|
||||
->addOption('2021', '2021')
|
||||
->addOption('2022', '2022')
|
||||
->addOption('2023', '2023')
|
||||
->addOption('2024', '2024')
|
||||
->addOption('2025', '2025')
|
||||
->addOption('2026', '2026')->label('')
|
||||
!!}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="padding-top:18px">
|
||||
<div class="col-md-5">
|
||||
@if (isset($amount) && $client && $account->showTokenCheckbox($storageGateway/* will contain gateway id */))
|
||||
<input id="token_billing" type="checkbox" name="token_billing" {{ $account->selectTokenCheckbox() ? 'CHECKED' : '' }} value="1" style="margin-left:0px; vertical-align:top">
|
||||
<label for="token_billing" class="checkbox" style="display: inline;">{{ trans('texts.token_billing') }}</label>
|
||||
<span class="help-block" style="font-size:15px">
|
||||
@if ($storageGateway == GATEWAY_STRIPE)
|
||||
{!! trans('texts.token_billing_secure', ['link' => link_to('https://stripe.com/', 'Stripe.com', ['target' => '_blank'])]) !!}
|
||||
@elseif ($storageGateway == GATEWAY_BRAINTREE)
|
||||
{!! trans('texts.token_billing_secure', ['link' => link_to('https://www.braintreepayments.com/', 'Braintree', ['target' => '_blank'])]) !!}
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="col-md-7">
|
||||
@if (isset($acceptedCreditCardTypes))
|
||||
<div class="pull-right">
|
||||
@foreach ($acceptedCreditCardTypes as $card)
|
||||
<img src="{{ $card['source'] }}" alt="{{ $card['alt'] }}" style="width: 70px; display: inline; margin-right: 6px;"/>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p> </p>
|
||||
<center>
|
||||
@if(isset($amount))
|
||||
{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) ))
|
||||
->submit()
|
||||
->large() !!}
|
||||
@else
|
||||
{!! Button::success(strtoupper(trans('texts.add_credit_card') ))
|
||||
->submit()
|
||||
->large() !!}
|
||||
@endif
|
||||
</center>
|
||||
<p> </p>
|
||||
@endif
|
||||
|
||||
<div id="js-error-message" style="display:none" class="alert alert-danger"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
|
||||
</div>
|
||||
|
||||
{!! Former::close() !!}
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$(function() {
|
||||
$('select').change(function() {
|
||||
$(this).css({color:'#444444'});
|
||||
});
|
||||
|
||||
$('#country_id').combobox();
|
||||
$('#country').combobox();
|
||||
$('#currency').combobox();
|
||||
$('#first_name').focus();
|
||||
|
||||
@if($paymentType == PAYMENT_TYPE_STRIPE_ACH)
|
||||
var routingNumberCache = {};
|
||||
$('#routing_number, #country').on('change keypress keyup keydown paste', function(){setTimeout(function () {
|
||||
var routingNumber = $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
|
||||
|
||||
if (routingNumber.length != 9 || $("#country").val() != 'US' || routingNumberCache[routingNumber] === false) {
|
||||
$('#bank_name').hide();
|
||||
} else if (routingNumberCache[routingNumber]) {
|
||||
$('#bank_name').empty().append(routingNumberCache[routingNumber]).show();
|
||||
} else {
|
||||
routingNumberCache[routingNumber] = false;
|
||||
$('#bank_name').hide();
|
||||
$.ajax({
|
||||
url:"{{ URL::to('/bank') }}/" + routingNumber,
|
||||
success:function(data) {
|
||||
var els = $().add(document.createTextNode(data.name + ", " + data.city + ", " + data.state));
|
||||
routingNumberCache[routingNumber] = els;
|
||||
|
||||
// Still the same number?
|
||||
if (routingNumber == $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')) {
|
||||
$('#bank_name').empty().append(els).show();
|
||||
}
|
||||
},
|
||||
error:function(xhr) {
|
||||
if (xhr.status == 404) {
|
||||
var els = $(document.createTextNode('{{trans('texts.unknown_bank')}}'));
|
||||
;
|
||||
routingNumberCache[routingNumber] = els;
|
||||
|
||||
// Still the same number?
|
||||
if (routingNumber == $('#routing_number').val().replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')) {
|
||||
$('#bank_name').empty().append(els).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},10)})
|
||||
@endif
|
||||
});
|
||||
|
||||
</script>
|
||||
@if (isset($accountGateway) && $accountGateway->getPlaidEnabled())
|
||||
<a href="https://plaid.com/products/auth/" target="_blank" style="display:none" id="secured_by_plaid"><img src="{{ URL::to('images/plaid-logowhite.svg') }}">{{ trans('texts.secured_by_plaid') }}</a>
|
||||
<script src="https://cdn.plaid.com/link/stable/link-initialize.js"></script>
|
||||
@endif
|
||||
@stop
|
@ -1,357 +0,0 @@
|
||||
@extends('public.header')
|
||||
|
||||
@section('head')
|
||||
@parent
|
||||
|
||||
@if ($accountGateway->getPublishableStripeKey())
|
||||
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
|
||||
<script type="text/javascript">
|
||||
Stripe.setPublishableKey('{{ $accountGateway->getPublishableStripeKey() }}');
|
||||
$(function() {
|
||||
$('.payment-form').submit(function(event) {
|
||||
var $form = $(this);
|
||||
|
||||
var data = {
|
||||
name: $('#first_name').val() + ' ' + $('#last_name').val(),
|
||||
email: $('#email').val(),
|
||||
address_line1: $('#address1').val(),
|
||||
address_line2: $('#address2').val(),
|
||||
address_city: $('#city').val(),
|
||||
address_state: $('#state').val(),
|
||||
address_zip: $('#postal_code').val(),
|
||||
address_country: $("#country_id option:selected").text(),
|
||||
number: $('#card_number').val(),
|
||||
exp_month: $('#expiration_month').val(),
|
||||
exp_year: $('#expiration_year').val()
|
||||
};
|
||||
|
||||
// allow space until there's a setting to disable
|
||||
if ($('#cvv').val() != ' ') {
|
||||
data.cvc = $('#cvv').val();
|
||||
}
|
||||
|
||||
// Validate the card details
|
||||
if (!Stripe.card.validateCardNumber(data.number)) {
|
||||
$('#js-error-message').html('{{ trans('texts.invalid_card_number') }}').fadeIn();
|
||||
return false;
|
||||
}
|
||||
if (!Stripe.card.validateExpiry(data.exp_month, data.exp_year)) {
|
||||
$('#js-error-message').html('{{ trans('texts.invalid_expiry') }}').fadeIn();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('cvc') && !Stripe.card.validateCVC(data.cvc)) {
|
||||
$('#js-error-message').html('{{ trans('texts.invalid_cvv') }}').fadeIn();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable the submit button to prevent repeated clicks
|
||||
$form.find('button').prop('disabled', true);
|
||||
$('#js-error-message').hide();
|
||||
|
||||
Stripe.card.createToken(data, stripeResponseHandler);
|
||||
|
||||
// Prevent the form from submitting with the default action
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
function stripeResponseHandler(status, response) {
|
||||
var $form = $('.payment-form');
|
||||
|
||||
if (response.error) {
|
||||
// Show the errors on the form
|
||||
var error = response.error.message;
|
||||
$form.find('button').prop('disabled', false);
|
||||
$('#js-error-message').html(error).fadeIn();
|
||||
} else {
|
||||
// response contains id and card, which contains additional card details
|
||||
var token = response.id;
|
||||
// Insert the token into the form so it gets submitted to the server
|
||||
$form.append($('<input type="hidden" name="stripeToken"/>').val(token));
|
||||
// and submit
|
||||
$form.get(0).submit();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@else
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
$('.payment-form').submit(function(event) {
|
||||
var $form = $(this);
|
||||
|
||||
// Disable the submit button to prevent repeated clicks
|
||||
$form.find('button').prop('disabled', true);
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endif
|
||||
|
||||
@stop
|
||||
|
||||
@section('content')
|
||||
|
||||
@include('payments.payment_css')
|
||||
|
||||
{!! Former::vertical_open($url)
|
||||
->autocomplete('on')
|
||||
->addClass('payment-form')
|
||||
->rules(array(
|
||||
'first_name' => 'required',
|
||||
'last_name' => 'required',
|
||||
'card_number' => 'required',
|
||||
'expiration_month' => 'required',
|
||||
'expiration_year' => 'required',
|
||||
'cvv' => 'required',
|
||||
'address1' => 'required',
|
||||
'city' => 'required',
|
||||
'state' => 'required',
|
||||
'postal_code' => 'required',
|
||||
'country_id' => 'required',
|
||||
'phone' => 'required',
|
||||
'email' => 'required|email'
|
||||
)) !!}
|
||||
|
||||
@if ($client)
|
||||
{{ Former::populate($client) }}
|
||||
{{ Former::populateField('first_name', $contact->first_name) }}
|
||||
{{ Former::populateField('last_name', $contact->last_name) }}
|
||||
{{ Former::populateField('email', $contact->email) }}
|
||||
@if (!$client->country_id && $client->account->country_id)
|
||||
{{ Former::populateField('country_id', $client->account->country_id) }}
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@if (Utils::isNinjaDev())
|
||||
{{ Former::populateField('first_name', 'Test') }}
|
||||
{{ Former::populateField('last_name', 'Test') }}
|
||||
{{ Former::populateField('address1', '350 5th Ave') }}
|
||||
{{ Former::populateField('city', 'New York') }}
|
||||
{{ Former::populateField('state', 'NY') }}
|
||||
{{ Former::populateField('postal_code', '10118') }}
|
||||
{{ Former::populateField('country_id', 840) }}
|
||||
@endif
|
||||
|
||||
|
||||
<div class="container">
|
||||
<p> </p>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<header>
|
||||
@if ($client)
|
||||
<h2>{{ $client->getDisplayName() }}</h2>
|
||||
<h3>{{ trans('texts.invoice') . ' ' . $invoiceNumber }}<span>| {{ trans('texts.amount_due') }}: <em>{{ $account->formatMoney($amount, $client, true) }}</em></span></h3>
|
||||
@elseif ($paymentTitle)
|
||||
<h2>{{ $paymentTitle }}<br/><small>{{ $paymentSubtitle }}</small></h2>
|
||||
@endif
|
||||
</header>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
@if (Request::secure() || Utils::isNinjaDev())
|
||||
<div class="secure">
|
||||
<h3>{{ trans('texts.secure_payment') }}</h3>
|
||||
<div>{{ trans('texts.256_encryption') }}</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p> <br/> </p>
|
||||
|
||||
<div>
|
||||
<h3>{{ trans('texts.contact_information') }}</h3>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('first_name')
|
||||
->placeholder(trans('texts.first_name'))
|
||||
->autocomplete('given-name')
|
||||
->label('') !!}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('last_name')
|
||||
->placeholder(trans('texts.last_name'))
|
||||
->autocomplete('family-name')
|
||||
->label('') !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="display:{{ isset($paymentTitle) ? 'block' : 'none' }}">
|
||||
<div class="col-md-12">
|
||||
{!! Former::text('email')
|
||||
->placeholder(trans('texts.email'))
|
||||
->autocomplete('email')
|
||||
->label('') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p> <br/> </p>
|
||||
|
||||
@if ($showAddress)
|
||||
<h3>{{ trans('texts.billing_address') }} <span class="help">{{ trans('texts.payment_footer1') }}</span></h3>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('address1')
|
||||
->autocomplete('address-line1')
|
||||
->placeholder(trans('texts.address1'))
|
||||
->label('') !!}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('address2')
|
||||
->autocomplete('address-line2')
|
||||
->placeholder(trans('texts.address2'))
|
||||
->label('') !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('city')
|
||||
->autocomplete('address-level2')
|
||||
->placeholder(trans('texts.city'))
|
||||
->label('') !!}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('state')
|
||||
->autocomplete('address-level1')
|
||||
->placeholder(trans('texts.state'))
|
||||
->label('') !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{!! Former::text('postal_code')
|
||||
->autocomplete('postal-code')
|
||||
->placeholder(trans('texts.postal_code'))
|
||||
->label('') !!}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{!! Former::select('country_id')
|
||||
->placeholder(trans('texts.country_id'))
|
||||
->fromQuery($countries, 'name', 'id')
|
||||
->addGroupClass('country-select')
|
||||
->label('') !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p> <br/> </p>
|
||||
@endif
|
||||
|
||||
<h3>{{ trans('texts.billing_method') }}</h3>
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
{!! Former::text($accountGateway->getPublishableStripeKey() ? '' : 'card_number')
|
||||
->id('card_number')
|
||||
->placeholder(trans('texts.card_number'))
|
||||
->autocomplete('cc-number')
|
||||
->label('') !!}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{!! Former::text($accountGateway->getPublishableStripeKey() ? '' : 'cvv')
|
||||
->id('cvv')
|
||||
->placeholder(trans('texts.cvv'))
|
||||
->autocomplete('off')
|
||||
->label('') !!}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
{!! Former::select($accountGateway->getPublishableStripeKey() ? '' : 'expiration_month')
|
||||
->id('expiration_month')
|
||||
->autocomplete('cc-exp-month')
|
||||
->placeholder(trans('texts.expiration_month'))
|
||||
->addOption('01 - January', '1')
|
||||
->addOption('02 - February', '2')
|
||||
->addOption('03 - March', '3')
|
||||
->addOption('04 - April', '4')
|
||||
->addOption('05 - May', '5')
|
||||
->addOption('06 - June', '6')
|
||||
->addOption('07 - July', '7')
|
||||
->addOption('08 - August', '8')
|
||||
->addOption('09 - September', '9')
|
||||
->addOption('10 - October', '10')
|
||||
->addOption('11 - November', '11')
|
||||
->addOption('12 - December', '12')->label('')
|
||||
!!}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{!! Former::select($accountGateway->getPublishableStripeKey() ? '' : 'expiration_year')
|
||||
->id('expiration_year')
|
||||
->autocomplete('cc-exp-year')
|
||||
->placeholder(trans('texts.expiration_year'))
|
||||
->addOption('2016', '2016')
|
||||
->addOption('2017', '2017')
|
||||
->addOption('2018', '2018')
|
||||
->addOption('2019', '2019')
|
||||
->addOption('2020', '2020')
|
||||
->addOption('2021', '2021')
|
||||
->addOption('2022', '2022')
|
||||
->addOption('2023', '2023')
|
||||
->addOption('2024', '2024')
|
||||
->addOption('2025', '2025')
|
||||
->addOption('2026', '2026')->label('')
|
||||
!!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row" style="padding-top:18px">
|
||||
<div class="col-md-5">
|
||||
@if ($client && $account->showTokenCheckbox())
|
||||
<input id="token_billing" type="checkbox" name="token_billing" {{ $account->selectTokenCheckbox() ? 'CHECKED' : '' }} value="1" style="margin-left:0px; vertical-align:top">
|
||||
<label for="token_billing" class="checkbox" style="display: inline;">{{ trans('texts.token_billing') }}</label>
|
||||
<span class="help-block" style="font-size:15px">{!! trans('texts.token_billing_secure', ['stripe_link' => link_to('https://stripe.com/', 'Stripe.com', ['target' => '_blank'])]) !!}</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="col-md-7">
|
||||
@if (isset($acceptedCreditCardTypes))
|
||||
<div class="pull-right">
|
||||
@foreach ($acceptedCreditCardTypes as $card)
|
||||
<img src="{{ $card['source'] }}" alt="{{ $card['alt'] }}" style="width: 70px; display: inline; margin-right: 6px;"/>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<p> </p>
|
||||
<center>
|
||||
{!! Button::success(strtoupper(trans('texts.pay_now') . ' - ' . $account->formatMoney($amount, $client, true) ))
|
||||
->submit()
|
||||
->large() !!}
|
||||
</center>
|
||||
<p> </p>
|
||||
|
||||
<div id="js-error-message" style="display:none" class="alert alert-danger"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
|
||||
</div>
|
||||
|
||||
{!! Former::close() !!}
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
$(function() {
|
||||
$('select').change(function() {
|
||||
$(this).css({color:'#444444'});
|
||||
});
|
||||
|
||||
$('#country_id').combobox();
|
||||
$('#first_name').focus();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@stop
|
@ -12,9 +12,14 @@ body {
|
||||
|
||||
.container input[type=text],
|
||||
.container input[type=email],
|
||||
.container select {
|
||||
.container select,
|
||||
.braintree-hosted {
|
||||
@if(!empty($account))
|
||||
{!! $account->getBodyFontCss() !!}
|
||||
@else
|
||||
font-weight: 300;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
@endif
|
||||
width: 100%;
|
||||
padding: 11px;
|
||||
color: #8c8c8c;
|
||||
@ -26,6 +31,12 @@ body {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.form-control.braintree-hosted-fields-focused{
|
||||
border-color: #66afe9;
|
||||
outline: 0;
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);
|
||||
}
|
||||
|
||||
div.col-md-3,
|
||||
div.col-md-5,
|
||||
div.col-md-6,
|
||||
@ -102,8 +113,6 @@ header h3 em {
|
||||
color: #eb8039;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.secure {
|
||||
text-align: right;
|
||||
float: right;
|
||||
@ -125,6 +134,84 @@ header h3 em {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#plaid_link_button img {
|
||||
height:30px;
|
||||
vertical-align:-7px;
|
||||
margin-right:5px;
|
||||
}
|
||||
|
||||
#plaid_link_button:hover img,
|
||||
#plaid_link_button .hoverimg{
|
||||
display:none;
|
||||
}
|
||||
|
||||
#plaid_link_button:hover .hoverimg{
|
||||
display:inline;
|
||||
}
|
||||
|
||||
#plaid_link_button {
|
||||
width:425px;
|
||||
border-color:#2A5A74;
|
||||
color:#2A5A74;
|
||||
}
|
||||
|
||||
#plaid_link_button:hover {
|
||||
width:425px;
|
||||
background-color:#2A5A74;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
#plaid_or,
|
||||
#plaid_container {
|
||||
text-align:center
|
||||
}
|
||||
|
||||
#plaid_or span{
|
||||
background:#fff;
|
||||
position:relative;
|
||||
bottom:-11px;
|
||||
font-size:125%;
|
||||
padding:0 10px;
|
||||
}
|
||||
|
||||
#plaid_or {
|
||||
border-bottom:1px solid #000;
|
||||
margin:10px 0 30px;
|
||||
}
|
||||
|
||||
#secured_by_plaid{
|
||||
position:fixed;
|
||||
z-index:999999999;
|
||||
bottom:5px;
|
||||
left:5px;
|
||||
color:#fff;
|
||||
border:1px solid #fff;
|
||||
padding:3px 7px 3px 3px;
|
||||
border-radius:3px;
|
||||
vertical-align:-5px;
|
||||
text-decoration: none!important;
|
||||
}
|
||||
#secured_by_plaid img{
|
||||
height:20px;
|
||||
margin-right:5px;
|
||||
}
|
||||
|
||||
#secured_by_plaid:hover{
|
||||
background-color:#2A5A74;
|
||||
}
|
||||
|
||||
#plaid_linked{
|
||||
margin:40px 0;
|
||||
display:none;
|
||||
}
|
||||
|
||||
#plaid_linked_status {
|
||||
margin-bottom:10px;
|
||||
font-size:150%;
|
||||
}
|
||||
|
||||
#bank_name {
|
||||
margin:5px 0 -5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
9
resources/views/payments/paymentmethods.blade.php
Normal file
@ -0,0 +1,9 @@
|
||||
@extends('public.header')
|
||||
|
||||
@section('content')
|
||||
<div class="container main-container">
|
||||
<h3>{{ $title }}</h3>
|
||||
@include('payments.paymentmethods_list')
|
||||
<p></p>
|
||||
</div>
|
||||
@stop
|
185
resources/views/payments/paymentmethods_list.blade.php
Normal file
@ -0,0 +1,185 @@
|
||||
<style type="text/css">
|
||||
.payment_method_img_container{
|
||||
width:37px;
|
||||
text-align: center;
|
||||
display:inline-block;
|
||||
margin-right:10px;
|
||||
}
|
||||
|
||||
.payment_method{
|
||||
margin:20px 0;
|
||||
}
|
||||
|
||||
.payment_method_number{
|
||||
margin-right:10px;
|
||||
width:65px;
|
||||
display:inline-block;
|
||||
}
|
||||
</style>
|
||||
@if (!empty($braintreeClientToken))
|
||||
<script type="text/javascript" src="https://js.braintreegateway.com/js/braintree-2.23.0.min.js"></script>
|
||||
<script type="text/javascript" >
|
||||
$(function() {
|
||||
var paypalLink = $('#add-paypal'),
|
||||
paypalUrl = paypalLink.attr('href'),
|
||||
checkout;
|
||||
braintree.setup("{{ $braintreeClientToken }}", "custom", {
|
||||
onReady: function (integration) {
|
||||
checkout = integration;
|
||||
},
|
||||
paypal: {
|
||||
container: "paypal-container",
|
||||
singleUse: false,
|
||||
enableShippingAddress: false,
|
||||
enableBillingAddress: false,
|
||||
headless: true,
|
||||
locale: "{{$client->language?$client->language->locale:$client->account->language->locale}}"
|
||||
},
|
||||
dataCollector: {
|
||||
paypal: true
|
||||
},
|
||||
onPaymentMethodReceived: function (obj) {
|
||||
window.location.href = paypalUrl + '/' + encodeURIComponent(obj.nonce)
|
||||
}
|
||||
});
|
||||
paypalLink.click(function(e){
|
||||
e.preventDefault();
|
||||
checkout.paypal.initAuthFlow();
|
||||
})
|
||||
});
|
||||
</script>
|
||||
@endif
|
||||
@if(!empty($paymentMethods))
|
||||
@foreach ($paymentMethods as $paymentMethod)
|
||||
<div class="payment_method">
|
||||
<span class="payment_method_img_container">
|
||||
<img height="22" src="{{URL::to('/images/credit_cards/'.str_replace(' ', '', strtolower($paymentMethod->payment_type->name).'.png'))}}" alt="{{trans("texts.card_" . str_replace(' ', '', strtolower($paymentMethod->payment_type->name)))}}">
|
||||
</span>
|
||||
@if(!empty($paymentMethod->last4))
|
||||
<span class="payment_method_number">•••••{{$paymentMethod->last4}}</span>
|
||||
@endif
|
||||
@if($paymentMethod->payment_type_id == PAYMENT_TYPE_ACH)
|
||||
@if($paymentMethod->bank())
|
||||
{{ $paymentMethod->bank()->name }}
|
||||
@endif
|
||||
@if($paymentMethod->status == PAYMENT_METHOD_STATUS_NEW)
|
||||
<a href="javasript::void" onclick="completeVerification('{{$paymentMethod->public_id}}','{{$paymentMethod->currency->symbol}}')">({{trans('texts.complete_verification')}})</a>
|
||||
@elseif($paymentMethod->status == PAYMENT_METHOD_STATUS_VERIFICATION_FAILED)
|
||||
({{trans('texts.verification_failed')}})
|
||||
@endif
|
||||
@elseif($paymentMethod->payment_type_id == PAYMENT_TYPE_ID_PAYPAL)
|
||||
{{ $paymentMethod->email }}
|
||||
@elseif($paymentMethod->expiration)
|
||||
{!! trans('texts.card_expiration', array('expires'=>Utils::fromSqlDate($paymentMethod->expiration, false)->format('m/y'))) !!}
|
||||
@endif
|
||||
@if($paymentMethod->id == $paymentMethod->account_gateway_token->default_payment_method_id)
|
||||
({{trans('texts.used_for_auto_bill')}})
|
||||
@elseif($paymentMethod->payment_type_id != PAYMENT_TYPE_ACH || $paymentMethod->status == PAYMENT_METHOD_STATUS_VERIFIED)
|
||||
<a href="#" onclick="setDefault('{{$paymentMethod->public_id}}')">({{trans('texts.use_for_auto_bill')}})</a>
|
||||
@endif
|
||||
<a href="javasript::void" class="payment_method_remove" onclick="removePaymentMethod('{{$paymentMethod->public_id}}')">×</a>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
<center>
|
||||
{!! Button::success(strtoupper(trans('texts.add_credit_card')))
|
||||
->asLinkTo(URL::to('/client/paymentmethods/add/'.($gateway->getPaymentType() == PAYMENT_TYPE_STRIPE ? 'stripe_credit_card' : 'credit_card'))) !!}
|
||||
@if($gateway->getACHEnabled())
|
||||
|
||||
{!! Button::success(strtoupper(trans('texts.add_bank_account')))
|
||||
->asLinkTo(URL::to('/client/paymentmethods/add/stripe_ach')) !!}
|
||||
@endif
|
||||
@if($gateway->getPayPalEnabled())
|
||||
|
||||
{!! Button::success(strtoupper(trans('texts.add_paypal_account')))
|
||||
->withAttributes(['id'=>'add-paypal'])
|
||||
->asLinkTo(URL::to('/client/paymentmethods/add/braintree_paypal')) !!}
|
||||
<div id="paypal-container"></div>
|
||||
@endif
|
||||
</center>
|
||||
|
||||
<div class="modal fade" id="completeVerificationModal" tabindex="-1" role="dialog" aria-labelledby="completeVerificationModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" style="min-width:150px">
|
||||
<div class="modal-content">
|
||||
{!! Former::open('/client/paymentmethods/verify') !!}
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title" id="completeVerificationModalLabel">{{ trans('texts.complete_verification') }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div style="display:none">
|
||||
{!! Former::text('source_id') !!}
|
||||
</div>
|
||||
<p>{{ trans('texts.bank_account_verification_help') }}</p>
|
||||
<div class="form-group">
|
||||
<label for="verification1" class="control-label col-sm-5">{{ trans('texts.verification_amount1') }}</label>
|
||||
<div class="col-sm-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><span class="payment_method_currenct_symbol"></span>0.</span>
|
||||
<input type="number" min="0" max="99" required class="form-control" id="verification1" name="verification1">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="verification2" class="control-label col-sm-5">{{ trans('texts.verification_amount2') }}</label>
|
||||
<div class="col-sm-3">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><span class="payment_method_currenct_symbol"></span>0.</span>
|
||||
<input type="number" min="0" max="99" required class="form-control" id="verification2" name="verification2">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" style="margin-top: 0px">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.cancel') }}</button>
|
||||
<button type="submit" class="btn btn-primary">{{ trans('texts.complete_verification') }}</button>
|
||||
</div>
|
||||
{!! Former::close() !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="removePaymentMethodModal" tabindex="-1" role="dialog" aria-labelledby="removePaymentMethodModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" style="min-width:150px">
|
||||
<div class="modal-content">
|
||||
{!! Former::open()->id('removeForm') !!}
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title" id="removePaymentMethodModalLabel">{{ trans('texts.remove_payment_method') }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>{{ trans('texts.confirm_remove_payment_method') }}</p>
|
||||
</div>
|
||||
<div class="modal-footer" style="margin-top: 0px">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.cancel') }}</button>
|
||||
<button type="submit" class="btn btn-primary">{{ trans('texts.remove') }}</button>
|
||||
</div>
|
||||
{!! Former::close() !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!! Former::open(URL::to('/client/paymentmethods/default'))->id('defaultSourceForm') !!}
|
||||
<input type="hidden" name="source" id="default_id">
|
||||
{!! Former::close() !!}
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
function completeVerification(sourceId, currencySymbol) {
|
||||
$('#source_id').val(sourceId);
|
||||
$('.payment_method_currenct_symbol').text(currencySymbol);
|
||||
$('#completeVerificationModal').modal('show');
|
||||
}
|
||||
|
||||
function removePaymentMethod(sourceId) {
|
||||
$('#removeForm').attr('action', '{{ URL::to('/client/paymentmethods/%s/remove') }}'.replace('%s', sourceId))
|
||||
$('#removePaymentMethodModal').modal('show');
|
||||
}
|
||||
|
||||
function setDefault(sourceId) {
|
||||
$('#default_id').val(sourceId);
|
||||
$('#defaultSourceForm').submit()
|
||||
}
|
||||
</script>
|
@ -7,9 +7,7 @@
|
||||
<link href="//fonts.googleapis.com/css?family=Roboto:400,700,900,100" rel="stylesheet" type="text/css">
|
||||
@endif
|
||||
<link href="{{ asset('css/built.public.css') }}?no_cache={{ NINJA_VERSION }}" rel="stylesheet" type="text/css"/>
|
||||
@if (!empty($clientViewCSS))
|
||||
<style type="text/css">{!! $clientViewCSS !!}</style>
|
||||
@endif
|
||||
<style type="text/css">{!! isset($account)?$account->clientViewCSS():'' !!}</style>
|
||||
@stop
|
||||
|
||||
@section('body')
|
||||
@ -68,15 +66,15 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
@if (!isset($hideLogo) || !$hideLogo)
|
||||
@if (!isset($account) || !$account->hasFeature(FEATURE_WHITE_LABEL))
|
||||
{{-- Per our license, please do not remove or modify this link. --}}
|
||||
<a class="navbar-brand" href="{{ URL::to(NINJA_WEB_URL) }}" target="_blank"><img src="{{ asset('images/invoiceninja-logo.png') }}" style="height:20px"></a>
|
||||
@endif
|
||||
</div>
|
||||
<div id="navbar" class="collapse navbar-collapse">
|
||||
@if (!isset($hideHeader) || !$hideHeader)
|
||||
@if (!isset($account) || $account->isNinjaAccount() || $account->enable_client_portal)
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
@if (!isset($hideDashboard) || !$hideDashboard)
|
||||
@if (!isset($account) || $account->enable_client_portal_dashboard)
|
||||
<li {{ Request::is('*client/dashboard') ? 'class="active"' : '' }}>
|
||||
{!! link_to('/client/dashboard', trans('texts.dashboard') ) !!}
|
||||
</li>
|
||||
@ -87,11 +85,16 @@
|
||||
<li {{ Request::is('*client/invoices') ? 'class="active"' : '' }}>
|
||||
{!! link_to('/client/invoices', trans('texts.invoices') ) !!}
|
||||
</li>
|
||||
@if (!empty($showDocuments))
|
||||
@if (isset($account) && $account->hasFeature(FEATURE_DOCUMENTS))
|
||||
<li {{ Request::is('*client/documents') ? 'class="active"' : '' }}>
|
||||
{!! link_to('/client/documents', trans('texts.documents') ) !!}
|
||||
</li>
|
||||
@endif
|
||||
@if (isset($account) && $account->getTokenGatewayId() && !$account->enable_client_portal_dashboard)
|
||||
<li {{ Request::is('*client/paymentmethods') ? 'class="active"' : '' }}>
|
||||
{!! link_to('/client/paymentmethods', trans('texts.payment_methods') ) !!}
|
||||
</li>
|
||||
@endif
|
||||
<li {{ Request::is('*client/payments') ? 'class="active"' : '' }}>
|
||||
{!! link_to('/client/payments', trans('texts.payments') ) !!}
|
||||
</li>
|
||||
@ -123,7 +126,7 @@
|
||||
<footer id="footer" role="contentinfo">
|
||||
<div class="top">
|
||||
<div class="wrap">
|
||||
@if (!isset($hideLogo) || !$hideLogo)
|
||||
@if (!isset($account) || !$account->hasFeature(FEATURE_WHITE_LABEL))
|
||||
<div id="footer-menu" class="menu-wrap">
|
||||
<ul id="menu-footer-menu" class="menu">
|
||||
<li id="menu-item-31" class="menu-item-31">
|
||||
@ -146,7 +149,7 @@
|
||||
|
||||
<div class="bottom">
|
||||
<div class="wrap">
|
||||
@if (!isset($hideLogo) || !$hideLogo)
|
||||
@if (!isset($account) || !$account->hasFeature(FEATURE_WHITE_LABEL))
|
||||
<div class="copy">Copyright ©{{ date('Y') }} <a href="{{ NINJA_WEB_URL }}" target="_blank">Invoice Ninja</a>. All rights reserved.</div>
|
||||
@endif
|
||||
</div><!-- .wrap -->
|
||||
|
@ -35,6 +35,11 @@
|
||||
</div>
|
||||
-->
|
||||
|
||||
@if($entityType == ENTITY_INVOICE && $account->getTokenGatewayId() && $client->hasAutoBillConfigurableInvoices())
|
||||
<div class="pull-right" style="margin-top:5px">
|
||||
{!! Button::info(trans("texts.manage_auto_bill"))->asLinkTo(URL::to('/client/invoices/recurring'))->appendIcon(Icon::create('repeat')) !!}
|
||||
</div>
|
||||
@endif
|
||||
<h3>{{ $title }}</h3>
|
||||
|
||||
{!! Datatable::table()
|
||||
@ -45,6 +50,22 @@
|
||||
|
||||
</div>
|
||||
|
||||
@if($entityType == ENTITY_RECURRING_INVOICE)
|
||||
{!! Former::open(URL::to('/client/invoices/auto_bill'))->id('auto_bill_form') !!}
|
||||
<input type="hidden" name="public_id" id="auto_bill_public_id">
|
||||
<input type="hidden" name="enable" id="auto_bill_enable">
|
||||
{!! Former::close() !!}
|
||||
|
||||
<script type="text/javascript">
|
||||
function setAutoBill(publicId, enable){
|
||||
$('#auto_bill_public_id').val(publicId);
|
||||
$('#auto_bill_enable').val(enable?'1':'0');
|
||||
$('#auto_bill_form').submit();
|
||||
}
|
||||
</script>
|
||||
@endif
|
||||
|
||||
|
||||
<p> </p>
|
||||
|
||||
@stop
|