Page MenuHomePhabricator

D11580.diff
No OneTemporary

D11580.diff

diff --git a/resources/sql/autopatches/20150130.phortune.1.subphid.sql b/resources/sql/autopatches/20150130.phortune.1.subphid.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150130.phortune.1.subphid.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_phortune.phortune_cart
+ ADD subscriptionPHID VARBINARY(64);
diff --git a/resources/sql/autopatches/20150130.phortune.2.subkey.sql b/resources/sql/autopatches/20150130.phortune.2.subkey.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150130.phortune.2.subkey.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_phortune.phortune_cart
+ ADD KEY `key_subscription` (subscriptionPHID);
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2811,9 +2811,11 @@
'PhortuneSchemaSpec' => 'applications/phortune/storage/PhortuneSchemaSpec.php',
'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php',
'PhortuneSubscription' => 'applications/phortune/storage/PhortuneSubscription.php',
+ 'PhortuneSubscriptionCart' => 'applications/phortune/cart/PhortuneSubscriptionCart.php',
'PhortuneSubscriptionImplementation' => 'applications/phortune/subscription/PhortuneSubscriptionImplementation.php',
'PhortuneSubscriptionListController' => 'applications/phortune/controller/PhortuneSubscriptionListController.php',
'PhortuneSubscriptionPHIDType' => 'applications/phortune/phid/PhortuneSubscriptionPHIDType.php',
+ 'PhortuneSubscriptionProduct' => 'applications/phortune/product/PhortuneSubscriptionProduct.php',
'PhortuneSubscriptionQuery' => 'applications/phortune/query/PhortuneSubscriptionQuery.php',
'PhortuneSubscriptionSearchEngine' => 'applications/phortune/query/PhortuneSubscriptionSearchEngine.php',
'PhortuneSubscriptionTableView' => 'applications/phortune/view/PhortuneSubscriptionTableView.php',
@@ -6169,8 +6171,10 @@
'PhortuneDAO',
'PhabricatorPolicyInterface',
),
+ 'PhortuneSubscriptionCart' => 'PhortuneCartImplementation',
'PhortuneSubscriptionListController' => 'PhortuneController',
'PhortuneSubscriptionPHIDType' => 'PhabricatorPHIDType',
+ 'PhortuneSubscriptionProduct' => 'PhortuneProductImplementation',
'PhortuneSubscriptionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneSubscriptionSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhortuneSubscriptionTableView' => 'AphrontView',
diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php
--- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php
+++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php
@@ -50,6 +50,8 @@
=> 'PhortuneSubscriptionListController',
'view/(?P<id>\d+)/'
=> 'PhortuneSubscriptionViewController',
+ 'order/(?P<subscriptionID>\d+)/'
+ => 'PhortuneCartListController',
),
'charge/(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhortuneChargeListController',
@@ -90,6 +92,8 @@
=> 'PhortuneSubscriptionListController',
'view/(?P<id>\d+)/'
=> 'PhortuneSubscriptionViewController',
+ 'order/(?P<subscriptionID>\d+)/'
+ => 'PhortuneCartListController',
),
'(?P<id>\d+)/' => 'PhortuneMerchantViewController',
),
diff --git a/src/applications/phortune/cart/PhortuneSubscriptionCart.php b/src/applications/phortune/cart/PhortuneSubscriptionCart.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/cart/PhortuneSubscriptionCart.php
@@ -0,0 +1,89 @@
+<?php
+
+final class PhortuneSubscriptionCart
+ extends PhortuneCartImplementation {
+
+ private $subscriptionPHID;
+ private $subscription;
+
+ public function setSubscriptionPHID($subscription_phid) {
+ $this->subscriptionPHID = $subscription_phid;
+ return $this;
+ }
+
+ public function getSubscriptionPHID() {
+ return $this->subscriptionPHID;
+ }
+
+ public function setSubscription(PhortuneSubscription $subscription) {
+ $this->subscription = $subscription;
+ return $this;
+ }
+
+ public function getSubscription() {
+ return $this->subscription;
+ }
+
+ public function getName(PhortuneCart $cart) {
+ return pht('Subscription');
+ }
+
+ public function willCreateCart(
+ PhabricatorUser $viewer,
+ PhortuneCart $cart) {
+
+ $subscription = $this->getSubscription();
+ if (!$subscription) {
+ throw new Exception(
+ pht('Call setSubscription() before building a cart!'));
+ }
+
+ $cart->setMetadataValue('subscriptionPHID', $subscription->getPHID());
+ }
+
+ public function loadImplementationsForCarts(
+ PhabricatorUser $viewer,
+ array $carts) {
+
+ $phids = array();
+ foreach ($carts as $cart) {
+ $phids[] = $cart->getMetadataValue('subscriptionPHID');
+ }
+
+ $subscriptions = id(new PhortuneSubscriptionQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($phids)
+ ->execute();
+ $subscriptions = mpull($subscriptions, null, 'getPHID');
+
+ $objects = array();
+ foreach ($carts as $key => $cart) {
+ $subscription_phid = $cart->getMetadataValue('subscriptionPHID');
+ $subscription = idx($subscriptions, $subscription_phid);
+ if (!$subscription) {
+ continue;
+ }
+
+ $object = id(new PhortuneSubscriptionCart())
+ ->setSubscriptionPHID($subscription_phid)
+ ->setSubscription($subscription);
+
+ $objects[$key] = $object;
+ }
+
+ return $objects;
+ }
+
+ public function getCancelURI(PhortuneCart $cart) {
+ return $this->getSubscription()->getURI();
+ }
+
+ public function getDoneURI(PhortuneCart $cart) {
+ return $this->getSubscription()->getURI();
+ }
+
+ public function getDoneActionName(PhortuneCart $cart) {
+ return pht('Return to Subscription');
+ }
+
+}
diff --git a/src/applications/phortune/controller/PhortuneCartListController.php b/src/applications/phortune/controller/PhortuneCartListController.php
--- a/src/applications/phortune/controller/PhortuneCartListController.php
+++ b/src/applications/phortune/controller/PhortuneCartListController.php
@@ -3,29 +3,35 @@
final class PhortuneCartListController
extends PhortuneController {
- private $accountID;
- private $merchantID;
- private $queryKey;
-
private $merchant;
private $account;
+ private $subscription;
- public function willProcessRequest(array $data) {
- $this->merchantID = idx($data, 'merchantID');
- $this->accountID = idx($data, 'accountID');
- $this->queryKey = idx($data, 'queryKey');
- }
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
- public function processRequest() {
- $request = $this->getRequest();
- $viewer = $request->getUser();
+ $merchant_id = $request->getURIData('merchantID');
+ $account_id = $request->getURIData('accountID');
+ $subscription_id = $request->getURIData('subscriptionID');
$engine = new PhortuneCartSearchEngine();
- if ($this->merchantID) {
+ if ($subscription_id) {
+ $subscription = id(new PhortuneSubscriptionQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($subscription_id))
+ ->executeOne();
+ if (!$subscription) {
+ return new Aphront404Response();
+ }
+ $this->subscription = $subscription;
+ $engine->setSubscription($subscription);
+ }
+
+ if ($merchant_id) {
$merchant = id(new PhortuneMerchantQuery())
->setViewer($viewer)
- ->withIDs(array($this->merchantID))
+ ->withIDs(array($merchant_id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
@@ -37,10 +43,10 @@
}
$this->merchant = $merchant;
$engine->setMerchant($merchant);
- } else if ($this->accountID) {
+ } else if ($account_id) {
$account = id(new PhortuneAccountQuery())
->setViewer($viewer)
- ->withIDs(array($this->accountID))
+ ->withIDs(array($account_id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
@@ -57,7 +63,7 @@
}
$controller = id(new PhabricatorApplicationSearchController())
- ->setQueryKey($this->queryKey)
+ ->setQueryKey($request->getURIData('queryKey'))
->setSearchEngine($engine)
->setNavigation($this->buildSideNavView());
@@ -82,26 +88,39 @@
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
+ $subscription = $this->subscription;
+
$merchant = $this->merchant;
if ($merchant) {
$id = $merchant->getID();
- $crumbs->addTextCrumb(
- $merchant->getName(),
- $this->getApplicationURI("merchant/{$id}/"));
- $crumbs->addTextCrumb(
- pht('Orders'),
- $this->getApplicationURI("merchant/orders/{$id}/"));
+ $this->addMerchantCrumb($crumbs, $merchant);
+ if (!$subscription) {
+ $crumbs->addTextCrumb(
+ pht('Orders'),
+ $this->getApplicationURI("merchant/orders/{$id}/"));
+ }
}
$account = $this->account;
if ($account) {
$id = $account->getID();
+ $this->addAccountCrumb($crumbs, $account);
+ if (!$subscription) {
+ $crumbs->addTextCrumb(
+ pht('Orders'),
+ $this->getApplicationURI("{$id}/order/"));
+ }
+ }
+
+ if ($subscription) {
+ if ($merchant) {
+ $subscription_uri = $subscription->getMerchantURI();
+ } else {
+ $subscription_uri = $subscription->getURI();
+ }
$crumbs->addTextCrumb(
- $account->getName(),
- $this->getApplicationURI("{$id}/"));
- $crumbs->addTextCrumb(
- pht('Orders'),
- $this->getApplicationURI("{$id}/order/"));
+ $subscription->getSubscriptionName(),
+ $subscription_uri);
}
return $crumbs;
diff --git a/src/applications/phortune/controller/PhortuneSubscriptionViewController.php b/src/applications/phortune/controller/PhortuneSubscriptionViewController.php
--- a/src/applications/phortune/controller/PhortuneSubscriptionViewController.php
+++ b/src/applications/phortune/controller/PhortuneSubscriptionViewController.php
@@ -15,6 +15,8 @@
}
$is_merchant = (bool)$request->getURIData('merchantID');
+ $merchant = $subscription->getMerchant();
+ $account = $subscription->getAccount();
$title = pht('Subscription: %s', $subscription->getSubscriptionName());
@@ -27,9 +29,9 @@
$crumbs = $this->buildApplicationCrumbs();
if ($is_merchant) {
- $this->addMerchantCrumb($crumbs, $subscription->getMerchant());
+ $this->addMerchantCrumb($crumbs, $merchant);
} else {
- $this->addAccountCrumb($crumbs, $subscription->getAccount());
+ $this->addAccountCrumb($crumbs, $account);
}
$crumbs->addTextCrumb(pht('Subscription %d', $subscription->getID()));
@@ -46,10 +48,66 @@
->setHeader($header)
->addPropertyList($properties);
+ $carts = id(new PhortuneCartQuery())
+ ->setViewer($viewer)
+ ->withSubscriptionPHIDs(array($subscription->getPHID()))
+ ->needPurchases(true)
+ ->withStatuses(
+ array(
+ PhortuneCart::STATUS_PURCHASING,
+ PhortuneCart::STATUS_CHARGED,
+ PhortuneCart::STATUS_HOLD,
+ PhortuneCart::STATUS_REVIEW,
+ PhortuneCart::STATUS_PURCHASED,
+ ))
+ ->execute();
+
+ $phids = array();
+ foreach ($carts as $cart) {
+ $phids[] = $cart->getPHID();
+ foreach ($cart->getPurchases() as $purchase) {
+ $phids[] = $purchase->getPHID();
+ }
+ }
+ $handles = $this->loadViewerHandles($phids);
+
+ $invoice_table = id(new PhortuneOrderTableView())
+ ->setUser($viewer)
+ ->setCarts($carts)
+ ->setHandles($handles);
+
+ $account_id = $account->getID();
+ $merchant_id = $merchant->getID();
+ $subscription_id = $subscription->getID();
+
+ if ($is_merchant) {
+ $invoices_uri = $this->getApplicationURI(
+ "merchant/{$merchant_id}/subscription/order/{$subscription_id}/");
+ } else {
+ $invoices_uri = $this->getApplicationURI(
+ "{$account_id}/subscription/order/{$subscription_id}/");
+ }
+
+ $invoice_header = id(new PHUIHeaderView())
+ ->setHeader(pht('Recent Invoices'))
+ ->addActionLink(
+ id(new PHUIButtonView())
+ ->setTag('a')
+ ->setIcon(
+ id(new PHUIIconView())
+ ->setIconFont('fa-list'))
+ ->setHref($invoices_uri)
+ ->setText(pht('View All Invoices')));
+
+ $invoice_box = id(new PHUIObjectBoxView())
+ ->setHeader($invoice_header)
+ ->appendChild($invoice_table);
+
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
+ $invoice_box,
),
array(
'title' => $title,
diff --git a/src/applications/phortune/product/PhortuneSubscriptionProduct.php b/src/applications/phortune/product/PhortuneSubscriptionProduct.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/product/PhortuneSubscriptionProduct.php
@@ -0,0 +1,90 @@
+<?php
+
+final class PhortuneSubscriptionProduct
+ extends PhortuneProductImplementation {
+
+ private $viewer;
+ private $subscriptionPHID;
+ private $subscription;
+
+ public function setSubscriptionPHID($subscription_phid) {
+ $this->subscriptionPHID = $subscription_phid;
+ return $this;
+ }
+
+ public function getSubscriptionPHID() {
+ return $this->subscriptionPHID;
+ }
+
+ public function setSubscription(PhortuneSubscription $subscription) {
+ $this->subscription = $subscription;
+ return $this;
+ }
+
+ public function getSubscription() {
+ return $this->subscription;
+ }
+
+ public function setViewer(PhabricatorUser $viewer) {
+ $this->viewer = $viewer;
+ return $this;
+ }
+
+ public function getViewer() {
+ return $this->viewer;
+ }
+
+ public function getRef() {
+ return $this->getSubscriptionPHID();
+ }
+
+ public function getName(PhortuneProduct $product) {
+ return $this->getSubscription()->getSubscriptionName();
+ }
+
+ public function getPriceAsCurrency(PhortuneProduct $product) {
+ return PhortuneCurrency::newEmptyCurrency();
+ }
+
+ public function didPurchaseProduct(
+ PhortuneProduct $product,
+ PhortunePurchase $purchase) {
+ // TODO: Callback the subscription.
+ return;
+ }
+
+ public function didRefundProduct(
+ PhortuneProduct $product,
+ PhortunePurchase $purchase,
+ PhortuneCurrency $amount) {
+ // TODO: Callback the subscription.
+ return;
+ }
+
+ public function loadImplementationsForRefs(
+ PhabricatorUser $viewer,
+ array $refs) {
+
+ $subscriptions = id(new PhortuneSubscriptionQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($refs)
+ ->execute();
+ $subscriptions = mpull($subscriptions, null, 'getPHID');
+
+ $objects = array();
+ foreach ($refs as $ref) {
+ $subscription = idx($subscriptions, $ref);
+ if (!$subscription) {
+ continue;
+ }
+
+ $objects[] = id(new PhortuneSubscriptionProduct())
+ ->setViewer($viewer)
+ ->setSubscriptionPHID($ref)
+ ->setSubscription($subscription);
+ }
+
+ return $objects;
+ }
+
+}
diff --git a/src/applications/phortune/query/PhortuneCartQuery.php b/src/applications/phortune/query/PhortuneCartQuery.php
--- a/src/applications/phortune/query/PhortuneCartQuery.php
+++ b/src/applications/phortune/query/PhortuneCartQuery.php
@@ -7,6 +7,7 @@
private $phids;
private $accountPHIDs;
private $merchantPHIDs;
+ private $subscriptionPHIDs;
private $statuses;
private $needPurchases;
@@ -31,6 +32,11 @@
return $this;
}
+ public function withSubscriptionPHIDs(array $subscription_phids) {
+ $this->subscriptionPHIDs = $subscription_phids;
+ return $this;
+ }
+
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
@@ -158,6 +164,13 @@
$this->merchantPHIDs);
}
+ if ($this->subscriptionPHIDs !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'cart.subscriptionPHID IN (%Ls)',
+ $this->subscriptionPHIDs);
+ }
+
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
diff --git a/src/applications/phortune/query/PhortuneCartSearchEngine.php b/src/applications/phortune/query/PhortuneCartSearchEngine.php
--- a/src/applications/phortune/query/PhortuneCartSearchEngine.php
+++ b/src/applications/phortune/query/PhortuneCartSearchEngine.php
@@ -5,6 +5,7 @@
private $merchant;
private $account;
+ private $subscription;
public function setAccount(PhortuneAccount $account) {
$this->account = $account;
@@ -24,6 +25,15 @@
return $this->merchant;
}
+ public function setSubscription(PhortuneSubscription $subscription) {
+ $this->subscription = $subscription;
+ return $this;
+ }
+
+ public function getSubscription() {
+ return $this->subscription;
+ }
+
public function getResultTypeDescription() {
return pht('Phortune Orders');
}
@@ -83,6 +93,11 @@
}
}
+ $subscription = $this->getSubscription();
+ if ($subscription) {
+ $query->withSubscriptionPHIDs(array($subscription->getPHID()));
+ }
+
return $query;
}
diff --git a/src/applications/phortune/storage/PhortuneCart.php b/src/applications/phortune/storage/PhortuneCart.php
--- a/src/applications/phortune/storage/PhortuneCart.php
+++ b/src/applications/phortune/storage/PhortuneCart.php
@@ -16,6 +16,7 @@
protected $accountPHID;
protected $authorPHID;
protected $merchantPHID;
+ protected $subscriptionPHID;
protected $cartClass;
protected $status;
protected $metadata = array();
@@ -518,6 +519,7 @@
'status' => 'text32',
'cartClass' => 'text128',
'mailKey' => 'bytes20',
+ 'subscriptionPHID' => 'phid?',
),
self::CONFIG_KEY_SCHEMA => array(
'key_account' => array(
@@ -526,6 +528,9 @@
'key_merchant' => array(
'columns' => array('merchantPHID'),
),
+ 'key_subscription' => array(
+ 'columns' => array('subscriptionPHID'),
+ ),
),
) + parent::getConfiguration();
}
diff --git a/src/applications/phortune/worker/PhortuneSubscriptionWorker.php b/src/applications/phortune/worker/PhortuneSubscriptionWorker.php
--- a/src/applications/phortune/worker/PhortuneSubscriptionWorker.php
+++ b/src/applications/phortune/worker/PhortuneSubscriptionWorker.php
@@ -8,8 +8,55 @@
$range = $this->getBillingPeriodRange($subscription);
list($last_epoch, $next_epoch) = $range;
- // TODO: Actual billing.
- echo "Bill from {$last_epoch} to {$next_epoch}.\n";
+ $account = $subscription->getAccount();
+ $merchant = $subscription->getMerchant();
+
+ $viewer = PhabricatorUser::getOmnipotentUser();
+
+ $product = id(new PhortuneProductQuery())
+ ->setViewer($viewer)
+ ->withClassAndRef('PhortuneSubscriptionProduct', $subscription->getPHID())
+ ->executeOne();
+
+ $cart_implementation = id(new PhortuneSubscriptionCart())
+ ->setSubscription($subscription);
+
+
+ // TODO: This isn't really ideal. It would be better to use an application
+ // actor than the original author of the subscription. In particular, if
+ // someone initiates a subscription, adds some other account managers, and
+ // later leaves the company, they'll continue "acting" here indefinitely.
+ // However, for now, some of the stuff later in the pipeline requires a
+ // valid actor with a real PHID. The subscription should eventually be
+ // able to create these invoices "as" the application it is acting on
+ // behalf of.
+ $actor = id(new PhabricatorPeopleQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($subscription->getAuthorPHID()))
+ ->executeOne();
+ if (!$actor) {
+ throw new Exception(pht('Failed to load actor to bill subscription!'));
+ }
+
+ $cart = $account->newCart($actor, $cart_implementation, $merchant);
+
+ $purchase = $cart->newPurchase($actor, $product);
+
+ // TODO: Consider allowing subscriptions to cost an amount other than one
+ // dollar and twenty-three cents.
+ $currency = PhortuneCurrency::newFromUserInput($actor, '1.23 USD');
+
+ $purchase
+ ->setBasePriceAsCurrency($currency)
+ ->setMetadataValue('subscriptionPHID', $subscription->getPHID())
+ ->save();
+
+ $cart->setSubscriptionPHID($subscription->getPHID());
+ $cart->activateCart();
+
+ // TODO: Autocharge this, etc.; this is still mostly faked up.
+ echo 'Okay, made a cart here: ';
+ echo $cart->getCheckoutURI()."\n\n";
}

File Metadata

Mime Type
text/plain
Expires
Thu, May 9, 8:13 PM (3 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6276488
Default Alt Text
D11580.diff (20 KB)

Event Timeline