@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+338 -15
+4
src/__phutil_library_map__.php
··· 2547 2547 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', 2548 2548 'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php', 2549 2549 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', 2550 + 'PhortuneAccountEditController' => 'applications/phortune/controller/PhortuneAccountEditController.php', 2550 2551 'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php', 2552 + 'PhortuneAccountListController' => 'applications/phortune/controller/PhortuneAccountListController.php', 2551 2553 'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php', 2552 2554 'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php', 2553 2555 'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php', ··· 5601 5603 'PhortuneDAO', 5602 5604 'PhabricatorPolicyInterface', 5603 5605 ), 5606 + 'PhortuneAccountEditController' => 'PhortuneController', 5604 5607 'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor', 5608 + 'PhortuneAccountListController' => 'PhortuneController', 5605 5609 'PhortuneAccountPHIDType' => 'PhabricatorPHIDType', 5606 5610 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5607 5611 'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction',
+20 -4
src/applications/fund/controller/FundInitiativeBackController.php
··· 39 39 ->addCancelButton($initiative_uri); 40 40 } 41 41 42 + $accounts = PhortuneAccountQuery::loadAccountsForUser( 43 + $viewer, 44 + PhabricatorContentSource::newFromRequest($request)); 45 + 42 46 $v_amount = null; 43 47 $e_amount = true; 48 + 49 + $v_account = head($accounts)->getPHID(); 50 + 44 51 $errors = array(); 45 52 if ($request->isFormPost()) { 46 53 $v_amount = $request->getStr('amount'); 54 + $v_account = $request->getStr('accountPHID'); 55 + 56 + if (empty($accounts[$v_account])) { 57 + $errors[] = pht('You must specify an account.'); 58 + } else { 59 + $account = $accounts[$v_account]; 60 + } 47 61 48 62 if (!strlen($v_amount)) { 49 63 $errors[] = pht( ··· 73 87 ->setViewer($viewer) 74 88 ->withClassAndRef('FundBackerProduct', $initiative->getPHID()) 75 89 ->executeOne(); 76 - 77 - $account = PhortuneAccountQuery::loadActiveAccountForUser( 78 - $viewer, 79 - PhabricatorContentSource::newFromRequest($request)); 80 90 81 91 $cart_implementation = id(new FundBackerCart()) 82 92 ->setInitiative($initiative); ··· 110 120 111 121 $form = id(new AphrontFormView()) 112 122 ->setUser($viewer) 123 + ->appendChild( 124 + id(new AphrontFormSelectControl()) 125 + ->setName('accountPHID') 126 + ->setLabel(pht('Account')) 127 + ->setValue($v_account) 128 + ->setOptions(mpull($accounts, 'getName', 'getPHID'))) 113 129 ->appendChild( 114 130 id(new AphrontFormTextControl()) 115 131 ->setName('amount')
+11 -5
src/applications/fund/phortune/FundBackerProduct.php
··· 79 79 public function didPurchaseProduct( 80 80 PhortuneProduct $product, 81 81 PhortunePurchase $purchase) { 82 - // TODO: This viewer may be wrong if the purchase completes after a hold 83 - // we should load the backer explicitly. 84 82 $viewer = $this->getViewer(); 85 83 86 84 $backer = id(new FundBackerQuery()) ··· 91 89 throw new Exception(pht('Unable to load FundBacker!')); 92 90 } 93 91 92 + // Load the actual backing user --they may not be the curent viewer if this 93 + // product purchase is completing from a background worker or a merchant 94 + // action. 95 + 96 + $actor = id(new PhabricatorPeopleQuery()) 97 + ->setViewer($viewer) 98 + ->withPHIDs(array($backer->getBackerPHID())) 99 + ->executeOne(); 100 + 94 101 $xactions = array(); 95 102 $xactions[] = id(new FundBackerTransaction()) 96 103 ->setTransactionType(FundBackerTransaction::TYPE_STATUS) 97 104 ->setNewValue(FundBacker::STATUS_PURCHASED); 98 105 99 106 $editor = id(new FundBackerEditor()) 100 - ->setActor($viewer) 107 + ->setActor($actor) 101 108 ->setContentSource($this->getContentSource()); 102 109 103 110 $editor->applyTransactions($backer, $xactions); 104 - 105 111 106 112 $xactions = array(); 107 113 $xactions[] = id(new FundInitiativeTransaction()) ··· 109 115 ->setNewValue($backer->getPHID()); 110 116 111 117 $editor = id(new FundInitiativeEditor()) 112 - ->setActor($viewer) 118 + ->setActor($actor) 113 119 ->setContentSource($this->getContentSource()); 114 120 115 121 $editor->applyTransactions($this->getInitiative(), $xactions);
+122
src/applications/phortune/controller/PhortuneAccountEditController.php
··· 1 + <?php 2 + 3 + final class PhortuneAccountEditController extends PhortuneController { 4 + 5 + private $id; 6 + 7 + public function willProcessRequest(array $data) { 8 + $this->id = idx($data, 'id'); 9 + } 10 + 11 + public function processRequest() { 12 + $request = $this->getRequest(); 13 + $viewer = $request->getUser(); 14 + 15 + if ($this->id) { 16 + $account = id(new PhortuneAccountQuery()) 17 + ->setViewer($viewer) 18 + ->withIDs(array($this->id)) 19 + ->requireCapabilities( 20 + array( 21 + PhabricatorPolicyCapability::CAN_VIEW, 22 + PhabricatorPolicyCapability::CAN_EDIT, 23 + )) 24 + ->executeOne(); 25 + if (!$account) { 26 + return new Aphront404Response(); 27 + } 28 + $is_new = false; 29 + } else { 30 + $account = PhortuneAccount::initializeNewAccount($viewer); 31 + $is_new = true; 32 + } 33 + 34 + $v_name = $account->getName(); 35 + $e_name = true; 36 + $validation_exception = null; 37 + 38 + if ($request->isFormPost()) { 39 + $v_name = $request->getStr('name'); 40 + 41 + $type_name = PhortuneAccountTransaction::TYPE_NAME; 42 + 43 + $xactions = array(); 44 + $xactions[] = id(new PhortuneAccountTransaction()) 45 + ->setTransactionType($type_name) 46 + ->setNewValue($v_name); 47 + 48 + if ($is_new) { 49 + $xactions[] = id(new PhortuneAccountTransaction()) 50 + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 51 + ->setMetadataValue( 52 + 'edge:type', 53 + PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER) 54 + ->setNewValue( 55 + array( 56 + '=' => array($viewer->getPHID() => $viewer->getPHID()), 57 + )); 58 + } 59 + 60 + $editor = id(new PhortuneAccountEditor()) 61 + ->setActor($viewer) 62 + ->setContentSourceFromRequest($request) 63 + ->setContinueOnNoEffect(true); 64 + 65 + try { 66 + $editor->applyTransactions($account, $xactions); 67 + 68 + $account_uri = $this->getApplicationURI($account->getID().'/'); 69 + return id(new AphrontRedirectResponse())->setURI($account_uri); 70 + } catch (PhabricatorApplicationTransactionValidationException $ex) { 71 + $validation_exception = $ex; 72 + $e_name = $ex->getShortMessage($type_name); 73 + } 74 + } 75 + 76 + $crumbs = $this->buildApplicationCrumbs(); 77 + 78 + if ($is_new) { 79 + $cancel_uri = $this->getApplicationURI('account/'); 80 + $crumbs->addTextCrumb(pht('Accounts'), $cancel_uri); 81 + $crumbs->addTextCrumb(pht('Create Account')); 82 + 83 + $title = pht('Create Payment Account'); 84 + $submit_button = pht('Create Account'); 85 + } else { 86 + $cancel_uri = $this->getApplicationURI($account->getID().'/'); 87 + $crumbs->addTextCrumb($account->getName(), $cancel_uri); 88 + $crumbs->addTextCrumb(pht('Edit')); 89 + 90 + $title = pht('Edit %s', $account->getName()); 91 + $submit_button = pht('Save Changes'); 92 + } 93 + 94 + $form = id(new AphrontFormView()) 95 + ->setUser($viewer) 96 + ->appendChild( 97 + id(new AphrontFormTextControl()) 98 + ->setName('name') 99 + ->setLabel(pht('Name')) 100 + ->setValue($v_name) 101 + ->setError($e_name)) 102 + ->appendChild( 103 + id(new AphrontFormSubmitControl()) 104 + ->setValue($submit_button) 105 + ->addCancelButton($cancel_uri)); 106 + 107 + $box = id(new PHUIObjectBoxView()) 108 + ->setHeaderText($title) 109 + ->setValidationException($validation_exception) 110 + ->appendChild($form); 111 + 112 + return $this->buildApplicationPage( 113 + array( 114 + $crumbs, 115 + $box, 116 + ), 117 + array( 118 + 'title' => $title, 119 + )); 120 + } 121 + 122 + }
+108
src/applications/phortune/controller/PhortuneAccountListController.php
··· 1 + <?php 2 + 3 + final class PhortuneAccountListController extends PhortuneController { 4 + 5 + public function processRequest() { 6 + $request = $this->getRequest(); 7 + $viewer = $request->getUser(); 8 + 9 + $accounts = id(new PhortuneAccountQuery()) 10 + ->setViewer($viewer) 11 + ->withMemberPHIDs(array($viewer->getPHID())) 12 + ->requireCapabilities( 13 + array( 14 + PhabricatorPolicyCapability::CAN_VIEW, 15 + PhabricatorPolicyCapability::CAN_EDIT, 16 + )) 17 + ->execute(); 18 + 19 + $merchants = id(new PhortuneMerchantQuery()) 20 + ->setViewer($viewer) 21 + ->requireCapabilities( 22 + array( 23 + PhabricatorPolicyCapability::CAN_VIEW, 24 + PhabricatorPolicyCapability::CAN_EDIT, 25 + )) 26 + ->execute(); 27 + 28 + $title = pht('Accounts'); 29 + 30 + $crumbs = $this->buildApplicationCrumbs(); 31 + $crumbs->addTextCrumb(pht('Accounts')); 32 + 33 + $payment_list = id(new PHUIObjectItemListView()) 34 + ->setUser($viewer) 35 + ->setNoDataString( 36 + pht( 37 + 'You are not a member of any payment accounts. Payment '. 38 + 'accounts are used to make purchases.')); 39 + 40 + foreach ($accounts as $account) { 41 + $item = id(new PHUIObjectItemView()) 42 + ->setObjectName(pht('Account %d', $account->getID())) 43 + ->setHeader($account->getName()) 44 + ->setHref($this->getApplicationURI($account->getID().'/')) 45 + ->setObject($account); 46 + 47 + $payment_list->addItem($item); 48 + } 49 + 50 + $payment_header = id(new PHUIHeaderView()) 51 + ->setHeader(pht('Payment Accounts')) 52 + ->addActionLink( 53 + id(new PHUIButtonView()) 54 + ->setTag('a') 55 + ->setHref($this->getApplicationURI('account/edit/')) 56 + ->setIcon( 57 + id(new PHUIIconView()) 58 + ->setIconFont('fa-plus')) 59 + ->setText(pht('Create Account'))); 60 + 61 + $payment_box = id(new PHUIObjectBoxView()) 62 + ->setHeader($payment_header) 63 + ->appendChild($payment_list); 64 + 65 + $merchant_list = id(new PHUIObjectItemListView()) 66 + ->setUser($viewer) 67 + ->setNoDataString( 68 + pht( 69 + 'You do not control any merchant accounts. Merchant accounts are '. 70 + 'used to receive payments.')); 71 + 72 + foreach ($merchants as $merchant) { 73 + $item = id(new PHUIObjectItemView()) 74 + ->setObjectName(pht('Merchant %d', $merchant->getID())) 75 + ->setHeader($merchant->getName()) 76 + ->setHref($this->getApplicationURI('/merchant/'.$merchant->getID().'/')) 77 + ->setObject($merchant); 78 + 79 + $merchant_list->addItem($item); 80 + } 81 + 82 + $merchant_header = id(new PHUIHeaderView()) 83 + ->setHeader(pht('Merchant Accounts')) 84 + ->addActionLink( 85 + id(new PHUIButtonView()) 86 + ->setTag('a') 87 + ->setHref($this->getApplicationURI('merchant/')) 88 + ->setIcon( 89 + id(new PHUIIconView()) 90 + ->setIconFont('fa-folder-open')) 91 + ->setText(pht('Browse Merchants'))); 92 + 93 + $merchant_box = id(new PHUIObjectBoxView()) 94 + ->setHeader($merchant_header) 95 + ->appendChild($merchant_list); 96 + 97 + return $this->buildApplicationPage( 98 + array( 99 + $crumbs, 100 + $payment_box, 101 + $merchant_box, 102 + ), 103 + array( 104 + 'title' => $title, 105 + )); 106 + } 107 + 108 + }
+22 -4
src/applications/phortune/controller/PhortuneAccountViewController.php
··· 17 17 // process orders but merchants should not be able to see all the details 18 18 // of an account. Ideally this page should be visible to merchants, too, 19 19 // just with less information. 20 + $can_edit = true; 20 21 21 22 $account = id(new PhortuneAccountQuery()) 22 23 ->setViewer($user) ··· 27 28 PhabricatorPolicyCapability::CAN_EDIT, 28 29 )) 29 30 ->executeOne(); 30 - 31 31 if (!$account) { 32 32 return new Aphront404Response(); 33 33 } ··· 35 35 $title = $account->getName(); 36 36 37 37 $crumbs = $this->buildApplicationCrumbs(); 38 - $crumbs->addTextCrumb(pht('Account'), $request->getRequestURI()); 38 + $crumbs->addTextCrumb( 39 + $account->getName(), 40 + $request->getRequestURI()); 39 41 40 42 $header = id(new PHUIHeaderView()) 41 43 ->setHeader($title); 42 44 45 + $edit_uri = $this->getApplicationURI('account/edit/'.$account->getID().'/'); 46 + 43 47 $actions = id(new PhabricatorActionListView()) 44 48 ->setUser($user) 45 49 ->setObjectURI($request->getRequestURI()) ··· 47 51 id(new PhabricatorActionView()) 48 52 ->setName(pht('Edit Account')) 49 53 ->setIcon('fa-pencil') 50 - ->setHref('#') 51 - ->setDisabled(true)) 54 + ->setHref($edit_uri) 55 + ->setDisabled(!$can_edit) 56 + ->setWorkflow(!$can_edit)) 52 57 ->addAction( 53 58 id(new PhabricatorActionView()) 54 59 ->setName(pht('Edit Members')) ··· 290 295 291 296 return $xaction_view; 292 297 } 298 + 299 + protected function buildApplicationCrumbs() { 300 + $crumbs = parent::buildApplicationCrumbs(); 301 + 302 + $crumbs->addAction( 303 + id(new PHUIListItemView()) 304 + ->setIcon('fa-exchange') 305 + ->setHref($this->getApplicationURI('account/')) 306 + ->setName(pht('Switch Accounts'))); 307 + 308 + return $crumbs; 309 + } 310 + 293 311 294 312 }
+2 -1
src/applications/phortune/controller/PhortuneCartCheckoutController.php
··· 114 114 ->setHeaderText(pht('Cart Contents')) 115 115 ->appendChild($cart_table); 116 116 117 - $title = pht('Buy Stuff'); 117 + $title = $cart->getName(); 118 118 119 119 if (!$methods) { 120 120 $method_control = id(new AphrontFormStaticControl()) ··· 210 210 ->appendChild($provider_form); 211 211 212 212 $crumbs = $this->buildApplicationCrumbs(); 213 + $crumbs->addTextCrumb(pht('Checkout')); 213 214 $crumbs->addTextCrumb($title); 214 215 215 216 return $this->buildApplicationPage(
+28
src/applications/phortune/editor/PhortuneAccountEditor.php
··· 67 67 return parent::applyCustomExternalTransaction($object, $xaction); 68 68 } 69 69 70 + protected function validateTransaction( 71 + PhabricatorLiskDAO $object, 72 + $type, 73 + array $xactions) { 74 + 75 + $errors = parent::validateTransaction($object, $type, $xactions); 76 + 77 + switch ($type) { 78 + case PhortuneAccountTransaction::TYPE_NAME: 79 + $missing = $this->validateIsEmptyTextField( 80 + $object->getName(), 81 + $xactions); 82 + 83 + if ($missing) { 84 + $error = new PhabricatorApplicationTransactionValidationError( 85 + $type, 86 + pht('Required'), 87 + pht('Account name is required.'), 88 + nonempty(last($xactions), null)); 89 + 90 + $error->setIsMissingFieldError(true); 91 + $errors[] = $error; 92 + } 93 + break; 94 + } 95 + 96 + return $errors; 97 + } 70 98 }
+20
src/applications/phortune/query/PhortuneAccountQuery.php
··· 7 7 private $phids; 8 8 private $memberPHIDs; 9 9 10 + public static function loadAccountsForUser( 11 + PhabricatorUser $user, 12 + PhabricatorContentSource $content_source) { 13 + 14 + $accounts = id(new PhortuneAccountQuery()) 15 + ->setViewer($user) 16 + ->withMemberPHIDs(array($user->getPHID())) 17 + ->execute(); 18 + 19 + if (!$accounts) { 20 + $accounts = array( 21 + PhortuneAccount::createNewAccount($user, $content_source), 22 + ); 23 + } 24 + 25 + $accounts = mpull($accounts, null, 'getPHID'); 26 + 27 + return $accounts; 28 + } 29 + 10 30 public static function loadActiveAccountForUser( 11 31 PhabricatorUser $user, 12 32 PhabricatorContentSource $content_source) {
+1 -1
src/applications/phortune/storage/PhortuneAccount.php
··· 30 30 $xactions = array(); 31 31 $xactions[] = id(new PhortuneAccountTransaction()) 32 32 ->setTransactionType(PhortuneAccountTransaction::TYPE_NAME) 33 - ->setNewValue(pht('Account (%s)', $actor->getUserName())); 33 + ->setNewValue(pht('Personal Account')); 34 34 35 35 $xactions[] = id(new PhortuneAccountTransaction()) 36 36 ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)