mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-21 13:00:56 +01:00
Support multiple payment accounts and account switching in Phortune
Summary: Ref T2787. Support multiple payment accounts so you can have personal vs company payment accounts. Test Plan: See screenshots. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T2787 Differential Revision: https://secure.phabricator.com/D10673
This commit is contained in:
parent
f53861aa9d
commit
fe5bc764b3
10 changed files with 338 additions and 15 deletions
|
@ -2547,7 +2547,9 @@ phutil_register_library_map(array(
|
|||
'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php',
|
||||
'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php',
|
||||
'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php',
|
||||
'PhortuneAccountEditController' => 'applications/phortune/controller/PhortuneAccountEditController.php',
|
||||
'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php',
|
||||
'PhortuneAccountListController' => 'applications/phortune/controller/PhortuneAccountListController.php',
|
||||
'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php',
|
||||
'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php',
|
||||
'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php',
|
||||
|
@ -5601,7 +5603,9 @@ phutil_register_library_map(array(
|
|||
'PhortuneDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'PhortuneAccountEditController' => 'PhortuneController',
|
||||
'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'PhortuneAccountListController' => 'PhortuneController',
|
||||
'PhortuneAccountPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction',
|
||||
|
|
|
@ -39,11 +39,25 @@ final class FundInitiativeBackController
|
|||
->addCancelButton($initiative_uri);
|
||||
}
|
||||
|
||||
$accounts = PhortuneAccountQuery::loadAccountsForUser(
|
||||
$viewer,
|
||||
PhabricatorContentSource::newFromRequest($request));
|
||||
|
||||
$v_amount = null;
|
||||
$e_amount = true;
|
||||
|
||||
$v_account = head($accounts)->getPHID();
|
||||
|
||||
$errors = array();
|
||||
if ($request->isFormPost()) {
|
||||
$v_amount = $request->getStr('amount');
|
||||
$v_account = $request->getStr('accountPHID');
|
||||
|
||||
if (empty($accounts[$v_account])) {
|
||||
$errors[] = pht('You must specify an account.');
|
||||
} else {
|
||||
$account = $accounts[$v_account];
|
||||
}
|
||||
|
||||
if (!strlen($v_amount)) {
|
||||
$errors[] = pht(
|
||||
|
@ -74,10 +88,6 @@ final class FundInitiativeBackController
|
|||
->withClassAndRef('FundBackerProduct', $initiative->getPHID())
|
||||
->executeOne();
|
||||
|
||||
$account = PhortuneAccountQuery::loadActiveAccountForUser(
|
||||
$viewer,
|
||||
PhabricatorContentSource::newFromRequest($request));
|
||||
|
||||
$cart_implementation = id(new FundBackerCart())
|
||||
->setInitiative($initiative);
|
||||
|
||||
|
@ -110,6 +120,12 @@ final class FundInitiativeBackController
|
|||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
->setName('accountPHID')
|
||||
->setLabel(pht('Account'))
|
||||
->setValue($v_account)
|
||||
->setOptions(mpull($accounts, 'getName', 'getPHID')))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setName('amount')
|
||||
|
|
|
@ -79,8 +79,6 @@ final class FundBackerProduct extends PhortuneProductImplementation {
|
|||
public function didPurchaseProduct(
|
||||
PhortuneProduct $product,
|
||||
PhortunePurchase $purchase) {
|
||||
// TODO: This viewer may be wrong if the purchase completes after a hold
|
||||
// we should load the backer explicitly.
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$backer = id(new FundBackerQuery())
|
||||
|
@ -91,25 +89,33 @@ final class FundBackerProduct extends PhortuneProductImplementation {
|
|||
throw new Exception(pht('Unable to load FundBacker!'));
|
||||
}
|
||||
|
||||
// Load the actual backing user --they may not be the curent viewer if this
|
||||
// product purchase is completing from a background worker or a merchant
|
||||
// action.
|
||||
|
||||
$actor = id(new PhabricatorPeopleQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($backer->getBackerPHID()))
|
||||
->executeOne();
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = id(new FundBackerTransaction())
|
||||
->setTransactionType(FundBackerTransaction::TYPE_STATUS)
|
||||
->setNewValue(FundBacker::STATUS_PURCHASED);
|
||||
|
||||
$editor = id(new FundBackerEditor())
|
||||
->setActor($viewer)
|
||||
->setActor($actor)
|
||||
->setContentSource($this->getContentSource());
|
||||
|
||||
$editor->applyTransactions($backer, $xactions);
|
||||
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = id(new FundInitiativeTransaction())
|
||||
->setTransactionType(FundInitiativeTransaction::TYPE_BACKER)
|
||||
->setNewValue($backer->getPHID());
|
||||
|
||||
$editor = id(new FundInitiativeEditor())
|
||||
->setActor($viewer)
|
||||
->setActor($actor)
|
||||
->setContentSource($this->getContentSource());
|
||||
|
||||
$editor->applyTransactions($this->getInitiative(), $xactions);
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
final class PhortuneAccountEditController extends PhortuneController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = idx($data, 'id');
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
if ($this->id) {
|
||||
$account = id(new PhortuneAccountQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
if (!$account) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
$is_new = false;
|
||||
} else {
|
||||
$account = PhortuneAccount::initializeNewAccount($viewer);
|
||||
$is_new = true;
|
||||
}
|
||||
|
||||
$v_name = $account->getName();
|
||||
$e_name = true;
|
||||
$validation_exception = null;
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$v_name = $request->getStr('name');
|
||||
|
||||
$type_name = PhortuneAccountTransaction::TYPE_NAME;
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = id(new PhortuneAccountTransaction())
|
||||
->setTransactionType($type_name)
|
||||
->setNewValue($v_name);
|
||||
|
||||
if ($is_new) {
|
||||
$xactions[] = id(new PhortuneAccountTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue(
|
||||
'edge:type',
|
||||
PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER)
|
||||
->setNewValue(
|
||||
array(
|
||||
'=' => array($viewer->getPHID() => $viewer->getPHID()),
|
||||
));
|
||||
}
|
||||
|
||||
$editor = id(new PhortuneAccountEditor())
|
||||
->setActor($viewer)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setContinueOnNoEffect(true);
|
||||
|
||||
try {
|
||||
$editor->applyTransactions($account, $xactions);
|
||||
|
||||
$account_uri = $this->getApplicationURI($account->getID().'/');
|
||||
return id(new AphrontRedirectResponse())->setURI($account_uri);
|
||||
} catch (PhabricatorApplicationTransactionValidationException $ex) {
|
||||
$validation_exception = $ex;
|
||||
$e_name = $ex->getShortMessage($type_name);
|
||||
}
|
||||
}
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
|
||||
if ($is_new) {
|
||||
$cancel_uri = $this->getApplicationURI('account/');
|
||||
$crumbs->addTextCrumb(pht('Accounts'), $cancel_uri);
|
||||
$crumbs->addTextCrumb(pht('Create Account'));
|
||||
|
||||
$title = pht('Create Payment Account');
|
||||
$submit_button = pht('Create Account');
|
||||
} else {
|
||||
$cancel_uri = $this->getApplicationURI($account->getID().'/');
|
||||
$crumbs->addTextCrumb($account->getName(), $cancel_uri);
|
||||
$crumbs->addTextCrumb(pht('Edit'));
|
||||
|
||||
$title = pht('Edit %s', $account->getName());
|
||||
$submit_button = pht('Save Changes');
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setName('name')
|
||||
->setLabel(pht('Name'))
|
||||
->setValue($v_name)
|
||||
->setError($e_name))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue($submit_button)
|
||||
->addCancelButton($cancel_uri));
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($title)
|
||||
->setValidationException($validation_exception)
|
||||
->appendChild($form);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$box,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
final class PhortuneAccountListController extends PhortuneController {
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$accounts = id(new PhortuneAccountQuery())
|
||||
->setViewer($viewer)
|
||||
->withMemberPHIDs(array($viewer->getPHID()))
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->execute();
|
||||
|
||||
$merchants = id(new PhortuneMerchantQuery())
|
||||
->setViewer($viewer)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->execute();
|
||||
|
||||
$title = pht('Accounts');
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Accounts'));
|
||||
|
||||
$payment_list = id(new PHUIObjectItemListView())
|
||||
->setUser($viewer)
|
||||
->setNoDataString(
|
||||
pht(
|
||||
'You are not a member of any payment accounts. Payment '.
|
||||
'accounts are used to make purchases.'));
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName(pht('Account %d', $account->getID()))
|
||||
->setHeader($account->getName())
|
||||
->setHref($this->getApplicationURI($account->getID().'/'))
|
||||
->setObject($account);
|
||||
|
||||
$payment_list->addItem($item);
|
||||
}
|
||||
|
||||
$payment_header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Payment Accounts'))
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setHref($this->getApplicationURI('account/edit/'))
|
||||
->setIcon(
|
||||
id(new PHUIIconView())
|
||||
->setIconFont('fa-plus'))
|
||||
->setText(pht('Create Account')));
|
||||
|
||||
$payment_box = id(new PHUIObjectBoxView())
|
||||
->setHeader($payment_header)
|
||||
->appendChild($payment_list);
|
||||
|
||||
$merchant_list = id(new PHUIObjectItemListView())
|
||||
->setUser($viewer)
|
||||
->setNoDataString(
|
||||
pht(
|
||||
'You do not control any merchant accounts. Merchant accounts are '.
|
||||
'used to receive payments.'));
|
||||
|
||||
foreach ($merchants as $merchant) {
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName(pht('Merchant %d', $merchant->getID()))
|
||||
->setHeader($merchant->getName())
|
||||
->setHref($this->getApplicationURI('/merchant/'.$merchant->getID().'/'))
|
||||
->setObject($merchant);
|
||||
|
||||
$merchant_list->addItem($item);
|
||||
}
|
||||
|
||||
$merchant_header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Merchant Accounts'))
|
||||
->addActionLink(
|
||||
id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setHref($this->getApplicationURI('merchant/'))
|
||||
->setIcon(
|
||||
id(new PHUIIconView())
|
||||
->setIconFont('fa-folder-open'))
|
||||
->setText(pht('Browse Merchants')));
|
||||
|
||||
$merchant_box = id(new PHUIObjectBoxView())
|
||||
->setHeader($merchant_header)
|
||||
->appendChild($merchant_list);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$payment_box,
|
||||
$merchant_box,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,7 @@ final class PhortuneAccountViewController extends PhortuneController {
|
|||
// process orders but merchants should not be able to see all the details
|
||||
// of an account. Ideally this page should be visible to merchants, too,
|
||||
// just with less information.
|
||||
$can_edit = true;
|
||||
|
||||
$account = id(new PhortuneAccountQuery())
|
||||
->setViewer($user)
|
||||
|
@ -27,7 +28,6 @@ final class PhortuneAccountViewController extends PhortuneController {
|
|||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->executeOne();
|
||||
|
||||
if (!$account) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
@ -35,11 +35,15 @@ final class PhortuneAccountViewController extends PhortuneController {
|
|||
$title = $account->getName();
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Account'), $request->getRequestURI());
|
||||
$crumbs->addTextCrumb(
|
||||
$account->getName(),
|
||||
$request->getRequestURI());
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($title);
|
||||
|
||||
$edit_uri = $this->getApplicationURI('account/edit/'.$account->getID().'/');
|
||||
|
||||
$actions = id(new PhabricatorActionListView())
|
||||
->setUser($user)
|
||||
->setObjectURI($request->getRequestURI())
|
||||
|
@ -47,8 +51,9 @@ final class PhortuneAccountViewController extends PhortuneController {
|
|||
id(new PhabricatorActionView())
|
||||
->setName(pht('Edit Account'))
|
||||
->setIcon('fa-pencil')
|
||||
->setHref('#')
|
||||
->setDisabled(true))
|
||||
->setHref($edit_uri)
|
||||
->setDisabled(!$can_edit)
|
||||
->setWorkflow(!$can_edit))
|
||||
->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Edit Members'))
|
||||
|
@ -291,4 +296,17 @@ final class PhortuneAccountViewController extends PhortuneController {
|
|||
return $xaction_view;
|
||||
}
|
||||
|
||||
protected function buildApplicationCrumbs() {
|
||||
$crumbs = parent::buildApplicationCrumbs();
|
||||
|
||||
$crumbs->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setIcon('fa-exchange')
|
||||
->setHref($this->getApplicationURI('account/'))
|
||||
->setName(pht('Switch Accounts')));
|
||||
|
||||
return $crumbs;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ final class PhortuneCartCheckoutController
|
|||
->setHeaderText(pht('Cart Contents'))
|
||||
->appendChild($cart_table);
|
||||
|
||||
$title = pht('Buy Stuff');
|
||||
$title = $cart->getName();
|
||||
|
||||
if (!$methods) {
|
||||
$method_control = id(new AphrontFormStaticControl())
|
||||
|
@ -210,6 +210,7 @@ final class PhortuneCartCheckoutController
|
|||
->appendChild($provider_form);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Checkout'));
|
||||
$crumbs->addTextCrumb($title);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
|
|
|
@ -67,4 +67,32 @@ final class PhortuneAccountEditor
|
|||
return parent::applyCustomExternalTransaction($object, $xaction);
|
||||
}
|
||||
|
||||
protected function validateTransaction(
|
||||
PhabricatorLiskDAO $object,
|
||||
$type,
|
||||
array $xactions) {
|
||||
|
||||
$errors = parent::validateTransaction($object, $type, $xactions);
|
||||
|
||||
switch ($type) {
|
||||
case PhortuneAccountTransaction::TYPE_NAME:
|
||||
$missing = $this->validateIsEmptyTextField(
|
||||
$object->getName(),
|
||||
$xactions);
|
||||
|
||||
if ($missing) {
|
||||
$error = new PhabricatorApplicationTransactionValidationError(
|
||||
$type,
|
||||
pht('Required'),
|
||||
pht('Account name is required.'),
|
||||
nonempty(last($xactions), null));
|
||||
|
||||
$error->setIsMissingFieldError(true);
|
||||
$errors[] = $error;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,26 @@ final class PhortuneAccountQuery
|
|||
private $phids;
|
||||
private $memberPHIDs;
|
||||
|
||||
public static function loadAccountsForUser(
|
||||
PhabricatorUser $user,
|
||||
PhabricatorContentSource $content_source) {
|
||||
|
||||
$accounts = id(new PhortuneAccountQuery())
|
||||
->setViewer($user)
|
||||
->withMemberPHIDs(array($user->getPHID()))
|
||||
->execute();
|
||||
|
||||
if (!$accounts) {
|
||||
$accounts = array(
|
||||
PhortuneAccount::createNewAccount($user, $content_source),
|
||||
);
|
||||
}
|
||||
|
||||
$accounts = mpull($accounts, null, 'getPHID');
|
||||
|
||||
return $accounts;
|
||||
}
|
||||
|
||||
public static function loadActiveAccountForUser(
|
||||
PhabricatorUser $user,
|
||||
PhabricatorContentSource $content_source) {
|
||||
|
|
|
@ -30,7 +30,7 @@ final class PhortuneAccount extends PhortuneDAO
|
|||
$xactions = array();
|
||||
$xactions[] = id(new PhortuneAccountTransaction())
|
||||
->setTransactionType(PhortuneAccountTransaction::TYPE_NAME)
|
||||
->setNewValue(pht('Account (%s)', $actor->getUserName()));
|
||||
->setNewValue(pht('Personal Account'));
|
||||
|
||||
$xactions[] = id(new PhortuneAccountTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
|
|
Loading…
Reference in a new issue