Geldbeträge

Geld und Geldbeträge nehmen einen nicht unwesentlichen Teil im Leben eines Shopsystems ein. In varisale kümmert sich die Klasse Varisale_Money um alle Belange (parsen, formatieren, umrechnen). Der Umgang mit ihr ist so einfach wie möglich gehalten und soll auf dieser Seite kurz beschrieben werden.

Grundlagen

Jeder Geldbetrag ist entweder absolut oder prozentual.

  • Absolute Beträge haben einen Betrag (z.B. 10,99) und eine Währung (z.B. EUR).
  • Prozentuale Beträge haben nur einen Betrag (ihre Einheit ist klar: %).

Die verfügbaren Währungen sind in der Datenbank in der Tabelle wv13_currencies definiert, inklusive der Umrechnungskurse. Die Kurse beziehen sich dabei immer auf die **System**währung, die in jedem varisale-Shop fest Euro ist (Varisale::SYSTEM_CURRENCY).

Daneben gibt es noch die **Standard**währung, die im Backend pro Sprache eingestellt werden kann. Im Frontend werden alle Geldbeträge automatisch in die Standardwährung konvertiert. Das bedeutet, dass Preise, die im Backend als Pfund eingetragen wurden, im Frontend automatisch auf dt. Seiten aus Euro und auf englischen Seiten beispielsweise als US-Dollar zurückgegeben werden.

Jedes Produkt hat genau einen Preis, der als Netto in der Datenbank abgespeichert wird. Steuersätze haben einen prozentualen Preis (z.B. 19 %). Liefer- und Zahlmethoden können einen absoluten oder prozentualen Preis haben.

Preise können addiert, subtrahiert etc. werden. Für absolute Beträge ist dies immer definiert (10 EUR + 10 EUR = 20 EUR). Prozentuale Beträge können an manchen Stellen ebenfalls verwendet werden, allerdings gibt es dabei Ausnahmen (z.B. ist -5 % minus 10EUR- nicht definiert und führt zu einer Exception). Bei Berechnungen mit Geldbeträgen werden ihre Währungen automatisch konvertiert, sodass auch “10 EUR + 20 GBP” problemlos möglich ist.

Preise werden mit voller Genauigkeit in der Datenbank abgelegt. Dies ist notwendig, damit bei der Umrechnung von Netto in Brutto keine krummen Preise entstehen. Die Anzahl der Stellen, die im Frontend/Backend ausgegeben werden, hängt hingegen vom Locale ab (siehe unten).

API

Bemerkung

Alle Instanzen sind unveränderlich. Operationen geben immer neue Objekte als Ergebnis zurück!

Objekte erzeugen

Um ein Money-Objekt zu erzeugen, rufen wir (Trommelwirbel!) den Konstruktur auf.

<?
$money = new Varisale_Money(10, 'EUR');

// Die API unterstützt noch viele weitere Arten und wird jeweils
// versuchen, die Preise so gut es geht zu parsen.

$money = new Varisale_Money('10EUR');
$money = new Varisale_Money($money); // Kopie erzeugen

// Beträge werden standardmäßig OHNE Berücksichtigung des Locales
// geparsed. Das heißt, dass 10,90 EUR immer als 10.90 geschrieben
// werden müssen. Die folgenden drei Aufrufe sind identisch:

$money = new Varisale_Money('10.90EUR');
$money = new Varisale_Money('10.90', 'EUR');
$money = new Varisale_Money(10.90, 'EUR');

// Ein Objekt mit einem Betrag von 0 kann auch wie folgt erzeugt werden:
$money = Varisale_Money::gratis();
// Das von ::gratis() zurückgelieferte Objekt ist ein Singleton!

Möchte man Eingaben parsen, kann man die statische ::parse-Methode verwenden. Diese verwendet das aktuelle Locale, um den Dezimaltrenner zu ermitteln.

<?
// Die Währung kann in der Eingabe selbst oder fest als zweiter Parameter angegeben werden.

$money = Varisale_Money::parse('10');        // 10 EUR
$money = Varisale_Money::parse('10', 'USD'); // 10 USD
$money = Varisale_Money::parse('10PLN');     // 10 PLN

// Das Parsen erfolgt in Abhängigkeit des Locales.

setlocale(LC_ALL, 'German');
$money = Varisale_Money::parse('10,98');     //    10.98 EUR
$money = Varisale_Money::parse('-1.234,98'); // -1234.98 EUR

setlocale(LC_ALL, 'English');
$money = Varisale_Money::parse('10.98');    //   10.98 EUR
$money = Varisale_Money::parse('1,234.98'); // 1234.98 EUR

Es zeigt sich also, dass es wichtig ist, im Frontend immer das richtige Locale zu setzen, damit Benutzer Preise auf die für sie typische Weise eingeben können. Möchte man das Parsen unabhängig vom Locale ausführen, kann man entweder den Konstruktor aufrufen oder (was identisch ist) die parse-Methode weiter steuern:

<?
$amount = sly_post('...');

// a) Konstruktor
$amount = new Varisale_Money($amount, 'EUR');

// b) parse (das false am Ende ist ausschlaggebend)
$amount = Varisale_Money::parse($amount, 'EUR', Varisale::UNDEFINED, null, false);

Da natürlich nie mit Sicherheit gesagt werden kann, dass Benutzer immer korrekte Werte eingeben, sollte das Ergebnis des Parsens auf der Website angezeigt werden (im einem Bestellprozess zum Beispiel auf der nächsten Seite oder auf einer letzten Zusammenfassungsseite). Dann kann der Benutzer, wenn Varisale_Money Fehler gemacht hat, seine Eingabe noch einmal korrigieren.

Objekte abrufen

In den meisten Fällen wir man eher Money-Objekte über die API abrufen. Produkte, Steuersätze etc. unterstützen dafür eine Reihe von Methoden. Bei Produkten, Items, Bestellungen und dem Warenkorb gibt es dabei jeweils Methoden für Netto- und für Bruttopreise.

<?
$product->getNettoPrice();
$product->getBruttoPrice();

$cart->getTotalNetto();
$cart->getTotalBrutto();
$cart->getBillingCosts();
$cart->getDeliveryCosts();
// ...

Eine vollständige Übersicht soll nicht Aufgabe dieser Seite sein. Viel wichtiger ist der Vermerk, dass man bei Produkten nie den Preis direkt aus dem Preis-Attribut auslesen sollte. Dies ist zwar technisch möglich, verhindert aber, dass Steuern, sonstige Kosten, Rabatte und andere Veränderungen auf den Preis angewandt werden können. Der Zugriff auf Preise sollte immer über die dafür vorgesehenen Methoden erfolgen!

Eigenschaften einer Instanz

Wie schon besprochen hat eine Instanz 2 wichtige Eigenschaften: einen Betrag und eine Einheit, wobei die Einheit entweder eine Währung oder “Prozent” sein kann. Zum Zugriff auf diese gibt es eine Reihe von Methoden:

<?
$money = new Varisale_Money('10EUR');

$money->getAmount();   // int(10)
$money->getCurrency(); // string "EUR"
$money->getType();     // Varisale_Money::ABSOLUTE

$money->isPlus();  // true
$money->isMinus(); // false
$money->isNull();  // false
$money->isFree();  // false (Alias für isNull)

$money->isAbsolute();   // true
$money->isPercentage(); // false

$money->isSystemCurrency();  // true
$money->isDefaultCurrency(); // true oder false

// und nun noch einmal für einen prozentualen Betrag

$money = new Varisale_Money('-5%');

$money->getAmount();   // int(-5)
$money->getCurrency(); // string "%"
$money->getType();     // Varisale_Money::PERCENTAGE

$money->isPlus();  // false
$money->isMinus(); // true
$money->isNull();  // false
$money->isFree();  // false (Alias für isNull)

$money->isAbsolute();   // false
$money->isPercentage(); // true

$money->isSystemCurrency();  // false
$money->isDefaultCurrency(); // false

Währungen umrechnen

Währungen können sehr leicht und an vielen Stellen umgerechnet werden.

<?
$money = new Varisale_Money('10EUR');

// getAmount() kann auch konvertieren
$money->getAmount();       // int(10)
$money->getAmount('USD');  // ... (hängt vom Kurs ab)

// in die eingestellte Standardwährung konvertieren
$money->convertToDefault();

// in die Systemwährung konvertieren
$money->convertToSystem();

// in irgendeine Währung konvertieren
$money->convertTo('USD');

Für Umrechnungen gilt:

  • Wenn das Objekt bereits in der gewünschten Zielwährung vorliegt, wird es direkt zurückgegeben (es wird kein neues Objekt erzeugt).
  • Prozentuale Beträge geben ebenfalls immer sich selbst zurück.

Vergleiche

Zwei Instanzen können über ->compareWith() miteinander verglichen werden. Dafür müssen beide den gleichen Typ besitzen (also beide absolut oder beide prozentual sein). Absolute Beträge werden dafür automatisch in ihren Währungen angeglichen.

Der Rückgabewert ist wie bei strcmp(). Damit kann ein Array von Money-Instanzen z.B. direkt über usort() sortiert werden.

costs vs. charge

An einigen Stellen gibt es Methoden, die entweder Charges oder Costs zurückgeben. Der Unterschied ist einfach zu erklären:

  • Charges sind die Kosten, die im Backend eingegeben wurden, zum Beispiel ein Steuersatz (7 %) oder die Kosten einer Liefermethode. Charges können damit prozentual oder absolut sein.
  • Costs sind die konkret entstandenen Kosten. Bei Steuern geben die TaxCosts also an, welcher Absolutbetrag an Steuern zustandegekommen ist. Costs sind immer absolut und in den meisten Fällen das, was man im Frontend benötigt.

Netto <-> Brutto

Natürlich unterstützt Varisale_Money die automatische Umrechnung von Netto in Brutto und umgekehrt.

<?
$netto  = new Varisale_Money('100');
$brutto = $netto->getBrutto(19); // 119 EUR

$brutto = new Varisale_Money('119');
$netto  = $brutto->getNetto(19); // 100 EUR

Eine Instanz weiß nicht, ob sie einen Netto- oder Bruttopreis repräsentiert. Es ist daher immer möglich, auf absoluten Beträgen ->getBrutto() und ->getNetto() aufzurufen. Der Aufrufer muss wissen, was er tut.

Berechnungen

Instanzen erlauben es, dass auf ihnen eine Reihe mathematischer Operationen durchgeführt werden. Dabei ist, wie immer, das Ergebnis wiederum eine neue Instanz.

Es ist hierbei anzumerken, dass nicht alle Operationen auf jeder Kombination von Objekten definiert ist. Außerdem mag die Semantik der Operationen nicht immer sofort einleuchtend sein.

Die erlaubten Operationen und ihre Ergebnisse lassen sich am einfachsten überblicken, indem der dazugehörige Kommentar aus der Klasse betrachtet wird:

100EUR + 100EUR = 200EUR
100EUR +  10  % = 110EUR
100EUR + 100    = 200EUR
 10  % +  10  % =  20  %
 10  % +  10    =  20  %

100EUR * 100EUR = <Exception>
100EUR *  10  % =    10EUR
100EUR * 100    = 10000EUR
 10  % *  10  % =     1  %
 10  % *  10    =   100  %

100EUR - 100EUR =   0EUR
100EUR -  10  % =  90EUR
100EUR - 100    =   0EUR
 10  % - 100EUR = <Exception>
 10  % -  10  % =   0  %
 10  % -  10    =   0  %

Bemerkung

Division ist nicht möglich.

Die Berechnungen können über die folgenden Methoden angestoßen werden:

<?
$m = new Varisale_Money('10EUR');
$m->add(10); // 20 EUR
$m->sub(5);  //  5 EUR

$n = new Varisale_Money('4USD');
$m->add($n); // 10 EUR + (4 USD in EUR)

// die anderen Operationen verlaufen analog

Beträge ohne Einheit müssen als Int/Float gegeben werden (wie in den ersten beiden Beispielen).

Ausgabe

Was wären Geldbeträge, wenn man sie nicht auch wieder ausgeben könnte. Zu diesem Zweck unterstützt varisale eine Reihe von Möglichkeiten, deren Verständnis kritisch für die Entwicklung des Frontends ist.

Bemerkung

Für die korrekte Ausgabe muss das jeweils passende Locale gesetzt sein!

Grundsätzlich gibt es zwei verschiedene Möglichkeiten, Preise auszugeben: Entweder über das lokale Währungsformat oder über das internationale Währungsformat. Beide haben ihre Vor- und Nachteile. Das internationale Format ist der Standard.

Die Ausgabe kann mit oder ohne Einheit ausgegeben werden. Die Variante ohne Einheit ist primär für Eingabefelder gedacht, bei denen die Währung oder das Prozentzeichen als Text hinter dem Feld ausgegeben werden und die Eingabe nur den Teil “- 50” oder dergleichen erfordert.

<?
setlocale(LC_ALL, 'German');

$m = new Varisale_Money('10EUR');
$m->format(true); // "10,00"

Die Ausgabe mit Einheit kann entweder das lokale oder das internationale Format verwenden.

  • Im lokalen Format hängt die Anordnung von Zeichen, Betrag und Währungssymbol vom Locale ab. Da das Währungssymbol (z.B. das Eurozeichen) verwendet wird, ist diese Darstellung nicht eindeutig, wenn der Shop mehrsprachig sein soll (“$5” können 5 US-Dollar oder 5 Mexikanische Peso sein).
  • Das internationale Format schreibt immer den Währungscode vor den Betrag und ist damit eindeutig (z.B. “EUR 5,00”). Aufgrund der Eindeutigkeit ist es das Standardformat in varisale.
<?
setlocale(LC_ALL, 'German');

$m = new Varisale_Money('1234EUR');
$m->format(); // "EUR 1.234,00"

Das lokale Format kann entweder pro Request oder pro Aufruf von ->format() ein- oder ausgeschaltet werden.

<?
setlocale(LC_ALL, 'German');

$m = new Varisale_Money('1234EUR');

Varisale_Money::useLocalFormat();
$m->format(); // "1.234,00 €"

Varisale_Money::useLocalFormat(false);
$m->format();            // "EUR 1.234,00"
$m->format(false, true); // "1.234,00 €"

Prozentuale Beträge werden immer unabhängig von lokalem/internationalem Format ausgegeben.

<?
setlocale(LC_ALL, 'German');

$m = new Varisale_Money('12%');
$m->format(true); // "12,00" (auch hier kann die Einheit weggelassen werden)

Varisale_Money::useLocalFormat();
$m->format(); // "12,00 %"

Varisale_Money::useLocalFormat(false);
$m->format(); // "12,00 %"

Parallel dazu können Objekte auch direkt ausgegeben werden:

<?
setlocale(LC_ALL, 'German');

$m = new Varisale_Money('12%');
print $m; // "12,00 %"

Das gesetzte Locale kann das Format stark beeinflussen!

<?
$m = new Varisale_Money('-1234567.89EUR');

setlocale(LC_ALL, 'Swiss');
print $m; // "EUR-1'234'567.89"

setlocale(LC_ALL, 'English');
print $m; // "(EUR 1'234'567.89)"

Inhalt

Vorheriges Thema

Häufig gestellte Fragen

Nächstes Thema

Rabatte

Diese Seite