diff --git a/resources/sql/autopatches/20140902.almanacdevice.1.sql b/resources/sql/autopatches/20140902.almanacdevice.1.sql --- a/resources/sql/autopatches/20140902.almanacdevice.1.sql +++ b/resources/sql/autopatches/20140902.almanacdevice.1.sql @@ -1,18 +1,18 @@ CREATE TABLE {$NAMESPACE}_almanac.almanac_device ( id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, phid VARBINARY(64) NOT NULL, - name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT}, + name VARCHAR(255) NOT NULL COLLATE utf8_bin, dateCreated INT UNSIGNED NOT NULL, dateModified INT UNSIGNED NOT NULL, UNIQUE KEY `key_phid` (phid) -) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; +) ENGINE=InnoDB, COLLATE utf8_bin; CREATE TABLE {$NAMESPACE}_almanac.almanac_deviceproperty ( id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, devicePHID VARBINARY(64) NOT NULL, - `key` VARCHAR(128) NOT NULL COLLATE {$COLLATE_TEXT}, + `key` VARCHAR(128) NOT NULL COLLATE utf8_bin, value LONGTEXT NOT NULL, dateCreated INT UNSIGNED NOT NULL, dateModified INT UNSIGNED NOT NULL, KEY `key_device` (devicePHID, `key`) -) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; +) ENGINE=InnoDB, COLLATE utf8_bin; diff --git a/resources/sql/autopatches/20141004.harborliskcounter.sql b/resources/sql/autopatches/20141004.harborliskcounter.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20141004.harborliskcounter.sql @@ -0,0 +1,4 @@ +CREATE TABLE `{$NAMESPACE}_harbormaster`.`lisk_counter` ( + counterName VARCHAR(64) COLLATE utf8_bin PRIMARY KEY, + counterValue BIGINT UNSIGNED NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/resources/sql/autopatches/20141004.currency.01.sql b/resources/sql/autopatches/20141004.currency.01.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20141004.currency.01.sql @@ -0,0 +1,4 @@ +TRUNCATE TABLE {$NAMESPACE}_fund.fund_backer; + +ALTER TABLE {$NAMESPACE}_fund.fund_backer + CHANGE amountInCents amountAsCurrency VARCHAR(64) NOT NULL COLLATE utf8_bin; diff --git a/resources/sql/autopatches/20141004.currency.02.sql b/resources/sql/autopatches/20141004.currency.02.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20141004.currency.02.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phortune.phortune_account + DROP balanceInCents; diff --git a/resources/sql/autopatches/20141004.currency.03.sql b/resources/sql/autopatches/20141004.currency.03.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20141004.currency.03.sql @@ -0,0 +1,4 @@ +TRUNCATE {$NAMESPACE}_phortune.phortune_charge; + +ALTER TABLE {$NAMESPACE}_phortune.phortune_charge + CHANGE amountInCents amountAsCurrency VARCHAR(64) NOT NULL COLLATE utf8_bin; diff --git a/resources/sql/autopatches/20141004.currency.04.sql b/resources/sql/autopatches/20141004.currency.04.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20141004.currency.04.sql @@ -0,0 +1,13 @@ +TRUNCATE {$NAMESPACE}_phortune.phortune_product; + +ALTER TABLE {$NAMESPACE}_phortune.phortune_product + DROP status; + +ALTER TABLE {$NAMESPACE}_phortune.phortune_product + DROP billingIntervalInMonths; + +ALTER TABLE {$NAMESPACE}_phortune.phortune_product + DROP trialPeriodInDays; + +ALTER TABLE {$NAMESPACE}_phortune.phortune_product + CHANGE priceInCents priceAsCurrency VARCHAR(64) NOT NULL collate utf8_bin; diff --git a/resources/sql/autopatches/20141004.currency.05.sql b/resources/sql/autopatches/20141004.currency.05.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20141004.currency.05.sql @@ -0,0 +1,8 @@ +TRUNCATE {$NAMESPACE}_phortune.phortune_purchase; + +ALTER TABLE {$NAMESPACE}_phortune.phortune_purchase + DROP totalPriceInCents; + +ALTER TABLE {$NAMESPACE}_phortune.phortune_purchase + CHANGE basePriceInCents basePriceAsCurrency VARCHAR(64) + NOT NULL collate utf8_bin; diff --git a/resources/sql/autopatches/20141004.currency.06.sql b/resources/sql/autopatches/20141004.currency.06.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20141004.currency.06.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phortune.phortune_product + DROP productType; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1716,6 +1716,7 @@ 'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php', 'PhabricatorLipsumMondrianArtist' => 'applications/lipsum/image/PhabricatorLipsumMondrianArtist.php', 'PhabricatorLiskDAO' => 'infrastructure/storage/lisk/PhabricatorLiskDAO.php', + 'PhabricatorLiskSerializer' => 'infrastructure/storage/lisk/PhabricatorLiskSerializer.php', 'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php', 'PhabricatorLocalTimeTestCase' => 'view/__tests__/PhabricatorLocalTimeTestCase.php', 'PhabricatorLogoutController' => 'applications/auth/controller/PhabricatorLogoutController.php', @@ -2561,6 +2562,7 @@ 'PhortuneController' => 'applications/phortune/controller/PhortuneController.php', 'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php', 'PhortuneCurrency' => 'applications/phortune/currency/PhortuneCurrency.php', + 'PhortuneCurrencySerializer' => 'applications/phortune/currency/PhortuneCurrencySerializer.php', 'PhortuneCurrencyTestCase' => 'applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php', 'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php', 'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php', @@ -5591,6 +5593,7 @@ 'PhortuneChargeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortuneController' => 'PhabricatorController', 'PhortuneCurrency' => 'Phobject', + 'PhortuneCurrencySerializer' => 'PhabricatorLiskSerializer', 'PhortuneCurrencyTestCase' => 'PhabricatorTestCase', 'PhortuneDAO' => 'PhabricatorLiskDAO', 'PhortuneErrCode' => 'PhortuneConstants', diff --git a/src/applications/fund/controller/FundInitiativeBackController.php b/src/applications/fund/controller/FundInitiativeBackController.php --- a/src/applications/fund/controller/FundInitiativeBackController.php +++ b/src/applications/fund/controller/FundInitiativeBackController.php @@ -57,7 +57,7 @@ $backer = FundBacker::initializeNewBacker($viewer) ->setInitiativePHID($initiative->getPHID()) ->attachInitiative($initiative) - ->setAmountInCents($currency->getValue()) + ->setAmountAsCurrency($currency) ->save(); // TODO: Here, we'd create a purchase and cart. diff --git a/src/applications/fund/query/FundBackerSearchEngine.php b/src/applications/fund/query/FundBackerSearchEngine.php --- a/src/applications/fund/query/FundBackerSearchEngine.php +++ b/src/applications/fund/query/FundBackerSearchEngine.php @@ -128,8 +128,7 @@ foreach ($backers as $backer) { $backer_handle = $handles[$backer->getBackerPHID()]; - $currency = PhortuneCurrency::newFromUSDCents( - $backer->getAmountInCents()); + $currency = $backer->getAmount(); $header = pht( '%s for %s', diff --git a/src/applications/fund/storage/FundBacker.php b/src/applications/fund/storage/FundBacker.php --- a/src/applications/fund/storage/FundBacker.php +++ b/src/applications/fund/storage/FundBacker.php @@ -7,7 +7,7 @@ protected $initiativePHID; protected $backerPHID; - protected $amountInCents; + protected $amountAsCurrency; protected $status; protected $properties = array(); @@ -28,9 +28,12 @@ self::CONFIG_SERIALIZATION => array( 'properties' => self::SERIALIZATION_JSON, ), + self::CONFIG_APPLICATION_SERIALIZERS => array( + 'amountAsCurrency' => new PhortuneCurrencySerializer(), + ), self::CONFIG_COLUMN_SCHEMA => array( 'status' => 'text32', - 'amountInCents' => 'uint32', + 'amountAsCurrency' => 'text64', ), self::CONFIG_KEY_SCHEMA => array( 'key_initiative' => array( @@ -47,11 +50,6 @@ return PhabricatorPHID::generateNewPHID(FundBackerPHIDType::TYPECONST); } - protected function didReadData() { - // The payment processing code is strict about types. - $this->amountInCents = (int)$this->amountInCents; - } - public function getProperty($key, $default = null) { return idx($this->properties, $key, $default); } diff --git a/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php b/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php --- a/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php +++ b/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php @@ -5,6 +5,23 @@ public function buildSchemata() { $this->buildEdgeSchemata(new HarbormasterBuildable()); + // NOTE: This table is not used by any Harbormaster objects, but is used + // by unit tests. + $this->buildRawSchema( + id(new HarbormasterObject())->getApplicationName(), + PhabricatorLiskDAO::COUNTER_TABLE_NAME, + array( + 'counterName' => 'text32', + 'counterValue' => 'id64', + ), + array( + 'PRIMARY' => array( + 'columns' => array('counterName'), + 'unique' => true, + ), + )); + + $this->buildRawSchema( id(new HarbormasterBuildable())->getApplicationName(), 'harbormaster_buildlogchunk', diff --git a/src/applications/phortune/controller/PhortuneAccountViewController.php b/src/applications/phortune/controller/PhortuneAccountViewController.php --- a/src/applications/phortune/controller/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/PhortuneAccountViewController.php @@ -51,7 +51,7 @@ ->setObject($account) ->setUser($user); - $properties->addProperty(pht('Balance'), $account->getBalanceInCents()); + $properties->addProperty(pht('Balance'), '-'); $properties->setActionList($actions); $payment_methods = $this->buildPaymentMethodsSection($account); @@ -189,8 +189,7 @@ foreach ($cart->getPurchases() as $purchase) { $id = $purchase->getID(); - $price = $purchase->getTotalPriceInCents(); - $price = PhortuneCurrency::newFromUSDCents($price)->formatForDisplay(); + $price = $purchase->getTotalPriceAsCurrency()->formatForDisplay(); $purchase_link = phutil_tag( 'a', diff --git a/src/applications/phortune/controller/PhortuneCartCheckoutController.php b/src/applications/phortune/controller/PhortuneCartCheckoutController.php --- a/src/applications/phortune/controller/PhortuneCartCheckoutController.php +++ b/src/applications/phortune/controller/PhortuneCartCheckoutController.php @@ -59,7 +59,7 @@ ->setAuthorPHID($viewer->getPHID()) ->setPaymentProviderKey($provider->getProviderKey()) ->setPaymentMethodPHID($method->getPHID()) - ->setAmountInCents($cart->getTotalPriceInCents()) + ->setAmountAsCurrency($cart->getTotalPriceAsCurrency()) ->setStatus(PhortuneCharge::STATUS_PENDING); $charge->openTransaction(); diff --git a/src/applications/phortune/controller/PhortuneCartController.php b/src/applications/phortune/controller/PhortuneCartController.php --- a/src/applications/phortune/controller/PhortuneCartController.php +++ b/src/applications/phortune/controller/PhortuneCartController.php @@ -6,18 +6,13 @@ protected function buildCartContents(PhortuneCart $cart) { $rows = array(); - $total = 0; foreach ($cart->getPurchases() as $purchase) { $rows[] = array( $purchase->getFullDisplayName(), - PhortuneCurrency::newFromUSDCents($purchase->getBasePriceInCents()) - ->formatForDisplay(), + $purchase->getBasePriceAsCurrency()->formatForDisplay(), $purchase->getQuantity(), - PhortuneCurrency::newFromUSDCents($purchase->getTotalPriceInCents()) - ->formatForDisplay(), + $purchase->getTotalPriceAsCurrency()->formatForDisplay(), ); - - $total += $purchase->getTotalPriceInCents(); } $rows[] = array( @@ -25,7 +20,7 @@ '', '', phutil_tag('strong', array(), - PhortuneCurrency::newFromUSDCents($total)->formatForDisplay()), + $cart->getTotalPriceAsCurrency()->formatForDisplay()), ); $table = new AphrontTableView($rows); diff --git a/src/applications/phortune/controller/PhortuneController.php b/src/applications/phortune/controller/PhortuneController.php --- a/src/applications/phortune/controller/PhortuneController.php +++ b/src/applications/phortune/controller/PhortuneController.php @@ -73,8 +73,7 @@ $cart_href, $charge->getPaymentProviderKey(), $charge->getPaymentMethodPHID(), - PhortuneCurrency::newFromUSDCents($charge->getAmountInCents()) - ->formatForDisplay(), + $charge->getAmountAsCurrency()->formatForDisplay(), $charge->getStatus(), phabricator_datetime($charge->getDateCreated(), $viewer), ); diff --git a/src/applications/phortune/controller/PhortuneProductEditController.php b/src/applications/phortune/controller/PhortuneProductEditController.php --- a/src/applications/phortune/controller/PhortuneProductEditController.php +++ b/src/applications/phortune/controller/PhortuneProductEditController.php @@ -25,19 +25,16 @@ $cancel_uri = $this->getApplicationURI( 'product/view/'.$this->productID.'/'); } else { - $product = new PhortuneProduct(); + $product = PhortuneProduct::initializeNewProduct(); $is_create = true; $cancel_uri = $this->getApplicationURI('product/'); } $v_name = $product->getProductName(); - $v_type = $product->getProductType(); - $v_price = (int)$product->getPriceInCents(); - $display_price = PhortuneCurrency::newFromUSDCents($v_price) - ->formatForDisplay(); + $v_price = $product->getPriceAsCurrency()->formatForDisplay(); + $display_price = $v_price; $e_name = true; - $e_type = null; $e_price = true; $errors = array(); @@ -50,21 +47,10 @@ $e_name = null; } - if ($is_create) { - $v_type = $request->getStr('type'); - $type_map = PhortuneProduct::getTypeMap(); - if (empty($type_map[$v_type])) { - $e_type = pht('Invalid'); - $errors[] = pht('Product type is invalid.'); - } else { - $e_type = null; - } - } - $display_price = $request->getStr('price'); try { $v_price = PhortuneCurrency::newFromUserInput($user, $display_price) - ->getValue(); + ->serializeForStorage(); $e_price = null; } catch (Exception $ex) { $errors[] = pht('Price should be formatted as: $1.23'); @@ -79,10 +65,6 @@ ->setNewValue($v_name); $xactions[] = id(new PhortuneProductTransaction()) - ->setTransactionType(PhortuneProductTransaction::TYPE_TYPE) - ->setNewValue($v_type); - - $xactions[] = id(new PhortuneProductTransaction()) ->setTransactionType(PhortuneProductTransaction::TYPE_PRICE) ->setNewValue($v_price); @@ -112,14 +94,6 @@ ->setValue($v_name) ->setError($e_name)) ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Type')) - ->setName('type') - ->setValue($v_type) - ->setError($e_type) - ->setOptions(PhortuneProduct::getTypeMap()) - ->setDisabled(!$is_create)) - ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Price')) ->setName('price') diff --git a/src/applications/phortune/controller/PhortuneProductListController.php b/src/applications/phortune/controller/PhortuneProductListController.php --- a/src/applications/phortune/controller/PhortuneProductListController.php +++ b/src/applications/phortune/controller/PhortuneProductListController.php @@ -32,15 +32,13 @@ $view_uri = $this->getApplicationURI( 'product/view/'.$product->getID().'/'); - $price = $product->getPriceInCents(); + $price = $product->getPriceAsCurrency(); $item = id(new PHUIObjectItemView()) ->setObjectName($product->getID()) ->setHeader($product->getProductName()) ->setHref($view_uri) - ->addAttribute( - PhortuneCurrency::newFromUSDCents($price)->formatForDisplay()) - ->addAttribute($product->getTypeName()); + ->addAttribute($price->formatForDisplay()); $product_list->addItem($item); } diff --git a/src/applications/phortune/controller/PhortuneProductPurchaseController.php b/src/applications/phortune/controller/PhortuneProductPurchaseController.php --- a/src/applications/phortune/controller/PhortuneProductPurchaseController.php +++ b/src/applications/phortune/controller/PhortuneProductPurchaseController.php @@ -49,10 +49,9 @@ $purchase->setAccountPHID($account->getPHID()); $purchase->setAuthorPHID($user->getPHID()); $purchase->setCartPHID($cart->getPHID()); - $purchase->setBasePriceInCents($product->getPriceInCents()); + $purchase->setBasePriceAsCurrency($product->getPriceAsCurrency()); $purchase->setQuantity(1); - $purchase->setTotalPriceInCents( - $purchase->getBasePriceInCents() * $purchase->getQuantity()); + $purchase->setStatus(PhortunePurchase::STATUS_PENDING); $purchase->save(); diff --git a/src/applications/phortune/controller/PhortuneProductViewController.php b/src/applications/phortune/controller/PhortuneProductViewController.php --- a/src/applications/phortune/controller/PhortuneProductViewController.php +++ b/src/applications/phortune/controller/PhortuneProductViewController.php @@ -60,11 +60,9 @@ $properties = id(new PHUIPropertyListView()) ->setUser($user) ->setActionList($actions) - ->addProperty(pht('Type'), $product->getTypeName()) ->addProperty( pht('Price'), - PhortuneCurrency::newFromUSDCents($product->getPriceInCents()) - ->formatForDisplay()); + $product->getPriceAsCurrency()->formatForDisplay()); $xactions = id(new PhortuneProductTransactionQuery()) ->setViewer($user) diff --git a/src/applications/phortune/currency/PhortuneCurrency.php b/src/applications/phortune/currency/PhortuneCurrency.php --- a/src/applications/phortune/currency/PhortuneCurrency.php +++ b/src/applications/phortune/currency/PhortuneCurrency.php @@ -9,7 +9,20 @@ // Intentionally private. } + public static function getDefaultCurrency() { + return 'USD'; + } + + public static function newEmptyCurrency() { + return self::newFromString('0.00 USD'); + } + public static function newFromUserInput(PhabricatorUser $user, $string) { + // Eventually, this might select a default currency based on user settings. + return self::newFromString($string, self::getDefaultCurrency()); + } + + public static function newFromString($string, $default = null) { $matches = null; $ok = preg_match( '/^([-$]*(?:\d+)?(?:[.]\d{0,2})?)(?:\s+([A-Z]+))?$/', @@ -34,7 +47,7 @@ $value = (float)$value; $value = (int)round(100 * $value); - $currency = idx($matches, 2, 'USD'); + $currency = idx($matches, 2, $default); if ($currency) { switch ($currency) { case 'USD': @@ -44,6 +57,10 @@ } } + return self::newFromValueAndCurrency($value, $currency); + } + + public static function newFromValueAndCurrency($value, $currency) { $obj = new PhortuneCurrency(); $obj->value = $value; @@ -56,31 +73,34 @@ assert_instances_of($list, 'PhortuneCurrency'); $total = 0; + $currency = null; foreach ($list as $item) { + if ($currency === null) { + $currency = $item->getCurrency(); + } else if ($currency === $item->getCurrency()) { + // Adding a value denominated in the same currency, which is + // fine. + } else { + throw new Exception( + pht('Trying to sum a list of unlike currencies.')); + } + // TODO: This should check for integer overflows, etc. $total += $item->getValue(); } - return PhortuneCurrency::newFromUSDCents($total); - } - - public static function newFromUSDCents($cents) { - if (!is_int($cents)) { - throw new Exception( - pht('USDCents value "%s" is not an integer!', $cents)); - } - - $obj = new PhortuneCurrency(); - - $obj->value = $cents; - $obj->currency = 'USD'; - - return $obj; + return PhortuneCurrency::newFromValueAndCurrency( + $total, + self::getDefaultCurrency()); } public function formatForDisplay() { $bare = $this->formatBareValue(); - return '$'.$bare.' USD'; + return '$'.$bare.' '.$this->currency; + } + + public function serializeForStorage() { + return $this->formatBareValue().' '.$this->currency; } public function formatBareValue() { @@ -88,8 +108,8 @@ case 'USD': return sprintf('%.02f', $this->value / 100); default: - throw new Exception('Unsupported currency!'); - + throw new Exception( + pht('Unsupported currency ("%s")!', $this->currency)); } } @@ -105,4 +125,6 @@ throw new Exception("Invalid currency format ('{$string}')."); } + + } diff --git a/src/applications/phortune/currency/PhortuneCurrencySerializer.php b/src/applications/phortune/currency/PhortuneCurrencySerializer.php new file mode 100644 --- /dev/null +++ b/src/applications/phortune/currency/PhortuneCurrencySerializer.php @@ -0,0 +1,20 @@ +serializeForStorage(); + } + +} diff --git a/src/applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php b/src/applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php --- a/src/applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php +++ b/src/applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php @@ -4,18 +4,18 @@ public function testCurrencyFormatForDisplay() { $map = array( - 0 => '$0.00 USD', - 1 => '$0.01 USD', - 100 => '$1.00 USD', - -123 => '$-1.23 USD', - 5000000 => '$50000.00 USD', + '0' => '$0.00 USD', + '.01' => '$0.01 USD', + '1.00' => '$1.00 USD', + '-1.23' => '$-1.23 USD', + '50000.00' => '$50000.00 USD', ); foreach ($map as $input => $expect) { $this->assertEqual( $expect, - PhortuneCurrency::newFromUSDCents($input)->formatForDisplay(), - "formatForDisplay({$input})"); + PhortuneCurrency::newFromString($input, 'USD')->formatForDisplay(), + "newFromString({$input})->formatForDisplay()"); } } @@ -25,22 +25,22 @@ // 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', + '0' => '0.00', + '.01' => '0.01', + '1.00' => '1.00', + '-1.23' => '-1.23', + '50000.00' => '50000.00', ); foreach ($map as $input => $expect) { $this->assertEqual( $expect, - PhortuneCurrency::newFromUSDCents($input)->formatBareValue(), - "formatBareValue({$input})"); + PhortuneCurrency::newFromString($input, 'USD')->formatBareValue(), + "newFromString({$input})->formatBareValue()"); } } - public function testCurrencyFromUserInput() { + public function testCurrencyFromString() { $map = array( '1.00' => 100, @@ -57,17 +57,15 @@ '$.99 USD' => 99, ); - $user = new PhabricatorUser(); - foreach ($map as $input => $expect) { $this->assertEqual( $expect, - PhortuneCurrency::newFromUserInput($user, $input)->getValue(), - "newFromUserInput({$input})->getValue()"); + PhortuneCurrency::newFromString($input, 'USD')->getValue(), + "newFromString({$input})->getValue()"); } } - public function testInvalidCurrencyFromUserInput() { + public function testInvalidCurrencyFromString() { $map = array( '--1', '$$1', @@ -77,12 +75,10 @@ '1 dollar', ); - $user = new PhabricatorUser(); - foreach ($map as $input) { $caught = null; try { - PhortuneCurrency::newFromUserInput($user, $input); + PhortuneCurrency::newFromString($input, 'USD'); } catch (Exception $ex) { $caught = $ex; } diff --git a/src/applications/phortune/editor/PhortuneProductEditor.php b/src/applications/phortune/editor/PhortuneProductEditor.php --- a/src/applications/phortune/editor/PhortuneProductEditor.php +++ b/src/applications/phortune/editor/PhortuneProductEditor.php @@ -16,7 +16,6 @@ $types = parent::getTransactionTypes(); $types[] = PhortuneProductTransaction::TYPE_NAME; - $types[] = PhortuneProductTransaction::TYPE_TYPE; $types[] = PhortuneProductTransaction::TYPE_PRICE; return $types; @@ -29,10 +28,8 @@ switch ($xaction->getTransactionType()) { case PhortuneProductTransaction::TYPE_NAME: return $object->getProductName(); - case PhortuneProductTransaction::TYPE_TYPE: - return $object->getProductType(); case PhortuneProductTransaction::TYPE_PRICE: - return $object->getPriceInCents(); + return $object->getPriceAsCurrency()->serializeForStorage(); } return parent::getCustomTransactionOldValue($object, $xaction); } @@ -42,7 +39,6 @@ PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhortuneProductTransaction::TYPE_NAME: - case PhortuneProductTransaction::TYPE_TYPE: case PhortuneProductTransaction::TYPE_PRICE: return $xaction->getNewValue(); } @@ -56,11 +52,9 @@ case PhortuneProductTransaction::TYPE_NAME: $object->setProductName($xaction->getNewValue()); return; - case PhortuneProductTransaction::TYPE_TYPE: - $object->setProductType($xaction->getNewValue()); - return; case PhortuneProductTransaction::TYPE_PRICE: - $object->setPriceInCents($xaction->getNewValue()); + $object->setPriceAsCurrency( + PhortuneCurrency::newFromString($xaction->getNewValue())); return; } return parent::applyCustomInternalTransaction($object, $xaction); @@ -71,7 +65,6 @@ PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhortuneProductTransaction::TYPE_NAME: - case PhortuneProductTransaction::TYPE_TYPE: case PhortuneProductTransaction::TYPE_PRICE: return; } diff --git a/src/applications/phortune/provider/PhortunePaypalPaymentProvider.php b/src/applications/phortune/provider/PhortunePaypalPaymentProvider.php --- a/src/applications/phortune/provider/PhortunePaypalPaymentProvider.php +++ b/src/applications/phortune/provider/PhortunePaypalPaymentProvider.php @@ -93,8 +93,7 @@ 'cartID' => $cart->getID(), )); - $total_in_cents = $cart->getTotalPriceInCents(); - $price = PhortuneCurrency::newFromUSDCents($total_in_cents); + $price = $cart->getTotalPriceAsCurrency(); $result = $this ->newPaypalAPICall() diff --git a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php --- a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php @@ -47,10 +47,12 @@ $root = dirname(phutil_get_library_root('phabricator')); require_once $root.'/externals/stripe-php/lib/Stripe.php'; + $price = $charge->getAmountAsCurrency(); + $secret_key = $this->getSecretKey(); $params = array( - 'amount' => $charge->getAmountInCents(), - 'currency' => 'usd', + 'amount' => $price->getValue(), + 'currency' => $price->getCurrency(), 'customer' => $method->getMetadataValue('stripe.customerID'), 'description' => $charge->getPHID(), 'capture' => true, diff --git a/src/applications/phortune/provider/PhortuneWePayPaymentProvider.php b/src/applications/phortune/provider/PhortuneWePayPaymentProvider.php --- a/src/applications/phortune/provider/PhortuneWePayPaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneWePayPaymentProvider.php @@ -116,8 +116,7 @@ 'cartID' => $cart->getID(), )); - $total_in_cents = $cart->getTotalPriceInCents(); - $price = PhortuneCurrency::newFromUSDCents($total_in_cents); + $price = $cart->getTotalPriceAsCurrency(); $params = array( 'account_id' => $this->getWePayAccountID(), @@ -176,10 +175,12 @@ $result->state)); } + $currency = PhortuneCurrency::newFromString($checkout->gross, 'USD'); + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $charge = id(new PhortuneCharge()) - ->setAmountInCents((int)$checkout->gross * 100) + ->setAmountAsCurrency($currency) ->setAccountPHID($cart->getAccount()->getPHID()) ->setAuthorPHID($viewer->getPHID()) ->setPaymentProviderKey($this->getProviderKey()) diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php --- a/src/applications/phortune/storage/PhortuneAccount.php +++ b/src/applications/phortune/storage/PhortuneAccount.php @@ -10,7 +10,6 @@ implements PhabricatorPolicyInterface { protected $name; - protected $balanceInCents = 0; private $memberPHIDs = self::ATTACHABLE; @@ -19,7 +18,6 @@ self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text255', - 'balanceInCents' => 'sint64', ), ) + parent::getConfiguration(); } diff --git a/src/applications/phortune/storage/PhortuneCart.php b/src/applications/phortune/storage/PhortuneCart.php --- a/src/applications/phortune/storage/PhortuneCart.php +++ b/src/applications/phortune/storage/PhortuneCart.php @@ -56,14 +56,13 @@ return $this->assertAttached($this->account); } - public function getTotalPriceInCents() { + public function getTotalPriceAsCurrency() { $prices = array(); foreach ($this->getPurchases() as $purchase) { - $prices[] = PhortuneCurrency::newFromUSDCents( - $purchase->getTotalPriceInCents()); + $prices[] = $purchase->getTotalPriceAsCurrency(); } - return PhortuneCurrency::newFromList($prices)->getValue(); + return PhortuneCurrency::newFromList($prices); } diff --git a/src/applications/phortune/storage/PhortuneCharge.php b/src/applications/phortune/storage/PhortuneCharge.php --- a/src/applications/phortune/storage/PhortuneCharge.php +++ b/src/applications/phortune/storage/PhortuneCharge.php @@ -20,7 +20,7 @@ protected $cartPHID; protected $paymentProviderKey; protected $paymentMethodPHID; - protected $amountInCents; + protected $amountAsCurrency; protected $status; protected $metadata = array(); @@ -33,10 +33,13 @@ self::CONFIG_SERIALIZATION => array( 'metadata' => self::SERIALIZATION_JSON, ), + self::CONFIG_APPLICATION_SERIALIZERS => array( + 'amountAsCurrency' => new PhortuneCurrencySerializer(), + ), self::CONFIG_COLUMN_SCHEMA => array( 'paymentProviderKey' => 'text128', 'paymentMethodPHID' => 'phid?', - 'amountInCents' => 'sint32', + 'amountAsCurrency' => 'text64', 'status' => 'text32', ), self::CONFIG_KEY_SCHEMA => array( @@ -55,11 +58,6 @@ PhabricatorPHIDConstants::PHID_TYPE_CHRG); } - protected function didReadData() { - // The payment processing code is strict about types. - $this->amountInCents = (int)$this->amountInCents; - } - public function getMetadataValue($key, $default = null) { return idx($this->metadata, $key, $default); } diff --git a/src/applications/phortune/storage/PhortuneProduct.php b/src/applications/phortune/storage/PhortuneProduct.php --- a/src/applications/phortune/storage/PhortuneProduct.php +++ b/src/applications/phortune/storage/PhortuneProduct.php @@ -1,24 +1,13 @@ array( 'metadata' => self::SERIALIZATION_JSON, ), + self::CONFIG_APPLICATION_SERIALIZERS => array( + 'priceAsCurrency' => new PhortuneCurrencySerializer(), + ), self::CONFIG_COLUMN_SCHEMA => array( 'productName' => 'text255', - 'productType' => 'text64', 'status' => 'text64', - 'priceInCents' => 'sint64', + 'priceAsCurrency' => 'text64', 'billingIntervalInMonths' => 'uint32?', 'trialPeriodInDays' => 'uint32?', ), - self::CONFIG_KEY_SCHEMA => array( - 'key_status' => array( - 'columns' => array('status'), - ), - ), ) + parent::getConfiguration(); } @@ -48,24 +34,9 @@ PhabricatorPHIDConstants::PHID_TYPE_PDCT); } - public static function getTypeMap() { - return array( - self::TYPE_BILL_ONCE => pht('Product (Charged Once)'), - self::TYPE_BILL_PLAN => pht('Flat Rate Plan (Charged Monthly)'), - ); - } - - public function getTypeName() { - return idx(self::getTypeMap(), $this->getProductType()); - } - - public function getPriceInCents() { - $price = parent::getPriceInCents(); - if ($price === null) { - return $price; - } else { - return (int)parent::getPriceInCents(); - } + public static function initializeNewProduct() { + return id(new PhortuneProduct()) + ->setPriceAsCurrency(PhortuneCurrency::newEmptyCurrency()); } diff --git a/src/applications/phortune/storage/PhortuneProductTransaction.php b/src/applications/phortune/storage/PhortuneProductTransaction.php --- a/src/applications/phortune/storage/PhortuneProductTransaction.php +++ b/src/applications/phortune/storage/PhortuneProductTransaction.php @@ -4,7 +4,6 @@ extends PhabricatorApplicationTransaction { const TYPE_NAME = 'product:name'; - const TYPE_TYPE = 'product:type'; const TYPE_PRICE = 'product:price'; public function getApplicationName() { @@ -44,33 +43,18 @@ return pht( '%s set product price to %s.', $this->renderHandleLink($author_phid), - PhortuneCurrency::newFromUSDCents($new) + PhortuneCurrency::newFromString($new) ->formatForDisplay()); } else { return pht( '%s changed product price from %s to %s.', $this->renderHandleLink($author_phid), - PhortuneCurrency::newFromUSDCents($old) + PhortuneCurrency::newFromString($old) ->formatForDisplay(), - PhortuneCurrency::newFromUSDCents($new) + PhortuneCurrency::newFromString($new) ->formatForDisplay()); } break; - case self::TYPE_TYPE: - $map = PhortuneProduct::getTypeMap(); - if ($old === null) { - return pht( - '%s set product type to "%s".', - $this->renderHandleLink($author_phid), - $map[$new]); - } else { - return pht( - '%s changed product type from "%s" to "%s".', - $this->renderHandleLink($author_phid), - $map[$old], - $map[$new]); - } - break; } return parent::getTitle(); diff --git a/src/applications/phortune/storage/PhortunePurchase.php b/src/applications/phortune/storage/PhortunePurchase.php --- a/src/applications/phortune/storage/PhortunePurchase.php +++ b/src/applications/phortune/storage/PhortunePurchase.php @@ -17,9 +17,8 @@ protected $accountPHID; protected $authorPHID; protected $cartPHID; - protected $basePriceInCents; + protected $basePriceAsCurrency; protected $quantity; - protected $totalPriceInCents; protected $status; protected $metadata; @@ -31,11 +30,13 @@ self::CONFIG_SERIALIZATION => array( 'metadata' => self::SERIALIZATION_JSON, ), + self::CONFIG_APPLICATION_SERIALIZERS => array( + 'basePriceAsCurrency' => new PhortuneCurrencySerializer(), + ), self::CONFIG_COLUMN_SCHEMA => array( 'cartPHID' => 'phid?', - 'basePriceInCents' => 'sint32', + 'basePriceAsCurrency' => 'text64', 'quantity' => 'uint32', - 'totalPriceInCents' => 'sint32', 'status' => 'text32', ), self::CONFIG_KEY_SCHEMA => array( @@ -60,16 +61,14 @@ return $this->assertAttached($this->cart); } - protected function didReadData() { - // The payment processing code is strict about types. - $this->basePriceInCents = (int)$this->basePriceInCents; - $this->totalPriceInCents = (int)$this->totalPriceInCents; - } - public function getFullDisplayName() { return pht('Goods and/or Services'); } + public function getTotalPriceAsCurrency() { + return $this->getBasePriceAsCurrency(); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php --- a/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php +++ b/src/infrastructure/storage/lisk/PhabricatorLiskDAO.php @@ -8,6 +8,7 @@ private static $namespaceStack = array(); const ATTACHABLE = ''; + const CONFIG_APPLICATION_SERIALIZERS = 'phabricator/serializers'; /* -( Configuring Storage )------------------------------------------------ */ @@ -209,14 +210,35 @@ return phutil_utf8ize($string); } - public function delete() { + protected function willReadData(array &$data) { + parent::willReadData($data); - // TODO: We should make some reasonable effort to destroy related - // infrastructure objects here, like edges, transactions, custom field - // storage, flags, Phrequent tracking, tokens, etc. This doesn't need to - // be exhaustive, but we can get a lot of it pretty easily. + static $custom; + if ($custom === null) { + $custom = $this->getConfigOption(self::CONFIG_APPLICATION_SERIALIZERS); + } - return parent::delete(); + if ($custom) { + foreach ($custom as $key => $serializer) { + $data[$key] = $serializer->willReadValue($data[$key]); + } + } } + protected function willWriteData(array &$data) { + static $custom; + if ($custom === null) { + $custom = $this->getConfigOption(self::CONFIG_APPLICATION_SERIALIZERS); + } + + if ($custom) { + foreach ($custom as $key => $serializer) { + $data[$key] = $serializer->willWriteValue($data[$key]); + } + } + + parent::willWriteData($data); + } + + } diff --git a/src/infrastructure/storage/lisk/PhabricatorLiskSerializer.php b/src/infrastructure/storage/lisk/PhabricatorLiskSerializer.php new file mode 100644 --- /dev/null +++ b/src/infrastructure/storage/lisk/PhabricatorLiskSerializer.php @@ -0,0 +1,8 @@ +