mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Added autolink to emails and fixed checkout.com integration
This commit is contained in:
parent
6e5237b7dd
commit
cbe2b2905e
@ -20,12 +20,9 @@ use App\Models\Product;
|
||||
use App\Models\TaxRate;
|
||||
use App\Models\InvoiceDesign;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Gateway;
|
||||
use App\Ninja\Mailers\ContactMailer as Mailer;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Ninja\Repositories\ClientRepository;
|
||||
use App\Events\InvoiceInvitationWasViewed;
|
||||
use App\Events\QuoteInvitationWasViewed;
|
||||
use App\Services\InvoiceService;
|
||||
use App\Services\RecurringInvoiceService;
|
||||
use App\Http\Requests\SaveInvoiceWithClientRequest;
|
||||
@ -86,119 +83,6 @@ class InvoiceController extends BaseController
|
||||
return $this->recurringInvoiceService->getDatatable($accountId, $clientPublicId, ENTITY_RECURRING_INVOICE, $search);
|
||||
}
|
||||
|
||||
public function view($invitationKey)
|
||||
{
|
||||
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
|
||||
return response()->view('error', [
|
||||
'error' => trans('texts.invoice_not_found'),
|
||||
'hideHeader' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$invoice = $invitation->invoice;
|
||||
$client = $invoice->client;
|
||||
$account = $invoice->account;
|
||||
|
||||
if (!$account->checkSubdomain(Request::server('HTTP_HOST'))) {
|
||||
return response()->view('error', [
|
||||
'error' => trans('texts.invoice_not_found'),
|
||||
'hideHeader' => true,
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!Input::has('phantomjs') && !Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
|
||||
if ($invoice->is_quote) {
|
||||
event(new QuoteInvitationWasViewed($invoice, $invitation));
|
||||
} else {
|
||||
event(new InvoiceInvitationWasViewed($invoice, $invitation));
|
||||
}
|
||||
}
|
||||
|
||||
Session::put($invitationKey, true); // track this invitation has been seen
|
||||
Session::put('invitation_key', $invitationKey); // track current invitation
|
||||
|
||||
$account->loadLocalizationSettings($client);
|
||||
|
||||
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
|
||||
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
|
||||
$invoice->is_pro = $account->isPro();
|
||||
$invoice->invoice_fonts = $account->getFontsData();
|
||||
|
||||
if ($invoice->invoice_design_id == CUSTOM_DESIGN) {
|
||||
$invoice->invoice_design->javascript = $account->custom_design;
|
||||
} else {
|
||||
$invoice->invoice_design->javascript = $invoice->invoice_design->pdfmake;
|
||||
}
|
||||
$contact = $invitation->contact; $contact->setVisible([
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone',
|
||||
]);
|
||||
|
||||
$paymentTypes = $this->getPaymentTypes($client, $invitation);
|
||||
$paymentURL = '';
|
||||
if (count($paymentTypes)) {
|
||||
$paymentURL = $paymentTypes[0]['url'];
|
||||
if (!$account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) {
|
||||
$paymentURL = URL::to($paymentURL);
|
||||
}
|
||||
}
|
||||
|
||||
$showApprove = $invoice->quote_invoice_id ? false : true;
|
||||
if ($invoice->due_date) {
|
||||
$showApprove = time() < strtotime($invoice->due_date);
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'showApprove' => $showApprove,
|
||||
'showBreadcrumbs' => false,
|
||||
'hideLogo' => $account->isWhiteLabel(),
|
||||
'hideHeader' => $account->isNinjaAccount(),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'invoice' => $invoice->hidePrivateFields(),
|
||||
'invitation' => $invitation,
|
||||
'invoiceLabels' => $account->getInvoiceLabels(),
|
||||
'contact' => $contact,
|
||||
'paymentTypes' => $paymentTypes,
|
||||
'paymentURL' => $paymentURL,
|
||||
'phantomjs' => Input::has('phantomjs'),
|
||||
);
|
||||
|
||||
return View::make('invoices.view', $data);
|
||||
}
|
||||
|
||||
private function getPaymentTypes($client, $invitation)
|
||||
{
|
||||
$paymentTypes = [];
|
||||
$account = $client->account;
|
||||
|
||||
if ($client->getGatewayToken()) {
|
||||
$paymentTypes[] = [
|
||||
'url' => URL::to("payment/{$invitation->invitation_key}/token"), 'label' => trans('texts.use_card_on_file')
|
||||
];
|
||||
}
|
||||
foreach(Gateway::$paymentTypes as $type) {
|
||||
if ($account->getGatewayByType($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")';
|
||||
}
|
||||
$paymentTypes[] = [
|
||||
'url' => $url, 'label' => trans('texts.'.strtolower($type))
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $paymentTypes;
|
||||
}
|
||||
|
||||
public function edit($publicId, $clone = false)
|
||||
{
|
||||
$account = Auth::user()->account;
|
||||
|
@ -532,7 +532,8 @@ class PaymentController extends BaseController
|
||||
|
||||
try {
|
||||
if (method_exists($gateway, 'completePurchase')
|
||||
&& !$accountGateway->isGateway(GATEWAY_TWO_CHECKOUT)) {
|
||||
&& !$accountGateway->isGateway(GATEWAY_TWO_CHECKOUT)
|
||||
&& !$accountGateway->isGateway(GATEWAY_CHECKOUT_COM)) {
|
||||
$details = $this->paymentService->getPaymentDetails($invitation, $accountGateway);
|
||||
$response = $this->paymentService->completePurchase($gateway, $accountGateway, $details, $token);
|
||||
$ref = $response->getTransactionReference() ?: $token;
|
||||
|
@ -1,25 +1,162 @@
|
||||
<?php namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use View;
|
||||
use DB;
|
||||
use URL;
|
||||
use Input;
|
||||
use Utils;
|
||||
use Request;
|
||||
use Session;
|
||||
use Datatable;
|
||||
use App\Models\Gateway;
|
||||
use App\Models\Invitation;
|
||||
use App\Ninja\Repositories\InvoiceRepository;
|
||||
use App\Ninja\Repositories\PaymentRepository;
|
||||
use App\Ninja\Repositories\ActivityRepository;
|
||||
use App\Events\InvoiceInvitationWasViewed;
|
||||
use App\Events\QuoteInvitationWasViewed;
|
||||
use App\Services\PaymentService;
|
||||
|
||||
class PublicClientController extends BaseController
|
||||
{
|
||||
private $invoiceRepo;
|
||||
private $paymentRepo;
|
||||
|
||||
public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo)
|
||||
public function __construct(InvoiceRepository $invoiceRepo, PaymentRepository $paymentRepo, ActivityRepository $activityRepo, PaymentService $paymentService)
|
||||
{
|
||||
$this->invoiceRepo = $invoiceRepo;
|
||||
$this->paymentRepo = $paymentRepo;
|
||||
$this->activityRepo = $activityRepo;
|
||||
$this->paymentService = $paymentService;
|
||||
}
|
||||
|
||||
public function view($invitationKey)
|
||||
{
|
||||
if (!$invitation = $this->invoiceRepo->findInvoiceByInvitation($invitationKey)) {
|
||||
return response()->view('error', [
|
||||
'error' => trans('texts.invoice_not_found'),
|
||||
'hideHeader' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$invoice = $invitation->invoice;
|
||||
$client = $invoice->client;
|
||||
$account = $invoice->account;
|
||||
|
||||
if (!$account->checkSubdomain(Request::server('HTTP_HOST'))) {
|
||||
return response()->view('error', [
|
||||
'error' => trans('texts.invoice_not_found'),
|
||||
'hideHeader' => true,
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!Input::has('phantomjs') && !Session::has($invitationKey) && (!Auth::check() || Auth::user()->account_id != $invoice->account_id)) {
|
||||
if ($invoice->is_quote) {
|
||||
event(new QuoteInvitationWasViewed($invoice, $invitation));
|
||||
} else {
|
||||
event(new InvoiceInvitationWasViewed($invoice, $invitation));
|
||||
}
|
||||
}
|
||||
|
||||
Session::put($invitationKey, true); // track this invitation has been seen
|
||||
Session::put('invitation_key', $invitationKey); // track current invitation
|
||||
|
||||
$account->loadLocalizationSettings($client);
|
||||
|
||||
$invoice->invoice_date = Utils::fromSqlDate($invoice->invoice_date);
|
||||
$invoice->due_date = Utils::fromSqlDate($invoice->due_date);
|
||||
$invoice->is_pro = $account->isPro();
|
||||
$invoice->invoice_fonts = $account->getFontsData();
|
||||
|
||||
if ($invoice->invoice_design_id == CUSTOM_DESIGN) {
|
||||
$invoice->invoice_design->javascript = $account->custom_design;
|
||||
} else {
|
||||
$invoice->invoice_design->javascript = $invoice->invoice_design->pdfmake;
|
||||
}
|
||||
$contact = $invitation->contact;
|
||||
$contact->setVisible([
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'phone',
|
||||
]);
|
||||
|
||||
$paymentTypes = $this->getPaymentTypes($client, $invitation);
|
||||
$paymentURL = '';
|
||||
if (count($paymentTypes)) {
|
||||
$paymentURL = $paymentTypes[0]['url'];
|
||||
if (!$account->isGatewayConfigured(GATEWAY_PAYPAL_EXPRESS)) {
|
||||
$paymentURL = URL::to($paymentURL);
|
||||
}
|
||||
}
|
||||
|
||||
$showApprove = $invoice->quote_invoice_id ? false : true;
|
||||
if ($invoice->due_date) {
|
||||
$showApprove = time() < strtotime($invoice->due_date);
|
||||
}
|
||||
|
||||
// Checkout.com requires first getting a payment token
|
||||
$checkoutComToken = false;
|
||||
$checkoutComKey = false;
|
||||
if ($accountGateway = $account->getGatewayConfig(GATEWAY_CHECKOUT_COM)) {
|
||||
if ($checkoutComToken = $this->paymentService->getCheckoutComToken($invitation)) {
|
||||
$checkoutComKey = $accountGateway->getConfigField('publicApiKey');
|
||||
$invitation->transaction_reference = $checkoutComToken;
|
||||
$invitation->save();
|
||||
}
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'account' => $account,
|
||||
'showApprove' => $showApprove,
|
||||
'showBreadcrumbs' => false,
|
||||
'hideLogo' => $account->isWhiteLabel(),
|
||||
'hideHeader' => $account->isNinjaAccount(),
|
||||
'clientViewCSS' => $account->clientViewCSS(),
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'invoice' => $invoice->hidePrivateFields(),
|
||||
'invitation' => $invitation,
|
||||
'invoiceLabels' => $account->getInvoiceLabels(),
|
||||
'contact' => $contact,
|
||||
'paymentTypes' => $paymentTypes,
|
||||
'paymentURL' => $paymentURL,
|
||||
'checkoutComToken' => $checkoutComToken,
|
||||
'checkoutComKey' => $checkoutComKey,
|
||||
'phantomjs' => Input::has('phantomjs'),
|
||||
);
|
||||
|
||||
return View::make('invoices.view', $data);
|
||||
}
|
||||
|
||||
private function getPaymentTypes($client, $invitation)
|
||||
{
|
||||
$paymentTypes = [];
|
||||
$account = $client->account;
|
||||
|
||||
if ($client->getGatewayToken()) {
|
||||
$paymentTypes[] = [
|
||||
'url' => URL::to("payment/{$invitation->invitation_key}/token"), 'label' => trans('texts.use_card_on_file')
|
||||
];
|
||||
}
|
||||
foreach(Gateway::$paymentTypes as $type) {
|
||||
if ($account->getGatewayByType($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")';
|
||||
}
|
||||
$paymentTypes[] = [
|
||||
'url' => $url, 'label' => trans('texts.'.strtolower($type))
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $paymentTypes;
|
||||
}
|
||||
|
||||
public function dashboard()
|
||||
|
@ -46,6 +46,11 @@ class Utils
|
||||
return file_exists(storage_path() . '/framework/down');
|
||||
}
|
||||
|
||||
public static function isCron()
|
||||
{
|
||||
return php_sapi_name() == 'cli';
|
||||
}
|
||||
|
||||
public static function isNinja()
|
||||
{
|
||||
return self::isNinjaProd() || self::isNinjaDev();
|
||||
|
335
app/Libraries/lib_autolink.php
Normal file
335
app/Libraries/lib_autolink.php
Normal file
@ -0,0 +1,335 @@
|
||||
<?php
|
||||
#
|
||||
# A PHP auto-linking library
|
||||
#
|
||||
# https://github.com/iamcal/lib_autolink
|
||||
#
|
||||
# By Cal Henderson <cal@iamcal.com>
|
||||
# This code is licensed under the MIT license
|
||||
#
|
||||
|
||||
####################################################################
|
||||
|
||||
#
|
||||
# These are global options. You can set them before calling the autolinking
|
||||
# functions to change the output.
|
||||
#
|
||||
|
||||
$GLOBALS['autolink_options'] = array(
|
||||
|
||||
# Should http:// be visibly stripped from the front
|
||||
# of URLs?
|
||||
'strip_protocols' => false,
|
||||
|
||||
);
|
||||
|
||||
####################################################################
|
||||
|
||||
function autolink($text, $limit=30, $tagfill='', $auto_title = true){
|
||||
|
||||
$text = autolink_do($text, '![a-z][a-z-]+://!i', $limit, $tagfill, $auto_title);
|
||||
$text = autolink_do($text, '!(mailto|skype):!i', $limit, $tagfill, $auto_title);
|
||||
$text = autolink_do($text, '!www\\.!i', $limit, $tagfill, $auto_title, 'http://');
|
||||
return $text;
|
||||
}
|
||||
|
||||
####################################################################
|
||||
|
||||
function autolink_do($text, $sub, $limit, $tagfill, $auto_title, $force_prefix=null){
|
||||
|
||||
$text_l = StrToLower($text);
|
||||
$cursor = 0;
|
||||
$loop = 1;
|
||||
$buffer = '';
|
||||
|
||||
while (($cursor < strlen($text)) && $loop){
|
||||
|
||||
$ok = 1;
|
||||
$matched = preg_match($sub, $text_l, $m, PREG_OFFSET_CAPTURE, $cursor);
|
||||
|
||||
if (!$matched){
|
||||
|
||||
$loop = 0;
|
||||
$ok = 0;
|
||||
|
||||
}else{
|
||||
|
||||
$pos = $m[0][1];
|
||||
$sub_len = strlen($m[0][0]);
|
||||
|
||||
$pre_hit = substr($text, $cursor, $pos-$cursor);
|
||||
$hit = substr($text, $pos, $sub_len);
|
||||
$pre = substr($text, 0, $pos);
|
||||
$post = substr($text, $pos + $sub_len);
|
||||
|
||||
$fail_text = $pre_hit.$hit;
|
||||
$fail_len = strlen($fail_text);
|
||||
|
||||
#
|
||||
# substring found - first check to see if we're inside a link tag already...
|
||||
#
|
||||
|
||||
$bits = preg_split("!</a>!i", $pre);
|
||||
$last_bit = array_pop($bits);
|
||||
if (preg_match("!<a\s!i", $last_bit)){
|
||||
|
||||
#echo "fail 1 at $cursor<br />\n";
|
||||
|
||||
$ok = 0;
|
||||
$cursor += $fail_len;
|
||||
$buffer .= $fail_text;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# looks like a nice spot to autolink from - check the pre
|
||||
# to see if there was whitespace before this match
|
||||
#
|
||||
|
||||
if ($ok){
|
||||
|
||||
if ($pre){
|
||||
if (!preg_match('![\s\(\[\{>]$!s', $pre)){
|
||||
|
||||
#echo "fail 2 at $cursor ($pre)<br />\n";
|
||||
|
||||
$ok = 0;
|
||||
$cursor += $fail_len;
|
||||
$buffer .= $fail_text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# we want to autolink here - find the extent of the url
|
||||
#
|
||||
|
||||
if ($ok){
|
||||
if (preg_match('/^([a-z0-9\-\.\/\-_%~!?=,:;&+*#@\(\)\$]+)/i', $post, $matches)){
|
||||
|
||||
$url = $hit.$matches[1];
|
||||
|
||||
$cursor += strlen($url) + strlen($pre_hit);
|
||||
$buffer .= $pre_hit;
|
||||
|
||||
$url = html_entity_decode($url);
|
||||
|
||||
|
||||
#
|
||||
# remove trailing punctuation from url
|
||||
#
|
||||
|
||||
while (preg_match('|[.,!;:?]$|', $url)){
|
||||
$url = substr($url, 0, strlen($url)-1);
|
||||
$cursor--;
|
||||
}
|
||||
foreach (array('()', '[]', '{}') as $pair){
|
||||
$o = substr($pair, 0, 1);
|
||||
$c = substr($pair, 1, 1);
|
||||
if (preg_match("!^(\\$c|^)[^\\$o]+\\$c$!", $url)){
|
||||
$url = substr($url, 0, strlen($url)-1);
|
||||
$cursor--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# nice-i-fy url here
|
||||
#
|
||||
|
||||
$link_url = $url;
|
||||
$display_url = $url;
|
||||
|
||||
if ($force_prefix) $link_url = $force_prefix.$link_url;
|
||||
|
||||
if ($GLOBALS['autolink_options']['strip_protocols']){
|
||||
if (preg_match('!^(http|https)://!i', $display_url, $m)){
|
||||
|
||||
$display_url = substr($display_url, strlen($m[1])+3);
|
||||
}
|
||||
}
|
||||
|
||||
$display_url = autolink_label($display_url, $limit);
|
||||
|
||||
|
||||
#
|
||||
# add the url
|
||||
#
|
||||
|
||||
$currentTagfill = $tagfill;
|
||||
if ($display_url != $link_url && !preg_match('@title=@msi',$currentTagfill) && $auto_title) {
|
||||
|
||||
$display_quoted = preg_quote($display_url, '!');
|
||||
|
||||
if (!preg_match("!^(http|https)://{$display_quoted}$!i", $link_url)){
|
||||
|
||||
$currentTagfill .= ' title="'.$link_url.'"';
|
||||
}
|
||||
}
|
||||
|
||||
$link_url_enc = HtmlSpecialChars($link_url);
|
||||
$display_url_enc = HtmlSpecialChars($display_url);
|
||||
|
||||
$buffer .= "<a href=\"{$link_url_enc}\"$currentTagfill>{$display_url_enc}</a>";
|
||||
|
||||
}else{
|
||||
#echo "fail 3 at $cursor<br />\n";
|
||||
|
||||
$ok = 0;
|
||||
$cursor += $fail_len;
|
||||
$buffer .= $fail_text;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#
|
||||
# add everything from the cursor to the end onto the buffer.
|
||||
#
|
||||
|
||||
$buffer .= substr($text, $cursor);
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
####################################################################
|
||||
|
||||
function autolink_label($text, $limit){
|
||||
|
||||
if (!$limit){ return $text; }
|
||||
|
||||
if (strlen($text) > $limit){
|
||||
return substr($text, 0, $limit-3).'...';
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
####################################################################
|
||||
|
||||
function autolink_email($text, $tagfill=''){
|
||||
|
||||
$atom = '[^()<>@,;:\\\\".\\[\\]\\x00-\\x20\\x7f]+'; # from RFC822
|
||||
|
||||
#die($atom);
|
||||
|
||||
$text_l = StrToLower($text);
|
||||
$cursor = 0;
|
||||
$loop = 1;
|
||||
$buffer = '';
|
||||
|
||||
while(($cursor < strlen($text)) && $loop){
|
||||
|
||||
#
|
||||
# find an '@' symbol
|
||||
#
|
||||
|
||||
$ok = 1;
|
||||
$pos = strpos($text_l, '@', $cursor);
|
||||
|
||||
if ($pos === false){
|
||||
|
||||
$loop = 0;
|
||||
$ok = 0;
|
||||
|
||||
}else{
|
||||
|
||||
$pre = substr($text, $cursor, $pos-$cursor);
|
||||
$hit = substr($text, $pos, 1);
|
||||
$post = substr($text, $pos + 1);
|
||||
|
||||
$fail_text = $pre.$hit;
|
||||
$fail_len = strlen($fail_text);
|
||||
|
||||
#die("$pre::$hit::$post::$fail_text");
|
||||
|
||||
#
|
||||
# substring found - first check to see if we're inside a link tag already...
|
||||
#
|
||||
|
||||
$bits = preg_split("!</a>!i", $pre);
|
||||
$last_bit = array_pop($bits);
|
||||
if (preg_match("!<a\s!i", $last_bit)){
|
||||
|
||||
#echo "fail 1 at $cursor<br />\n";
|
||||
|
||||
$ok = 0;
|
||||
$cursor += $fail_len;
|
||||
$buffer .= $fail_text;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# check backwards
|
||||
#
|
||||
|
||||
if ($ok){
|
||||
if (preg_match("!($atom(\.$atom)*)\$!", $pre, $matches)){
|
||||
|
||||
# move matched part of address into $hit
|
||||
|
||||
$len = strlen($matches[1]);
|
||||
$plen = strlen($pre);
|
||||
|
||||
$hit = substr($pre, $plen-$len).$hit;
|
||||
$pre = substr($pre, 0, $plen-$len);
|
||||
|
||||
}else{
|
||||
|
||||
#echo "fail 2 at $cursor ($pre)<br />\n";
|
||||
|
||||
$ok = 0;
|
||||
$cursor += $fail_len;
|
||||
$buffer .= $fail_text;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# check forwards
|
||||
#
|
||||
|
||||
if ($ok){
|
||||
if (preg_match("!^($atom(\.$atom)*)!", $post, $matches)){
|
||||
|
||||
# move matched part of address into $hit
|
||||
|
||||
$len = strlen($matches[1]);
|
||||
|
||||
$hit .= substr($post, 0, $len);
|
||||
$post = substr($post, $len);
|
||||
|
||||
}else{
|
||||
#echo "fail 3 at $cursor ($post)<br />\n";
|
||||
|
||||
$ok = 0;
|
||||
$cursor += $fail_len;
|
||||
$buffer .= $fail_text;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# commit
|
||||
#
|
||||
|
||||
if ($ok) {
|
||||
|
||||
$cursor += strlen($pre) + strlen($hit);
|
||||
$buffer .= $pre;
|
||||
$buffer .= "<a href=\"mailto:$hit\"$tagfill>$hit</a>";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#
|
||||
# add everything from the cursor to the end onto the buffer.
|
||||
#
|
||||
|
||||
$buffer .= substr($text, $cursor);
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
####################################################################
|
||||
|
||||
?>
|
@ -260,7 +260,8 @@ class ContactMailer extends Mailer
|
||||
}
|
||||
|
||||
$str = str_replace(array_keys($variables), array_values($variables), $template);
|
||||
|
||||
$str = autolink($str, 100);
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ class AppServiceProvider extends ServiceProvider {
|
||||
});
|
||||
|
||||
HTML::macro('image_data', function($imagePath) {
|
||||
return 'data:image/jpeg;base64,' . base64_encode(file_get_contents(public_path().'/'.$imagePath));
|
||||
return 'data:image/jpeg;base64,' . base64_encode(file_get_contents($imagePath));
|
||||
});
|
||||
|
||||
HTML::macro('flatButton', function($label, $color) {
|
||||
|
@ -184,6 +184,30 @@ class PaymentService extends BaseService
|
||||
return $cardReference;
|
||||
}
|
||||
|
||||
public function getCheckoutComToken($invitation)
|
||||
{
|
||||
$token = false;
|
||||
$invoice = $invitation->invoice;
|
||||
$client = $invoice->client;
|
||||
$account = $invoice->account;
|
||||
|
||||
$accountGateway = $account->getGatewayConfig(GATEWAY_CHECKOUT_COM);
|
||||
$gateway = $this->createGateway($accountGateway);
|
||||
|
||||
$response = $gateway->purchase([
|
||||
'amount' => $invoice->getRequestedAmount(),
|
||||
'currency' => $client->currency ? $client->currency->code : ($account->currency ? $account->currency->code : 'USD')
|
||||
])->send();
|
||||
|
||||
if ($response->isRedirect()) {
|
||||
$token = $response->getTransactionReference();
|
||||
}
|
||||
|
||||
Session::set($invitation->id . 'payment_type', PAYMENT_TYPE_CREDIT_CARD);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
public function createPayment($invitation, $accountGateway, $ref, $payerId = null)
|
||||
{
|
||||
$invoice = $invitation->invoice;
|
||||
|
@ -86,7 +86,10 @@
|
||||
],
|
||||
"psr-4": {
|
||||
"App\\": "app/"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"app/Libraries/lib_autolink.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"classmap": [
|
||||
|
@ -110,7 +110,7 @@ class PaymentLibrariesSeeder extends Seeder
|
||||
['name' => 'Argentine Peso', 'code' => 'ARS', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => '.', 'decimal_separator' => ','],
|
||||
['name' => 'Bangladeshi Taka', 'code' => 'BDT', 'symbol' => 'Tk', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['name' => 'United Arab Emirates Dirham', 'code' => 'AED', 'symbol' => 'DH ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['name' => 'Hong Kong Dollar', 'code' => 'HKD', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['name' => 'Hong Kong Dollar', 'code' => 'HKD', 'symbol' => 'HKD ', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['name' => 'Indonesian Rupiah', 'code' => 'IDR', 'symbol' => 'Rp', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['name' => 'Mexican Peso', 'code' => 'MXN', 'symbol' => '$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
['name' => 'Egyptian Pound', 'code' => 'EGP', 'symbol' => 'E£', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'],
|
||||
|
@ -5,8 +5,6 @@
|
||||
# Invoice Ninja
|
||||
### [https://www.invoiceninja.com](https://www.invoiceninja.com)
|
||||
|
||||
We're starting to work on our expenses, vendors and receipts module. If you'd like to help us design it please send us an email to join the discussion.
|
||||
|
||||
[](https://gitter.im/hillelcoren/invoice-ninja?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
### Referral Program
|
||||
|
@ -3,7 +3,7 @@
|
||||
<a href="{{ $account->website }}" style="color: #19BB40; text-decoration: underline;">
|
||||
@endif
|
||||
|
||||
<img src="{{ $message->embed($account->getLogoPath()) }}" style="max-height:50px; max-width:140px; margin-left: 33px;" />
|
||||
<img src="{{ $message->embed($account->getAbsoluteLogoPath()) }}" style="max-height:50px; max-width:140px; margin-left: 33px;" />
|
||||
|
||||
@if ($account->website)
|
||||
</a>
|
||||
|
@ -21,24 +21,28 @@
|
||||
|
||||
<div class="container">
|
||||
|
||||
<p> </p>
|
||||
<div class="pull-right" style="text-align:right">
|
||||
@if ($invoice->is_quote)
|
||||
{!! Button::normal(trans('texts.download_pdf'))->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!}
|
||||
@if ($showApprove)
|
||||
{!! Button::success(trans('texts.approve'))->asLinkTo(URL::to('/approve/' . $invitation->invitation_key))->large() !!}
|
||||
@endif
|
||||
@elseif ($invoice->client->account->isGatewayConfigured() && !$invoice->isPaid() && !$invoice->is_recurring)
|
||||
{!! Button::normal(trans('texts.download_pdf'))->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!}
|
||||
@if (count($paymentTypes) > 1)
|
||||
{!! DropdownButton::success(trans('texts.pay_now'))->withContents($paymentTypes)->large() !!}
|
||||
@else
|
||||
<a href='{!! $paymentURL !!}' class="btn btn-success btn-lg">{{ trans('texts.pay_now') }}</a>
|
||||
@endif
|
||||
@else
|
||||
{!! Button::normal('Download PDF')->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!}
|
||||
@endif
|
||||
</div>
|
||||
@if ($checkoutComToken)
|
||||
@include('partials.checkout_com_payment')
|
||||
@else
|
||||
<p> </p>
|
||||
<div class="pull-right" style="text-align:right">
|
||||
@if ($invoice->is_quote)
|
||||
{!! Button::normal(trans('texts.download_pdf'))->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!}
|
||||
@if ($showApprove)
|
||||
{!! Button::success(trans('texts.approve'))->asLinkTo(URL::to('/approve/' . $invitation->invitation_key))->large() !!}
|
||||
@endif
|
||||
@elseif ($invoice->client->account->isGatewayConfigured() && !$invoice->isPaid() && !$invoice->is_recurring)
|
||||
{!! Button::normal(trans('texts.download_pdf'))->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!}
|
||||
@if (count($paymentTypes) > 1)
|
||||
{!! DropdownButton::success(trans('texts.pay_now'))->withContents($paymentTypes)->large() !!}
|
||||
@else
|
||||
<a href='{!! $paymentURL !!}' class="btn btn-success btn-lg">{{ trans('texts.pay_now') }}</a>
|
||||
@endif
|
||||
@else
|
||||
{!! Button::normal('Download PDF')->withAttributes(['onclick' => 'onDownloadClick()'])->large() !!}
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="clearfix"></div><p> </p>
|
||||
|
||||
|
22
resources/views/partials/checkout_com_payment.blade.php
Normal file
22
resources/views/partials/checkout_com_payment.blade.php
Normal file
@ -0,0 +1,22 @@
|
||||
<script src="https://sandbox.checkout.com/js/v1/checkout.js"></script>
|
||||
|
||||
<form method="POST" class="payment-form">
|
||||
<script>
|
||||
Checkout.render({
|
||||
debugMode: true,
|
||||
publicKey: '{{ $checkoutComKey }}',
|
||||
paymentToken: '{{ $checkoutComToken }}',
|
||||
customerEmail: '{{ $contact->email }}',
|
||||
customerName: '{{ $contact->getFullName() }}',
|
||||
value: {{ $invoice->getRequestedAmount() * 100 }},
|
||||
currency: '{{ $invoice->getCurrencyCode() }}',
|
||||
widgetContainerSelector: '.payment-form',
|
||||
widgetColor: '#333',
|
||||
themeColor: '#3075dd',
|
||||
buttonColor:'#51c470',
|
||||
cardCharged: function(event){
|
||||
location.href = '{{ URL::to('/complete?token=' . $checkoutComToken) }}';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</form>
|
103
tests/acceptance/TaxRatesCest.php
Normal file
103
tests/acceptance/TaxRatesCest.php
Normal file
@ -0,0 +1,103 @@
|
||||
/<?php
|
||||
|
||||
use Codeception\Util\Fixtures;
|
||||
use \AcceptanceTester;
|
||||
use Faker\Factory;
|
||||
|
||||
class TaxRatesCest
|
||||
{
|
||||
private $faker;
|
||||
|
||||
public function _before(AcceptanceTester $I)
|
||||
{
|
||||
$I->checkIfLogin($I);
|
||||
|
||||
$this->faker = Factory::create();
|
||||
}
|
||||
|
||||
public function taxRates(AcceptanceTester $I)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
public function onlinePayment(AcceptanceTester $I)
|
||||
{
|
||||
$I->wantTo('test an online payment');
|
||||
|
||||
$clientEmail = $this->faker->safeEmail;
|
||||
$productKey = $this->faker->text(10);
|
||||
|
||||
// set gateway info
|
||||
$I->wantTo('create a gateway');
|
||||
$I->amOnPage('/settings/online_payments');
|
||||
|
||||
if (strpos($I->grabFromCurrentUrl(), 'create') !== false) {
|
||||
$I->fillField(['name' =>'23_apiKey'], Fixtures::get('gateway_key'));
|
||||
$I->selectOption('#token_billing_type_id', 4);
|
||||
$I->click('Save');
|
||||
$I->see('Successfully created gateway');
|
||||
}
|
||||
|
||||
// create client
|
||||
$I->amOnPage('/clients/create');
|
||||
$I->fillField(['name' => 'contacts[0][email]'], $clientEmail);
|
||||
$I->click('Save');
|
||||
$I->see($clientEmail);
|
||||
|
||||
// create product
|
||||
$I->amOnPage('/products/create');
|
||||
$I->fillField(['name' => 'product_key'], $productKey);
|
||||
$I->fillField(['name' => 'notes'], $this->faker->text(80));
|
||||
$I->fillField(['name' => 'cost'], $this->faker->numberBetween(1, 20));
|
||||
$I->click('Save');
|
||||
$I->wait(1);
|
||||
$I->see($productKey);
|
||||
|
||||
// create invoice
|
||||
$I->amOnPage('/invoices/create');
|
||||
$I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle');
|
||||
$I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey);
|
||||
$I->click('Save');
|
||||
$I->see($clientEmail);
|
||||
|
||||
// enter payment
|
||||
$clientId = $I->grabFromDatabase('contacts', 'client_id', ['email' => $clientEmail]);
|
||||
$invoiceId = $I->grabFromDatabase('invoices', 'id', ['client_id' => $clientId]);
|
||||
$invitationKey = $I->grabFromDatabase('invitations', 'invitation_key', ['invoice_id' => $invoiceId]);
|
||||
|
||||
$clientSession = $I->haveFriend('client');
|
||||
$clientSession->does(function(AcceptanceTester $I) use ($invitationKey) {
|
||||
$I->amOnPage('/view/' . $invitationKey);
|
||||
$I->click('Pay Now');
|
||||
|
||||
$I->fillField(['name' => 'first_name'], $this->faker->firstName);
|
||||
$I->fillField(['name' => 'last_name'], $this->faker->lastName);
|
||||
$I->fillField(['name' => 'address1'], $this->faker->streetAddress);
|
||||
$I->fillField(['name' => 'address2'], $this->faker->streetAddress);
|
||||
$I->fillField(['name' => 'city'], $this->faker->city);
|
||||
$I->fillField(['name' => 'state'], $this->faker->state);
|
||||
$I->fillField(['name' => 'postal_code'], $this->faker->postcode);
|
||||
$I->selectDropdown($I, 'United States', '.country-select .dropdown-toggle');
|
||||
$I->fillField(['name' => 'card_number'], '4242424242424242');
|
||||
$I->fillField(['name' => 'cvv'], '1234');
|
||||
$I->selectOption('#expiration_month', 12);
|
||||
$I->selectOption('#expiration_year', date('Y'));
|
||||
$I->click('.btn-success');
|
||||
$I->see('Successfully applied payment');
|
||||
});
|
||||
|
||||
$I->wait(1);
|
||||
|
||||
// create recurring invoice and auto-bill
|
||||
$I->amOnPage('/recurring_invoices/create');
|
||||
$I->selectDropdown($I, $clientEmail, '.client_select .dropdown-toggle');
|
||||
$I->fillField('table.invoice-table tbody tr:nth-child(1) #product_key', $productKey);
|
||||
$I->checkOption('#auto_bill');
|
||||
$I->executeJS('preparePdfData(\'email\')');
|
||||
$I->wait(2);
|
||||
$I->see("$0.00");
|
||||
|
||||
}
|
||||
*/
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user