diff --git a/src/applications/phortune/cart/PhortuneSubscriptionCart.php b/src/applications/phortune/cart/PhortuneSubscriptionCart.php --- a/src/applications/phortune/cart/PhortuneSubscriptionCart.php +++ b/src/applications/phortune/cart/PhortuneSubscriptionCart.php @@ -25,7 +25,7 @@ } public function getName(PhortuneCart $cart) { - return pht('Subscription'); + return $this->getSubscription()->getCartName($cart); } public function willCreateCart( diff --git a/src/applications/phortune/controller/PhortuneAccountViewController.php b/src/applications/phortune/controller/PhortuneAccountViewController.php --- a/src/applications/phortune/controller/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/PhortuneAccountViewController.php @@ -2,15 +2,8 @@ final class PhortuneAccountViewController extends PhortuneController { - private $accountID; - - public function willProcessRequest(array $data) { - $this->accountID = $data['accountID']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); // TODO: Currently, you must be able to edit an account to view the detail // page, because the account must be broadly visible so merchants can @@ -20,8 +13,8 @@ $can_edit = true; $account = id(new PhortuneAccountQuery()) - ->setViewer($user) - ->withIDs(array($this->accountID)) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('accountID'))) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -34,6 +27,13 @@ $title = $account->getName(); + $invoices = id(new PhortuneCartQuery()) + ->setViewer($viewer) + ->withAccountPHIDs(array($account->getPHID())) + ->needPurchases(true) + ->withInvoices(true) + ->execute(); + $crumbs = $this->buildApplicationCrumbs(); $this->addAccountCrumb($crumbs, $account, $link = false); @@ -43,7 +43,7 @@ $edit_uri = $this->getApplicationURI('account/edit/'.$account->getID().'/'); $actions = id(new PhabricatorActionListView()) - ->setUser($user) + ->setUser($viewer) ->setObjectURI($request->getRequestURI()) ->addAction( id(new PhabricatorActionView()) @@ -55,7 +55,7 @@ $properties = id(new PHUIPropertyListView()) ->setObject($account) - ->setUser($user); + ->setUser($viewer); $this->loadHandles($account->getMemberPHIDs()); @@ -63,12 +63,29 @@ pht('Members'), $this->renderHandlesForPHIDs($account->getMemberPHIDs())); + $status_items = $this->getStatusItemsForAccount($account, $invoices); + $status_view = new PHUIStatusListView(); + foreach ($status_items as $item) { + $status_view->addItem( + id(new PHUIStatusItemView()) + ->setIcon( + idx($item, 'icon'), + idx($item, 'color'), + idx($item, 'label')) + ->setTarget(idx($item, 'target')) + ->setNote(idx($item, 'note'))); + } + $properties->addProperty( + pht('Status'), + $status_view); + $properties->setActionList($actions); - $payment_methods = $this->buildPaymentMethodsSection($account); + $invoices = $this->buildInvoicesSection($account, $invoices); $purchase_history = $this->buildPurchaseHistorySection($account); $charge_history = $this->buildChargeHistorySection($account); $subscriptions = $this->buildSubscriptionsSection($account); + $payment_methods = $this->buildPaymentMethodsSection($account); $timeline = $this->buildTransactionTimeline( $account, @@ -83,10 +100,11 @@ array( $crumbs, $object_box, - $payment_methods, + $invoices, $purchase_history, $charge_history, $subscriptions, + $payment_methods, $timeline, ), array( @@ -165,6 +183,38 @@ ->appendChild($list); } + private function buildInvoicesSection( + PhortuneAccount $account, + array $carts) { + + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $phids = array(); + foreach ($carts as $cart) { + $phids[] = $cart->getPHID(); + $phids[] = $cart->getMerchantPHID(); + foreach ($cart->getPurchases() as $purchase) { + $phids[] = $purchase->getPHID(); + } + } + $handles = $this->loadViewerHandles($phids); + + $table = id(new PhortuneOrderTableView()) + ->setNoDataString(pht('You have no unpaid invoices.')) + ->setIsInvoices(true) + ->setUser($viewer) + ->setCarts($carts) + ->setHandles($handles); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Invoices Due')); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($table); + } + private function buildPurchaseHistorySection(PhortuneAccount $account) { $request = $this->getRequest(); $viewer = $request->getUser(); @@ -308,4 +358,34 @@ return $crumbs; } + private function getStatusItemsForAccount( + PhortuneAccount $account, + array $invoices) { + + assert_instances_of($invoices, 'PhortuneCart'); + + $items = array(); + + if ($invoices) { + $items[] = array( + 'icon' => PHUIStatusItemView::ICON_WARNING, + 'color' => 'yellow', + 'target' => pht('Invoices'), + 'note' => pht('You have %d unpaid invoice(s).', count($invoices)), + ); + } else { + $items[] = array( + 'icon' => PHUIStatusItemView::ICON_ACCEPT, + 'color' => 'green', + 'target' => pht('Invoices'), + 'note' => pht('This account has no unpaid invoices.'), + ); + } + + // TODO: If a payment method has expired or is expiring soon, we should + // add a status check for it. + + return $items; + } + } 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 @@ -9,6 +9,7 @@ private $merchantPHIDs; private $subscriptionPHIDs; private $statuses; + private $invoices; private $needPurchases; @@ -42,6 +43,18 @@ return $this; } + + /** + * Include or exclude carts which represent invoices with payments due. + * + * @param bool `true` to select invoices; `false` to exclude invoices. + * @return this + */ + public function withInvoices($invoices) { + $this->invoices = $invoices; + return $this; + } + public function needPurchases($need_purchases) { $this->needPurchases = $need_purchases; return $this; @@ -178,6 +191,20 @@ $this->statuses); } + if ($this->invoices !== null) { + if ($this->invoices) { + $where[] = qsprintf( + $conn, + 'cart.status = %s AND cart.subscriptionPHID IS NOT NULL', + PhortuneCart::STATUS_READY); + } else { + $where[] = qsprintf( + $conn, + 'cart.status != %s OR cart.subscriptionPHID IS NULL', + PhortuneCart::STATUS_READY); + } + } + return $this->formatWhereClause($where); } diff --git a/src/applications/phortune/storage/PhortuneSubscription.php b/src/applications/phortune/storage/PhortuneSubscription.php --- a/src/applications/phortune/storage/PhortuneSubscription.php +++ b/src/applications/phortune/storage/PhortuneSubscription.php @@ -166,6 +166,10 @@ return $this->getImplementation()->getName($this); } + public function getCartName(PhortuneCart $cart) { + return $this->getImplementation()->getCartName($this, $cart); + } + public function getURI() { $account_id = $this->getAccount()->getID(); $id = $this->getID(); diff --git a/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php b/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php --- a/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php +++ b/src/applications/phortune/subscription/PhortuneSubscriptionImplementation.php @@ -15,4 +15,10 @@ array()); } + public function getCartName( + PhortuneSubscription $subscription, + PhortuneCart $cart) { + return pht('Subscription'); + } + } diff --git a/src/applications/phortune/view/PhortuneOrderTableView.php b/src/applications/phortune/view/PhortuneOrderTableView.php --- a/src/applications/phortune/view/PhortuneOrderTableView.php +++ b/src/applications/phortune/view/PhortuneOrderTableView.php @@ -4,6 +4,8 @@ private $carts; private $handles; + private $noDataString; + private $isInvoices; public function setHandles(array $handles) { $this->handles = $handles; @@ -23,11 +25,31 @@ return $this->carts; } + public function setIsInvoices($is_invoices) { + $this->isInvoices = $is_invoices; + return $this; + } + + public function getIsInvoices() { + return $this->isInvoices; + } + + public function setNoDataString($no_data_string) { + $this->noDataString = $no_data_string; + return $this; + } + + public function getNoDataString() { + return $this->noDataString; + } + public function render() { $carts = $this->getCarts(); $handles = $this->getHandles(); $viewer = $this->getUser(); + $is_invoices = $this->getIsInvoices(); + $rows = array(); $rowc = array(); foreach ($carts as $cart) { @@ -42,9 +64,16 @@ $purchase_name = ''; } + if ($is_invoices) { + $merchant_link = $handles[$cart->getMerchantPHID()]->renderLink(); + } else { + $merchant_link = null; + } + $rowc[] = ''; $rows[] = array( $cart->getID(), + $merchant_link, phutil_tag( 'strong', array(), @@ -56,6 +85,14 @@ $cart->getTotalPriceAsCurrency()->formatForDisplay()), PhortuneCart::getNameForStatus($cart->getStatus()), phabricator_datetime($cart->getDateModified(), $viewer), + phabricator_datetime($cart->getDateCreated(), $viewer), + phutil_tag( + 'a', + array( + 'href' => $cart->getCheckoutURI(), + 'class' => 'small green button', + ), + pht('Pay Now')), ); foreach ($purchases as $purchase) { $id = $purchase->getID(); @@ -65,33 +102,55 @@ $rowc[] = ''; $rows[] = array( '', + '', $handles[$purchase->getPHID()]->renderLink(), $price, '', '', + '', + '', ); } } $table = id(new AphrontTableView($rows)) + ->setNoDataString($this->getNoDataString()) ->setRowClasses($rowc) ->setHeaders( array( pht('ID'), - pht('Order'), + pht('Merchant'), + $is_invoices ? pht('Invoice') : pht('Order'), pht('Purchase'), pht('Amount'), pht('Status'), pht('Updated'), + pht('Invoice Date'), + null, )) ->setColumnClasses( array( '', '', + '', 'wide', 'right', '', 'right', + 'right', + '', + )) + ->setColumnVisibility( + array( + true, + $is_invoices, + true, + true, + true, + !$is_invoices, + !$is_invoices, + $is_invoices, + $is_invoices, )); return $table;