1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-16 16:58:38 +01:00

Update the Phortune cart/invoice workflow for policy changes

Summary:
Depends on D20734. Ref T13366. This makes the cart/order flow work under the new policy scheme with no "grantAuthority()" calls.

It prepares for a "Void Invoice" action, although the action doesn't actually do anything yet.

Test Plan: With and without merchant authority, viewed and paid invoices and went through the other invoice interaction workflows.

Maniphest Tasks: T13366

Differential Revision: https://secure.phabricator.com/D20735
This commit is contained in:
epriestley 2019-08-22 21:28:37 -07:00
parent b3f8045b87
commit a39a37fc0e
20 changed files with 300 additions and 564 deletions

View file

@ -5279,6 +5279,7 @@ phutil_register_library_map(array(
'PhortuneCartTransactionQuery' => 'applications/phortune/query/PhortuneCartTransactionQuery.php',
'PhortuneCartUpdateController' => 'applications/phortune/controller/cart/PhortuneCartUpdateController.php',
'PhortuneCartViewController' => 'applications/phortune/controller/cart/PhortuneCartViewController.php',
'PhortuneCartVoidController' => 'applications/phortune/controller/cart/PhortuneCartVoidController.php',
'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php',
'PhortuneChargePHIDType' => 'applications/phortune/phid/PhortuneChargePHIDType.php',
'PhortuneChargeQuery' => 'applications/phortune/query/PhortuneChargeQuery.php',
@ -5372,10 +5373,8 @@ phutil_register_library_map(array(
'PhortuneSubscription' => 'applications/phortune/storage/PhortuneSubscription.php',
'PhortuneSubscriptionAutopayTransaction' => 'applications/phortune/xaction/subscription/PhortuneSubscriptionAutopayTransaction.php',
'PhortuneSubscriptionCart' => 'applications/phortune/cart/PhortuneSubscriptionCart.php',
'PhortuneSubscriptionEditController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php',
'PhortuneSubscriptionEditor' => 'applications/phortune/editor/PhortuneSubscriptionEditor.php',
'PhortuneSubscriptionImplementation' => 'applications/phortune/subscription/PhortuneSubscriptionImplementation.php',
'PhortuneSubscriptionListController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionListController.php',
'PhortuneSubscriptionPHIDType' => 'applications/phortune/phid/PhortuneSubscriptionPHIDType.php',
'PhortuneSubscriptionPolicyCodex' => 'applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php',
'PhortuneSubscriptionProduct' => 'applications/phortune/product/PhortuneSubscriptionProduct.php',
@ -11861,6 +11860,7 @@ phutil_register_library_map(array(
'PhortuneCartTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortuneCartUpdateController' => 'PhortuneCartController',
'PhortuneCartViewController' => 'PhortuneCartController',
'PhortuneCartVoidController' => 'PhortuneCartController',
'PhortuneCharge' => array(
'PhortuneDAO',
'PhabricatorPolicyInterface',
@ -11984,10 +11984,8 @@ phutil_register_library_map(array(
),
'PhortuneSubscriptionAutopayTransaction' => 'PhortuneSubscriptionTransactionType',
'PhortuneSubscriptionCart' => 'PhortuneCartImplementation',
'PhortuneSubscriptionEditController' => 'PhortuneController',
'PhortuneSubscriptionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortuneSubscriptionImplementation' => 'Phobject',
'PhortuneSubscriptionListController' => 'PhortuneController',
'PhortuneSubscriptionPHIDType' => 'PhabricatorPHIDType',
'PhortuneSubscriptionPolicyCodex' => 'PhabricatorPolicyCodex',
'PhortuneSubscriptionProduct' => 'PhortuneProductImplementation',

View file

@ -43,6 +43,8 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication {
'checkout/' => 'PhortuneCartCheckoutController',
'(?P<action>print)/' => 'PhortuneCartViewController',
'(?P<action>cancel|refund)/' => 'PhortuneCartCancelController',
'accept/' => 'PhortuneCartAcceptController',
'void/' => 'PhortuneCartVoidController',
'update/' => 'PhortuneCartUpdateController',
),
'account/' => array(

View file

@ -2,42 +2,6 @@
abstract class PhortuneController extends PhabricatorController {
protected function addAccountCrumb(
$crumbs,
PhortuneAccount $account,
$link = true) {
$name = $account->getName();
$href = null;
if ($link) {
$href = $this->getApplicationURI($account->getID().'/');
$crumbs->addTextCrumb($name, $href);
} else {
$crumbs->addTextCrumb($name);
}
}
protected function addMerchantCrumb(
$crumbs,
PhortuneMerchant $merchant,
$link = true) {
$name = $merchant->getName();
$href = null;
$crumbs->addTextCrumb(
pht('Merchants'),
$this->getApplicationURI('merchant/'));
if ($link) {
$href = $this->getApplicationURI('merchant/'.$merchant->getID().'/');
$crumbs->addTextCrumb($name, $href);
} else {
$crumbs->addTextCrumb($name);
}
}
private function loadEnabledProvidersForMerchant(PhortuneMerchant $merchant) {
$viewer = $this->getRequest()->getUser();
@ -84,30 +48,4 @@ abstract class PhortuneController extends PhabricatorController {
return $providers;
}
protected function loadMerchantAuthority() {
$request = $this->getRequest();
$viewer = $this->getViewer();
$is_merchant = (bool)$request->getURIData('merchantID');
if (!$is_merchant) {
return null;
}
$merchant = id(new PhortuneMerchantQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('merchantID')))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$merchant) {
return null;
}
$viewer->grantAuthority($merchant);
return $merchant;
}
}

View file

@ -12,7 +12,7 @@ final class PhortuneAccountChargesController
$title = $account->getName();
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Order History'))
->addTextCrumb(pht('Orders'))
->setBorder(true);
$header = $this->buildHeaderView();
@ -46,22 +46,11 @@ final class PhortuneAccountChargesController
->setLimit(100)
->execute();
$phids = array();
foreach ($charges as $charge) {
$phids[] = $charge->getProviderPHID();
$phids[] = $charge->getCartPHID();
$phids[] = $charge->getMerchantPHID();
$phids[] = $charge->getPaymentMethodPHID();
}
$handles = $this->loadViewerHandles($phids);
$charges_uri = $account->getChargeListURI();
$table = id(new PhortuneChargeTableView())
->setUser($viewer)
->setCharges($charges)
->setHandles($handles);
->setCharges($charges);
$header = id(new PHUIHeaderView())
->setHeader(pht('Recent Charges'))

View file

@ -12,7 +12,7 @@ final class PhortuneAccountOrdersController
$title = $account->getName();
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Order History'))
->addTextCrumb(pht('Orders'))
->setBorder(true);
$header = $this->buildHeaderView();

View file

@ -66,13 +66,13 @@ abstract class PhortuneAccountProfileController
$nav->addFilter(
'orders',
pht('Order History'),
pht('Orders'),
$account->getOrdersURI(),
'fa-shopping-bag');
$nav->addFilter(
'charges',
pht('Charge History'),
pht('Charges'),
$account->getChargesURI(),
'fa-calculator');

View file

@ -3,27 +3,19 @@
final class PhortuneCartAcceptController
extends PhortuneCartController {
public function handleRequest(AphrontRequest $request) {
protected function shouldRequireAccountAuthority() {
return false;
}
protected function shouldRequireMerchantAuthority() {
return true;
}
protected function handleCartRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$cart = $this->getCart();
// You must control the merchant to accept orders.
$authority = $this->loadMerchantAuthority();
if (!$authority) {
return new Aphront404Response();
}
$cart = id(new PhortuneCartQuery())
->setViewer($viewer)
->withIDs(array($id))
->withMerchantPHIDs(array($authority->getPHID()))
->needPurchases(true)
->executeOne();
if (!$cart) {
return new Aphront404Response();
}
$cancel_uri = $cart->getDetailURI($authority);
$cancel_uri = $cart->getDetailURI();
if ($cart->getStatus() !== PhortuneCart::STATUS_REVIEW) {
return $this->newDialog()

View file

@ -3,26 +3,21 @@
final class PhortuneCartCancelController
extends PhortuneCartController {
public function handleRequest(AphrontRequest $request) {
protected function shouldRequireAccountAuthority() {
return false;
}
protected function shouldRequireMerchantAuthority() {
return false;
}
protected function handleCartRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$action = $request->getURIData('action');
$authority = $this->loadMerchantAuthority();
$cart_query = id(new PhortuneCartQuery())
->setViewer($viewer)
->withIDs(array($id))
->needPurchases(true);
if ($authority) {
$cart_query->withMerchantPHIDs(array($authority->getPHID()));
}
$cart = $cart_query->executeOne();
if (!$cart) {
return new Aphront404Response();
}
$cart = $this->getCart();
$authority = $this->getMerchantAuthority();
switch ($action) {
case 'cancel':
@ -45,7 +40,7 @@ final class PhortuneCartCancelController
return new Aphront404Response();
}
$cancel_uri = $cart->getDetailURI($authority);
$cancel_uri = $cart->getDetailURI();
$merchant = $cart->getMerchant();
try {
@ -60,7 +55,7 @@ final class PhortuneCartCancelController
return $this->newDialog()
->setTitle($title)
->appendChild($ex->getMessage())
->addCancelButton($cancel_uri);
->addCancelButton($cancel_uri, pht('Rats'));
}
$charges = id(new PhortuneChargeQuery())

View file

@ -3,18 +3,17 @@
final class PhortuneCartCheckoutController
extends PhortuneCartController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
protected function shouldRequireAccountAuthority() {
return true;
}
$cart = id(new PhortuneCartQuery())
->setViewer($viewer)
->withIDs(array($id))
->needPurchases(true)
->executeOne();
if (!$cart) {
return new Aphront404Response();
}
protected function shouldRequireMerchantAuthority() {
return false;
}
protected function handleCartRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$cart = $this->getCart();
$cancel_uri = $cart->getCancelURI();
$merchant = $cart->getMerchant();
@ -139,7 +138,10 @@ final class PhortuneCartCheckoutController
'cartID' => $cart->getID(),
);
$payment_method_uri = $this->getApplicationURI("{$account_id}/card/new/");
$payment_method_uri = urisprintf(
'account/%d/methods/new/',
$account->getID());
$payment_method_uri = $this->getApplicationURI($payment_method_uri);
$payment_method_uri = new PhutilURI($payment_method_uri, $params);
$form = id(new AphrontFormView())

View file

@ -3,6 +3,77 @@
abstract class PhortuneCartController
extends PhortuneController {
private $cart;
private $merchantAuthority;
abstract protected function shouldRequireAccountAuthority();
abstract protected function shouldRequireMerchantAuthority();
abstract protected function handleCartRequest(AphrontRequest $request);
final public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
if ($this->shouldRequireAccountAuthority()) {
$capabilities = array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
} else {
$capabilities = array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
$cart = id(new PhortuneCartQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->needPurchases(true)
->requireCapabilities($capabilities)
->executeOne();
if (!$cart) {
return new Aphront404Response();
}
if ($this->shouldRequireMerchantAuthority()) {
PhabricatorPolicyFilter::requireCapability(
$viewer,
$cart->getMerchant(),
PhabricatorPolicyCapability::CAN_EDIT);
}
$this->cart = $cart;
$can_edit = PhortuneMerchantQuery::canViewersEditMerchants(
array($viewer->getPHID()),
array($cart->getMerchantPHID()));
if ($can_edit) {
$this->merchantAuthority = $cart->getMerchant();
} else {
$this->merchantAuthority = null;
}
return $this->handleCartRequest($request);
}
final protected function getCart() {
return $this->cart;
}
final protected function getMerchantAuthority() {
return $this->merchantAuthority;
}
final protected function hasMerchantAuthority() {
return (bool)$this->merchantAuthority;
}
final protected function hasAccountAuthority() {
return (bool)PhabricatorPolicyFilter::hasCapability(
$this->getViewer(),
$this->getCart(),
PhabricatorPolicyCapability::CAN_EDIT);
}
protected function buildCartContentTable(PhortuneCart $cart) {
$rows = array();

View file

@ -3,25 +3,20 @@
final class PhortuneCartUpdateController
extends PhortuneCartController {
public function handleRequest(AphrontRequest $request) {
protected function shouldRequireAccountAuthority() {
return false;
}
protected function shouldRequireMerchantAuthority() {
return false;
}
protected function handleCartRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$authority = $this->loadMerchantAuthority();
$cart_query = id(new PhortuneCartQuery())
->setViewer($viewer)
->withIDs(array($id))
->needPurchases(true);
if ($authority) {
$cart_query->withMerchantPHIDs(array($authority->getPHID()));
}
$cart = $cart_query->executeOne();
if (!$cart) {
return new Aphront404Response();
}
$cart = $this->getCart();
$authority = $this->getMerchantAuthority();
$charges = id(new PhortuneChargeQuery())
->setViewer($viewer)
@ -60,7 +55,7 @@ final class PhortuneCartUpdateController
}
return id(new AphrontRedirectResponse())
->setURI($cart->getDetailURI($authority));
->setURI($cart->getDetailURI());
}
}

View file

@ -5,62 +5,33 @@ final class PhortuneCartViewController
private $action = null;
public function handleRequest(AphrontRequest $request) {
protected function shouldRequireAccountAuthority() {
return false;
}
protected function shouldRequireMerchantAuthority() {
return false;
}
protected function handleCartRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$cart = $this->getCart();
$authority = $this->getMerchantAuthority();
$can_edit = $this->hasAccountAuthority();
$this->action = $request->getURIData('action');
$authority = $this->loadMerchantAuthority();
require_celerity_resource('phortune-css');
$query = id(new PhortuneCartQuery())
->setViewer($viewer)
->withIDs(array($id))
->needPurchases(true);
if ($authority) {
$query->withMerchantPHIDs(array($authority->getPHID()));
}
$cart = $query->executeOne();
if (!$cart) {
return new Aphront404Response();
}
$cart_table = $this->buildCartContentTable($cart);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$cart,
PhabricatorPolicyCapability::CAN_EDIT);
$errors = array();
$error_view = null;
$resume_uri = null;
switch ($cart->getStatus()) {
case PhortuneCart::STATUS_READY:
if ($authority && $cart->getIsInvoice()) {
// We arrived here by following the ad-hoc invoice workflow, and
// are acting with merchant authority.
$checkout_uri = PhabricatorEnv::getURI($cart->getCheckoutURI());
$invoice_message = array(
pht(
'Manual invoices do not automatically notify recipients yet. '.
'Send the payer this checkout link:'),
' ',
phutil_tag(
'a',
array(
'href' => $checkout_uri,
),
$checkout_uri),
);
if ($cart->getIsInvoice()) {
$error_view = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors(array($invoice_message));
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->appendChild(pht('This invoice is ready for payment.'));
}
break;
case PhortuneCart::STATUS_PURCHASING:
@ -133,7 +104,7 @@ final class PhortuneCartViewController
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($cart->getName())
->setHeaderIcon('fa-shopping-cart');
->setHeaderIcon('fa-shopping-bag');
if ($cart->getStatus() == PhortuneCart::STATUS_PURCHASED) {
$done_uri = $cart->getDoneURI();
@ -160,18 +131,8 @@ final class PhortuneCartViewController
->needCarts(true)
->execute();
$phids = array();
foreach ($charges as $charge) {
$phids[] = $charge->getProviderPHID();
$phids[] = $charge->getCartPHID();
$phids[] = $charge->getMerchantPHID();
$phids[] = $charge->getPaymentMethodPHID();
}
$handles = $this->loadViewerHandles($phids);
$charges_table = id(new PhortuneChargeTableView())
->setUser($viewer)
->setHandles($handles)
->setCharges($charges)
->setShowOrder(false);
@ -182,14 +143,13 @@ final class PhortuneCartViewController
$account = $cart->getAccount();
$crumbs = $this->buildApplicationCrumbs();
if ($authority) {
$this->addMerchantCrumb($crumbs, $authority);
} else {
$this->addAccountCrumb($crumbs, $cart->getAccount());
}
$crumbs->addTextCrumb(pht('Cart %d', $cart->getID()));
$crumbs->setBorder(true);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($account->getName(), $account->getURI())
->addTextCrumb(pht('Orders'), $account->getOrdersURI())
->addTextCrumb(pht('Cart %d', $cart->getID()))
->setBorder(true);
require_celerity_resource('phortune-css');
if (!$this->action) {
$class = 'phortune-cart-page';
@ -267,6 +227,7 @@ final class PhortuneCartViewController
if ($crumbs) {
$page->setCrumbs($crumbs);
}
return $page;
}
@ -318,20 +279,31 @@ final class PhortuneCartViewController
$viewer = $this->getViewer();
$id = $cart->getID();
$curtain = $this->newCurtainView($cart);
$status = $cart->getStatus();
$is_ready = ($status === PhortuneCart::STATUS_READY);
$can_cancel = ($can_edit && $cart->canCancelOrder());
$can_checkout = ($can_edit && $is_ready);
$can_accept = ($status === PhortuneCart::STATUS_REVIEW);
$can_refund = ($authority && $cart->canRefundOrder());
$can_void = ($authority && $cart->canVoidOrder());
if ($authority) {
$prefix = 'merchant/'.$authority->getID().'/';
} else {
$prefix = '';
}
$cancel_uri = $this->getApplicationURI("cart/{$id}/cancel/");
$refund_uri = $this->getApplicationURI("cart/{$id}/refund/");
$update_uri = $this->getApplicationURI("cart/{$id}/update/");
$accept_uri = $this->getApplicationURI("cart/{$id}/accept/");
$print_uri = $this->getApplicationURI("cart/{$id}/print/");
$checkout_uri = $cart->getCheckoutURI();
$void_uri = $this->getApplicationURI("cart/{$id}/void/");
$cancel_uri = $this->getApplicationURI("{$prefix}cart/{$id}/cancel/");
$refund_uri = $this->getApplicationURI("{$prefix}cart/{$id}/refund/");
$update_uri = $this->getApplicationURI("{$prefix}cart/{$id}/update/");
$accept_uri = $this->getApplicationURI("{$prefix}cart/{$id}/accept/");
$print_uri = $this->getApplicationURI("{$prefix}cart/{$id}/print/");
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Pay Now'))
->setIcon('fa-credit-card')
->setDisabled(!$can_checkout)
->setWorkflow(!$can_checkout)
->setHref($checkout_uri));
$curtain->addAction(
id(new PhabricatorActionView())
@ -341,24 +313,6 @@ final class PhortuneCartViewController
->setWorkflow(true)
->setHref($cancel_uri));
if ($authority) {
if ($cart->getStatus() == PhortuneCart::STATUS_REVIEW) {
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Accept Order'))
->setIcon('fa-check')
->setWorkflow(true)
->setHref($accept_uri));
}
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Refund Order'))
->setIcon('fa-reply')
->setWorkflow(true)
->setHref($refund_uri));
}
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Update Status'))
@ -369,7 +323,7 @@ final class PhortuneCartViewController
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Continue Checkout'))
->setIcon('fa-shopping-cart')
->setIcon('fa-shopping-bag')
->setHref($resume_uri));
}
@ -380,6 +334,36 @@ final class PhortuneCartViewController
->setOpenInNewWindow(true)
->setIcon('fa-print'));
if ($authority) {
$curtain->addAction(
id(new PhabricatorActionView())
->setType(PhabricatorActionView::TYPE_DIVIDER));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Accept Order'))
->setIcon('fa-check')
->setWorkflow(true)
->setDisabled(!$can_accept)
->setHref($accept_uri));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Refund Order'))
->setIcon('fa-reply')
->setWorkflow(true)
->setDisabled(!$can_refund)
->setHref($refund_uri));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Void Invoice'))
->setIcon('fa-times')
->setWorkflow(true)
->setDisabled(!$can_void)
->setHref($void_uri));
}
return $curtain;
}

View file

@ -0,0 +1,43 @@
<?php
final class PhortuneCartVoidController
extends PhortuneCartController {
protected function shouldRequireAccountAuthority() {
return false;
}
protected function shouldRequireMerchantAuthority() {
return true;
}
protected function handleCartRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$cart = $this->getCart();
$cancel_uri = $cart->getDetailURI();
try {
$title = pht('Unable to Void Invoice');
$cart->assertCanVoidOrder();
} catch (Exception $ex) {
return $this->newDialog()
->setTitle($title)
->appendChild($ex->getMessage())
->addCancelButton($cancel_uri);
}
if ($request->isFormPost()) {
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
}
return $this->newDialog()
->setTitle(pht('Void Invoice?'))
->appendParagraph(
pht(
'Really void this invoice? The customer will no longer be asked '.
'to submit payment for it.'))
->addCancelButton($cancel_uri)
->addSubmitButton(pht('Void Invoice'));
}
}

View file

@ -1,185 +0,0 @@
<?php
final class PhortuneSubscriptionEditController extends PhortuneController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$added = $request->getBool('added');
$subscription = id(new PhortuneSubscriptionQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$subscription) {
return new Aphront404Response();
}
id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession(
$viewer,
$request,
$subscription->getURI());
$merchant = $subscription->getMerchant();
$account = $subscription->getAccount();
$title = pht('Subscription: %s', $subscription->getSubscriptionName());
$header = id(new PHUIHeaderView())
->setHeader($subscription->getSubscriptionName());
$view_uri = $subscription->getURI();
$valid_methods = id(new PhortunePaymentMethodQuery())
->setViewer($viewer)
->withAccountPHIDs(array($account->getPHID()))
->withStatuses(
array(
PhortunePaymentMethod::STATUS_ACTIVE,
))
->withMerchantPHIDs(array($merchant->getPHID()))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->execute();
$valid_methods = mpull($valid_methods, null, 'getPHID');
$current_phid = $subscription->getDefaultPaymentMethodPHID();
$e_method = null;
if ($current_phid && empty($valid_methods[$current_phid])) {
$e_method = pht('Needs Update');
}
$errors = array();
if ($request->isFormPost()) {
$default_method_phid = $request->getStr('defaultPaymentMethodPHID');
if (!$default_method_phid) {
$default_method_phid = null;
$e_method = null;
} else if (empty($valid_methods[$default_method_phid])) {
$e_method = pht('Invalid');
if ($default_method_phid == $current_phid) {
$errors[] = pht(
'This subscription is configured to autopay with a payment method '.
'that has been deleted. Choose a valid payment method or disable '.
'autopay.');
} else {
$errors[] = pht('You must select a valid default payment method.');
}
}
// TODO: We should use transactions here, and move the validation logic
// inside the Editor.
if (!$errors) {
$subscription->setDefaultPaymentMethodPHID($default_method_phid);
$subscription->save();
return id(new AphrontRedirectResponse())
->setURI($view_uri);
}
}
// Add the option to disable autopay.
$disable_options = array(
'' => pht('(Disable Autopay)'),
);
// Don't require the user to make a valid selection if the current method
// has become invalid.
if ($current_phid && empty($valid_methods[$current_phid])) {
$current_options = array(
$current_phid => pht('<Deleted Payment Method>'),
);
} else {
$current_options = array();
}
// Add any available options.
$valid_options = mpull($valid_methods, 'getFullDisplayName', 'getPHID');
$options = $disable_options + $current_options + $valid_options;
$crumbs = $this->buildApplicationCrumbs();
$this->addAccountCrumb($crumbs, $account);
$crumbs->addTextCrumb(
pht('Subscription %d', $subscription->getID()),
$view_uri);
$crumbs->addTextCrumb(pht('Edit'));
$crumbs->setBorder(true);
$uri = $this->getApplicationURI($account->getID().'/card/new/');
$uri = new PhutilURI($uri);
$uri->replaceQueryParam('merchantID', $merchant->getID());
$uri->replaceQueryParam('subscriptionID', $subscription->getID());
$add_method_button = phutil_tag(
'a',
array(
'href' => $uri,
'class' => 'button button-grey',
),
pht('Add Payment Method...'));
$radio = id(new AphrontFormRadioButtonControl())
->setName('defaultPaymentMethodPHID')
->setLabel(pht('Autopay With'))
->setValue($current_phid)
->setError($e_method);
foreach ($options as $key => $value) {
$radio->addButton($key, $value, null);
}
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild($radio)
->appendChild(
id(new AphrontFormMarkupControl())
->setValue($add_method_button))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Changes'))
->addCancelButton($view_uri));
$box = id(new PHUIObjectBoxView())
->setUser($viewer)
->setHeaderText(pht('Subscription'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setFormErrors($errors)
->appendChild($form);
if ($added) {
$info_view = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_SUCCESS)
->appendChild(pht('Payment method has been successfully added.'));
$box->setInfoView($info_view);
}
$header = id(new PHUIHeaderView())
->setHeader(pht('Edit %s', $subscription->getSubscriptionName()))
->setHeaderIcon('fa-pencil');
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$box,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($view);
}
}

View file

@ -1,99 +0,0 @@
<?php
final class PhortuneSubscriptionListController
extends PhortuneController {
private $merchant;
private $account;
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$querykey = $request->getURIData('queryKey');
$merchant_id = $request->getURIData('merchantID');
$account_id = $request->getURIData('accountID');
$engine = new PhortuneSubscriptionSearchEngine();
if ($merchant_id) {
$merchant = id(new PhortuneMerchantQuery())
->setViewer($viewer)
->withIDs(array($merchant_id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$merchant) {
return new Aphront404Response();
}
$this->merchant = $merchant;
$viewer->grantAuthority($merchant);
$engine->setMerchant($merchant);
} else if ($account_id) {
$account = id(new PhortuneAccountQuery())
->setViewer($viewer)
->withIDs(array($account_id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$account) {
return new Aphront404Response();
}
$this->account = $account;
$engine->setAccount($account);
} else {
return new Aphront404Response();
}
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($querykey)
->setSearchEngine($engine)
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
}
public function buildSideNavView() {
$viewer = $this->getViewer();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new PhortuneSubscriptionSearchEngine())
->setViewer($viewer)
->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
return $nav;
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$merchant = $this->merchant;
if ($merchant) {
$id = $merchant->getID();
$this->addMerchantCrumb($crumbs, $merchant);
$crumbs->addTextCrumb(
pht('Subscriptions'),
$this->getApplicationURI("merchant/subscriptions/{$id}/"));
}
$account = $this->account;
if ($account) {
$id = $account->getID();
$this->addAccountCrumb($crumbs, $account);
$crumbs->addTextCrumb(
pht('Subscriptions'),
$this->getApplicationURI("{$id}/subscription/"));
}
return $crumbs;
}
}

View file

@ -105,7 +105,7 @@ final class PhortuneCartSearchEngine
$merchant = $this->getMerchant();
$account = $this->getAccount();
if ($merchant) {
return '/phortune/merchant/orders/'.$merchant->getID().'/'.$path;
return $merchant->getOrderListURI($path);
} else if ($account) {
return $account->getOrderListURI($path);
} else {

View file

@ -62,7 +62,7 @@ final class PhortuneChargeSearchEngine
protected function getURI($path) {
$account = $this->getAccount();
if ($account) {
return '/phortune/'.$account->getID().'/charge/';
return $account->getChargeListURI($path);
} else {
return '/phortune/charge/'.$path;
}
@ -89,20 +89,6 @@ final class PhortuneChargeSearchEngine
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function getRequiredHandlePHIDsForResultList(
array $charges,
PhabricatorSavedQuery $query) {
$phids = array();
foreach ($charges as $charge) {
$phids[] = $charge->getProviderPHID();
$phids[] = $charge->getCartPHID();
$phids[] = $charge->getMerchantPHID();
$phids[] = $charge->getPaymentMethodPHID();
}
return $phids;
}
protected function renderResultList(
array $charges,
@ -114,8 +100,7 @@ final class PhortuneChargeSearchEngine
$table = id(new PhortuneChargeTableView())
->setUser($viewer)
->setCharges($charges)
->setHandles($handles);
->setCharges($charges);
$result = new PhabricatorApplicationSearchResultView();
$result->setTable($table);

View file

@ -96,9 +96,9 @@ final class PhortuneSubscriptionSearchEngine
$merchant = $this->getMerchant();
$account = $this->getAccount();
if ($merchant) {
return '/phortune/merchant/'.$merchant->getID().'/subscription/'.$path;
return $merchant->getSubscriptionListURI($path);
} else if ($account) {
return '/phortune/'.$account->getID().'/subscription/';
return $account->getSubscriptionListURI($path);
} else {
return '/phortune/subscription/'.$path;
}

View file

@ -471,13 +471,10 @@ final class PhortuneCart extends PhortuneDAO
return $this->getImplementation()->getDescription($this);
}
public function getDetailURI(PhortuneMerchant $authority = null) {
if ($authority) {
$prefix = 'merchant/'.$authority->getID().'/';
} else {
$prefix = '';
}
return '/phortune/'.$prefix.'cart/'.$this->getID().'/';
public function getDetailURI() {
return urisprintf(
'/phortune/cart/%d/',
$this->getID());
}
public function getCheckoutURI() {
@ -502,6 +499,15 @@ final class PhortuneCart extends PhortuneDAO
}
}
public function canVoidOrder() {
try {
$this->assertCanVoidOrder();
return true;
} catch (Exception $ex) {
return false;
}
}
public function assertCanCancelOrder() {
switch ($this->getStatus()) {
case self::STATUS_BUILDING:
@ -534,6 +540,27 @@ final class PhortuneCart extends PhortuneDAO
return $this->getImplementation()->assertCanRefundOrder($this);
}
public function assertCanVoidOrder() {
if (!$this->getIsInvoice()) {
throw new Exception(
pht(
'This order can not be voided because it is not an invoice.'));
}
switch ($this->getStatus()) {
case self::STATUS_READY:
break;
default:
throw new Exception(
pht(
'This order can not be voided because it is not ready for '.
'payment.'));
}
return null;
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,

View file

@ -3,7 +3,6 @@
final class PhortuneChargeTableView extends AphrontView {
private $charges;
private $handles;
private $showOrder;
public function setShowOrder($show_order) {
@ -15,15 +14,6 @@ final class PhortuneChargeTableView extends AphrontView {
return $this->showOrder;
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function getHandles() {
return $this->handles;
}
public function setCharges(array $charges) {
$this->charges = $charges;
return $this;
@ -35,8 +25,17 @@ final class PhortuneChargeTableView extends AphrontView {
public function render() {
$charges = $this->getCharges();
$handles = $this->getHandles();
$viewer = $this->getUser();
$viewer = $this->getViewer();
$phids = array();
foreach ($charges as $charge) {
$phids[] = $charge->getCartPHID();
$phids[] = $charge->getProviderPHID();
$phids[] = $charge->getPaymentMethodPHID();
$phids[] = $charge->getMerchantPHID();
}
$handles = $viewer->loadHandles($phids);
$rows = array();
foreach ($charges as $charge) {