@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Show prices in "$... USD" in Phortune

Summary:
Ref T2787. See discussion in D5834.

- Replace `PhortuneUtil` with `PhortuneCurrency`, which feels a little better and more flexible / future-proof.
- Add unit tests.
- Display prices explicitly as "$... USD".

Test Plan: Hit product list, cart, purchase flow, verified displays.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2787

Differential Revision: https://secure.phabricator.com/D5841

+214 -52
+3 -1
src/__phutil_library_map__.php
··· 1592 1592 'PhortuneConstants' => 'applications/phortune/constants/PhortuneConstants.php', 1593 1593 'PhortuneController' => 'applications/phortune/controller/PhortuneController.php', 1594 1594 'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php', 1595 + 'PhortuneCurrency' => 'applications/phortune/currency/PhortuneCurrency.php', 1596 + 'PhortuneCurrencyTestCase' => 'applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php', 1595 1597 'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php', 1596 1598 'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php', 1597 1599 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', ··· 1620 1622 'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php', 1621 1623 'PhortuneTestExtraPaymentProvider' => 'applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php', 1622 1624 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', 1623 - 'PhortuneUtil' => 'applications/phortune/util/PhortuneUtil.php', 1624 1625 'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php', 1625 1626 'PhrequentDAO' => 'applications/phrequent/storage/PhrequentDAO.php', 1626 1627 'PhrequentListController' => 'applications/phrequent/controller/PhrequentListController.php', ··· 3324 3325 'PhortuneCart' => 'PhortuneDAO', 3325 3326 'PhortuneCharge' => 'PhortuneDAO', 3326 3327 'PhortuneController' => 'PhabricatorController', 3328 + 'PhortuneCurrencyTestCase' => 'PhabricatorTestCase', 3327 3329 'PhortuneDAO' => 'PhabricatorLiskDAO', 3328 3330 'PhortuneErrCode' => 'PhortuneConstants', 3329 3331 'PhortuneLandingController' => 'PhortuneController',
+6 -3
src/applications/phortune/controller/PhortuneAccountBuyController.php
··· 56 56 foreach ($cart->getPurchases() as $purchase) { 57 57 $rows[] = array( 58 58 $purchase->getPurchaseName(), 59 - PhortuneUtil::formatCurrency($purchase->getBasePriceInCents()), 59 + PhortuneCurrency::newFromUSDCents($purchase->getBasePriceInCents()) 60 + ->formatForDisplay(), 60 61 $purchase->getQuantity(), 61 - PhortuneUtil::formatCurrency($purchase->getTotalPriceInCents()), 62 + PhortuneCurrency::newFromUSDCents($purchase->getTotalPriceInCents()) 63 + ->formatForDisplay(), 62 64 ); 63 65 64 66 $total += $purchase->getTotalPriceInCents(); ··· 68 70 phutil_tag('strong', array(), pht('Total')), 69 71 '', 70 72 '', 71 - phutil_tag('strong', array(), PhortuneUtil::formatCurrency($total)), 73 + phutil_tag('strong', array(), 74 + PhortuneCurrency::newFromUSDCents($total)->formatForDisplay()), 72 75 ); 73 76 74 77 $table = new AphrontTableView($rows);
+4 -2
src/applications/phortune/controller/PhortuneProductEditController.php
··· 33 33 $v_name = $product->getProductName(); 34 34 $v_type = $product->getProductType(); 35 35 $v_price = (int)$product->getPriceInCents(); 36 - $display_price = PhortuneUtil::formatCurrency($v_price); 36 + $display_price = PhortuneCurrency::newFromUSDCents($v_price) 37 + ->formatForDisplay(); 37 38 38 39 $e_name = true; 39 40 $e_type = null; ··· 62 63 63 64 $display_price = $request->getStr('price'); 64 65 try { 65 - $v_price = PhortuneUtil::parseCurrency($display_price); 66 + $v_price = PhortuneCurrency::newFromUserInput($user, $display_price) 67 + ->getValue(); 66 68 $e_price = null; 67 69 } catch (Exception $ex) { 68 70 $errors[] = pht('Price should be formatted as: $1.23');
+2 -1
src/applications/phortune/controller/PhortuneProductListController.php
··· 44 44 ->setObjectName($product->getID()) 45 45 ->setHeader($product->getProductName()) 46 46 ->setHref($view_uri) 47 - ->addAttribute(PhortuneUtil::formatCurrency($price)) 47 + ->addAttribute( 48 + PhortuneCurrency::newFromUSDCents($price)->formatForDisplay()) 48 49 ->addAttribute($product->getTypeName()); 49 50 50 51 $product_list->addItem($item);
+2 -1
src/applications/phortune/controller/PhortuneProductViewController.php
··· 63 63 ->addProperty(pht('Type'), $product->getTypeName()) 64 64 ->addProperty( 65 65 pht('Price'), 66 - PhortuneUtil::formatCurrency($product->getPriceInCents())); 66 + PhortuneCurrency::newFromUSDCents($product->getPriceInCents()) 67 + ->formatForDisplay()); 67 68 68 69 $xactions = id(new PhortuneProductTransactionQuery()) 69 70 ->setViewer($user)
+95
src/applications/phortune/currency/PhortuneCurrency.php
··· 1 + <?php 2 + 3 + final class PhortuneCurrency { 4 + 5 + private $value; 6 + private $currency; 7 + 8 + private function __construct() { 9 + // Intentionally private. 10 + } 11 + 12 + public static function newFromUserInput(PhabricatorUser $user, $string) { 13 + $matches = null; 14 + $ok = preg_match( 15 + '/^([-$]*(?:\d+)?(?:[.]\d{0,2})?)(?:\s+([A-Z]+))?$/', 16 + trim($string), 17 + $matches); 18 + 19 + if (!$ok) { 20 + self::throwFormatException($string); 21 + } 22 + 23 + $value = $matches[1]; 24 + 25 + if (substr_count($value, '-') > 1) { 26 + self::throwFormatException($string); 27 + } 28 + 29 + if (substr_count($value, '$') > 1) { 30 + self::throwFormatException($string); 31 + } 32 + 33 + $value = str_replace('$', '', $value); 34 + $value = (float)$value; 35 + $value = (int)round(100 * $value); 36 + 37 + $currency = idx($matches, 2, 'USD'); 38 + if ($currency) { 39 + switch ($currency) { 40 + case 'USD': 41 + break; 42 + default: 43 + throw new Exception("Unsupported currency '{$currency}'!"); 44 + } 45 + } 46 + 47 + $obj = new PhortuneCurrency(); 48 + 49 + $obj->value = $value; 50 + $obj->currency = $currency; 51 + 52 + return $obj; 53 + } 54 + 55 + public static function newFromUSDCents($cents) { 56 + if (!is_int($cents)) { 57 + throw new Exception("USDCents value is not an integer!"); 58 + } 59 + 60 + $obj = new PhortuneCurrency(); 61 + 62 + $obj->value = $cents; 63 + $obj->currency = 'USD'; 64 + 65 + return $obj; 66 + } 67 + 68 + public function formatForDisplay() { 69 + $bare = $this->formatBareValue(); 70 + return '$'.$bare.' USD'; 71 + } 72 + 73 + public function formatBareValue() { 74 + switch ($this->currency) { 75 + case 'USD': 76 + return sprintf('%.02f', $this->value / 100); 77 + default: 78 + throw new Exception("Unsupported currency!"); 79 + 80 + } 81 + } 82 + 83 + public function getValue() { 84 + return $this->value; 85 + } 86 + 87 + public function getCurrency() { 88 + return $this->currency; 89 + } 90 + 91 + private static function throwFormatException($string) { 92 + throw new Exception("Invalid currency format ('{$string}')."); 93 + } 94 + 95 + }
+93
src/applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php
··· 1 + <?php 2 + 3 + final class PhortuneCurrencyTestCase extends PhabricatorTestCase { 4 + 5 + public function testCurrencyFormatForDisplay() { 6 + $map = array( 7 + 0 => '$0.00 USD', 8 + 1 => '$0.01 USD', 9 + 100 => '$1.00 USD', 10 + -123 => '$-1.23 USD', 11 + 5000000 => '$50000.00 USD', 12 + ); 13 + 14 + foreach ($map as $input => $expect) { 15 + $this->assertEqual( 16 + $expect, 17 + PhortuneCurrency::newFromUSDCents($input)->formatForDisplay(), 18 + "formatForDisplay({$input})"); 19 + } 20 + } 21 + 22 + 23 + public function testCurrencyFormatBareValue() { 24 + 25 + // NOTE: The PayPal API depends on the behavior of the bare value format! 26 + 27 + $map = array( 28 + 0 => '0.00', 29 + 1 => '0.01', 30 + 100 => '1.00', 31 + -123 => '-1.23', 32 + 5000000 => '50000.00', 33 + ); 34 + 35 + foreach ($map as $input => $expect) { 36 + $this->assertEqual( 37 + $expect, 38 + PhortuneCurrency::newFromUSDCents($input)->formatBareValue(), 39 + "formatBareValue({$input})"); 40 + } 41 + } 42 + 43 + public function testCurrencyFromUserInput() { 44 + 45 + $map = array( 46 + '1.00' => 100, 47 + '1.00 USD' => 100, 48 + '$1.00' => 100, 49 + '$1.00 USD' => 100, 50 + '-$1.00 USD' => -100, 51 + '$-1.00 USD' => -100, 52 + '1' => 100, 53 + '.99' => 99, 54 + '$.99' => 99, 55 + '-$.99' => -99, 56 + '$-.99' => -99, 57 + '$.99 USD' => 99, 58 + ); 59 + 60 + $user = new PhabricatorUser(); 61 + 62 + foreach ($map as $input => $expect) { 63 + $this->assertEqual( 64 + $expect, 65 + PhortuneCurrency::newFromUserInput($user, $input)->getValue(), 66 + "newFromUserInput({$input})->getValue()"); 67 + } 68 + } 69 + 70 + public function testInvalidCurrencyFromUserInput() { 71 + $map = array( 72 + '--1', 73 + '$$1', 74 + '1 JPY', 75 + 'buck fiddy', 76 + '1.2.3', 77 + '1 dollar', 78 + ); 79 + 80 + $user = new PhabricatorUser(); 81 + 82 + foreach ($map as $input) { 83 + $caught = null; 84 + try { 85 + PhortuneCurrency::newFromUserInput($user, $input); 86 + } catch (Exception $ex) { 87 + $caught = $ex; 88 + } 89 + $this->assertEqual(true, ($caught instanceof Exception), "{$input}"); 90 + } 91 + } 92 + 93 + }
+3 -3
src/applications/phortune/provider/PhortunePaypalPaymentProvider.php
··· 122 122 )); 123 123 124 124 $total_in_cents = $cart->getTotalInCents(); 125 - $price = PhortuneUtil::formatBareCurrency($total_in_cents); 125 + $price = PhortuneCurrency::newFromUSDCents($total_in_cents); 126 126 127 127 $result = $this 128 128 ->newPaypalAPICall() 129 129 ->setRawPayPalQuery( 130 130 'SetExpressCheckout', 131 131 array( 132 - 'PAYMENTREQUEST_0_AMT' => $price, 133 - 'PAYMENTREQUEST_0_CURRENCYCODE' => 'USD', 132 + 'PAYMENTREQUEST_0_AMT' => $price->formatBareValue(), 133 + 'PAYMENTREQUEST_0_CURRENCYCODE' => $price->getCurrency(), 134 134 'RETURNURL' => $return_uri, 135 135 'CANCELURL' => $cancel_uri, 136 136 'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale',
+6 -3
src/applications/phortune/storage/PhortuneProductTransaction.php
··· 48 48 return pht( 49 49 '%s set product price to %s.', 50 50 $this->renderHandleLink($author_phid), 51 - PhortuneUtil::formatCurrency($new)); 51 + PhortuneCurrency::newFromUSDCents($new) 52 + ->formatForDisplay()); 52 53 } else { 53 54 return pht( 54 55 '%s changed product price from %s to %s.', 55 56 $this->renderHandleLink($author_phid), 56 - PhortuneUtil::formatCurrency($old), 57 - PhortuneUtil::formatCurrency($new)); 57 + PhortuneCurrency::newFromUSDCents($old) 58 + ->formatForDisplay(), 59 + PhortuneCurrency::newFromUSDCents($new) 60 + ->formatForDisplay()); 58 61 } 59 62 break; 60 63 case self::TYPE_TYPE:
-38
src/applications/phortune/util/PhortuneUtil.php
··· 1 - <?php 2 - 3 - final class PhortuneUtil { 4 - 5 - public static function parseCurrency($string) { 6 - $string = trim($string); 7 - if (!preg_match('/^[-]?[$]?\d+([.]\d{0,2})?$/', $string)) { 8 - throw new Exception("Invalid currency format ('{$string}')."); 9 - } 10 - 11 - $value = str_replace('$', '', $string); 12 - $value = (float)$value; 13 - $value = (int)round(100 * $value); 14 - 15 - return $value; 16 - } 17 - 18 - public static function formatCurrency($price_in_cents) { 19 - if (!preg_match('/^[-]?\d+$/', $price_in_cents)) { 20 - throw new Exception("Invalid price in cents ('{$price_in_cents}')."); 21 - } 22 - 23 - $negative = ($price_in_cents < 0); 24 - $price_in_cents = abs($price_in_cents); 25 - $display_value = sprintf('$%.02f', $price_in_cents / 100); 26 - 27 - if ($negative) { 28 - $display_value = '-'.$display_value; 29 - } 30 - 31 - return $display_value; 32 - } 33 - 34 - public static function formatBareCurrency($price_in_cents) { 35 - return str_replace('$', '', self::formatCurrency($price_in_cents)); 36 - } 37 - 38 - }