From 8f6a1ab015949d88977de7d260fd9dca99e34333 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 23 Aug 2019 08:07:41 -0700 Subject: [PATCH] Roughly support external/email user views of Phortune recipts and invoices Summary: Ref T13366. This gives each account email address an "external portal" section so they can access invoices and receipts without an account. Test Plan: Viewed portal as user with authority and in an incognito window. Maniphest Tasks: T13366 Differential Revision: https://secure.phabricator.com/D20737 --- src/__phutil_library_map__.php | 4 + .../PhabricatorPhortuneApplication.php | 3 + .../PhortuneAccountEmailViewController.php | 14 ++ .../external/PhortuneExternalController.php | 147 ++++++++++++++++++ .../PhortuneExternalOverviewController.php | 91 +++++++++++ .../query/PhortuneAccountEmailQuery.php | 13 ++ .../phortune/storage/PhortuneAccountEmail.php | 7 + .../phortune/view/PhortuneOrderTableView.php | 24 +-- 8 files changed, 293 insertions(+), 10 deletions(-) create mode 100644 src/applications/phortune/controller/external/PhortuneExternalController.php create mode 100644 src/applications/phortune/controller/external/PhortuneExternalOverviewController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9e01eb700d..ec3ef2231b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5294,6 +5294,8 @@ phutil_register_library_map(array( 'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php', 'PhortuneDisplayException' => 'applications/phortune/exception/PhortuneDisplayException.php', 'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php', + 'PhortuneExternalController' => 'applications/phortune/controller/external/PhortuneExternalController.php', + 'PhortuneExternalOverviewController' => 'applications/phortune/controller/external/PhortuneExternalOverviewController.php', 'PhortuneInvoiceView' => 'applications/phortune/view/PhortuneInvoiceView.php', 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', 'PhortuneMemberHasAccountEdgeType' => 'applications/phortune/edge/PhortuneMemberHasAccountEdgeType.php', @@ -11879,6 +11881,8 @@ phutil_register_library_map(array( 'PhortuneDAO' => 'PhabricatorLiskDAO', 'PhortuneDisplayException' => 'Exception', 'PhortuneErrCode' => 'PhortuneConstants', + 'PhortuneExternalController' => 'PhortuneController', + 'PhortuneExternalOverviewController' => 'PhortuneExternalController', 'PhortuneInvoiceView' => 'AphrontTagView', 'PhortuneLandingController' => 'PhortuneController', 'PhortuneMemberHasAccountEdgeType' => 'PhabricatorEdgeType', diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php index 18a5d45f69..43594c9653 100644 --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -104,6 +104,9 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { '(?P\d+)/(?P[^/]+)/' => 'PhortuneProviderActionController', ), + 'external/(?P[^/]+)/(?P[^/]+)/' => array( + '' => 'PhortuneExternalOverviewController', + ), 'merchant/' => array( $this->getQueryRoutePattern() => 'PhortuneMerchantListController', diff --git a/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php b/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php index 14c2b842f9..97d90946f5 100644 --- a/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php +++ b/src/applications/phortune/controller/account/PhortuneAccountEmailViewController.php @@ -67,6 +67,12 @@ final class PhortuneAccountEmailViewController $account->getID(), $address->getID())); + if ($can_edit) { + $external_uri = $address->getExternalURI(); + } else { + $external_uri = null; + } + $curtain = $this->newCurtainView($account); $curtain->addAction( @@ -77,6 +83,14 @@ final class PhortuneAccountEmailViewController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit)); + $curtain->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Show External View')) + ->setIcon('fa-eye') + ->setHref($external_uri) + ->setDisabled(!$can_edit) + ->setOpenInNewWindow(true)); + return $curtain; } diff --git a/src/applications/phortune/controller/external/PhortuneExternalController.php b/src/applications/phortune/controller/external/PhortuneExternalController.php new file mode 100644 index 0000000000..72ea02a955 --- /dev/null +++ b/src/applications/phortune/controller/external/PhortuneExternalController.php @@ -0,0 +1,147 @@ +email; + } + + final protected function getAccountEmail() { + return $this->email; + } + + final protected function getExternalViewer() { + return PhabricatorUser::getOmnipotentUser(); + } + + final public function handleRequest(AphrontRequest $request) { + $address_key = $request->getURIData('addressKey'); + $access_key = $request->getURIData('accessKey'); + + $viewer = $this->getViewer(); + $xviewer = $this->getExternalViewer(); + + $email = id(new PhortuneAccountEmailQuery()) + ->setViewer($xviewer) + ->withAddressKeys(array($address_key)) + ->executeOne(); + if (!$email) { + return new Aphront404Response(); + } + + $account = $email->getAccount(); + + $can_see = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_EDIT); + + $email_display = phutil_tag('strong', array(), $email->getAddress()); + $user_display = phutil_tag('strong', array(), $viewer->getUsername()); + + $actual_key = $email->getAccessKey(); + if (!phutil_hashes_are_identical($access_key, $actual_key)) { + $dialog = $this->newDialog() + ->setTitle(pht('Email Access Link Out of Date')) + ->appendParagraph( + pht( + 'You are trying to access this payment account as: %s', + $email_display)) + ->appendParagraph( + pht( + 'The access link you have followed is out of date and no longer '. + 'works.')); + + if ($can_see) { + $dialog->appendParagraph( + pht( + 'You are currently logged in as a user (%s) who has '. + 'permission to manage the payment account, so you can '. + 'continue to the updated link.', + $user_display)); + + $dialog->addCancelButton( + $email->getExternalURI(), + pht('Continue to Updated Link')); + } else { + $dialog->appendParagraph( + pht( + 'To access information about this payment account, follow '. + 'a more recent link or ask a user with access to give you '. + 'an updated link.')); + } + + return $dialog; + } + + // TODO: Test that status is good. + + $this->email = $email; + + return $this->handleExternalRequest($request); + } + + final protected function newExternalCrumbs() { + $viewer = $this->getViewer(); + + $crumbs = new PHUICrumbsView(); + + if ($this->hasAccountEmail()) { + $email = $this->getAccountEmail(); + $account = $email->getAccount(); + + $crumb_name = pht( + 'Payment Account: %s', + $account->getName()); + + $crumb = id(new PHUICrumbView()) + ->setIcon('fa-diamond') + ->setName($crumb_name); + + $can_see = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_VIEW); + if ($can_see) { + $crumb->setHref($account->getURI()); + } + + $crumbs + ->addCrumb($crumb) + ->addTextCrumb(pht('Viewing As "%s"', $email->getAddress())); + } else { + $crumb = id(new PHUICrumbView()) + ->setIcon('fa-diamond') + ->setText(pht('External Account View')); + + $crumbs->addCrumb($crumb); + } + + return $crumbs; + } + + final protected function newExternalView() { + $email = $this->getAccountEmail(); + + $messages = array(); + $messages[] = pht( + 'You are viewing this payment account as: %s', + phutil_tag('strong', array(), $email->getAddress())); + $messages[] = pht( + 'Anyone who has a link to this page can view order history for '. + 'this payment account.'); + + return id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors($messages); + } +} diff --git a/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php b/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php new file mode 100644 index 0000000000..bcd68e2f4a --- /dev/null +++ b/src/applications/phortune/controller/external/PhortuneExternalOverviewController.php @@ -0,0 +1,91 @@ +getExternalViewer(); + $email = $this->getAccountEmail(); + $account = $email->getAccount(); + + $crumbs = $this->newExternalCrumbs() + ->setBorder(true); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Invoices and Receipts: %s', $account->getName())); + + $external_view = $this->newExternalView(); + $invoices_view = $this->newInvoicesView(); + $receipts_view = $this->newReceiptsView(); + + $column_view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter( + array( + $external_view, + $invoices_view, + $receipts_view, + )); + + return $this->newPage() + ->setCrumbs($crumbs) + ->setTitle( + array( + pht('Invoices and Receipts'), + $account->getName(), + )) + ->appendChild($column_view); + } + + private function newInvoicesView() { + $xviewer = $this->getExternalViewer(); + $email = $this->getAccountEmail(); + $account = $email->getAccount(); + + $invoices = id(new PhortuneCartQuery()) + ->setViewer($xviewer) + ->withAccountPHIDs(array($account->getPHID())) + ->needPurchases(true) + ->withInvoices(true) + ->execute(); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Invoices')); + + $invoices_table = id(new PhortuneOrderTableView()) + ->setViewer($xviewer) + ->setCarts($invoices) + ->setIsInvoices(true); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($invoices_table); + } + + private function newReceiptsView() { + $xviewer = $this->getExternalViewer(); + $email = $this->getAccountEmail(); + $account = $email->getAccount(); + + $receipts = id(new PhortuneCartQuery()) + ->setViewer($xviewer) + ->withAccountPHIDs(array($account->getPHID())) + ->needPurchases(true) + ->withInvoices(false) + ->execute(); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Receipts')); + + $receipts_table = id(new PhortuneOrderTableView()) + ->setViewer($xviewer) + ->setCarts($receipts); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setTable($receipts_table); + } + +} diff --git a/src/applications/phortune/query/PhortuneAccountEmailQuery.php b/src/applications/phortune/query/PhortuneAccountEmailQuery.php index 4494372ef6..81684d1fdd 100644 --- a/src/applications/phortune/query/PhortuneAccountEmailQuery.php +++ b/src/applications/phortune/query/PhortuneAccountEmailQuery.php @@ -6,6 +6,7 @@ final class PhortuneAccountEmailQuery private $ids; private $phids; private $accountPHIDs; + private $addressKeys; public function withIDs(array $ids) { $this->ids = $ids; @@ -22,6 +23,11 @@ final class PhortuneAccountEmailQuery return $this; } + public function withAddressKeys(array $keys) { + $this->addressKeys = $keys; + return $this; + } + public function newResultObject() { return new PhortuneAccountEmail(); } @@ -77,6 +83,13 @@ final class PhortuneAccountEmailQuery $this->accountPHIDs); } + if ($this->addressKeys !== null) { + $where[] = qsprintf( + $conn, + 'address.addressKey IN (%Ls)', + $this->addressKeys); + } + return $where; } diff --git a/src/applications/phortune/storage/PhortuneAccountEmail.php b/src/applications/phortune/storage/PhortuneAccountEmail.php index 5f1ede8414..65c67c56d2 100644 --- a/src/applications/phortune/storage/PhortuneAccountEmail.php +++ b/src/applications/phortune/storage/PhortuneAccountEmail.php @@ -78,6 +78,13 @@ final class PhortuneAccountEmail $this->getID()); } + public function getExternalURI() { + return urisprintf( + '/phortune/external/%s/%s/', + $this->getAddressKey(), + $this->getAccessKey()); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/phortune/view/PhortuneOrderTableView.php b/src/applications/phortune/view/PhortuneOrderTableView.php index b4b8866160..28dd1e58b2 100644 --- a/src/applications/phortune/view/PhortuneOrderTableView.php +++ b/src/applications/phortune/view/PhortuneOrderTableView.php @@ -49,6 +49,7 @@ final class PhortuneOrderTableView extends AphrontView { $is_invoices = $this->getIsInvoices(); $is_merchant = $this->getIsMerchantView(); + $is_external = (!$viewer->getPHID()); $phids = array(); foreach ($carts as $cart) { @@ -69,14 +70,18 @@ final class PhortuneOrderTableView extends AphrontView { if (count($purchases) == 1) { $purchase = head($purchases); - $purchase_name = $handles[$purchase->getPHID()]->renderLink(); + $purchase_name = $handles[$purchase->getPHID()]->getName(); $purchases = array(); } else { $purchase_name = ''; } if ($is_invoices) { - $merchant_link = $handles[$cart->getMerchantPHID()]->renderLink(); + if ($is_external) { + $merchant_link = $handles[$cart->getMerchantPHID()]->getName(); + } else { + $merchant_link = $handles[$cart->getMerchantPHID()]->renderLink(); + } } else { $merchant_link = null; } @@ -97,13 +102,12 @@ final class PhortuneOrderTableView extends AphrontView { PhortuneCart::getNameForStatus($cart->getStatus()), phabricator_datetime($cart->getDateModified(), $viewer), phabricator_datetime($cart->getDateCreated(), $viewer), - phutil_tag( - 'a', - array( - 'href' => $cart->getCheckoutURI(), - 'class' => 'small button button-green', - ), - pht('Pay Now')), + id(new PHUIButtonView()) + ->setTag('a') + ->setColor('green') + ->setHref($cart->getCheckoutURI()) + ->setText(pht('Pay Now')) + ->setIcon('fa-credit-card'), ); foreach ($purchases as $purchase) { $id = $purchase->getID(); @@ -164,7 +168,7 @@ final class PhortuneOrderTableView extends AphrontView { // We show "Pay Now" for due invoices, but not if the viewer is the // merchant, since it doesn't make sense for them to pay. - ($is_invoices && !$is_merchant), + ($is_invoices && !$is_merchant && !$is_external), )); return $table;