mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-28 16:30:59 +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:
parent
f86f9dc512
commit
e9615b74a5
19 changed files with 338 additions and 460 deletions
22
resources/sql/autopatches/20141005.phortuneproduct.sql
Normal file
22
resources/sql/autopatches/20141005.phortuneproduct.sql
Normal 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);
|
|
@ -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(
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
64
src/applications/fund/phortune/FundBackerProduct.php
Normal file
64
src/applications/fund/phortune/FundBackerProduct.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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',
|
||||
),
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class PhortuneProductTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new PhortuneProductTransaction();
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue