diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f00a229587..44086d4155 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1592,6 +1592,8 @@ phutil_register_library_map(array( 'PhortuneConstants' => 'applications/phortune/constants/PhortuneConstants.php', 'PhortuneController' => 'applications/phortune/controller/PhortuneController.php', 'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php', + 'PhortuneCurrency' => 'applications/phortune/currency/PhortuneCurrency.php', + 'PhortuneCurrencyTestCase' => 'applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php', 'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php', 'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php', 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', @@ -1620,7 +1622,6 @@ phutil_register_library_map(array( 'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php', 'PhortuneTestExtraPaymentProvider' => 'applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php', 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', - 'PhortuneUtil' => 'applications/phortune/util/PhortuneUtil.php', 'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php', 'PhrequentDAO' => 'applications/phrequent/storage/PhrequentDAO.php', 'PhrequentListController' => 'applications/phrequent/controller/PhrequentListController.php', @@ -3324,6 +3325,7 @@ phutil_register_library_map(array( 'PhortuneCart' => 'PhortuneDAO', 'PhortuneCharge' => 'PhortuneDAO', 'PhortuneController' => 'PhabricatorController', + 'PhortuneCurrencyTestCase' => 'PhabricatorTestCase', 'PhortuneDAO' => 'PhabricatorLiskDAO', 'PhortuneErrCode' => 'PhortuneConstants', 'PhortuneLandingController' => 'PhortuneController', diff --git a/src/applications/phortune/controller/PhortuneAccountBuyController.php b/src/applications/phortune/controller/PhortuneAccountBuyController.php index 6f21876e1e..06f6ac04ee 100644 --- a/src/applications/phortune/controller/PhortuneAccountBuyController.php +++ b/src/applications/phortune/controller/PhortuneAccountBuyController.php @@ -56,9 +56,11 @@ final class PhortuneAccountBuyController foreach ($cart->getPurchases() as $purchase) { $rows[] = array( $purchase->getPurchaseName(), - PhortuneUtil::formatCurrency($purchase->getBasePriceInCents()), + PhortuneCurrency::newFromUSDCents($purchase->getBasePriceInCents()) + ->formatForDisplay(), $purchase->getQuantity(), - PhortuneUtil::formatCurrency($purchase->getTotalPriceInCents()), + PhortuneCurrency::newFromUSDCents($purchase->getTotalPriceInCents()) + ->formatForDisplay(), ); $total += $purchase->getTotalPriceInCents(); @@ -68,7 +70,8 @@ final class PhortuneAccountBuyController phutil_tag('strong', array(), pht('Total')), '', '', - phutil_tag('strong', array(), PhortuneUtil::formatCurrency($total)), + phutil_tag('strong', array(), + PhortuneCurrency::newFromUSDCents($total)->formatForDisplay()), ); $table = new AphrontTableView($rows); diff --git a/src/applications/phortune/controller/PhortuneProductEditController.php b/src/applications/phortune/controller/PhortuneProductEditController.php index 18101fc255..2a0b875b03 100644 --- a/src/applications/phortune/controller/PhortuneProductEditController.php +++ b/src/applications/phortune/controller/PhortuneProductEditController.php @@ -33,7 +33,8 @@ final class PhortuneProductEditController extends PhabricatorController { $v_name = $product->getProductName(); $v_type = $product->getProductType(); $v_price = (int)$product->getPriceInCents(); - $display_price = PhortuneUtil::formatCurrency($v_price); + $display_price = PhortuneCurrency::newFromUSDCents($v_price) + ->formatForDisplay(); $e_name = true; $e_type = null; @@ -62,7 +63,8 @@ final class PhortuneProductEditController extends PhabricatorController { $display_price = $request->getStr('price'); try { - $v_price = PhortuneUtil::parseCurrency($display_price); + $v_price = PhortuneCurrency::newFromUserInput($user, $display_price) + ->getValue(); $e_price = null; } catch (Exception $ex) { $errors[] = pht('Price should be formatted as: $1.23'); diff --git a/src/applications/phortune/controller/PhortuneProductListController.php b/src/applications/phortune/controller/PhortuneProductListController.php index a1cc6ba36b..53b6a1b8c3 100644 --- a/src/applications/phortune/controller/PhortuneProductListController.php +++ b/src/applications/phortune/controller/PhortuneProductListController.php @@ -44,7 +44,8 @@ final class PhortuneProductListController extends PhabricatorController { ->setObjectName($product->getID()) ->setHeader($product->getProductName()) ->setHref($view_uri) - ->addAttribute(PhortuneUtil::formatCurrency($price)) + ->addAttribute( + PhortuneCurrency::newFromUSDCents($price)->formatForDisplay()) ->addAttribute($product->getTypeName()); $product_list->addItem($item); diff --git a/src/applications/phortune/controller/PhortuneProductViewController.php b/src/applications/phortune/controller/PhortuneProductViewController.php index a0a8942e09..851aea4a75 100644 --- a/src/applications/phortune/controller/PhortuneProductViewController.php +++ b/src/applications/phortune/controller/PhortuneProductViewController.php @@ -63,7 +63,8 @@ final class PhortuneProductViewController extends PhortuneController { ->addProperty(pht('Type'), $product->getTypeName()) ->addProperty( pht('Price'), - PhortuneUtil::formatCurrency($product->getPriceInCents())); + PhortuneCurrency::newFromUSDCents($product->getPriceInCents()) + ->formatForDisplay()); $xactions = id(new PhortuneProductTransactionQuery()) ->setViewer($user) diff --git a/src/applications/phortune/currency/PhortuneCurrency.php b/src/applications/phortune/currency/PhortuneCurrency.php new file mode 100644 index 0000000000..68bc3d0be1 --- /dev/null +++ b/src/applications/phortune/currency/PhortuneCurrency.php @@ -0,0 +1,95 @@ + 1) { + self::throwFormatException($string); + } + + if (substr_count($value, '$') > 1) { + self::throwFormatException($string); + } + + $value = str_replace('$', '', $value); + $value = (float)$value; + $value = (int)round(100 * $value); + + $currency = idx($matches, 2, 'USD'); + if ($currency) { + switch ($currency) { + case 'USD': + break; + default: + throw new Exception("Unsupported currency '{$currency}'!"); + } + } + + $obj = new PhortuneCurrency(); + + $obj->value = $value; + $obj->currency = $currency; + + return $obj; + } + + public static function newFromUSDCents($cents) { + if (!is_int($cents)) { + throw new Exception("USDCents value is not an integer!"); + } + + $obj = new PhortuneCurrency(); + + $obj->value = $cents; + $obj->currency = 'USD'; + + return $obj; + } + + public function formatForDisplay() { + $bare = $this->formatBareValue(); + return '$'.$bare.' USD'; + } + + public function formatBareValue() { + switch ($this->currency) { + case 'USD': + return sprintf('%.02f', $this->value / 100); + default: + throw new Exception("Unsupported currency!"); + + } + } + + public function getValue() { + return $this->value; + } + + public function getCurrency() { + return $this->currency; + } + + private static function throwFormatException($string) { + throw new Exception("Invalid currency format ('{$string}')."); + } + +} diff --git a/src/applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php b/src/applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php new file mode 100644 index 0000000000..d52f5e0e78 --- /dev/null +++ b/src/applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php @@ -0,0 +1,93 @@ + '$0.00 USD', + 1 => '$0.01 USD', + 100 => '$1.00 USD', + -123 => '$-1.23 USD', + 5000000 => '$50000.00 USD', + ); + + foreach ($map as $input => $expect) { + $this->assertEqual( + $expect, + PhortuneCurrency::newFromUSDCents($input)->formatForDisplay(), + "formatForDisplay({$input})"); + } + } + + + public function testCurrencyFormatBareValue() { + + // NOTE: The PayPal API depends on the behavior of the bare value format! + + $map = array( + 0 => '0.00', + 1 => '0.01', + 100 => '1.00', + -123 => '-1.23', + 5000000 => '50000.00', + ); + + foreach ($map as $input => $expect) { + $this->assertEqual( + $expect, + PhortuneCurrency::newFromUSDCents($input)->formatBareValue(), + "formatBareValue({$input})"); + } + } + + public function testCurrencyFromUserInput() { + + $map = array( + '1.00' => 100, + '1.00 USD' => 100, + '$1.00' => 100, + '$1.00 USD' => 100, + '-$1.00 USD' => -100, + '$-1.00 USD' => -100, + '1' => 100, + '.99' => 99, + '$.99' => 99, + '-$.99' => -99, + '$-.99' => -99, + '$.99 USD' => 99, + ); + + $user = new PhabricatorUser(); + + foreach ($map as $input => $expect) { + $this->assertEqual( + $expect, + PhortuneCurrency::newFromUserInput($user, $input)->getValue(), + "newFromUserInput({$input})->getValue()"); + } + } + + public function testInvalidCurrencyFromUserInput() { + $map = array( + '--1', + '$$1', + '1 JPY', + 'buck fiddy', + '1.2.3', + '1 dollar', + ); + + $user = new PhabricatorUser(); + + foreach ($map as $input) { + $caught = null; + try { + PhortuneCurrency::newFromUserInput($user, $input); + } catch (Exception $ex) { + $caught = $ex; + } + $this->assertEqual(true, ($caught instanceof Exception), "{$input}"); + } + } + +} diff --git a/src/applications/phortune/provider/PhortunePaypalPaymentProvider.php b/src/applications/phortune/provider/PhortunePaypalPaymentProvider.php index 321305602b..a56cc5112f 100644 --- a/src/applications/phortune/provider/PhortunePaypalPaymentProvider.php +++ b/src/applications/phortune/provider/PhortunePaypalPaymentProvider.php @@ -122,15 +122,15 @@ final class PhortunePaypalPaymentProvider extends PhortunePaymentProvider { )); $total_in_cents = $cart->getTotalInCents(); - $price = PhortuneUtil::formatBareCurrency($total_in_cents); + $price = PhortuneCurrency::newFromUSDCents($total_in_cents); $result = $this ->newPaypalAPICall() ->setRawPayPalQuery( 'SetExpressCheckout', array( - 'PAYMENTREQUEST_0_AMT' => $price, - 'PAYMENTREQUEST_0_CURRENCYCODE' => 'USD', + 'PAYMENTREQUEST_0_AMT' => $price->formatBareValue(), + 'PAYMENTREQUEST_0_CURRENCYCODE' => $price->getCurrency(), 'RETURNURL' => $return_uri, 'CANCELURL' => $cancel_uri, 'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale', diff --git a/src/applications/phortune/storage/PhortuneProductTransaction.php b/src/applications/phortune/storage/PhortuneProductTransaction.php index c06af5e81c..1a9e770d1e 100644 --- a/src/applications/phortune/storage/PhortuneProductTransaction.php +++ b/src/applications/phortune/storage/PhortuneProductTransaction.php @@ -48,13 +48,16 @@ final class PhortuneProductTransaction return pht( '%s set product price to %s.', $this->renderHandleLink($author_phid), - PhortuneUtil::formatCurrency($new)); + PhortuneCurrency::newFromUSDCents($new) + ->formatForDisplay()); } else { return pht( '%s changed product price from %s to %s.', $this->renderHandleLink($author_phid), - PhortuneUtil::formatCurrency($old), - PhortuneUtil::formatCurrency($new)); + PhortuneCurrency::newFromUSDCents($old) + ->formatForDisplay(), + PhortuneCurrency::newFromUSDCents($new) + ->formatForDisplay()); } break; case self::TYPE_TYPE: diff --git a/src/applications/phortune/util/PhortuneUtil.php b/src/applications/phortune/util/PhortuneUtil.php deleted file mode 100644 index 57709ab927..0000000000 --- a/src/applications/phortune/util/PhortuneUtil.php +++ /dev/null @@ -1,38 +0,0 @@ -