mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-18 12:52:42 +01:00
Make Currency a more formal type
Summary: Ref T2787. Phortune currently stores a bunch of stuff as `...inUSDCents`. This ends up being pretty cumbersome and I worry it will create a huge headache down the road (and possibly not that far off if we do Coinbase/Bitcoin soon). Even now, it's more of a pain than I figured it would be. Instead: - Provide an application-level serialization mechanism. - Provide currency serialization. - Store currency in an abstract way (currently, as "1.23 USD") that can handle currencies in the future. - Change all `...inUSDCents` to `..asCurrency`. - This generally simplifies all the application code. - Also remove some columns which don't make sense or don't make sense anymore. Notably, `Product` is going to get more abstract and mostly be provided by applications. Test Plan: - Created a new product. - Purchased a product. - Backed an initiative. - Ran unit tests. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T2787 Differential Revision: https://secure.phabricator.com/D10633
This commit is contained in:
parent
3463ce8a51
commit
f86f9dc512
36 changed files with 241 additions and 213 deletions
|
@ -1,18 +1,18 @@
|
||||||
CREATE TABLE {$NAMESPACE}_almanac.almanac_device (
|
CREATE TABLE {$NAMESPACE}_almanac.almanac_device (
|
||||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
phid VARBINARY(64) NOT NULL,
|
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,
|
dateCreated INT UNSIGNED NOT NULL,
|
||||||
dateModified INT UNSIGNED NOT NULL,
|
dateModified INT UNSIGNED NOT NULL,
|
||||||
UNIQUE KEY `key_phid` (phid)
|
UNIQUE KEY `key_phid` (phid)
|
||||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
) ENGINE=InnoDB, COLLATE utf8_bin;
|
||||||
|
|
||||||
CREATE TABLE {$NAMESPACE}_almanac.almanac_deviceproperty (
|
CREATE TABLE {$NAMESPACE}_almanac.almanac_deviceproperty (
|
||||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
devicePHID VARBINARY(64) NOT NULL,
|
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,
|
value LONGTEXT NOT NULL,
|
||||||
dateCreated INT UNSIGNED NOT NULL,
|
dateCreated INT UNSIGNED NOT NULL,
|
||||||
dateModified INT UNSIGNED NOT NULL,
|
dateModified INT UNSIGNED NOT NULL,
|
||||||
KEY `key_device` (devicePHID, `key`)
|
KEY `key_device` (devicePHID, `key`)
|
||||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
) ENGINE=InnoDB, COLLATE utf8_bin;
|
||||||
|
|
4
resources/sql/autopatches/20141004.currency.01.sql
Normal file
4
resources/sql/autopatches/20141004.currency.01.sql
Normal file
|
@ -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;
|
2
resources/sql/autopatches/20141004.currency.02.sql
Normal file
2
resources/sql/autopatches/20141004.currency.02.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_phortune.phortune_account
|
||||||
|
DROP balanceInCents;
|
4
resources/sql/autopatches/20141004.currency.03.sql
Normal file
4
resources/sql/autopatches/20141004.currency.03.sql
Normal file
|
@ -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;
|
13
resources/sql/autopatches/20141004.currency.04.sql
Normal file
13
resources/sql/autopatches/20141004.currency.04.sql
Normal file
|
@ -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;
|
8
resources/sql/autopatches/20141004.currency.05.sql
Normal file
8
resources/sql/autopatches/20141004.currency.05.sql
Normal file
|
@ -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;
|
2
resources/sql/autopatches/20141004.currency.06.sql
Normal file
2
resources/sql/autopatches/20141004.currency.06.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_phortune.phortune_product
|
||||||
|
DROP productType;
|
4
resources/sql/autopatches/20141004.harborliskcounter.sql
Normal file
4
resources/sql/autopatches/20141004.harborliskcounter.sql
Normal file
|
@ -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;
|
|
@ -1716,6 +1716,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php',
|
'PhabricatorLipsumManagementWorkflow' => 'applications/lipsum/management/PhabricatorLipsumManagementWorkflow.php',
|
||||||
'PhabricatorLipsumMondrianArtist' => 'applications/lipsum/image/PhabricatorLipsumMondrianArtist.php',
|
'PhabricatorLipsumMondrianArtist' => 'applications/lipsum/image/PhabricatorLipsumMondrianArtist.php',
|
||||||
'PhabricatorLiskDAO' => 'infrastructure/storage/lisk/PhabricatorLiskDAO.php',
|
'PhabricatorLiskDAO' => 'infrastructure/storage/lisk/PhabricatorLiskDAO.php',
|
||||||
|
'PhabricatorLiskSerializer' => 'infrastructure/storage/lisk/PhabricatorLiskSerializer.php',
|
||||||
'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php',
|
'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php',
|
||||||
'PhabricatorLocalTimeTestCase' => 'view/__tests__/PhabricatorLocalTimeTestCase.php',
|
'PhabricatorLocalTimeTestCase' => 'view/__tests__/PhabricatorLocalTimeTestCase.php',
|
||||||
'PhabricatorLogoutController' => 'applications/auth/controller/PhabricatorLogoutController.php',
|
'PhabricatorLogoutController' => 'applications/auth/controller/PhabricatorLogoutController.php',
|
||||||
|
@ -2561,6 +2562,7 @@ phutil_register_library_map(array(
|
||||||
'PhortuneController' => 'applications/phortune/controller/PhortuneController.php',
|
'PhortuneController' => 'applications/phortune/controller/PhortuneController.php',
|
||||||
'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php',
|
'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php',
|
||||||
'PhortuneCurrency' => 'applications/phortune/currency/PhortuneCurrency.php',
|
'PhortuneCurrency' => 'applications/phortune/currency/PhortuneCurrency.php',
|
||||||
|
'PhortuneCurrencySerializer' => 'applications/phortune/currency/PhortuneCurrencySerializer.php',
|
||||||
'PhortuneCurrencyTestCase' => 'applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php',
|
'PhortuneCurrencyTestCase' => 'applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php',
|
||||||
'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php',
|
'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php',
|
||||||
'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php',
|
'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php',
|
||||||
|
@ -5591,6 +5593,7 @@ phutil_register_library_map(array(
|
||||||
'PhortuneChargeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhortuneChargeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhortuneController' => 'PhabricatorController',
|
'PhortuneController' => 'PhabricatorController',
|
||||||
'PhortuneCurrency' => 'Phobject',
|
'PhortuneCurrency' => 'Phobject',
|
||||||
|
'PhortuneCurrencySerializer' => 'PhabricatorLiskSerializer',
|
||||||
'PhortuneCurrencyTestCase' => 'PhabricatorTestCase',
|
'PhortuneCurrencyTestCase' => 'PhabricatorTestCase',
|
||||||
'PhortuneDAO' => 'PhabricatorLiskDAO',
|
'PhortuneDAO' => 'PhabricatorLiskDAO',
|
||||||
'PhortuneErrCode' => 'PhortuneConstants',
|
'PhortuneErrCode' => 'PhortuneConstants',
|
||||||
|
|
|
@ -57,7 +57,7 @@ final class FundInitiativeBackController
|
||||||
$backer = FundBacker::initializeNewBacker($viewer)
|
$backer = FundBacker::initializeNewBacker($viewer)
|
||||||
->setInitiativePHID($initiative->getPHID())
|
->setInitiativePHID($initiative->getPHID())
|
||||||
->attachInitiative($initiative)
|
->attachInitiative($initiative)
|
||||||
->setAmountInCents($currency->getValue())
|
->setAmountAsCurrency($currency)
|
||||||
->save();
|
->save();
|
||||||
|
|
||||||
// TODO: Here, we'd create a purchase and cart.
|
// TODO: Here, we'd create a purchase and cart.
|
||||||
|
|
|
@ -128,8 +128,7 @@ final class FundBackerSearchEngine
|
||||||
foreach ($backers as $backer) {
|
foreach ($backers as $backer) {
|
||||||
$backer_handle = $handles[$backer->getBackerPHID()];
|
$backer_handle = $handles[$backer->getBackerPHID()];
|
||||||
|
|
||||||
$currency = PhortuneCurrency::newFromUSDCents(
|
$currency = $backer->getAmount();
|
||||||
$backer->getAmountInCents());
|
|
||||||
|
|
||||||
$header = pht(
|
$header = pht(
|
||||||
'%s for %s',
|
'%s for %s',
|
||||||
|
|
|
@ -7,7 +7,7 @@ final class FundBacker extends FundDAO
|
||||||
|
|
||||||
protected $initiativePHID;
|
protected $initiativePHID;
|
||||||
protected $backerPHID;
|
protected $backerPHID;
|
||||||
protected $amountInCents;
|
protected $amountAsCurrency;
|
||||||
protected $status;
|
protected $status;
|
||||||
protected $properties = array();
|
protected $properties = array();
|
||||||
|
|
||||||
|
@ -28,9 +28,12 @@ final class FundBacker extends FundDAO
|
||||||
self::CONFIG_SERIALIZATION => array(
|
self::CONFIG_SERIALIZATION => array(
|
||||||
'properties' => self::SERIALIZATION_JSON,
|
'properties' => self::SERIALIZATION_JSON,
|
||||||
),
|
),
|
||||||
|
self::CONFIG_APPLICATION_SERIALIZERS => array(
|
||||||
|
'amountAsCurrency' => new PhortuneCurrencySerializer(),
|
||||||
|
),
|
||||||
self::CONFIG_COLUMN_SCHEMA => array(
|
self::CONFIG_COLUMN_SCHEMA => array(
|
||||||
'status' => 'text32',
|
'status' => 'text32',
|
||||||
'amountInCents' => 'uint32',
|
'amountAsCurrency' => 'text64',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
'key_initiative' => array(
|
'key_initiative' => array(
|
||||||
|
@ -47,11 +50,6 @@ final class FundBacker extends FundDAO
|
||||||
return PhabricatorPHID::generateNewPHID(FundBackerPHIDType::TYPECONST);
|
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) {
|
public function getProperty($key, $default = null) {
|
||||||
return idx($this->properties, $key, $default);
|
return idx($this->properties, $key, $default);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,23 @@ final class HarbormasterSchemaSpec extends PhabricatorConfigSchemaSpec {
|
||||||
public function buildSchemata() {
|
public function buildSchemata() {
|
||||||
$this->buildEdgeSchemata(new HarbormasterBuildable());
|
$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(
|
$this->buildRawSchema(
|
||||||
id(new HarbormasterBuildable())->getApplicationName(),
|
id(new HarbormasterBuildable())->getApplicationName(),
|
||||||
'harbormaster_buildlogchunk',
|
'harbormaster_buildlogchunk',
|
||||||
|
|
|
@ -51,7 +51,7 @@ final class PhortuneAccountViewController extends PhortuneController {
|
||||||
->setObject($account)
|
->setObject($account)
|
||||||
->setUser($user);
|
->setUser($user);
|
||||||
|
|
||||||
$properties->addProperty(pht('Balance'), $account->getBalanceInCents());
|
$properties->addProperty(pht('Balance'), '-');
|
||||||
$properties->setActionList($actions);
|
$properties->setActionList($actions);
|
||||||
|
|
||||||
$payment_methods = $this->buildPaymentMethodsSection($account);
|
$payment_methods = $this->buildPaymentMethodsSection($account);
|
||||||
|
@ -189,8 +189,7 @@ final class PhortuneAccountViewController extends PhortuneController {
|
||||||
foreach ($cart->getPurchases() as $purchase) {
|
foreach ($cart->getPurchases() as $purchase) {
|
||||||
$id = $purchase->getID();
|
$id = $purchase->getID();
|
||||||
|
|
||||||
$price = $purchase->getTotalPriceInCents();
|
$price = $purchase->getTotalPriceAsCurrency()->formatForDisplay();
|
||||||
$price = PhortuneCurrency::newFromUSDCents($price)->formatForDisplay();
|
|
||||||
|
|
||||||
$purchase_link = phutil_tag(
|
$purchase_link = phutil_tag(
|
||||||
'a',
|
'a',
|
||||||
|
|
|
@ -59,7 +59,7 @@ final class PhortuneCartCheckoutController
|
||||||
->setAuthorPHID($viewer->getPHID())
|
->setAuthorPHID($viewer->getPHID())
|
||||||
->setPaymentProviderKey($provider->getProviderKey())
|
->setPaymentProviderKey($provider->getProviderKey())
|
||||||
->setPaymentMethodPHID($method->getPHID())
|
->setPaymentMethodPHID($method->getPHID())
|
||||||
->setAmountInCents($cart->getTotalPriceInCents())
|
->setAmountAsCurrency($cart->getTotalPriceAsCurrency())
|
||||||
->setStatus(PhortuneCharge::STATUS_PENDING);
|
->setStatus(PhortuneCharge::STATUS_PENDING);
|
||||||
|
|
||||||
$charge->openTransaction();
|
$charge->openTransaction();
|
||||||
|
|
|
@ -6,18 +6,13 @@ abstract class PhortuneCartController
|
||||||
protected function buildCartContents(PhortuneCart $cart) {
|
protected function buildCartContents(PhortuneCart $cart) {
|
||||||
|
|
||||||
$rows = array();
|
$rows = array();
|
||||||
$total = 0;
|
|
||||||
foreach ($cart->getPurchases() as $purchase) {
|
foreach ($cart->getPurchases() as $purchase) {
|
||||||
$rows[] = array(
|
$rows[] = array(
|
||||||
$purchase->getFullDisplayName(),
|
$purchase->getFullDisplayName(),
|
||||||
PhortuneCurrency::newFromUSDCents($purchase->getBasePriceInCents())
|
$purchase->getBasePriceAsCurrency()->formatForDisplay(),
|
||||||
->formatForDisplay(),
|
|
||||||
$purchase->getQuantity(),
|
$purchase->getQuantity(),
|
||||||
PhortuneCurrency::newFromUSDCents($purchase->getTotalPriceInCents())
|
$purchase->getTotalPriceAsCurrency()->formatForDisplay(),
|
||||||
->formatForDisplay(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$total += $purchase->getTotalPriceInCents();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows[] = array(
|
$rows[] = array(
|
||||||
|
@ -25,7 +20,7 @@ abstract class PhortuneCartController
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
phutil_tag('strong', array(),
|
phutil_tag('strong', array(),
|
||||||
PhortuneCurrency::newFromUSDCents($total)->formatForDisplay()),
|
$cart->getTotalPriceAsCurrency()->formatForDisplay()),
|
||||||
);
|
);
|
||||||
|
|
||||||
$table = new AphrontTableView($rows);
|
$table = new AphrontTableView($rows);
|
||||||
|
|
|
@ -73,8 +73,7 @@ abstract class PhortuneController extends PhabricatorController {
|
||||||
$cart_href,
|
$cart_href,
|
||||||
$charge->getPaymentProviderKey(),
|
$charge->getPaymentProviderKey(),
|
||||||
$charge->getPaymentMethodPHID(),
|
$charge->getPaymentMethodPHID(),
|
||||||
PhortuneCurrency::newFromUSDCents($charge->getAmountInCents())
|
$charge->getAmountAsCurrency()->formatForDisplay(),
|
||||||
->formatForDisplay(),
|
|
||||||
$charge->getStatus(),
|
$charge->getStatus(),
|
||||||
phabricator_datetime($charge->getDateCreated(), $viewer),
|
phabricator_datetime($charge->getDateCreated(), $viewer),
|
||||||
);
|
);
|
||||||
|
|
|
@ -25,19 +25,16 @@ final class PhortuneProductEditController extends PhabricatorController {
|
||||||
$cancel_uri = $this->getApplicationURI(
|
$cancel_uri = $this->getApplicationURI(
|
||||||
'product/view/'.$this->productID.'/');
|
'product/view/'.$this->productID.'/');
|
||||||
} else {
|
} else {
|
||||||
$product = new PhortuneProduct();
|
$product = PhortuneProduct::initializeNewProduct();
|
||||||
$is_create = true;
|
$is_create = true;
|
||||||
$cancel_uri = $this->getApplicationURI('product/');
|
$cancel_uri = $this->getApplicationURI('product/');
|
||||||
}
|
}
|
||||||
|
|
||||||
$v_name = $product->getProductName();
|
$v_name = $product->getProductName();
|
||||||
$v_type = $product->getProductType();
|
$v_price = $product->getPriceAsCurrency()->formatForDisplay();
|
||||||
$v_price = (int)$product->getPriceInCents();
|
$display_price = $v_price;
|
||||||
$display_price = PhortuneCurrency::newFromUSDCents($v_price)
|
|
||||||
->formatForDisplay();
|
|
||||||
|
|
||||||
$e_name = true;
|
$e_name = true;
|
||||||
$e_type = null;
|
|
||||||
$e_price = true;
|
$e_price = true;
|
||||||
$errors = array();
|
$errors = array();
|
||||||
|
|
||||||
|
@ -50,21 +47,10 @@ final class PhortuneProductEditController extends PhabricatorController {
|
||||||
$e_name = null;
|
$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');
|
$display_price = $request->getStr('price');
|
||||||
try {
|
try {
|
||||||
$v_price = PhortuneCurrency::newFromUserInput($user, $display_price)
|
$v_price = PhortuneCurrency::newFromUserInput($user, $display_price)
|
||||||
->getValue();
|
->serializeForStorage();
|
||||||
$e_price = null;
|
$e_price = null;
|
||||||
} catch (Exception $ex) {
|
} catch (Exception $ex) {
|
||||||
$errors[] = pht('Price should be formatted as: $1.23');
|
$errors[] = pht('Price should be formatted as: $1.23');
|
||||||
|
@ -78,10 +64,6 @@ final class PhortuneProductEditController extends PhabricatorController {
|
||||||
->setTransactionType(PhortuneProductTransaction::TYPE_NAME)
|
->setTransactionType(PhortuneProductTransaction::TYPE_NAME)
|
||||||
->setNewValue($v_name);
|
->setNewValue($v_name);
|
||||||
|
|
||||||
$xactions[] = id(new PhortuneProductTransaction())
|
|
||||||
->setTransactionType(PhortuneProductTransaction::TYPE_TYPE)
|
|
||||||
->setNewValue($v_type);
|
|
||||||
|
|
||||||
$xactions[] = id(new PhortuneProductTransaction())
|
$xactions[] = id(new PhortuneProductTransaction())
|
||||||
->setTransactionType(PhortuneProductTransaction::TYPE_PRICE)
|
->setTransactionType(PhortuneProductTransaction::TYPE_PRICE)
|
||||||
->setNewValue($v_price);
|
->setNewValue($v_price);
|
||||||
|
@ -111,14 +93,6 @@ final class PhortuneProductEditController extends PhabricatorController {
|
||||||
->setName('name')
|
->setName('name')
|
||||||
->setValue($v_name)
|
->setValue($v_name)
|
||||||
->setError($e_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(
|
->appendChild(
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
->setLabel(pht('Price'))
|
->setLabel(pht('Price'))
|
||||||
|
|
|
@ -32,15 +32,13 @@ final class PhortuneProductListController extends PhabricatorController {
|
||||||
$view_uri = $this->getApplicationURI(
|
$view_uri = $this->getApplicationURI(
|
||||||
'product/view/'.$product->getID().'/');
|
'product/view/'.$product->getID().'/');
|
||||||
|
|
||||||
$price = $product->getPriceInCents();
|
$price = $product->getPriceAsCurrency();
|
||||||
|
|
||||||
$item = id(new PHUIObjectItemView())
|
$item = id(new PHUIObjectItemView())
|
||||||
->setObjectName($product->getID())
|
->setObjectName($product->getID())
|
||||||
->setHeader($product->getProductName())
|
->setHeader($product->getProductName())
|
||||||
->setHref($view_uri)
|
->setHref($view_uri)
|
||||||
->addAttribute(
|
->addAttribute($price->formatForDisplay());
|
||||||
PhortuneCurrency::newFromUSDCents($price)->formatForDisplay())
|
|
||||||
->addAttribute($product->getTypeName());
|
|
||||||
|
|
||||||
$product_list->addItem($item);
|
$product_list->addItem($item);
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,10 +49,9 @@ final class PhortuneProductPurchaseController
|
||||||
$purchase->setAccountPHID($account->getPHID());
|
$purchase->setAccountPHID($account->getPHID());
|
||||||
$purchase->setAuthorPHID($user->getPHID());
|
$purchase->setAuthorPHID($user->getPHID());
|
||||||
$purchase->setCartPHID($cart->getPHID());
|
$purchase->setCartPHID($cart->getPHID());
|
||||||
$purchase->setBasePriceInCents($product->getPriceInCents());
|
$purchase->setBasePriceAsCurrency($product->getPriceAsCurrency());
|
||||||
$purchase->setQuantity(1);
|
$purchase->setQuantity(1);
|
||||||
$purchase->setTotalPriceInCents(
|
|
||||||
$purchase->getBasePriceInCents() * $purchase->getQuantity());
|
|
||||||
$purchase->setStatus(PhortunePurchase::STATUS_PENDING);
|
$purchase->setStatus(PhortunePurchase::STATUS_PENDING);
|
||||||
$purchase->save();
|
$purchase->save();
|
||||||
|
|
||||||
|
|
|
@ -60,11 +60,9 @@ final class PhortuneProductViewController extends PhortuneController {
|
||||||
$properties = id(new PHUIPropertyListView())
|
$properties = id(new PHUIPropertyListView())
|
||||||
->setUser($user)
|
->setUser($user)
|
||||||
->setActionList($actions)
|
->setActionList($actions)
|
||||||
->addProperty(pht('Type'), $product->getTypeName())
|
|
||||||
->addProperty(
|
->addProperty(
|
||||||
pht('Price'),
|
pht('Price'),
|
||||||
PhortuneCurrency::newFromUSDCents($product->getPriceInCents())
|
$product->getPriceAsCurrency()->formatForDisplay());
|
||||||
->formatForDisplay());
|
|
||||||
|
|
||||||
$xactions = id(new PhortuneProductTransactionQuery())
|
$xactions = id(new PhortuneProductTransactionQuery())
|
||||||
->setViewer($user)
|
->setViewer($user)
|
||||||
|
|
|
@ -9,7 +9,20 @@ final class PhortuneCurrency extends Phobject {
|
||||||
// Intentionally private.
|
// 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) {
|
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;
|
$matches = null;
|
||||||
$ok = preg_match(
|
$ok = preg_match(
|
||||||
'/^([-$]*(?:\d+)?(?:[.]\d{0,2})?)(?:\s+([A-Z]+))?$/',
|
'/^([-$]*(?:\d+)?(?:[.]\d{0,2})?)(?:\s+([A-Z]+))?$/',
|
||||||
|
@ -34,7 +47,7 @@ final class PhortuneCurrency extends Phobject {
|
||||||
$value = (float)$value;
|
$value = (float)$value;
|
||||||
$value = (int)round(100 * $value);
|
$value = (int)round(100 * $value);
|
||||||
|
|
||||||
$currency = idx($matches, 2, 'USD');
|
$currency = idx($matches, 2, $default);
|
||||||
if ($currency) {
|
if ($currency) {
|
||||||
switch ($currency) {
|
switch ($currency) {
|
||||||
case 'USD':
|
case 'USD':
|
||||||
|
@ -44,6 +57,10 @@ final class PhortuneCurrency extends Phobject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return self::newFromValueAndCurrency($value, $currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function newFromValueAndCurrency($value, $currency) {
|
||||||
$obj = new PhortuneCurrency();
|
$obj = new PhortuneCurrency();
|
||||||
|
|
||||||
$obj->value = $value;
|
$obj->value = $value;
|
||||||
|
@ -56,31 +73,34 @@ final class PhortuneCurrency extends Phobject {
|
||||||
assert_instances_of($list, 'PhortuneCurrency');
|
assert_instances_of($list, 'PhortuneCurrency');
|
||||||
|
|
||||||
$total = 0;
|
$total = 0;
|
||||||
|
$currency = null;
|
||||||
foreach ($list as $item) {
|
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.
|
// TODO: This should check for integer overflows, etc.
|
||||||
$total += $item->getValue();
|
$total += $item->getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
return PhortuneCurrency::newFromUSDCents($total);
|
return PhortuneCurrency::newFromValueAndCurrency(
|
||||||
}
|
$total,
|
||||||
|
self::getDefaultCurrency());
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function formatForDisplay() {
|
public function formatForDisplay() {
|
||||||
$bare = $this->formatBareValue();
|
$bare = $this->formatBareValue();
|
||||||
return '$'.$bare.' USD';
|
return '$'.$bare.' '.$this->currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function serializeForStorage() {
|
||||||
|
return $this->formatBareValue().' '.$this->currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function formatBareValue() {
|
public function formatBareValue() {
|
||||||
|
@ -88,8 +108,8 @@ final class PhortuneCurrency extends Phobject {
|
||||||
case 'USD':
|
case 'USD':
|
||||||
return sprintf('%.02f', $this->value / 100);
|
return sprintf('%.02f', $this->value / 100);
|
||||||
default:
|
default:
|
||||||
throw new Exception('Unsupported currency!');
|
throw new Exception(
|
||||||
|
pht('Unsupported currency ("%s")!', $this->currency));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,4 +125,6 @@ final class PhortuneCurrency extends Phobject {
|
||||||
throw new Exception("Invalid currency format ('{$string}').");
|
throw new Exception("Invalid currency format ('{$string}').");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhortuneCurrencySerializer extends PhabricatorLiskSerializer {
|
||||||
|
|
||||||
|
public function willReadValue($value) {
|
||||||
|
return PhortuneCurrency::newFromString($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function willWriteValue($value) {
|
||||||
|
if (!($value instanceof PhortuneCurrency)) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Trying to save object with a currency column, but the column '.
|
||||||
|
'value is not a PhortuneCurrency object.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value->serializeForStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,18 +4,18 @@ final class PhortuneCurrencyTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
public function testCurrencyFormatForDisplay() {
|
public function testCurrencyFormatForDisplay() {
|
||||||
$map = array(
|
$map = array(
|
||||||
0 => '$0.00 USD',
|
'0' => '$0.00 USD',
|
||||||
1 => '$0.01 USD',
|
'.01' => '$0.01 USD',
|
||||||
100 => '$1.00 USD',
|
'1.00' => '$1.00 USD',
|
||||||
-123 => '$-1.23 USD',
|
'-1.23' => '$-1.23 USD',
|
||||||
5000000 => '$50000.00 USD',
|
'50000.00' => '$50000.00 USD',
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($map as $input => $expect) {
|
foreach ($map as $input => $expect) {
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
$expect,
|
$expect,
|
||||||
PhortuneCurrency::newFromUSDCents($input)->formatForDisplay(),
|
PhortuneCurrency::newFromString($input, 'USD')->formatForDisplay(),
|
||||||
"formatForDisplay({$input})");
|
"newFromString({$input})->formatForDisplay()");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,22 +25,22 @@ final class PhortuneCurrencyTestCase extends PhabricatorTestCase {
|
||||||
// NOTE: The PayPal API depends on the behavior of the bare value format!
|
// NOTE: The PayPal API depends on the behavior of the bare value format!
|
||||||
|
|
||||||
$map = array(
|
$map = array(
|
||||||
0 => '0.00',
|
'0' => '0.00',
|
||||||
1 => '0.01',
|
'.01' => '0.01',
|
||||||
100 => '1.00',
|
'1.00' => '1.00',
|
||||||
-123 => '-1.23',
|
'-1.23' => '-1.23',
|
||||||
5000000 => '50000.00',
|
'50000.00' => '50000.00',
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($map as $input => $expect) {
|
foreach ($map as $input => $expect) {
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
$expect,
|
$expect,
|
||||||
PhortuneCurrency::newFromUSDCents($input)->formatBareValue(),
|
PhortuneCurrency::newFromString($input, 'USD')->formatBareValue(),
|
||||||
"formatBareValue({$input})");
|
"newFromString({$input})->formatBareValue()");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCurrencyFromUserInput() {
|
public function testCurrencyFromString() {
|
||||||
|
|
||||||
$map = array(
|
$map = array(
|
||||||
'1.00' => 100,
|
'1.00' => 100,
|
||||||
|
@ -57,17 +57,15 @@ final class PhortuneCurrencyTestCase extends PhabricatorTestCase {
|
||||||
'$.99 USD' => 99,
|
'$.99 USD' => 99,
|
||||||
);
|
);
|
||||||
|
|
||||||
$user = new PhabricatorUser();
|
|
||||||
|
|
||||||
foreach ($map as $input => $expect) {
|
foreach ($map as $input => $expect) {
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
$expect,
|
$expect,
|
||||||
PhortuneCurrency::newFromUserInput($user, $input)->getValue(),
|
PhortuneCurrency::newFromString($input, 'USD')->getValue(),
|
||||||
"newFromUserInput({$input})->getValue()");
|
"newFromString({$input})->getValue()");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInvalidCurrencyFromUserInput() {
|
public function testInvalidCurrencyFromString() {
|
||||||
$map = array(
|
$map = array(
|
||||||
'--1',
|
'--1',
|
||||||
'$$1',
|
'$$1',
|
||||||
|
@ -77,12 +75,10 @@ final class PhortuneCurrencyTestCase extends PhabricatorTestCase {
|
||||||
'1 dollar',
|
'1 dollar',
|
||||||
);
|
);
|
||||||
|
|
||||||
$user = new PhabricatorUser();
|
|
||||||
|
|
||||||
foreach ($map as $input) {
|
foreach ($map as $input) {
|
||||||
$caught = null;
|
$caught = null;
|
||||||
try {
|
try {
|
||||||
PhortuneCurrency::newFromUserInput($user, $input);
|
PhortuneCurrency::newFromString($input, 'USD');
|
||||||
} catch (Exception $ex) {
|
} catch (Exception $ex) {
|
||||||
$caught = $ex;
|
$caught = $ex;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ final class PhortuneProductEditor
|
||||||
$types = parent::getTransactionTypes();
|
$types = parent::getTransactionTypes();
|
||||||
|
|
||||||
$types[] = PhortuneProductTransaction::TYPE_NAME;
|
$types[] = PhortuneProductTransaction::TYPE_NAME;
|
||||||
$types[] = PhortuneProductTransaction::TYPE_TYPE;
|
|
||||||
$types[] = PhortuneProductTransaction::TYPE_PRICE;
|
$types[] = PhortuneProductTransaction::TYPE_PRICE;
|
||||||
|
|
||||||
return $types;
|
return $types;
|
||||||
|
@ -29,10 +28,8 @@ final class PhortuneProductEditor
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case PhortuneProductTransaction::TYPE_NAME:
|
case PhortuneProductTransaction::TYPE_NAME:
|
||||||
return $object->getProductName();
|
return $object->getProductName();
|
||||||
case PhortuneProductTransaction::TYPE_TYPE:
|
|
||||||
return $object->getProductType();
|
|
||||||
case PhortuneProductTransaction::TYPE_PRICE:
|
case PhortuneProductTransaction::TYPE_PRICE:
|
||||||
return $object->getPriceInCents();
|
return $object->getPriceAsCurrency()->serializeForStorage();
|
||||||
}
|
}
|
||||||
return parent::getCustomTransactionOldValue($object, $xaction);
|
return parent::getCustomTransactionOldValue($object, $xaction);
|
||||||
}
|
}
|
||||||
|
@ -42,7 +39,6 @@ final class PhortuneProductEditor
|
||||||
PhabricatorApplicationTransaction $xaction) {
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case PhortuneProductTransaction::TYPE_NAME:
|
case PhortuneProductTransaction::TYPE_NAME:
|
||||||
case PhortuneProductTransaction::TYPE_TYPE:
|
|
||||||
case PhortuneProductTransaction::TYPE_PRICE:
|
case PhortuneProductTransaction::TYPE_PRICE:
|
||||||
return $xaction->getNewValue();
|
return $xaction->getNewValue();
|
||||||
}
|
}
|
||||||
|
@ -56,11 +52,9 @@ final class PhortuneProductEditor
|
||||||
case PhortuneProductTransaction::TYPE_NAME:
|
case PhortuneProductTransaction::TYPE_NAME:
|
||||||
$object->setProductName($xaction->getNewValue());
|
$object->setProductName($xaction->getNewValue());
|
||||||
return;
|
return;
|
||||||
case PhortuneProductTransaction::TYPE_TYPE:
|
|
||||||
$object->setProductType($xaction->getNewValue());
|
|
||||||
return;
|
|
||||||
case PhortuneProductTransaction::TYPE_PRICE:
|
case PhortuneProductTransaction::TYPE_PRICE:
|
||||||
$object->setPriceInCents($xaction->getNewValue());
|
$object->setPriceAsCurrency(
|
||||||
|
PhortuneCurrency::newFromString($xaction->getNewValue()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return parent::applyCustomInternalTransaction($object, $xaction);
|
return parent::applyCustomInternalTransaction($object, $xaction);
|
||||||
|
@ -71,7 +65,6 @@ final class PhortuneProductEditor
|
||||||
PhabricatorApplicationTransaction $xaction) {
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
switch ($xaction->getTransactionType()) {
|
switch ($xaction->getTransactionType()) {
|
||||||
case PhortuneProductTransaction::TYPE_NAME:
|
case PhortuneProductTransaction::TYPE_NAME:
|
||||||
case PhortuneProductTransaction::TYPE_TYPE:
|
|
||||||
case PhortuneProductTransaction::TYPE_PRICE:
|
case PhortuneProductTransaction::TYPE_PRICE:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,8 +93,7 @@ final class PhortunePaypalPaymentProvider extends PhortunePaymentProvider {
|
||||||
'cartID' => $cart->getID(),
|
'cartID' => $cart->getID(),
|
||||||
));
|
));
|
||||||
|
|
||||||
$total_in_cents = $cart->getTotalPriceInCents();
|
$price = $cart->getTotalPriceAsCurrency();
|
||||||
$price = PhortuneCurrency::newFromUSDCents($total_in_cents);
|
|
||||||
|
|
||||||
$result = $this
|
$result = $this
|
||||||
->newPaypalAPICall()
|
->newPaypalAPICall()
|
||||||
|
|
|
@ -47,10 +47,12 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider {
|
||||||
$root = dirname(phutil_get_library_root('phabricator'));
|
$root = dirname(phutil_get_library_root('phabricator'));
|
||||||
require_once $root.'/externals/stripe-php/lib/Stripe.php';
|
require_once $root.'/externals/stripe-php/lib/Stripe.php';
|
||||||
|
|
||||||
|
$price = $charge->getAmountAsCurrency();
|
||||||
|
|
||||||
$secret_key = $this->getSecretKey();
|
$secret_key = $this->getSecretKey();
|
||||||
$params = array(
|
$params = array(
|
||||||
'amount' => $charge->getAmountInCents(),
|
'amount' => $price->getValue(),
|
||||||
'currency' => 'usd',
|
'currency' => $price->getCurrency(),
|
||||||
'customer' => $method->getMetadataValue('stripe.customerID'),
|
'customer' => $method->getMetadataValue('stripe.customerID'),
|
||||||
'description' => $charge->getPHID(),
|
'description' => $charge->getPHID(),
|
||||||
'capture' => true,
|
'capture' => true,
|
||||||
|
|
|
@ -116,8 +116,7 @@ final class PhortuneWePayPaymentProvider extends PhortunePaymentProvider {
|
||||||
'cartID' => $cart->getID(),
|
'cartID' => $cart->getID(),
|
||||||
));
|
));
|
||||||
|
|
||||||
$total_in_cents = $cart->getTotalPriceInCents();
|
$price = $cart->getTotalPriceAsCurrency();
|
||||||
$price = PhortuneCurrency::newFromUSDCents($total_in_cents);
|
|
||||||
|
|
||||||
$params = array(
|
$params = array(
|
||||||
'account_id' => $this->getWePayAccountID(),
|
'account_id' => $this->getWePayAccountID(),
|
||||||
|
@ -176,10 +175,12 @@ final class PhortuneWePayPaymentProvider extends PhortunePaymentProvider {
|
||||||
$result->state));
|
$result->state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$currency = PhortuneCurrency::newFromString($checkout->gross, 'USD');
|
||||||
|
|
||||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||||
|
|
||||||
$charge = id(new PhortuneCharge())
|
$charge = id(new PhortuneCharge())
|
||||||
->setAmountInCents((int)$checkout->gross * 100)
|
->setAmountAsCurrency($currency)
|
||||||
->setAccountPHID($cart->getAccount()->getPHID())
|
->setAccountPHID($cart->getAccount()->getPHID())
|
||||||
->setAuthorPHID($viewer->getPHID())
|
->setAuthorPHID($viewer->getPHID())
|
||||||
->setPaymentProviderKey($this->getProviderKey())
|
->setPaymentProviderKey($this->getProviderKey())
|
||||||
|
|
|
@ -10,7 +10,6 @@ final class PhortuneAccount extends PhortuneDAO
|
||||||
implements PhabricatorPolicyInterface {
|
implements PhabricatorPolicyInterface {
|
||||||
|
|
||||||
protected $name;
|
protected $name;
|
||||||
protected $balanceInCents = 0;
|
|
||||||
|
|
||||||
private $memberPHIDs = self::ATTACHABLE;
|
private $memberPHIDs = self::ATTACHABLE;
|
||||||
|
|
||||||
|
@ -19,7 +18,6 @@ final class PhortuneAccount extends PhortuneDAO
|
||||||
self::CONFIG_AUX_PHID => true,
|
self::CONFIG_AUX_PHID => true,
|
||||||
self::CONFIG_COLUMN_SCHEMA => array(
|
self::CONFIG_COLUMN_SCHEMA => array(
|
||||||
'name' => 'text255',
|
'name' => 'text255',
|
||||||
'balanceInCents' => 'sint64',
|
|
||||||
),
|
),
|
||||||
) + parent::getConfiguration();
|
) + parent::getConfiguration();
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,14 +56,13 @@ final class PhortuneCart extends PhortuneDAO
|
||||||
return $this->assertAttached($this->account);
|
return $this->assertAttached($this->account);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTotalPriceInCents() {
|
public function getTotalPriceAsCurrency() {
|
||||||
$prices = array();
|
$prices = array();
|
||||||
foreach ($this->getPurchases() as $purchase) {
|
foreach ($this->getPurchases() as $purchase) {
|
||||||
$prices[] = PhortuneCurrency::newFromUSDCents(
|
$prices[] = $purchase->getTotalPriceAsCurrency();
|
||||||
$purchase->getTotalPriceInCents());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return PhortuneCurrency::newFromList($prices)->getValue();
|
return PhortuneCurrency::newFromList($prices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ final class PhortuneCharge extends PhortuneDAO
|
||||||
protected $cartPHID;
|
protected $cartPHID;
|
||||||
protected $paymentProviderKey;
|
protected $paymentProviderKey;
|
||||||
protected $paymentMethodPHID;
|
protected $paymentMethodPHID;
|
||||||
protected $amountInCents;
|
protected $amountAsCurrency;
|
||||||
protected $status;
|
protected $status;
|
||||||
protected $metadata = array();
|
protected $metadata = array();
|
||||||
|
|
||||||
|
@ -33,10 +33,13 @@ final class PhortuneCharge extends PhortuneDAO
|
||||||
self::CONFIG_SERIALIZATION => array(
|
self::CONFIG_SERIALIZATION => array(
|
||||||
'metadata' => self::SERIALIZATION_JSON,
|
'metadata' => self::SERIALIZATION_JSON,
|
||||||
),
|
),
|
||||||
|
self::CONFIG_APPLICATION_SERIALIZERS => array(
|
||||||
|
'amountAsCurrency' => new PhortuneCurrencySerializer(),
|
||||||
|
),
|
||||||
self::CONFIG_COLUMN_SCHEMA => array(
|
self::CONFIG_COLUMN_SCHEMA => array(
|
||||||
'paymentProviderKey' => 'text128',
|
'paymentProviderKey' => 'text128',
|
||||||
'paymentMethodPHID' => 'phid?',
|
'paymentMethodPHID' => 'phid?',
|
||||||
'amountInCents' => 'sint32',
|
'amountAsCurrency' => 'text64',
|
||||||
'status' => 'text32',
|
'status' => 'text32',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
|
@ -55,11 +58,6 @@ final class PhortuneCharge extends PhortuneDAO
|
||||||
PhabricatorPHIDConstants::PHID_TYPE_CHRG);
|
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) {
|
public function getMetadataValue($key, $default = null) {
|
||||||
return idx($this->metadata, $key, $default);
|
return idx($this->metadata, $key, $default);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A product is something users can purchase. It may be a one-time purchase,
|
* A product is something users can purchase.
|
||||||
* or a plan which is billed monthly.
|
|
||||||
*/
|
*/
|
||||||
final class PhortuneProduct extends PhortuneDAO
|
final class PhortuneProduct extends PhortuneDAO
|
||||||
implements PhabricatorPolicyInterface {
|
implements PhabricatorPolicyInterface {
|
||||||
|
|
||||||
const TYPE_BILL_ONCE = 'phortune:thing';
|
|
||||||
const TYPE_BILL_PLAN = 'phortune:plan';
|
|
||||||
|
|
||||||
const STATUS_ACTIVE = 'product:active';
|
|
||||||
const STATUS_DISABLED = 'product:disabled';
|
|
||||||
|
|
||||||
protected $productName;
|
protected $productName;
|
||||||
protected $productType;
|
protected $priceAsCurrency;
|
||||||
protected $status = self::STATUS_ACTIVE;
|
|
||||||
protected $priceInCents;
|
|
||||||
protected $billingIntervalInMonths;
|
|
||||||
protected $trialPeriodInDays;
|
|
||||||
protected $metadata;
|
protected $metadata;
|
||||||
|
|
||||||
public function getConfiguration() {
|
public function getConfiguration() {
|
||||||
|
@ -27,19 +16,16 @@ final class PhortuneProduct extends PhortuneDAO
|
||||||
self::CONFIG_SERIALIZATION => array(
|
self::CONFIG_SERIALIZATION => array(
|
||||||
'metadata' => self::SERIALIZATION_JSON,
|
'metadata' => self::SERIALIZATION_JSON,
|
||||||
),
|
),
|
||||||
|
self::CONFIG_APPLICATION_SERIALIZERS => array(
|
||||||
|
'priceAsCurrency' => new PhortuneCurrencySerializer(),
|
||||||
|
),
|
||||||
self::CONFIG_COLUMN_SCHEMA => array(
|
self::CONFIG_COLUMN_SCHEMA => array(
|
||||||
'productName' => 'text255',
|
'productName' => 'text255',
|
||||||
'productType' => 'text64',
|
|
||||||
'status' => 'text64',
|
'status' => 'text64',
|
||||||
'priceInCents' => 'sint64',
|
'priceAsCurrency' => 'text64',
|
||||||
'billingIntervalInMonths' => 'uint32?',
|
'billingIntervalInMonths' => 'uint32?',
|
||||||
'trialPeriodInDays' => 'uint32?',
|
'trialPeriodInDays' => 'uint32?',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
|
||||||
'key_status' => array(
|
|
||||||
'columns' => array('status'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
) + parent::getConfiguration();
|
) + parent::getConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,24 +34,9 @@ final class PhortuneProduct extends PhortuneDAO
|
||||||
PhabricatorPHIDConstants::PHID_TYPE_PDCT);
|
PhabricatorPHIDConstants::PHID_TYPE_PDCT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getTypeMap() {
|
public static function initializeNewProduct() {
|
||||||
return array(
|
return id(new PhortuneProduct())
|
||||||
self::TYPE_BILL_ONCE => pht('Product (Charged Once)'),
|
->setPriceAsCurrency(PhortuneCurrency::newEmptyCurrency());
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ final class PhortuneProductTransaction
|
||||||
extends PhabricatorApplicationTransaction {
|
extends PhabricatorApplicationTransaction {
|
||||||
|
|
||||||
const TYPE_NAME = 'product:name';
|
const TYPE_NAME = 'product:name';
|
||||||
const TYPE_TYPE = 'product:type';
|
|
||||||
const TYPE_PRICE = 'product:price';
|
const TYPE_PRICE = 'product:price';
|
||||||
|
|
||||||
public function getApplicationName() {
|
public function getApplicationName() {
|
||||||
|
@ -44,33 +43,18 @@ final class PhortuneProductTransaction
|
||||||
return pht(
|
return pht(
|
||||||
'%s set product price to %s.',
|
'%s set product price to %s.',
|
||||||
$this->renderHandleLink($author_phid),
|
$this->renderHandleLink($author_phid),
|
||||||
PhortuneCurrency::newFromUSDCents($new)
|
PhortuneCurrency::newFromString($new)
|
||||||
->formatForDisplay());
|
->formatForDisplay());
|
||||||
} else {
|
} else {
|
||||||
return pht(
|
return pht(
|
||||||
'%s changed product price from %s to %s.',
|
'%s changed product price from %s to %s.',
|
||||||
$this->renderHandleLink($author_phid),
|
$this->renderHandleLink($author_phid),
|
||||||
PhortuneCurrency::newFromUSDCents($old)
|
PhortuneCurrency::newFromString($old)
|
||||||
->formatForDisplay(),
|
->formatForDisplay(),
|
||||||
PhortuneCurrency::newFromUSDCents($new)
|
PhortuneCurrency::newFromString($new)
|
||||||
->formatForDisplay());
|
->formatForDisplay());
|
||||||
}
|
}
|
||||||
break;
|
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();
|
return parent::getTitle();
|
||||||
|
|
|
@ -17,9 +17,8 @@ final class PhortunePurchase extends PhortuneDAO
|
||||||
protected $accountPHID;
|
protected $accountPHID;
|
||||||
protected $authorPHID;
|
protected $authorPHID;
|
||||||
protected $cartPHID;
|
protected $cartPHID;
|
||||||
protected $basePriceInCents;
|
protected $basePriceAsCurrency;
|
||||||
protected $quantity;
|
protected $quantity;
|
||||||
protected $totalPriceInCents;
|
|
||||||
protected $status;
|
protected $status;
|
||||||
protected $metadata;
|
protected $metadata;
|
||||||
|
|
||||||
|
@ -31,11 +30,13 @@ final class PhortunePurchase extends PhortuneDAO
|
||||||
self::CONFIG_SERIALIZATION => array(
|
self::CONFIG_SERIALIZATION => array(
|
||||||
'metadata' => self::SERIALIZATION_JSON,
|
'metadata' => self::SERIALIZATION_JSON,
|
||||||
),
|
),
|
||||||
|
self::CONFIG_APPLICATION_SERIALIZERS => array(
|
||||||
|
'basePriceAsCurrency' => new PhortuneCurrencySerializer(),
|
||||||
|
),
|
||||||
self::CONFIG_COLUMN_SCHEMA => array(
|
self::CONFIG_COLUMN_SCHEMA => array(
|
||||||
'cartPHID' => 'phid?',
|
'cartPHID' => 'phid?',
|
||||||
'basePriceInCents' => 'sint32',
|
'basePriceAsCurrency' => 'text64',
|
||||||
'quantity' => 'uint32',
|
'quantity' => 'uint32',
|
||||||
'totalPriceInCents' => 'sint32',
|
|
||||||
'status' => 'text32',
|
'status' => 'text32',
|
||||||
),
|
),
|
||||||
self::CONFIG_KEY_SCHEMA => array(
|
self::CONFIG_KEY_SCHEMA => array(
|
||||||
|
@ -60,16 +61,14 @@ final class PhortunePurchase extends PhortuneDAO
|
||||||
return $this->assertAttached($this->cart);
|
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() {
|
public function getFullDisplayName() {
|
||||||
return pht('Goods and/or Services');
|
return pht('Goods and/or Services');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getTotalPriceAsCurrency() {
|
||||||
|
return $this->getBasePriceAsCurrency();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ abstract class PhabricatorLiskDAO extends LiskDAO {
|
||||||
private static $namespaceStack = array();
|
private static $namespaceStack = array();
|
||||||
|
|
||||||
const ATTACHABLE = '<attachable>';
|
const ATTACHABLE = '<attachable>';
|
||||||
|
const CONFIG_APPLICATION_SERIALIZERS = 'phabricator/serializers';
|
||||||
|
|
||||||
/* -( Configuring Storage )------------------------------------------------ */
|
/* -( Configuring Storage )------------------------------------------------ */
|
||||||
|
|
||||||
|
@ -209,14 +210,35 @@ abstract class PhabricatorLiskDAO extends LiskDAO {
|
||||||
return phutil_utf8ize($string);
|
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
|
static $custom;
|
||||||
// infrastructure objects here, like edges, transactions, custom field
|
if ($custom === null) {
|
||||||
// storage, flags, Phrequent tracking, tokens, etc. This doesn't need to
|
$custom = $this->getConfigOption(self::CONFIG_APPLICATION_SERIALIZERS);
|
||||||
// be exhaustive, but we can get a lot of it pretty easily.
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class PhabricatorLiskSerializer {
|
||||||
|
|
||||||
|
abstract public function willReadValue($value);
|
||||||
|
abstract public function willWriteValue($value);
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue