Skip to content

Commit

Permalink
Refactor invoices
Browse files Browse the repository at this point in the history
These changes utilize the Money library to format money values from the invoice and invoice line objects. It makes use of the invoice's currency to make sure there's no currency conflict when trying to display the values.

New Composer requirements:

- moneyphp/money: used for formatting money values
- ext-intl: used for the above library to properly format currencies

This brings along some changes as well:

- Removed the $currencySymbol setting on the Cashier object along with the useCurrencySymbol and usesCurrencySymbol methods
- Removed the $symbol parameter from the useCurrency method on the Cashier object and its guessCurrencySymbol method
- Refactored the formatAmount method on the Cashier object to accept an optional $currency parameter. By default it'll use the current set Currency. Also refactored its internals to use the Money library to format the value.
- The starting balance is now no longer subtracted from the subtotal of an invoice
- The rawTotal method now returns an integer instead of a float
- A new rawDiscount() method is added to the Invoice object which return the discount but as a raw integer value
- A new tax() method is added to the Invoice object which returns the invoice tax formatted with its currency

And finally the invoice pdf got a make over:

- Subtotal is now displayed below the amount of each row and a total of all rows combined
- Discount and Tax are displayed below subtotal
- Total is now the summary of Subtotal - Discount - Tax
- Starting balance is shown right above the total
  • Loading branch information
driesvints committed Jun 7, 2019
1 parent a844ad4 commit c24139f
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 108 deletions.
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
],
"require": {
"php": "^7.1.3",
"ext-intl": "*",
"ext-json": "*",
"dompdf/dompdf": "^0.8.0",
"illuminate/contracts": "~5.8.0|~5.9.0",
Expand All @@ -23,6 +24,7 @@
"illuminate/routing": "~5.8.0|~5.9.0",
"illuminate/support": "~5.8.0|~5.9.0",
"illuminate/view": "~5.8.0|~5.9.0",
"moneyphp/money": "^3.2",
"nesbot/carbon": "^1.26.3|^2.0",
"stripe/stripe-php": "^6.0",
"symfony/http-kernel": "^4.2"
Expand Down
71 changes: 38 additions & 33 deletions resources/views/receipt.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Invoice</title>

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
background: #fff;
Expand All @@ -19,38 +19,34 @@
font-size:28px;
color:#cccccc;
}
.bold {
font-weight: bold;
}
.container {
padding-top:30px;
}
.invoice-head td {
padding: 0 8px;
}
.invoice-body{
background-color:transparent;
}
.logo {
padding-bottom: 10px;
}
.table th {
vertical-align: bottom;
font-weight: bold;
padding: 8px;
line-height: 20px;
text-align: left;
border-bottom: 1px solid #dddddd;
}
.table tr.row td {
border-bottom: 1px solid #dddddd;
}
.table td {
padding: 8px;
line-height: 20px;
text-align: left;
vertical-align: top;
border-top: 1px solid #dddddd;
}
.well {
margin-top: 15px;
}
</style>
</head>

<body>
<div class="container">
<table style="margin-left: auto; margin-right: auto" width="550">
Expand Down Expand Up @@ -121,24 +117,17 @@
<th align="right">Amount</th>
</tr>

<!-- Existing Balance -->
<tr>
<td>Starting Balance</td>
<td>&nbsp;</td>
<td>{{ $invoice->startingBalance() }}</td>
</tr>

<!-- Display The Invoice Items -->
@foreach ($invoice->invoiceItems() as $item)
<tr>
<tr class="row">
<td colspan="2">{{ $item->description }}</td>
<td>{{ $item->total() }}</td>
</tr>
@endforeach

<!-- Display The Subscriptions -->
@foreach ($invoice->subscriptions() as $subscription)
<tr>
<tr class="row">
<td>Subscription ({{ $subscription->quantity }})</td>
<td>
{{ $subscription->startDateAsCarbon()->formatLocalized('%B %e, %Y') }} -
Expand All @@ -148,32 +137,48 @@
</tr>
@endforeach

<!-- Display The Subtotal -->
@if ($invoice->hasDiscount() || $invoice->tax_percent)
<tr>
<td colspan="2" style="text-align: right;">Subtotal</td>
<td>{{ $invoice->subtotal() }}</td>
</tr>
@endif

<!-- Display The Discount -->
@if ($invoice->hasDiscount())
<tr>
@if ($invoice->discountIsPercentage())
<td>{{ $invoice->coupon() }} ({{ $invoice->percentOff() }}% Off)</td>
@else
<td>{{ $invoice->coupon() }} ({{ $invoice->amountOff() }} Off)</td>
@endif
<td>&nbsp;</td>
<td colspan="2" style="text-align: right;">
@if ($invoice->discountIsPercentage())
{{ $invoice->coupon() }} ({{ $invoice->percentOff() }}% Off)
@else
{{ $invoice->coupon() }} ({{ $invoice->amountOff() }} Off)
@endif
</td>

<td>-{{ $invoice->discount() }}</td>
</tr>
@endif

<!-- Display The Tax Amount -->
@if ($invoice->tax_percent)
<tr>
<td>Tax ({{ $invoice->tax_percent }}%)</td>
<td>&nbsp;</td>
<td>{{ Laravel\Cashier\Cashier::formatAmount($invoice->tax) }}</td>
<td colspan="2" style="text-align: right;">Tax ({{ $invoice->tax_percent }}%)</td>
<td>{{ $invoice->tax() }}</td>
</tr>
@endif

<!-- Starting Balance -->
@if ($invoice->hasStartingBalance())
<tr>
<td colspan="2" style="text-align: right;">Starting Balance</td>
<td>{{ $invoice->startingBalance() }}</td>
</tr>
@endif

<!-- Display The Final Total -->
<tr style="border-top:2px solid #000;">
<td>&nbsp;</td>
<td style="text-align: right;"><strong>Total</strong></td>
<tr {{ $invoice->hasStartingBalance() ? '' : 'class="bold"' }}>
<td colspan="2" style="text-align: right;"><strong>Total</strong></td>
<td><strong>{{ $invoice->total() }}</strong></td>
</tr>
</table>
Expand Down
72 changes: 19 additions & 53 deletions src/Cashier.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace Laravel\Cashier;

use Exception;
use Illuminate\Support\Str;
use Money\Money;
use Money\Currency;
use NumberFormatter;
use Money\Currencies\ISOCurrencies;
use Money\Formatter\IntlMoneyFormatter;

class Cashier
{
Expand Down Expand Up @@ -36,11 +39,11 @@ class Cashier
protected static $currency = 'usd';

/**
* The current currency symbol.
* The locale used to format money values.
*
* @var string
*/
protected static $currencySymbol = '$';
protected static $currencyLocale = 'en_US';

/**
* The custom currency formatter.
Expand Down Expand Up @@ -142,38 +145,11 @@ public static function stripeModel()
* Set the currency to be used when billing Stripe models.
*
* @param string $currency
* @param string|null $symbol
* @return void
* @throws \Exception
*/
public static function useCurrency($currency, $symbol = null)
public static function useCurrency($currency)
{
static::$currency = $currency;

static::useCurrencySymbol($symbol ?: static::guessCurrencySymbol($currency));
}

/**
* Guess the currency symbol for the given currency.
*
* @param string $currency
* @return string
* @throws \Exception
*/
protected static function guessCurrencySymbol($currency)
{
switch (strtolower($currency)) {
case 'usd':
case 'aud':
case 'cad':
return '$';
case 'eur':
return '';
case 'gbp':
return '£';
default:
throw new Exception('Unable to guess symbol for currency. Please explicitly specify it.');
}
}

/**
Expand All @@ -187,24 +163,14 @@ public static function usesCurrency()
}

/**
* Set the currency symbol to be used when formatting currency.
* Set the currency locale to format money.
*
* @param string $symbol
* @param string $currencyLocale
* @return void
*/
public static function useCurrencySymbol($symbol)
public static function useCurrencyLocale($currencyLocale)
{
static::$currencySymbol = $symbol;
}

/**
* Get the currency symbol currently in use.
*
* @return string
*/
public static function usesCurrencySymbol()
{
return static::$currencySymbol;
static::$currencyLocale = $currencyLocale;
}

/**
Expand All @@ -222,21 +188,21 @@ public static function formatCurrencyUsing(callable $callback)
* Format the given amount into a displayable currency.
*
* @param int $amount
* @param string|null $currency
* @return string
*/
public static function formatAmount($amount)
public static function formatAmount($amount, $currency = null)
{
if (static::$formatCurrencyUsing) {
return call_user_func(static::$formatCurrencyUsing, $amount);
return call_user_func(static::$formatCurrencyUsing, $amount, $currency);
}

$amount = number_format($amount / 100, 2);
$money = new Money($amount, new Currency(strtoupper($currency ?? static::usesCurrency())));

if (Str::startsWith($amount, '-')) {
return '-'.static::usesCurrencySymbol().ltrim($amount, '-');
}
$numberFormatter = new NumberFormatter(static::$currencyLocale, NumberFormatter::CURRENCY);
$moneyFormatter = new IntlMoneyFormatter($numberFormatter, new ISOCurrencies());

return static::usesCurrencySymbol().$amount;
return $moneyFormatter->format($money);
}

/**
Expand Down
Loading

0 comments on commit c24139f

Please sign in to comment.