@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

Adjust payment workflows to deal with merchants and configurable providers in Phortune

Summary:
Ref T2787. Builds on D10649 by rebining existing objects (carts, charges, etc) to merchantPHIDs and providerPHIDs instead of an implicit global merchant and weird global artifacts (providerType / providerKey).

Basically:

- When you create something that users can pay for, you specify a merchant to control where the payment goes.
- Accounts are install-wide, but payment methods are bound to merchants. This seems to do a reasonable job of balancing usability and technical concerns.
- Replace a bunch of weird links between objects with standard PHIDs.
- Improve "add payment method" flow.

Test Plan: Went through the Fund flow with Stripe and WePay, funding an initiative.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T2787

Differential Revision: https://secure.phabricator.com/D10652

+403 -78
+2
resources/sql/autopatches/20141007.fundmerchant.sql
··· 1 + ALTER TABLE {$NAMESPACE}_fund.fund_initiative 2 + ADD merchantPHID VARBINARY(64);
+5
resources/sql/autopatches/20141007.phortunecartmerchant.sql
··· 1 + ALTER TABLE {$NAMESPACE}_phortune.phortune_cart 2 + ADD merchantPHID VARBINARY(64) NOT NULL; 3 + 4 + ALTER TABLE {$NAMESPACE}_phortune.phortune_cart 5 + ADD KEY `key_merchant` (merchantPHID);
+16
resources/sql/autopatches/20141007.phortunecharge.sql
··· 1 + TRUNCATE TABLE {$NAMESPACE}_phortune.phortune_charge; 2 + 3 + ALTER TABLE {$NAMESPACE}_phortune.phortune_charge 4 + DROP paymentProviderKey; 5 + 6 + ALTER TABLE {$NAMESPACE}_phortune.phortune_charge 7 + ADD merchantPHID VARBINARY(64) NOT NULL; 8 + 9 + ALTER TABLE {$NAMESPACE}_phortune.phortune_charge 10 + ADD providerPHID VARBINARY(64) NOT NULL; 11 + 12 + ALTER TABLE {$NAMESPACE}_phortune.phortune_charge 13 + ADD KEY `key_merchant` (merchantPHID); 14 + 15 + ALTER TABLE {$NAMESPACE}_phortune.phortune_charge 16 + ADD KEY `key_provider` (providerPHID);
+16
resources/sql/autopatches/20141007.phortunepayment.sql
··· 1 + TRUNCATE TABLE {$NAMESPACE}_phortune.phortune_paymentmethod; 2 + 3 + ALTER TABLE {$NAMESPACE}_phortune.phortune_paymentmethod 4 + DROP providerType; 5 + 6 + ALTER TABLE {$NAMESPACE}_phortune.phortune_paymentmethod 7 + DROP providerDomain; 8 + 9 + ALTER TABLE {$NAMESPACE}_phortune.phortune_paymentmethod 10 + ADD merchantPHID VARBINARY(64) NOT NULL; 11 + 12 + ALTER TABLE {$NAMESPACE}_phortune.phortune_paymentmethod 13 + ADD providerPHID VARBINARY(64) NOT NULL; 14 + 15 + ALTER TABLE {$NAMESPACE}_phortune.phortune_paymentmethod 16 + ADD KEY `key_merchant` (merchantPHID, accountPHID);
+9 -1
src/applications/fund/controller/FundInitiativeBackController.php
··· 21 21 return new Aphront404Response(); 22 22 } 23 23 24 + $merchant = id(new PhortuneMerchantQuery()) 25 + ->setViewer($viewer) 26 + ->withPHIDs(array($initiative->getMerchantPHID())) 27 + ->executeOne(); 28 + if (!$merchant) { 29 + return new Aphront404Response(); 30 + } 31 + 24 32 $initiative_uri = '/'.$initiative->getMonogram(); 25 33 26 34 if ($initiative->isClosed()) { ··· 73 81 $cart_implementation = id(new FundBackerCart()) 74 82 ->setInitiative($initiative); 75 83 76 - $cart = $account->newCart($viewer, $cart_implementation); 84 + $cart = $account->newCart($viewer, $cart_implementation, $merchant); 77 85 78 86 $purchase = $cart->newPurchase($viewer, $product); 79 87 $purchase
+56
src/applications/fund/controller/FundInitiativeEditController.php
··· 48 48 $e_name = true; 49 49 $v_name = $initiative->getName(); 50 50 51 + $e_merchant = null; 52 + $v_merchant = $initiative->getMerchantPHID(); 53 + 51 54 $v_desc = $initiative->getDescription(); 52 55 53 56 if ($is_new) { ··· 65 68 $v_desc = $request->getStr('description'); 66 69 $v_view = $request->getStr('viewPolicy'); 67 70 $v_edit = $request->getStr('editPolicy'); 71 + $v_merchant = $request->getStr('merchantPHID'); 68 72 $v_projects = $request->getArr('projects'); 69 73 70 74 $type_name = FundInitiativeTransaction::TYPE_NAME; 71 75 $type_desc = FundInitiativeTransaction::TYPE_DESCRIPTION; 76 + $type_merchant = FundInitiativeTransaction::TYPE_MERCHANT; 72 77 $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; 73 78 $type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY; 74 79 ··· 83 88 ->setNewValue($v_desc); 84 89 85 90 $xactions[] = id(new FundInitiativeTransaction()) 91 + ->setTransactionType($type_merchant) 92 + ->setNewValue($v_merchant); 93 + 94 + $xactions[] = id(new FundInitiativeTransaction()) 86 95 ->setTransactionType($type_view) 87 96 ->setNewValue($v_view); 88 97 ··· 110 119 $validation_exception = $ex; 111 120 112 121 $e_name = $ex->getShortMessage($type_name); 122 + $e_merchant = $ex->getShortMessage($type_merchant); 113 123 114 124 $initiative->setViewPolicy($v_view); 115 125 $initiative->setEditPolicy($v_edit); ··· 127 137 $project_handles = array(); 128 138 } 129 139 140 + $merchants = id(new PhortuneMerchantQuery()) 141 + ->setViewer($viewer) 142 + ->requireCapabilities( 143 + array( 144 + PhabricatorPolicyCapability::CAN_VIEW, 145 + PhabricatorPolicyCapability::CAN_EDIT, 146 + )) 147 + ->execute(); 148 + 149 + $merchant_options = array(); 150 + foreach ($merchants as $merchant) { 151 + $merchant_options[$merchant->getPHID()] = pht( 152 + 'Merchant %d %s', 153 + $merchant->getID(), 154 + $merchant->getName()); 155 + } 156 + 157 + if ($v_merchant && empty($merchant_options[$v_merchant])) { 158 + $merchant_options = array( 159 + $v_merchant => pht('(Restricted Merchant)'), 160 + ) + $merchant_options; 161 + } 162 + 163 + if (!$merchant_options) { 164 + return $this->newDialog() 165 + ->setTitle(pht('No Valid Phortune Merchant Accounts')) 166 + ->appendParagraph( 167 + pht( 168 + 'You do not control any merchant accounts which can receive '. 169 + 'payments from this initiative. When you create an initiative, '. 170 + 'you need to specify a merchant account where funds will be paid '. 171 + 'to.')) 172 + ->appendParagraph( 173 + pht( 174 + 'Create a merchant account in the Phortune application before '. 175 + 'creating an initiative in Fund.')) 176 + ->addCancelButton($this->getApplicationURI()); 177 + } 178 + 130 179 $form = id(new AphrontFormView()) 131 180 ->setUser($viewer) 132 181 ->appendChild( ··· 135 184 ->setLabel(pht('Name')) 136 185 ->setValue($v_name) 137 186 ->setError($e_name)) 187 + ->appendChild( 188 + id(new AphrontFormSelectControl()) 189 + ->setName('merchantPHID') 190 + ->setLabel(pht('Pay To Merchant')) 191 + ->setValue($v_merchant) 192 + ->setError($e_merchant) 193 + ->setOptions($merchant_options)) 138 194 ->appendChild( 139 195 id(new PhabricatorRemarkupControl()) 140 196 ->setName('description')
+48
src/applications/fund/editor/FundInitiativeEditor.php
··· 18 18 $types[] = FundInitiativeTransaction::TYPE_DESCRIPTION; 19 19 $types[] = FundInitiativeTransaction::TYPE_STATUS; 20 20 $types[] = FundInitiativeTransaction::TYPE_BACKER; 21 + $types[] = FundInitiativeTransaction::TYPE_MERCHANT; 21 22 $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; 22 23 $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 23 24 ··· 36 37 return $object->getStatus(); 37 38 case FundInitiativeTransaction::TYPE_BACKER: 38 39 return null; 40 + case FundInitiativeTransaction::TYPE_MERCHANT: 41 + return $object->getMerchantPHID(); 39 42 } 40 43 41 44 return parent::getCustomTransactionOldValue($object, $xaction); ··· 50 53 case FundInitiativeTransaction::TYPE_DESCRIPTION: 51 54 case FundInitiativeTransaction::TYPE_STATUS: 52 55 case FundInitiativeTransaction::TYPE_BACKER: 56 + case FundInitiativeTransaction::TYPE_MERCHANT: 53 57 return $xaction->getNewValue(); 54 58 } 55 59 ··· 67 71 case FundInitiativeTransaction::TYPE_DESCRIPTION: 68 72 $object->setDescription($xaction->getNewValue()); 69 73 return; 74 + case FundInitiativeTransaction::TYPE_MERCHANT: 75 + $object->setMerchantPHID($xaction->getNewValue()); 76 + return; 70 77 case FundInitiativeTransaction::TYPE_STATUS: 71 78 $object->setStatus($xaction->getNewValue()); 72 79 return; ··· 89 96 case FundInitiativeTransaction::TYPE_NAME: 90 97 case FundInitiativeTransaction::TYPE_DESCRIPTION: 91 98 case FundInitiativeTransaction::TYPE_STATUS: 99 + case FundInitiativeTransaction::TYPE_MERCHANT: 92 100 case FundInitiativeTransaction::TYPE_BACKER: 93 101 // TODO: Maybe we should apply the backer transaction from here? 94 102 return; ··· 122 130 123 131 $error->setIsMissingFieldError(true); 124 132 $errors[] = $error; 133 + } 134 + break; 135 + case FundInitiativeTransaction::TYPE_MERCHANT: 136 + $missing = $this->validateIsEmptyTextField( 137 + $object->getName(), 138 + $xactions); 139 + if ($missing) { 140 + $error = new PhabricatorApplicationTransactionValidationError( 141 + $type, 142 + pht('Required'), 143 + pht('Payable merchant is required.'), 144 + nonempty(last($xactions), null)); 145 + 146 + $error->setIsMissingFieldError(true); 147 + $errors[] = $error; 148 + } else if ($xactions) { 149 + $merchant_phid = last($xactions)->getNewValue(); 150 + 151 + // Make sure the actor has permission to edit the merchant they're 152 + // selecting. You aren't allowed to send payments to an account you 153 + // do not control. 154 + $merchants = id(new PhortuneMerchantQuery()) 155 + ->setViewer($this->requireActor()) 156 + ->withPHIDs(array($merchant_phid)) 157 + ->requireCapabilities( 158 + array( 159 + PhabricatorPolicyCapability::CAN_VIEW, 160 + PhabricatorPolicyCapability::CAN_EDIT, 161 + )) 162 + ->execute(); 163 + if (!$merchants) { 164 + $error = new PhabricatorApplicationTransactionValidationError( 165 + $type, 166 + pht('Invalid'), 167 + pht( 168 + 'You must specify a merchant account you control as the '. 169 + 'recipient of funds from this initiative.'), 170 + last($xactions)); 171 + $errors[] = $error; 172 + } 125 173 } 126 174 break; 127 175 }
+2
src/applications/fund/storage/FundInitiative.php
··· 13 13 14 14 protected $name; 15 15 protected $ownerPHID; 16 + protected $merchantPHID; 16 17 protected $description; 17 18 protected $viewPolicy; 18 19 protected $editPolicy; ··· 52 53 'name' => 'text255', 53 54 'description' => 'text', 54 55 'status' => 'text32', 56 + 'merchantPHID' => 'phid?', 55 57 ), 56 58 self::CONFIG_KEY_SCHEMA => array( 57 59 'key_status' => array(
+36
src/applications/fund/storage/FundInitiativeTransaction.php
··· 7 7 const TYPE_DESCRIPTION = 'fund:description'; 8 8 const TYPE_STATUS = 'fund:status'; 9 9 const TYPE_BACKER = 'fund:backer'; 10 + const TYPE_MERCHANT = 'fund:merchant'; 10 11 11 12 public function getApplicationName() { 12 13 return 'fund'; ··· 20 21 return null; 21 22 } 22 23 24 + public function getRequiredHandlePHIDs() { 25 + $phids = parent::getRequiredHandlePHIDs(); 26 + 27 + $old = $this->getOldValue(); 28 + $new = $this->getNewValue(); 29 + 30 + $type = $this->getTransactionType(); 31 + switch ($type) { 32 + case FundInitiativeTransaction::TYPE_MERCHANT: 33 + if ($old) { 34 + $phids[] = $old; 35 + } 36 + if ($new) { 37 + $phids[] = $new; 38 + } 39 + break; 40 + } 41 + 42 + return $phids; 43 + } 44 + 23 45 public function getTitle() { 24 46 $author_phid = $this->getAuthorPHID(); 25 47 $object_phid = $this->getObjectPHID(); ··· 62 84 return pht( 63 85 '%s backed this initiative.', 64 86 $this->renderHandleLink($author_phid)); 87 + case FundInitiativeTransaction::TYPE_MERCHANT: 88 + if ($old === null) { 89 + return pht( 90 + '%s set this initiative to pay to %s.', 91 + $this->renderHandleLink($author_phid), 92 + $this->renderHandleLink($new)); 93 + } else { 94 + return pht( 95 + '%s changed the merchant receiving funds from this '. 96 + 'initiative from %s to %s.', 97 + $this->renderHandleLink($author_phid), 98 + $this->renderHandleLink($old), 99 + $this->renderHandleLink($new)); 100 + } 65 101 } 66 102 67 103 return parent::getTitle();
+1 -7
src/applications/phortune/controller/PhortuneAccountViewController.php
··· 89 89 $id = $account->getID(); 90 90 91 91 $header = id(new PHUIHeaderView()) 92 - ->setHeader(pht('Payment Methods')) 93 - ->addActionLink( 94 - id(new PHUIButtonView()) 95 - ->setTag('a') 96 - ->setHref($this->getApplicationURI($id.'/card/new/')) 97 - ->setText(pht('Add Payment Method')) 98 - ->setIcon(id(new PHUIIconView())->setIconFont('fa-plus'))); 92 + ->setHeader(pht('Payment Methods')); 99 93 100 94 $list = id(new PHUIObjectItemListView()) 101 95 ->setUser($viewer)
+14 -4
src/applications/phortune/controller/PhortuneCartCheckoutController.php
··· 23 23 } 24 24 25 25 $cancel_uri = $cart->getCancelURI(); 26 + $merchant = $cart->getMerchant(); 26 27 27 28 switch ($cart->getStatus()) { 28 29 case PhortuneCart::STATUS_BUILDING: ··· 83 84 $methods = id(new PhortunePaymentMethodQuery()) 84 85 ->setViewer($viewer) 85 86 ->withAccountPHIDs(array($account->getPHID())) 87 + ->withMerchantPHIDs(array($merchant->getPHID())) 86 88 ->withStatuses(array(PhortunePaymentMethod::STATUS_ACTIVE)) 87 89 ->execute(); 88 90 ··· 142 144 143 145 $method_control->setError($e_method); 144 146 145 - $payment_method_uri = $this->getApplicationURI( 146 - $account->getID().'/card/new/'); 147 + $account_id = $account->getID(); 148 + 149 + $payment_method_uri = $this->getApplicationURI("{$account_id}/card/new/"); 150 + $payment_method_uri = new PhutilURI($payment_method_uri); 151 + $payment_method_uri->setQueryParams( 152 + array( 153 + 'merchantID' => $merchant->getID(), 154 + 'cartID' => $cart->getID(), 155 + )); 147 156 148 157 $form = id(new AphrontFormView()) 149 158 ->setUser($viewer) 150 159 ->appendChild($method_control); 151 160 152 - $add_providers = PhortunePaymentProvider::getProvidersForAddPaymentMethod(); 161 + $add_providers = $this->loadCreatePaymentMethodProvidersForMerchant( 162 + $merchant); 153 163 if ($add_providers) { 154 164 $new_method = phutil_tag( 155 165 'a', ··· 178 188 179 189 $provider_form = null; 180 190 181 - $pay_providers = PhortunePaymentProvider::getProvidersForOneTimePayment(); 191 + $pay_providers = $this->loadOneTimePaymentProvidersForMerchant($merchant); 182 192 if ($pay_providers) { 183 193 $one_time_options = array(); 184 194 foreach ($pay_providers as $provider) {
+46
src/applications/phortune/controller/PhortuneController.php
··· 86 86 } 87 87 } 88 88 89 + private function loadEnabledProvidersForMerchant(PhortuneMerchant $merchant) { 90 + $viewer = $this->getRequest()->getUser(); 91 + 92 + $provider_configs = id(new PhortunePaymentProviderConfigQuery()) 93 + ->setViewer($viewer) 94 + ->withMerchantPHIDs(array($merchant->getPHID())) 95 + ->execute(); 96 + $providers = mpull($provider_configs, 'buildProvider', 'getID'); 97 + 98 + foreach ($providers as $key => $provider) { 99 + if (!$provider->isEnabled()) { 100 + unset($providers[$key]); 101 + } 102 + } 103 + 104 + return $providers; 105 + } 106 + 107 + protected function loadCreatePaymentMethodProvidersForMerchant( 108 + PhortuneMerchant $merchant) { 109 + 110 + $providers = $this->loadEnabledProvidersForMerchant($merchant); 111 + foreach ($providers as $key => $provider) { 112 + if (!$provider->canCreatePaymentMethods()) { 113 + unset($providers[$key]); 114 + continue; 115 + } 116 + } 117 + 118 + return $providers; 119 + } 120 + 121 + protected function loadOneTimePaymentProvidersForMerchant( 122 + PhortuneMerchant $merchant) { 123 + 124 + $providers = $this->loadEnabledProvidersForMerchant($merchant); 125 + foreach ($providers as $key => $provider) { 126 + if (!$provider->canProcessOneTimePayments()) { 127 + unset($providers[$key]); 128 + continue; 129 + } 130 + } 131 + 132 + return $providers; 133 + } 134 + 89 135 }
+37 -19
src/applications/phortune/controller/PhortunePaymentMethodCreateController.php
··· 11 11 12 12 public function processRequest() { 13 13 $request = $this->getRequest(); 14 - $user = $request->getUser(); 14 + $viewer = $request->getUser(); 15 15 16 16 $account = id(new PhortuneAccountQuery()) 17 - ->setViewer($user) 17 + ->setViewer($viewer) 18 18 ->withIDs(array($this->accountID)) 19 19 ->executeOne(); 20 20 if (!$account) { 21 21 return new Aphront404Response(); 22 22 } 23 23 24 + $merchant = id(new PhortuneMerchantQuery()) 25 + ->setViewer($viewer) 26 + ->withIDs(array($request->getInt('merchantID'))) 27 + ->executeOne(); 28 + if (!$merchant) { 29 + return new Aphront404Response(); 30 + } 31 + 24 32 $cancel_uri = $this->getApplicationURI($account->getID().'/'); 25 33 $account_uri = $this->getApplicationURI($account->getID().'/'); 26 34 27 - $providers = PhortunePaymentProvider::getProvidersForAddPaymentMethod(); 35 + $providers = $this->loadCreatePaymentMethodProvidersForMerchant($merchant); 28 36 if (!$providers) { 29 37 throw new Exception( 30 38 'There are no payment providers enabled that can add payment '. 31 39 'methods.'); 32 40 } 33 41 34 - $provider_key = $request->getStr('providerKey'); 35 - if (empty($providers[$provider_key])) { 42 + $provider_id = $request->getInt('providerID'); 43 + if (empty($providers[$provider_id])) { 36 44 $choices = array(); 37 45 foreach ($providers as $provider) { 38 46 $choices[] = $this->renderSelectProvider($provider); ··· 53 61 ->addCancelButton($account_uri); 54 62 } 55 63 56 - $provider = $providers[$provider_key]; 64 + $provider = $providers[$provider_id]; 57 65 58 66 $errors = array(); 59 67 if ($request->isFormPost() && $request->getBool('isProviderForm')) { 60 68 $method = id(new PhortunePaymentMethod()) 61 69 ->setAccountPHID($account->getPHID()) 62 - ->setAuthorPHID($user->getPHID()) 63 - ->setStatus(PhortunePaymentMethod::STATUS_ACTIVE) 64 - ->setProviderType($provider->getProviderType()) 65 - ->setProviderDomain($provider->getProviderDomain()); 70 + ->setAuthorPHID($viewer->getPHID()) 71 + ->setMerchantPHID($merchant->getPHID()) 72 + ->setProviderPHID($provider->getProviderConfig()->getPHID()) 73 + ->setStatus(PhortunePaymentMethod::STATUS_ACTIVE); 66 74 67 75 if (!$errors) { 68 76 $errors = $this->processClientErrors( ··· 97 105 if (!$errors) { 98 106 $method->save(); 99 107 100 - $save_uri = new PhutilURI($account_uri); 101 - $save_uri->setFragment('payment'); 102 - return id(new AphrontRedirectResponse())->setURI($save_uri); 108 + // If we added this method on a cart flow, return to the cart to 109 + // check out. 110 + $cart_id = $request->getInt('cartID'); 111 + if ($cart_id) { 112 + $next_uri = $this->getApplicationURI( 113 + "cart/{$cart_id}/checkout/?paymentMethodID=".$method->getID()); 114 + } else { 115 + $next_uri = new PhutilURI($account_uri); 116 + $next_uri->setFragment('payment'); 117 + } 118 + 119 + return id(new AphrontRedirectResponse())->setURI($next_uri); 103 120 } else { 104 121 $dialog = id(new AphrontDialogView()) 105 - ->setUser($user) 122 + ->setUser($viewer) 106 123 ->setTitle(pht('Error Adding Payment Method')) 107 124 ->appendChild(id(new AphrontErrorView())->setErrors($errors)) 108 125 ->addCancelButton($request->getRequestURI()); ··· 114 131 $form = $provider->renderCreatePaymentMethodForm($request, $errors); 115 132 116 133 $form 117 - ->setUser($user) 134 + ->setUser($viewer) 118 135 ->setAction($request->getRequestURI()) 119 136 ->setWorkflow(true) 120 - ->addHiddenInput('providerKey', $provider_key) 137 + ->addHiddenInput('providerID', $provider_id) 138 + ->addHiddenInput('cartID', $request->getInt('cartID')) 121 139 ->addHiddenInput('isProviderForm', true) 122 140 ->appendChild( 123 141 id(new AphrontFormSubmitControl()) ··· 145 163 PhortunePaymentProvider $provider) { 146 164 147 165 $request = $this->getRequest(); 148 - $user = $request->getUser(); 166 + $viewer = $request->getUser(); 149 167 150 168 $description = $provider->getPaymentMethodDescription(); 151 169 $icon_uri = $provider->getPaymentMethodIcon(); ··· 165 183 ->setSubtext($details); 166 184 167 185 $form = id(new AphrontFormView()) 168 - ->setUser($user) 169 - ->addHiddenInput('providerKey', $provider->getProviderKey()) 186 + ->setUser($viewer) 187 + ->addHiddenInput('providerID', $provider->getProviderConfig()->getID()) 170 188 ->appendChild($button); 171 189 172 190 return $form;
+1 -1
src/applications/phortune/phid/PhortuneMerchantPHIDType.php
··· 30 30 31 31 $id = $merchant->getID(); 32 32 33 - $handle->setName(pht('Merchant %d', $id)); 33 + $handle->setName(pht('Merchant %d %s', $id, $merchant->getName())); 34 34 $handle->setURI("/phortune/merchant/{$id}/"); 35 35 } 36 36 }
-30
src/applications/phortune/provider/PhortunePaymentProvider.php
··· 120 120 ->loadObjects(); 121 121 } 122 122 123 - public static function getEnabledProviders() { 124 - $providers = self::getAllProviders(); 125 - foreach ($providers as $key => $provider) { 126 - if (!$provider->isEnabled()) { 127 - unset($providers[$key]); 128 - } 129 - } 130 - return $providers; 131 - } 132 - 133 - public static function getProvidersForAddPaymentMethod() { 134 - $providers = self::getEnabledProviders(); 135 - foreach ($providers as $key => $provider) { 136 - if (!$provider->canCreatePaymentMethods()) { 137 - unset($providers[$key]); 138 - } 139 - } 140 - return $providers; 141 - } 142 - 143 - public static function getProvidersForOneTimePayment() { 144 - $providers = self::getEnabledProviders(); 145 - foreach ($providers as $key => $provider) { 146 - if (!$provider->canProcessOneTimePayments()) { 147 - unset($providers[$key]); 148 - } 149 - } 150 - return $providers; 151 - } 152 - 153 123 abstract public function isEnabled(); 154 124 155 125 abstract public function getPaymentMethodDescription();
+15
src/applications/phortune/query/PhortuneCartQuery.php
··· 66 66 $cart->attachAccount($account); 67 67 } 68 68 69 + $merchants = id(new PhortuneMerchantQuery()) 70 + ->setViewer($this->getViewer()) 71 + ->withPHIDs(mpull($carts, 'getMerchantPHID')) 72 + ->execute(); 73 + $merchants = mpull($merchants, null, 'getPHID'); 74 + 75 + foreach ($carts as $key => $cart) { 76 + $merchant = idx($merchants, $cart->getMerchantPHID()); 77 + if (!$merchant) { 78 + unset($carts[$key]); 79 + continue; 80 + } 81 + $cart->attachMerchant($merchant); 82 + } 83 + 69 84 $implementations = array(); 70 85 71 86 $cart_map = mgroup($carts, 'getCartClass');
+43 -9
src/applications/phortune/query/PhortunePaymentMethodQuery.php
··· 6 6 private $ids; 7 7 private $phids; 8 8 private $accountPHIDs; 9 + private $merchantPHIDs; 9 10 private $statuses; 10 11 11 12 public function withIDs(array $ids) { ··· 20 21 21 22 public function withAccountPHIDs(array $phids) { 22 23 $this->accountPHIDs = $phids; 24 + return $this; 25 + } 26 + 27 + public function withMerchantPHIDs(array $phids) { 28 + $this->merchantPHIDs = $phids; 23 29 return $this; 24 30 } 25 31 ··· 44 50 } 45 51 46 52 protected function willFilterPage(array $methods) { 47 - foreach ($methods as $key => $method) { 48 - try { 49 - $method->buildPaymentProvider(); 50 - } catch (Exception $ex) { 51 - unset($methods[$key]); 52 - continue; 53 - } 54 - } 55 - 56 53 $accounts = id(new PhortuneAccountQuery()) 57 54 ->setViewer($this->getViewer()) 58 55 ->withPHIDs(mpull($methods, 'getAccountPHID')) ··· 68 65 $method->attachAccount($account); 69 66 } 70 67 68 + $merchants = id(new PhortuneMerchantQuery()) 69 + ->setViewer($this->getViewer()) 70 + ->withPHIDs(mpull($methods, 'getMerchantPHID')) 71 + ->execute(); 72 + $merchants = mpull($merchants, null, 'getPHID'); 73 + 74 + foreach ($methods as $key => $method) { 75 + $merchant = idx($merchants, $method->getMerchantPHID()); 76 + if (!$merchant) { 77 + unset($methods[$key]); 78 + continue; 79 + } 80 + $method->attachMerchant($merchant); 81 + } 82 + 83 + $provider_configs = id(new PhortunePaymentProviderConfigQuery()) 84 + ->setViewer($this->getViewer()) 85 + ->withPHIDs(mpull($methods, 'getProviderPHID')) 86 + ->execute(); 87 + $provider_configs = mpull($provider_configs, null, 'getPHID'); 88 + 89 + foreach ($methods as $key => $method) { 90 + $provider_config = idx($provider_configs, $method->getProviderPHID()); 91 + if (!$provider_config) { 92 + unset($methods[$key]); 93 + continue; 94 + } 95 + $method->attachProviderConfig($provider_config); 96 + } 97 + 71 98 return $methods; 72 99 } 73 100 ··· 93 120 $conn, 94 121 'accountPHID IN (%Ls)', 95 122 $this->accountPHIDs); 123 + } 124 + 125 + if ($this->merchantPHIDs !== null) { 126 + $where[] = qsprintf( 127 + $conn, 128 + 'merchantPHID IN (%Ls)', 129 + $this->merchantPHIDs); 96 130 } 97 131 98 132 if ($this->statuses !== null) {
+3 -2
src/applications/phortune/storage/PhortuneAccount.php
··· 58 58 59 59 public function newCart( 60 60 PhabricatorUser $actor, 61 - PhortuneCartImplementation $implementation) { 61 + PhortuneCartImplementation $implementation, 62 + PhortuneMerchant $merchant) { 62 63 63 - $cart = PhortuneCart::initializeNewCart($actor, $this); 64 + $cart = PhortuneCart::initializeNewCart($actor, $this, $merchant); 64 65 65 66 $cart->setCartClass(get_class($implementation)); 66 67 $cart->attachImplementation($implementation);
+20 -3
src/applications/phortune/storage/PhortuneCart.php
··· 11 11 12 12 protected $accountPHID; 13 13 protected $authorPHID; 14 + protected $merchantPHID; 14 15 protected $cartClass; 15 16 protected $status; 16 17 protected $metadata = array(); ··· 18 19 private $account = self::ATTACHABLE; 19 20 private $purchases = self::ATTACHABLE; 20 21 private $implementation = self::ATTACHABLE; 22 + private $merchant = self::ATTACHABLE; 21 23 22 24 public static function initializeNewCart( 23 25 PhabricatorUser $actor, 24 - PhortuneAccount $account) { 26 + PhortuneAccount $account, 27 + PhortuneMerchant $merchant) { 25 28 $cart = id(new PhortuneCart()) 26 29 ->setAuthorPHID($actor->getPHID()) 27 30 ->setStatus(self::STATUS_BUILDING) 28 - ->setAccountPHID($account->getPHID()); 31 + ->setAccountPHID($account->getPHID()) 32 + ->setMerchantPHID($merchant->getPHID()); 29 33 30 34 $cart->account = $account; 31 35 $cart->purchases = array(); ··· 63 67 ->setAccountPHID($account->getPHID()) 64 68 ->setCartPHID($this->getPHID()) 65 69 ->setAuthorPHID($actor->getPHID()) 66 - ->setPaymentProviderKey($provider->getProviderKey()) 70 + ->setMerchantPHID($this->getMerchant()->getPHID()) 71 + ->setProviderPHID($provider->getProviderConfig()->getPHID()) 67 72 ->setAmountAsCurrency($this->getTotalPriceAsCurrency()); 68 73 69 74 if ($method) { ··· 154 159 'key_account' => array( 155 160 'columns' => array('accountPHID'), 156 161 ), 162 + 'key_merchant' => array( 163 + 'columns' => array('merchantPHID'), 164 + ), 157 165 ), 158 166 ) + parent::getConfiguration(); 159 167 } ··· 180 188 181 189 public function getAccount() { 182 190 return $this->assertAttached($this->account); 191 + } 192 + 193 + public function attachMerchant(PhortuneMerchant $merchant) { 194 + $this->merchant = $merchant; 195 + return $this; 196 + } 197 + 198 + public function getMerchant() { 199 + return $this->assertAttached($this->merchant); 183 200 } 184 201 185 202 public function attachImplementation(
+8 -1
src/applications/phortune/storage/PhortuneCharge.php
··· 16 16 protected $accountPHID; 17 17 protected $authorPHID; 18 18 protected $cartPHID; 19 - protected $paymentProviderKey; 19 + protected $providerPHID; 20 + protected $merchantPHID; 20 21 protected $paymentMethodPHID; 21 22 protected $amountAsCurrency; 22 23 protected $status; ··· 51 52 ), 52 53 'key_account' => array( 53 54 'columns' => array('accountPHID'), 55 + ), 56 + 'key_merchant' => array( 57 + 'columns' => array('merchantPHID'), 58 + ), 59 + 'key_provider' => array( 60 + 'columns' => array('providerPHID'), 54 61 ), 55 62 ), 56 63 ) + parent::getConfiguration();
+25 -1
src/applications/phortune/storage/PhortunePaymentMethod.php
··· 14 14 protected $status; 15 15 protected $accountPHID; 16 16 protected $authorPHID; 17 + protected $merchantPHID; 17 18 protected $providerPHID; 18 19 protected $expires; 19 20 protected $metadata = array(); ··· 21 22 protected $lastFourDigits; 22 23 23 24 private $account = self::ATTACHABLE; 25 + private $merchant = self::ATTACHABLE; 26 + private $providerConfig = self::ATTACHABLE; 24 27 25 28 public function getConfiguration() { 26 29 return array( ··· 39 42 'key_account' => array( 40 43 'columns' => array('accountPHID', 'status'), 41 44 ), 45 + 'key_merchant' => array( 46 + 'columns' => array('merchantPHID', 'accountPHID'), 47 + ), 42 48 ), 43 49 ) + parent::getConfiguration(); 44 50 } ··· 57 63 return $this->assertAttached($this->account); 58 64 } 59 65 66 + public function attachMerchant(PhortuneMerchant $merchant) { 67 + $this->merchant = $merchant; 68 + return $this; 69 + } 70 + 71 + public function getMerchant() { 72 + return $this->assertAttached($this->merchant); 73 + } 74 + 75 + public function attachProviderConfig(PhortunePaymentProviderConfig $config) { 76 + $this->providerConfig = $config; 77 + return $this; 78 + } 79 + 80 + public function getProviderConfig() { 81 + return $this->assertAttached($this->providerConfig); 82 + } 83 + 60 84 public function getDescription() { 61 85 $provider = $this->buildPaymentProvider(); 62 86 return $provider->getPaymentMethodProviderDescription(); ··· 72 96 } 73 97 74 98 public function buildPaymentProvider() { 75 - throw new Exception(pht('TODO: Reimplement this junk.')); 99 + return $this->getProviderConfig()->buildProvider(); 76 100 } 77 101 78 102 public function getDisplayName() {