1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-10 00:42:41 +01:00

Move Phortune product logic into applications

Summary: Ref T2787. `Product` is currently a fairly heavy object, but as Phortune develops it makes a lot of sense to make it a lighter object and put more product logic in applications. Convert it into a fairly lightweight reference to applications. The idea is that Phortune is mostly providing a cart flow, and applications manage the details of products.

Test Plan: Funded an initiative for $1.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T2787

Differential Revision: https://secure.phabricator.com/D10634
This commit is contained in:
epriestley 2014-10-06 10:30:06 -07:00
parent f86f9dc512
commit e9615b74a5
19 changed files with 338 additions and 460 deletions

View file

@ -0,0 +1,22 @@
DROP TABLE {$NAMESPACE}_phortune.phortune_producttransaction;
ALTER TABLE {$NAMESPACE}_phortune.phortune_product
DROP productName;
ALTER TABLE {$NAMESPACE}_phortune.phortune_product
DROP priceAsCurrency;
ALTER TABLE {$NAMESPACE}_phortune.phortune_product
ADD productClassKey BINARY(12) NOT NULL;
ALTER TABLE {$NAMESPACE}_phortune.phortune_product
ADD productClass VARCHAR(128) NOT NULL COLLATE utf8_bin;
ALTER TABLE {$NAMESPACE}_phortune.phortune_product
ADD productRefKey BINARY(12) NOT NULL;
ALTER TABLE {$NAMESPACE}_phortune.phortune_product
ADD productRef VARCHAR(128) NOT NULL COLLATE utf8_bin;
ALTER TABLE {$NAMESPACE}_phortune.phortune_product
ADD UNIQUE KEY `key_product` (productClassKey, productRefKey);

View file

@ -669,6 +669,7 @@ phutil_register_library_map(array(
'FundBackerEditor' => 'applications/fund/editor/FundBackerEditor.php',
'FundBackerListController' => 'applications/fund/controller/FundBackerListController.php',
'FundBackerPHIDType' => 'applications/fund/phid/FundBackerPHIDType.php',
'FundBackerProduct' => 'applications/fund/phortune/FundBackerProduct.php',
'FundBackerQuery' => 'applications/fund/query/FundBackerQuery.php',
'FundBackerSearchEngine' => 'applications/fund/query/FundBackerSearchEngine.php',
'FundBackerTransaction' => 'applications/fund/storage/FundBackerTransaction.php',
@ -2580,13 +2581,9 @@ phutil_register_library_map(array(
'PhortunePaymentProviderTestCase' => 'applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php',
'PhortunePaypalPaymentProvider' => 'applications/phortune/provider/PhortunePaypalPaymentProvider.php',
'PhortuneProduct' => 'applications/phortune/storage/PhortuneProduct.php',
'PhortuneProductEditController' => 'applications/phortune/controller/PhortuneProductEditController.php',
'PhortuneProductEditor' => 'applications/phortune/editor/PhortuneProductEditor.php',
'PhortuneProductImplementation' => 'applications/phortune/product/PhortuneProductImplementation.php',
'PhortuneProductListController' => 'applications/phortune/controller/PhortuneProductListController.php',
'PhortuneProductPurchaseController' => 'applications/phortune/controller/PhortuneProductPurchaseController.php',
'PhortuneProductQuery' => 'applications/phortune/query/PhortuneProductQuery.php',
'PhortuneProductTransaction' => 'applications/phortune/storage/PhortuneProductTransaction.php',
'PhortuneProductTransactionQuery' => 'applications/phortune/query/PhortuneProductTransactionQuery.php',
'PhortuneProductViewController' => 'applications/phortune/controller/PhortuneProductViewController.php',
'PhortuneProviderController' => 'applications/phortune/controller/PhortuneProviderController.php',
'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php',
@ -3520,6 +3517,7 @@ phutil_register_library_map(array(
'FundBackerEditor' => 'PhabricatorApplicationTransactionEditor',
'FundBackerListController' => 'FundController',
'FundBackerPHIDType' => 'PhabricatorPHIDType',
'FundBackerProduct' => 'PhortuneProductImplementation',
'FundBackerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'FundBackerSearchEngine' => 'PhabricatorApplicationSearchEngine',
'FundBackerTransaction' => 'PhabricatorApplicationTransaction',
@ -5616,13 +5614,8 @@ phutil_register_library_map(array(
'PhortuneDAO',
'PhabricatorPolicyInterface',
),
'PhortuneProductEditController' => 'PhabricatorController',
'PhortuneProductEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortuneProductListController' => 'PhabricatorController',
'PhortuneProductPurchaseController' => 'PhortuneController',
'PhortuneProductQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneProductTransaction' => 'PhabricatorApplicationTransaction',
'PhortuneProductTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortuneProductViewController' => 'PhortuneController',
'PhortuneProviderController' => 'PhortuneController',
'PhortunePurchase' => array(

View file

@ -60,7 +60,19 @@ final class FundInitiativeBackController
->setAmountAsCurrency($currency)
->save();
// TODO: Here, we'd create a purchase and cart.
$product = id(new PhortuneProductQuery())
->setViewer($viewer)
->withClassAndRef('FundBackerProduct', $initiative->getPHID())
->executeOne();
$account = PhortuneAccountQuery::loadActiveAccountForUser(
$viewer,
PhabricatorContentSource::newFromRequest($request));
$cart = $account->newCart($viewer);
$purchase = $cart->newPurchase($viewer, $product);
$purchase->setBasePriceAsCurrency($currency)->save();
$xactions = array();
@ -74,9 +86,8 @@ final class FundInitiativeBackController
$editor->applyTransactions($backer, $xactions);
// TODO: Here, we'd ship the user into Phortune.
return id(new AphrontRedirectResponse())->setURI($initiative_uri);
return id(new AphrontRedirectResponse())
->setURI($cart->getCheckoutURI());
}
}

View file

@ -0,0 +1,64 @@
<?php
final class FundBackerProduct extends PhortuneProductImplementation {
private $initiativePHID;
private $initiative;
public function getRef() {
return $this->getInitiativePHID();
}
public function getName(PhortuneProduct $product) {
return pht('Back Initiative %s', $this->initiativePHID);
}
public function getPriceAsCurrency(PhortuneProduct $product) {
return PhortuneCurrency::newEmptyCurrency();
}
public function setInitiativePHID($initiative_phid) {
$this->initiativePHID = $initiative_phid;
return $this;
}
public function getInitiativePHID() {
return $this->initiativePHID;
}
public function setInitiative(FundInitiative $initiative) {
$this->initiative = $initiative;
return $this;
}
public function getInitiative() {
return $this->initiative;
}
public function loadImplementationsForRefs(
PhabricatorUser $viewer,
array $refs) {
$initiatives = id(new FundInitiativeQuery())
->setViewer($viewer)
->withPHIDs($refs)
->execute();
$initiatives = mpull($initiatives, null, 'getPHID');
$objects = array();
foreach ($refs as $ref) {
$object = id(new FundBackerProduct())
->setInitiativePHID($ref);
$initiative = idx($initiatives, $ref);
if ($initiative) {
$object->setInitiative($initiative);
}
$objects[] = $object;
}
return $objects;
}
}

View file

@ -58,9 +58,6 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication {
'view/(?P<id>\d+)/' => 'PhortuneProductViewController',
'edit/(?:(?P<id>\d+)/)?' => 'PhortuneProductEditController',
),
'purchase/(?P<id>\d+)/' => array(
'' => 'PhortunePurchaseViewController',
),
'provider/(?P<digest>[^/]+)/(?P<action>[^/]+)/'
=> 'PhortuneProviderController',
),

View file

@ -3,53 +3,9 @@
abstract class PhortuneController extends PhabricatorController {
protected function loadActiveAccount(PhabricatorUser $user) {
$accounts = id(new PhortuneAccountQuery())
->setViewer($user)
->withMemberPHIDs(array($user->getPHID()))
->execute();
if (!$accounts) {
return $this->createUserAccount($user);
} else if (count($accounts) == 1) {
return head($accounts);
} else {
throw new Exception('TODO: No account selection yet.');
}
}
protected function createUserAccount(PhabricatorUser $user) {
$request = $this->getRequest();
$xactions = array();
$xactions[] = id(new PhortuneAccountTransaction())
->setTransactionType(PhortuneAccountTransaction::TYPE_NAME)
->setNewValue(pht('Account (%s)', $user->getUserName()));
$xactions[] = id(new PhortuneAccountTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue(
'edge:type',
PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER)
->setNewValue(
array(
'=' => array($user->getPHID() => $user->getPHID()),
));
$account = id(new PhortuneAccount())
->attachMemberPHIDs(array());
$editor = id(new PhortuneAccountEditor())
->setActor($user)
->setContentSourceFromRequest($request);
// We create an account for you the first time you visit Phortune.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$editor->applyTransactions($account, $xactions);
unset($unguarded);
return $account;
return PhortuneAccountQuery::loadActiveAccountForUser(
$user,
PhabricatorContentSource::newFromRequest($this->getRequest()));
}
protected function buildChargesTable(array $charges, $show_cart = true) {

View file

@ -1,133 +0,0 @@
<?php
final class PhortuneProductEditController extends PhabricatorController {
private $productID;
public function willProcessRequest(array $data) {
$this->productID = idx($data, 'id');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($this->productID) {
$product = id(new PhortuneProductQuery())
->setViewer($user)
->withIDs(array($this->productID))
->executeOne();
if (!$product) {
return new Aphront404Response();
}
$is_create = false;
$cancel_uri = $this->getApplicationURI(
'product/view/'.$this->productID.'/');
} else {
$product = PhortuneProduct::initializeNewProduct();
$is_create = true;
$cancel_uri = $this->getApplicationURI('product/');
}
$v_name = $product->getProductName();
$v_price = $product->getPriceAsCurrency()->formatForDisplay();
$display_price = $v_price;
$e_name = true;
$e_price = true;
$errors = array();
if ($request->isFormPost()) {
$v_name = $request->getStr('name');
if (!strlen($v_name)) {
$e_name = pht('Required');
$errors[] = pht('Product must have a name.');
} else {
$e_name = null;
}
$display_price = $request->getStr('price');
try {
$v_price = PhortuneCurrency::newFromUserInput($user, $display_price)
->serializeForStorage();
$e_price = null;
} catch (Exception $ex) {
$errors[] = pht('Price should be formatted as: $1.23');
$e_price = pht('Invalid');
}
if (!$errors) {
$xactions = array();
$xactions[] = id(new PhortuneProductTransaction())
->setTransactionType(PhortuneProductTransaction::TYPE_NAME)
->setNewValue($v_name);
$xactions[] = id(new PhortuneProductTransaction())
->setTransactionType(PhortuneProductTransaction::TYPE_PRICE)
->setNewValue($v_price);
$editor = id(new PhortuneProductEditor())
->setActor($user)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
$editor->applyTransactions($product, $xactions);
return id(new AphrontRedirectResponse())->setURI(
$this->getApplicationURI('product/view/'.$product->getID().'/'));
}
}
if ($errors) {
$errors = id(new AphrontErrorView())
->setErrors($errors);
}
$form = id(new AphrontFormView())
->setUser($user)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($v_name)
->setError($e_name))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Price'))
->setName('price')
->setValue($display_price)
->setError($e_price))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(
$is_create
? pht('Create Product')
: pht('Save Product'))
->addCancelButton($cancel_uri));
$title = pht('Edit Product');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Products'),
$this->getApplicationURI('product/'));
$crumbs->addTextCrumb(
$is_create ? pht('Create') : pht('Edit'),
$request->getRequestURI());
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Edit Product'))
->appendChild($form);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
));
}
}

View file

@ -1,71 +0,0 @@
<?php
final class PhortuneProductPurchaseController
extends PhortuneController {
private $accountID;
private $productID;
public function willProcessRequest(array $data) {
$this->accountID = $data['accountID'];
$this->productID = $data['productID'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$account = id(new PhortuneAccountQuery())
->setViewer($user)
->withIDs(array($this->accountID))
->executeOne();
if (!$account) {
return new Aphront404Response();
}
$account_uri = $this->getApplicationURI($account->getID().'/');
$product = id(new PhortuneProductQuery())
->setViewer($user)
->withIDs(array($this->productID))
->executeOne();
if (!$product) {
return new Aphront404Response();
}
if ($request->isFormPost()) {
// TODO: Use ApplicationTransations.
$cart = new PhortuneCart();
$cart->openTransaction();
$cart->setStatus(PhortuneCart::STATUS_READY);
$cart->setAccountPHID($account->getPHID());
$cart->setAuthorPHID($user->getPHID());
$cart->save();
$purchase = new PhortunePurchase();
$purchase->setProductPHID($product->getPHID());
$purchase->setAccountPHID($account->getPHID());
$purchase->setAuthorPHID($user->getPHID());
$purchase->setCartPHID($cart->getPHID());
$purchase->setBasePriceAsCurrency($product->getPriceAsCurrency());
$purchase->setQuantity(1);
$purchase->setStatus(PhortunePurchase::STATUS_PENDING);
$purchase->save();
$cart->saveTransaction();
$cart_id = $cart->getID();
$cart_uri = $this->getApplicationURI('/cart/'.$cart_id.'/checkout/');
return id(new AphrontRedirectResponse())->setURI($cart_uri);
}
return $this->newDialog()
->setTitle(pht('Purchase Product'))
->appendParagraph(pht('Really purchase this stuff?'))
->addSubmitButton(pht('Checkout'))
->addCancelButton($account_uri);
}
}

View file

@ -34,19 +34,7 @@ final class PhortuneProductViewController extends PhortuneController {
$actions = id(new PhabricatorActionListView())
->setUser($user)
->setObjectURI($request->getRequestURI())
->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Product'))
->setHref($edit_uri)
->setIcon('fa-pencil'))
->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setName(pht('Purchase'))
->setHref($cart_uri)
->setIcon('fa-shopping-cart')
->setWorkflow(true));
->setObjectURI($request->getRequestURI());
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
@ -64,20 +52,6 @@ final class PhortuneProductViewController extends PhortuneController {
pht('Price'),
$product->getPriceAsCurrency()->formatForDisplay());
$xactions = id(new PhortuneProductTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($product->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setObjectPHID($product->getPHID())
->setTransactions($xactions)
->setMarkupEngine($engine);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
@ -86,7 +60,6 @@ final class PhortuneProductViewController extends PhortuneController {
array(
$crumbs,
$object_box,
$xaction_view,
),
array(
'title' => $title,

View file

@ -1,74 +0,0 @@
<?php
final class PhortuneProductEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorPhortuneApplication';
}
public function getEditorObjectsDescription() {
return pht('Phortune Products');
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhortuneProductTransaction::TYPE_NAME;
$types[] = PhortuneProductTransaction::TYPE_PRICE;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhortuneProductTransaction::TYPE_NAME:
return $object->getProductName();
case PhortuneProductTransaction::TYPE_PRICE:
return $object->getPriceAsCurrency()->serializeForStorage();
}
return parent::getCustomTransactionOldValue($object, $xaction);
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhortuneProductTransaction::TYPE_NAME:
case PhortuneProductTransaction::TYPE_PRICE:
return $xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhortuneProductTransaction::TYPE_NAME:
$object->setProductName($xaction->getNewValue());
return;
case PhortuneProductTransaction::TYPE_PRICE:
$object->setPriceAsCurrency(
PhortuneCurrency::newFromString($xaction->getNewValue()));
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhortuneProductTransaction::TYPE_NAME:
case PhortuneProductTransaction::TYPE_PRICE:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
}

View file

@ -0,0 +1,13 @@
<?php
abstract class PhortuneProductImplementation {
abstract public function loadImplementationsForRefs(
PhabricatorUser $viewer,
array $refs);
abstract public function getRef();
abstract public function getName(PhortuneProduct $product);
abstract public function getPriceAsCurrency(PhortuneProduct $product);
}

View file

@ -7,6 +7,24 @@ final class PhortuneAccountQuery
private $phids;
private $memberPHIDs;
public static function loadActiveAccountForUser(
PhabricatorUser $user,
PhabricatorContentSource $content_source) {
$accounts = id(new PhortuneAccountQuery())
->setViewer($user)
->withMemberPHIDs(array($user->getPHID()))
->execute();
if (!$accounts) {
return PhortuneAccount::createNewAccount($user, $content_source);
} else if (count($accounts) == 1) {
return head($accounts);
} else {
throw new Exception('TODO: No account selection yet.');
}
}
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;

View file

@ -5,6 +5,7 @@ final class PhortuneProductQuery
private $ids;
private $phids;
private $refMap;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -16,6 +17,11 @@ final class PhortuneProductQuery
return $this;
}
public function withClassAndRef($class, $ref) {
$this->refMap = array($class => array($ref));
return $this;
}
protected function loadPage() {
$table = new PhortuneProduct();
$conn = $table->establishConnection('r');
@ -28,26 +34,80 @@ final class PhortuneProductQuery
$this->buildOrderClause($conn),
$this->buildLimitClause($conn));
return $table->loadAllFromArray($rows);
$page = $table->loadAllFromArray($rows);
// NOTE: We're loading product implementations here, but also creating any
// products which do not yet exist.
$class_map = mgroup($page, 'getProductClass');
if ($this->refMap) {
$class_map += array_fill_keys(array_keys($this->refMap), array());
}
foreach ($class_map as $class => $products) {
$refs = mpull($products, null, 'getProductRef');
if (isset($this->refMap[$class])) {
$refs += array_fill_keys($this->refMap[$class], null);
}
$implementations = newv($class, array())->loadImplementationsForRefs(
$this->getViewer(),
array_keys($refs));
$implementations = mpull($implementations, null, 'getRef');
foreach ($implementations as $ref => $implementation) {
$product = idx($refs, $ref);
if ($product === null) {
// If this product does not exist yet, create it and add it to the
// result page.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$product = PhortuneProduct::initializeNewProduct()
->setProductClass($class)
->setProductRef($ref)
->save();
unset($unguarded);
$page[] = $product;
}
$product->attachImplementation($implementation);
}
}
return $page;
}
private function buildWhereClause(AphrontDatabaseConnection $conn) {
$where = array();
if ($this->ids) {
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
$this->phids);
}
if ($this->refMap !== null) {
$sql = array();
foreach ($this->refMap as $class => $refs) {
foreach ($refs as $ref) {
$sql[] = qsprintf(
$conn,
'(productClassKey = %s AND productRefKey = %s)',
PhabricatorHash::digestForIndex($class),
PhabricatorHash::digestForIndex($ref));
}
}
$where[] = implode(' OR ', $sql);
}
$where[] = $this->buildPagingClause($conn);
return $this->formatWhereClause($where);

View file

@ -1,10 +0,0 @@
<?php
final class PhortuneProductTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new PhortuneProductTransaction();
}
}

View file

@ -13,6 +13,54 @@ final class PhortuneAccount extends PhortuneDAO
private $memberPHIDs = self::ATTACHABLE;
public static function initializeNewAccount(PhabricatorUser $actor) {
$account = id(new PhortuneAccount());
$account->memberPHIDs = array();
return $account;
}
public static function createNewAccount(
PhabricatorUser $actor,
PhabricatorContentSource $content_source) {
$account = PhortuneAccount::initializeNewAccount($actor);
$xactions = array();
$xactions[] = id(new PhortuneAccountTransaction())
->setTransactionType(PhortuneAccountTransaction::TYPE_NAME)
->setNewValue(pht('Account (%s)', $actor->getUserName()));
$xactions[] = id(new PhortuneAccountTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue(
'edge:type',
PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER)
->setNewValue(
array(
'=' => array($actor->getPHID() => $actor->getPHID()),
));
$editor = id(new PhortuneAccountEditor())
->setActor($actor)
->setContentSource($content_source);
// We create an account for you the first time you visit Phortune.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$editor->applyTransactions($account, $xactions);
unset($unguarded);
return $account;
}
public function newCart(PhabricatorUser $actor) {
return PhortuneCart::initializeNewCart($actor, $this)
->save();
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,

View file

@ -15,6 +15,38 @@ final class PhortuneCart extends PhortuneDAO
private $account = self::ATTACHABLE;
private $purchases = self::ATTACHABLE;
public static function initializeNewCart(
PhabricatorUser $actor,
PhortuneAccount $account) {
$cart = id(new PhortuneCart())
->setAuthorPHID($actor->getPHID())
->setStatus(self::STATUS_READY)
->setAccountPHID($account->getPHID());
$cart->account = $account;
$cart->purchases = array();
return $cart;
}
public function newPurchase(
PhabricatorUser $actor,
PhortuneProduct $product) {
$purchase = PhortunePurchase::initializeNewPurchase($actor, $product)
->setAccountPHID($this->getAccount()->getPHID())
->setCartPHID($this->getPHID())
->save();
$this->purchases[] = $purchase;
return $purchase;
}
public function getCheckoutURI() {
return '/phortune/cart/'.$this->getID().'/checkout/';
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,

View file

@ -6,9 +6,13 @@
final class PhortuneProduct extends PhortuneDAO
implements PhabricatorPolicyInterface {
protected $productName;
protected $priceAsCurrency;
protected $metadata;
protected $productClassKey;
protected $productClass;
protected $productRefKey;
protected $productRef;
protected $metadata = array();
private $implementation = self::ATTACHABLE;
public function getConfiguration() {
return array(
@ -16,15 +20,17 @@ final class PhortuneProduct extends PhortuneDAO
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
self::CONFIG_APPLICATION_SERIALIZERS => array(
'priceAsCurrency' => new PhortuneCurrencySerializer(),
),
self::CONFIG_COLUMN_SCHEMA => array(
'productName' => 'text255',
'status' => 'text64',
'priceAsCurrency' => 'text64',
'billingIntervalInMonths' => 'uint32?',
'trialPeriodInDays' => 'uint32?',
'productClassKey' => 'bytes12',
'productClass' => 'text128',
'productRefKey' => 'bytes12',
'productRef' => 'text128',
),
self::CONFIG_KEY_SCHEMA => array(
'key_product' => array(
'columns' => array('productClassKey', 'productRefKey'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
@ -35,8 +41,33 @@ final class PhortuneProduct extends PhortuneDAO
}
public static function initializeNewProduct() {
return id(new PhortuneProduct())
->setPriceAsCurrency(PhortuneCurrency::newEmptyCurrency());
return id(new PhortuneProduct());
}
public function attachImplementation(PhortuneProductImplementation $impl) {
$this->implementation = $impl;
}
public function getImplementation() {
return $this->assertAttached($this->implementation);
}
public function save() {
$this->productClassKey = PhabricatorHash::digestForIndex(
$this->productClass);
$this->productRefKey = PhabricatorHash::digestForIndex(
$this->productRef);
return parent::save();
}
public function getPriceAsCurrency() {
return $this->getImplementation()->getPriceAsCurrency($this);
}
public function getProductName() {
return $this->getImplementation()->getName($this);
}

View file

@ -1,63 +0,0 @@
<?php
final class PhortuneProductTransaction
extends PhabricatorApplicationTransaction {
const TYPE_NAME = 'product:name';
const TYPE_PRICE = 'product:price';
public function getApplicationName() {
return 'phortune';
}
public function getApplicationTransactionType() {
return PhabricatorPHIDConstants::PHID_TYPE_PDCT;
}
public function getApplicationTransactionCommentObject() {
return null;
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_NAME:
if ($old === null) {
return pht(
'%s created this product.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s renamed this product from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
break;
case self::TYPE_PRICE:
if ($old === null) {
return pht(
'%s set product price to %s.',
$this->renderHandleLink($author_phid),
PhortuneCurrency::newFromString($new)
->formatForDisplay());
} else {
return pht(
'%s changed product price from %s to %s.',
$this->renderHandleLink($author_phid),
PhortuneCurrency::newFromString($old)
->formatForDisplay(),
PhortuneCurrency::newFromString($new)
->formatForDisplay());
}
break;
}
return parent::getTitle();
}
}

View file

@ -20,10 +20,21 @@ final class PhortunePurchase extends PhortuneDAO
protected $basePriceAsCurrency;
protected $quantity;
protected $status;
protected $metadata;
protected $metadata = array();
private $cart = self::ATTACHABLE;
public static function initializeNewPurchase(
PhabricatorUser $actor,
PhortuneProduct $product) {
return id(new PhortunePurchase())
->setAuthorPHID($actor->getPHID())
->setProductPHID($product->getPHID())
->setQuantity(1)
->setStatus(self::STATUS_PENDING)
->setBasePriceAsCurrency($product->getPriceAsCurrency());
}
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,