@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.

Make Phortune payment methods transaction-oriented and always support "Add Payment Method"

Summary:
Depends on D20718. Ref T13366. Ref T13367.

- Phortune payment methods currently do not use transactions; update them.
- Give them a proper view page with a transaction log.
- Add an "Add Payment Method" button which always works.
- Show which subscriptions a payment method is associated with.
- Get rid of the "Active" status indicator since we now treat "disabled" as "removed", to align with user expectation/intent.
- Swap out of some of the super weird div-form-button UI into the new "big, clickable" UI for choice dialogs among a small number of options on a single dimension.

Test Plan:
- As a mechant-authority and account-authority, created payment methods from carts, subscriptions, and accounts. Edited and viewed payment methods.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13367, T13366

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

+906 -424
+2 -2
resources/celerity/map.php
··· 92 92 'rsrc/css/application/pholio/pholio.css' => '88ef5ef1', 93 93 'rsrc/css/application/phortune/phortune-credit-card-form.css' => '3b9868a8', 94 94 'rsrc/css/application/phortune/phortune-invoice.css' => '4436b241', 95 - 'rsrc/css/application/phortune/phortune.css' => '12e8251a', 95 + 'rsrc/css/application/phortune/phortune.css' => '508a1a5e', 96 96 'rsrc/css/application/phrequent/phrequent.css' => 'bd79cc67', 97 97 'rsrc/css/application/phriction/phriction-document-css.css' => '03380da0', 98 98 'rsrc/css/application/policy/policy-edit.css' => '8794e2ed', ··· 810 810 'pholio-inline-comments-css' => '722b48c2', 811 811 'phortune-credit-card-form' => 'd12d214f', 812 812 'phortune-credit-card-form-css' => '3b9868a8', 813 - 'phortune-css' => '12e8251a', 813 + 'phortune-css' => '508a1a5e', 814 814 'phortune-invoice-css' => '4436b241', 815 815 'phrequent-css' => 'bd79cc67', 816 816 'phriction-document-css' => '03380da0',
+19
resources/sql/autopatches/20190816.payment.01.xaction.sql
··· 1 + CREATE TABLE {$NAMESPACE}_phortune.phortune_paymentmethodtransaction ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + authorPHID VARBINARY(64) NOT NULL, 5 + objectPHID VARBINARY(64) NOT NULL, 6 + viewPolicy VARBINARY(64) NOT NULL, 7 + editPolicy VARBINARY(64) NOT NULL, 8 + commentPHID VARBINARY(64) DEFAULT NULL, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) NOT NULL, 11 + oldValue LONGTEXT NOT NULL, 12 + newValue LONGTEXT NOT NULL, 13 + contentSource LONGTEXT NOT NULL, 14 + metadata LONGTEXT NOT NULL, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + UNIQUE KEY `key_phid` (`phid`), 18 + KEY `key_object` (`objectPHID`) 19 + ) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};
+20 -5
src/__phutil_library_map__.php
··· 5249 5249 'PhortuneAccountOrdersController' => 'applications/phortune/controller/account/PhortuneAccountOrdersController.php', 5250 5250 'PhortuneAccountOverviewController' => 'applications/phortune/controller/account/PhortuneAccountOverviewController.php', 5251 5251 'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php', 5252 - 'PhortuneAccountPaymentMethodsController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php', 5252 + 'PhortuneAccountPaymentMethodListController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php', 5253 + 'PhortuneAccountPaymentMethodViewController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodViewController.php', 5253 5254 'PhortuneAccountProfileController' => 'applications/phortune/controller/account/PhortuneAccountProfileController.php', 5254 5255 'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php', 5255 5256 'PhortuneAccountSubscriptionController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionController.php', ··· 5325 5326 'PhortuneOrderTableView' => 'applications/phortune/view/PhortuneOrderTableView.php', 5326 5327 'PhortunePayPalPaymentProvider' => 'applications/phortune/provider/PhortunePayPalPaymentProvider.php', 5327 5328 'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php', 5328 - 'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php', 5329 - 'PhortunePaymentMethodDisableController' => 'applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php', 5330 - 'PhortunePaymentMethodEditController' => 'applications/phortune/controller/payment/PhortunePaymentMethodEditController.php', 5329 + 'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodCreateController.php', 5330 + 'PhortunePaymentMethodDisableController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php', 5331 + 'PhortunePaymentMethodEditController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodEditController.php', 5332 + 'PhortunePaymentMethodEditor' => 'applications/phortune/editor/PhortunePaymentMethodEditor.php', 5333 + 'PhortunePaymentMethodNameTransaction' => 'applications/phortune/xaction/paymentmethod/PhortunePaymentMethodNameTransaction.php', 5331 5334 'PhortunePaymentMethodPHIDType' => 'applications/phortune/phid/PhortunePaymentMethodPHIDType.php', 5332 5335 'PhortunePaymentMethodPolicyCodex' => 'applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php', 5333 5336 'PhortunePaymentMethodQuery' => 'applications/phortune/query/PhortunePaymentMethodQuery.php', 5337 + 'PhortunePaymentMethodStatusTransaction' => 'applications/phortune/xaction/paymentmethod/PhortunePaymentMethodStatusTransaction.php', 5338 + 'PhortunePaymentMethodTransaction' => 'applications/phortune/storage/PhortunePaymentMethodTransaction.php', 5339 + 'PhortunePaymentMethodTransactionQuery' => 'applications/phortune/query/PhortunePaymentMethodTransactionQuery.php', 5340 + 'PhortunePaymentMethodTransactionType' => 'applications/phortune/xaction/paymentmethod/PhortunePaymentMethodTransactionType.php', 5334 5341 'PhortunePaymentProvider' => 'applications/phortune/provider/PhortunePaymentProvider.php', 5335 5342 'PhortunePaymentProviderConfig' => 'applications/phortune/storage/PhortunePaymentProviderConfig.php', 5336 5343 'PhortunePaymentProviderConfigEditor' => 'applications/phortune/editor/PhortunePaymentProviderConfigEditor.php', ··· 11805 11812 'PhortuneAccountOrdersController' => 'PhortuneAccountProfileController', 11806 11813 'PhortuneAccountOverviewController' => 'PhortuneAccountProfileController', 11807 11814 'PhortuneAccountPHIDType' => 'PhabricatorPHIDType', 11808 - 'PhortuneAccountPaymentMethodsController' => 'PhortuneAccountProfileController', 11815 + 'PhortuneAccountPaymentMethodListController' => 'PhortuneAccountProfileController', 11816 + 'PhortuneAccountPaymentMethodViewController' => 'PhortuneAccountController', 11809 11817 'PhortuneAccountProfileController' => 'PhortuneAccountController', 11810 11818 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 11811 11819 'PhortuneAccountSubscriptionController' => 'PhortuneAccountProfileController', ··· 11896 11904 'PhabricatorPolicyInterface', 11897 11905 'PhabricatorExtendedPolicyInterface', 11898 11906 'PhabricatorPolicyCodexInterface', 11907 + 'PhabricatorApplicationTransactionInterface', 11899 11908 ), 11900 11909 'PhortunePaymentMethodCreateController' => 'PhortuneController', 11901 11910 'PhortunePaymentMethodDisableController' => 'PhortuneController', 11902 11911 'PhortunePaymentMethodEditController' => 'PhortuneController', 11912 + 'PhortunePaymentMethodEditor' => 'PhabricatorApplicationTransactionEditor', 11913 + 'PhortunePaymentMethodNameTransaction' => 'PhortunePaymentMethodTransactionType', 11903 11914 'PhortunePaymentMethodPHIDType' => 'PhabricatorPHIDType', 11904 11915 'PhortunePaymentMethodPolicyCodex' => 'PhabricatorPolicyCodex', 11905 11916 'PhortunePaymentMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 11917 + 'PhortunePaymentMethodStatusTransaction' => 'PhortunePaymentMethodTransactionType', 11918 + 'PhortunePaymentMethodTransaction' => 'PhabricatorModularTransaction', 11919 + 'PhortunePaymentMethodTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 11920 + 'PhortunePaymentMethodTransactionType' => 'PhabricatorModularTransactionType', 11906 11921 'PhortunePaymentProvider' => 'Phobject', 11907 11922 'PhortunePaymentProviderConfig' => array( 11908 11923 'PhortuneDAO',
+4 -1
src/applications/phortune/application/PhabricatorPhortuneApplication.php
··· 72 72 73 73 '(?P<accountID>\d+)/' => array( 74 74 'details/' => 'PhortuneAccountDetailsController', 75 - 'methods/' => 'PhortuneAccountPaymentMethodsController', 75 + 'methods/' => array( 76 + '' => 'PhortuneAccountPaymentMethodListController', 77 + '(?P<id>\d+)/' => 'PhortuneAccountPaymentMethodViewController', 78 + ), 76 79 'orders/' => 'PhortuneAccountOrdersController', 77 80 'charges/' => 'PhortuneAccountChargesController', 78 81 'subscriptions/' => 'PhortuneAccountSubscriptionController',
+8 -2
src/applications/phortune/controller/account/PhortuneAccountController.php
··· 23 23 abstract protected function shouldRequireAccountEditCapability(); 24 24 abstract protected function handleAccountRequest(AphrontRequest $request); 25 25 26 + private function hasAccount() { 27 + return (bool)$this->account; 28 + } 29 + 26 30 final protected function getAccount() { 27 31 if ($this->account === null) { 28 32 throw new Exception( ··· 37 41 protected function buildApplicationCrumbs() { 38 42 $crumbs = parent::buildApplicationCrumbs(); 39 43 40 - $account = $this->getAccount(); 41 - if ($account) { 44 + // If we hit a policy exception, we can make it here without finding 45 + // an account. 46 + if ($this->hasAccount()) { 47 + $account = $this->getAccount(); 42 48 $crumbs->addTextCrumb($account->getName(), $account->getURI()); 43 49 } 44 50
+154
src/applications/phortune/controller/account/PhortuneAccountPaymentMethodViewController.php
··· 1 + <?php 2 + 3 + final class PhortuneAccountPaymentMethodViewController 4 + extends PhortuneAccountController { 5 + 6 + protected function shouldRequireAccountEditCapability() { 7 + return false; 8 + } 9 + 10 + protected function handleAccountRequest(AphrontRequest $request) { 11 + $viewer = $this->getViewer(); 12 + $account = $this->getAccount(); 13 + 14 + $method = id(new PhortunePaymentMethodQuery()) 15 + ->setViewer($viewer) 16 + ->withAccountPHIDs(array($account->getPHID())) 17 + ->withIDs(array($request->getURIData('id'))) 18 + ->withStatuses( 19 + array( 20 + PhortunePaymentMethod::STATUS_ACTIVE, 21 + )) 22 + ->executeOne(); 23 + if (!$method) { 24 + return new Aphront404Response(); 25 + } 26 + 27 + $crumbs = $this->buildApplicationCrumbs() 28 + ->addTextCrumb(pht('Payment Methods'), $account->getPaymentMethodsURI()) 29 + ->addTextCrumb($method->getObjectName()) 30 + ->setBorder(true); 31 + 32 + $header = id(new PHUIHeaderView()) 33 + ->setHeader($method->getFullDisplayName()); 34 + 35 + $details = $this->newDetailsView($method); 36 + 37 + $timeline = $this->buildTransactionTimeline( 38 + $method, 39 + new PhortunePaymentMethodTransactionQuery()); 40 + $timeline->setShouldTerminate(true); 41 + 42 + $autopay = $this->newAutopayView($method); 43 + 44 + $curtain = $this->buildCurtainView($method); 45 + 46 + $view = id(new PHUITwoColumnView()) 47 + ->setHeader($header) 48 + ->setCurtain($curtain) 49 + ->setMainColumn( 50 + array( 51 + $details, 52 + $autopay, 53 + $timeline, 54 + )); 55 + 56 + return $this->newPage() 57 + ->setTitle($method->getObjectName()) 58 + ->setCrumbs($crumbs) 59 + ->appendChild($view); 60 + } 61 + 62 + private function buildCurtainView(PhortunePaymentMethod $method) { 63 + $viewer = $this->getViewer(); 64 + 65 + $can_edit = PhabricatorPolicyFilter::hasCapability( 66 + $viewer, 67 + $method, 68 + PhabricatorPolicyCapability::CAN_EDIT); 69 + 70 + $edit_uri = $this->getApplicationURI( 71 + urisprintf( 72 + 'card/%d/edit/', 73 + $method->getID())); 74 + 75 + $remove_uri = $this->getApplicationURI( 76 + urisprintf( 77 + 'card/%d/disable/', 78 + $method->getID())); 79 + 80 + $curtain = $this->newCurtainView($method); 81 + 82 + $curtain->addAction( 83 + id(new PhabricatorActionView()) 84 + ->setName(pht('Edit Payment Method')) 85 + ->setIcon('fa-pencil') 86 + ->setHref($edit_uri) 87 + ->setDisabled(!$can_edit) 88 + ->setWorkflow(!$can_edit)); 89 + 90 + $curtain->addAction( 91 + id(new PhabricatorActionView()) 92 + ->setName(pht('Remove Payment Method')) 93 + ->setIcon('fa-times') 94 + ->setHref($remove_uri) 95 + ->setDisabled(!$can_edit) 96 + ->setWorkflow(true)); 97 + 98 + return $curtain; 99 + } 100 + 101 + private function newDetailsView(PhortunePaymentMethod $method) { 102 + $viewer = $this->getViewer(); 103 + 104 + $merchant_phid = $method->getMerchantPHID(); 105 + $handles = $viewer->loadHandles( 106 + array( 107 + $merchant_phid, 108 + )); 109 + 110 + $view = id(new PHUIPropertyListView()) 111 + ->setUser($viewer); 112 + 113 + if (strlen($method->getName())) { 114 + $view->addProperty(pht('Name'), $method->getDisplayName()); 115 + } 116 + 117 + $view->addProperty(pht('Summary'), $method->getSummary()); 118 + $view->addProperty(pht('Expires'), $method->getDisplayExpires()); 119 + 120 + $view->addProperty( 121 + pht('Merchant'), 122 + $handles[$merchant_phid]->renderLink()); 123 + 124 + return id(new PHUIObjectBoxView()) 125 + ->setHeaderText(pht('Payment Method Details')) 126 + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 127 + ->addPropertyList($view); 128 + } 129 + 130 + private function newAutopayView(PhortunePaymentMethod $method) { 131 + $viewer = $this->getViewer(); 132 + 133 + $subscriptions = id(new PhortuneSubscriptionQuery()) 134 + ->setViewer($viewer) 135 + ->withPaymentMethodPHIDs(array($method->getPHID())) 136 + ->execute(); 137 + 138 + $table = id(new PhortuneSubscriptionTableView()) 139 + ->setViewer($viewer) 140 + ->setSubscriptions($subscriptions) 141 + ->newTableView(); 142 + 143 + $table->setNoDataString( 144 + pht( 145 + 'This payment method is not the default payment method for '. 146 + 'any subscriptions.')); 147 + 148 + return id(new PHUIObjectBoxView()) 149 + ->setHeaderText(pht('Autopay Subscriptions')) 150 + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 151 + ->setTable($table); 152 + } 153 + 154 + }
+11 -34
src/applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php src/applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php
··· 1 1 <?php 2 2 3 - final class PhortuneAccountPaymentMethodsController 3 + final class PhortuneAccountPaymentMethodListController 4 4 extends PhortuneAccountProfileController { 5 5 6 6 protected function shouldRequireAccountEditCapability() { ··· 46 46 47 47 $id = $account->getID(); 48 48 49 - // TODO: Allow adding a card here directly 50 49 $add = id(new PHUIButtonView()) 51 50 ->setTag('a') 52 - ->setText(pht('New Payment Method')) 51 + ->setText(pht('Add Payment Method')) 53 52 ->setIcon('fa-plus') 54 - ->setHref($this->getApplicationURI("{$id}/card/new/")); 53 + ->setHref($this->getApplicationURI("{$id}/card/new/")) 54 + ->setDisabled(!$can_edit) 55 + ->setWorkflow(!$can_edit); 55 56 56 57 $header = id(new PHUIHeaderView()) 57 - ->setHeader(pht('Payment Methods')); 58 + ->setHeader(pht('Payment Methods')) 59 + ->addActionLink($add); 58 60 59 61 $list = id(new PHUIObjectItemListView()) 60 62 ->setUser($viewer) ··· 74 76 foreach ($methods as $method) { 75 77 $id = $method->getID(); 76 78 77 - $item = new PHUIObjectItemView(); 78 - $item->setHeader($method->getFullDisplayName()); 79 - 80 - switch ($method->getStatus()) { 81 - case PhortunePaymentMethod::STATUS_ACTIVE: 82 - $item->setStatusIcon('fa-check green'); 83 - 84 - $disable_uri = $this->getApplicationURI('card/'.$id.'/disable/'); 85 - $item->addAction( 86 - id(new PHUIListItemView()) 87 - ->setIcon('fa-times') 88 - ->setHref($disable_uri) 89 - ->setDisabled(!$can_edit) 90 - ->setWorkflow(true)); 91 - break; 92 - case PhortunePaymentMethod::STATUS_DISABLED: 93 - $item->setStatusIcon('fa-ban lightbluetext'); 94 - $item->setDisabled(true); 95 - break; 96 - } 79 + $item = id(new PHUIObjectItemView()) 80 + ->setObjectName($method->getObjectName()) 81 + ->setHeader($method->getFullDisplayName()) 82 + ->setHref($method->getURI()); 97 83 98 84 $provider = $method->buildPaymentProvider(); 99 85 $item->addAttribute($provider->getPaymentMethodProviderDescription()); 100 - 101 - $edit_uri = $this->getApplicationURI('card/'.$id.'/edit/'); 102 - 103 - $item->addAction( 104 - id(new PHUIListItemView()) 105 - ->setIcon('fa-pencil') 106 - ->setHref($edit_uri) 107 - ->setDisabled(!$can_edit) 108 - ->setWorkflow(!$can_edit)); 109 86 110 87 $list->addItem($item); 111 88 }
-3
src/applications/phortune/controller/account/PhortuneAccountSubscriptionController.php
··· 47 47 ->setLimit(25) 48 48 ->execute(); 49 49 50 - $handles = $this->loadViewerHandles(mpull($subscriptions, 'getPHID')); 51 - 52 50 $table = id(new PhortuneSubscriptionTableView()) 53 51 ->setUser($viewer) 54 - ->setHandles($handles) 55 52 ->setSubscriptions($subscriptions); 56 53 57 54 $header = id(new PHUIHeaderView())
-303
src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php
··· 1 - <?php 2 - 3 - final class PhortunePaymentMethodCreateController 4 - extends PhortuneController { 5 - 6 - public function handleRequest(AphrontRequest $request) { 7 - $viewer = $request->getViewer(); 8 - $account_id = $request->getURIData('accountID'); 9 - 10 - $account = id(new PhortuneAccountQuery()) 11 - ->setViewer($viewer) 12 - ->withIDs(array($account_id)) 13 - ->executeOne(); 14 - if (!$account) { 15 - return new Aphront404Response(); 16 - } 17 - $account_id = $account->getID(); 18 - 19 - $merchant = id(new PhortuneMerchantQuery()) 20 - ->setViewer($viewer) 21 - ->withIDs(array($request->getInt('merchantID'))) 22 - ->executeOne(); 23 - if (!$merchant) { 24 - return new Aphront404Response(); 25 - } 26 - 27 - $cart_id = $request->getInt('cartID'); 28 - $subscription_id = $request->getInt('subscriptionID'); 29 - if ($cart_id) { 30 - $cancel_uri = $this->getApplicationURI("cart/{$cart_id}/checkout/"); 31 - } else if ($subscription_id) { 32 - $cancel_uri = $this->getApplicationURI( 33 - "{$account_id}/subscription/edit/{$subscription_id}/"); 34 - } else { 35 - $cancel_uri = $this->getApplicationURI($account->getID().'/'); 36 - } 37 - 38 - $providers = $this->loadCreatePaymentMethodProvidersForMerchant($merchant); 39 - if (!$providers) { 40 - throw new Exception( 41 - pht( 42 - 'There are no payment providers enabled that can add payment '. 43 - 'methods.')); 44 - } 45 - 46 - if (count($providers) == 1) { 47 - // If there's only one provider, always choose it. 48 - $provider_id = head_key($providers); 49 - } else { 50 - $provider_id = $request->getInt('providerID'); 51 - if (empty($providers[$provider_id])) { 52 - $choices = array(); 53 - foreach ($providers as $provider) { 54 - $choices[] = $this->renderSelectProvider($provider); 55 - } 56 - 57 - $content = phutil_tag( 58 - 'div', 59 - array( 60 - 'class' => 'phortune-payment-method-list', 61 - ), 62 - $choices); 63 - 64 - return $this->newDialog() 65 - ->setRenderDialogAsDiv(true) 66 - ->setTitle(pht('Add Payment Method')) 67 - ->appendParagraph(pht('Choose a payment method to add:')) 68 - ->appendChild($content) 69 - ->addCancelButton($cancel_uri); 70 - } 71 - } 72 - 73 - $provider = $providers[$provider_id]; 74 - 75 - $errors = array(); 76 - $display_exception = null; 77 - if ($request->isFormPost() && $request->getBool('isProviderForm')) { 78 - $method = id(new PhortunePaymentMethod()) 79 - ->setAccountPHID($account->getPHID()) 80 - ->setAuthorPHID($viewer->getPHID()) 81 - ->setMerchantPHID($merchant->getPHID()) 82 - ->setProviderPHID($provider->getProviderConfig()->getPHID()) 83 - ->setStatus(PhortunePaymentMethod::STATUS_ACTIVE); 84 - 85 - // Limit the rate at which you can attempt to add payment methods. This 86 - // is intended as a line of defense against using Phortune to validate a 87 - // large list of stolen credit card numbers. 88 - 89 - PhabricatorSystemActionEngine::willTakeAction( 90 - array($viewer->getPHID()), 91 - new PhortuneAddPaymentMethodAction(), 92 - 1); 93 - 94 - if (!$errors) { 95 - $errors = $this->processClientErrors( 96 - $provider, 97 - $request->getStr('errors')); 98 - } 99 - 100 - if (!$errors) { 101 - $client_token_raw = $request->getStr('token'); 102 - $client_token = null; 103 - try { 104 - $client_token = phutil_json_decode($client_token_raw); 105 - } catch (PhutilJSONParserException $ex) { 106 - $errors[] = pht( 107 - 'There was an error decoding token information submitted by the '. 108 - 'client. Expected a JSON-encoded token dictionary, received: %s.', 109 - nonempty($client_token_raw, pht('nothing'))); 110 - } 111 - 112 - if (!$provider->validateCreatePaymentMethodToken($client_token)) { 113 - $errors[] = pht( 114 - 'There was an error with the payment token submitted by the '. 115 - 'client. Expected a valid dictionary, received: %s.', 116 - $client_token_raw); 117 - } 118 - 119 - if (!$errors) { 120 - try { 121 - $provider->createPaymentMethodFromRequest( 122 - $request, 123 - $method, 124 - $client_token); 125 - } catch (PhortuneDisplayException $exception) { 126 - $display_exception = $exception; 127 - } catch (Exception $ex) { 128 - $errors = array( 129 - pht('There was an error adding this payment method:'), 130 - $ex->getMessage(), 131 - ); 132 - } 133 - } 134 - } 135 - 136 - if (!$errors && !$display_exception) { 137 - $method->save(); 138 - 139 - // If we added this method on a cart flow, return to the cart to 140 - // check out. 141 - if ($cart_id) { 142 - $next_uri = $this->getApplicationURI( 143 - "cart/{$cart_id}/checkout/?paymentMethodID=".$method->getID()); 144 - } else if ($subscription_id) { 145 - $next_uri = new PhutilURI($cancel_uri); 146 - $next_uri->replaceQueryParam('added', true); 147 - } else { 148 - $account_uri = $this->getApplicationURI($account->getID().'/'); 149 - $next_uri = new PhutilURI($account_uri); 150 - $next_uri->setFragment('payment'); 151 - } 152 - 153 - return id(new AphrontRedirectResponse())->setURI($next_uri); 154 - } else { 155 - if ($display_exception) { 156 - $dialog_body = $display_exception->getView(); 157 - } else { 158 - $dialog_body = id(new PHUIInfoView()) 159 - ->setErrors($errors); 160 - } 161 - 162 - return $this->newDialog() 163 - ->setTitle(pht('Error Adding Payment Method')) 164 - ->appendChild($dialog_body) 165 - ->addCancelButton($request->getRequestURI()); 166 - } 167 - } 168 - 169 - $form = $provider->renderCreatePaymentMethodForm($request, $errors); 170 - 171 - $form 172 - ->setUser($viewer) 173 - ->setAction($request->getRequestURI()) 174 - ->setWorkflow(true) 175 - ->addHiddenInput('providerID', $provider_id) 176 - ->addHiddenInput('cartID', $request->getInt('cartID')) 177 - ->addHiddenInput('subscriptionID', $request->getInt('subscriptionID')) 178 - ->addHiddenInput('isProviderForm', true) 179 - ->appendChild( 180 - id(new AphrontFormSubmitControl()) 181 - ->setValue(pht('Add Payment Method')) 182 - ->addCancelButton($cancel_uri)); 183 - 184 - $box = id(new PHUIObjectBoxView()) 185 - ->setHeaderText(pht('Method')) 186 - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 187 - ->setForm($form); 188 - 189 - $crumbs = $this->buildApplicationCrumbs(); 190 - $crumbs->addTextCrumb(pht('Add Payment Method')); 191 - $crumbs->setBorder(true); 192 - 193 - $header = id(new PHUIHeaderView()) 194 - ->setHeader(pht('Add Payment Method')) 195 - ->setHeaderIcon('fa-plus-square'); 196 - 197 - $view = id(new PHUITwoColumnView()) 198 - ->setHeader($header) 199 - ->setFooter(array( 200 - $box, 201 - )); 202 - 203 - return $this->newPage() 204 - ->setTitle($provider->getPaymentMethodDescription()) 205 - ->setCrumbs($crumbs) 206 - ->appendChild($view); 207 - 208 - } 209 - 210 - private function renderSelectProvider( 211 - PhortunePaymentProvider $provider) { 212 - 213 - $request = $this->getRequest(); 214 - $viewer = $request->getUser(); 215 - 216 - $description = $provider->getPaymentMethodDescription(); 217 - $icon_uri = $provider->getPaymentMethodIcon(); 218 - $details = $provider->getPaymentMethodProviderDescription(); 219 - 220 - $this->requireResource('phortune-css'); 221 - 222 - $icon = id(new PHUIIconView()) 223 - ->setSpriteSheet(PHUIIconView::SPRITE_LOGIN) 224 - ->setSpriteIcon($provider->getPaymentMethodIcon()); 225 - 226 - $button = id(new PHUIButtonView()) 227 - ->setSize(PHUIButtonView::BIG) 228 - ->setColor(PHUIButtonView::GREY) 229 - ->setIcon($icon) 230 - ->setText($description) 231 - ->setSubtext($details) 232 - ->setMetadata(array('disableWorkflow' => true)); 233 - 234 - $form = id(new AphrontFormView()) 235 - ->setUser($viewer) 236 - ->setAction($request->getRequestURI()) 237 - ->addHiddenInput('providerID', $provider->getProviderConfig()->getID()) 238 - ->appendChild($button); 239 - 240 - return $form; 241 - } 242 - 243 - private function processClientErrors( 244 - PhortunePaymentProvider $provider, 245 - $client_errors_raw) { 246 - 247 - $errors = array(); 248 - 249 - $client_errors = null; 250 - try { 251 - $client_errors = phutil_json_decode($client_errors_raw); 252 - } catch (PhutilJSONParserException $ex) { 253 - $errors[] = pht( 254 - 'There was an error decoding error information submitted by the '. 255 - 'client. Expected a JSON-encoded list of error codes, received: %s.', 256 - nonempty($client_errors_raw, pht('nothing'))); 257 - } 258 - 259 - foreach (array_unique($client_errors) as $key => $client_error) { 260 - $client_errors[$key] = $provider->translateCreatePaymentMethodErrorCode( 261 - $client_error); 262 - } 263 - 264 - foreach (array_unique($client_errors) as $client_error) { 265 - switch ($client_error) { 266 - case PhortuneErrCode::ERR_CC_INVALID_NUMBER: 267 - $message = pht( 268 - 'The card number you entered is not a valid card number. Check '. 269 - 'that you entered it correctly.'); 270 - break; 271 - case PhortuneErrCode::ERR_CC_INVALID_CVC: 272 - $message = pht( 273 - 'The CVC code you entered is not a valid CVC code. Check that '. 274 - 'you entered it correctly. The CVC code is a 3-digit or 4-digit '. 275 - 'numeric code which usually appears on the back of the card.'); 276 - break; 277 - case PhortuneErrCode::ERR_CC_INVALID_EXPIRY: 278 - $message = pht( 279 - 'The card expiration date is not a valid expiration date. Check '. 280 - 'that you entered it correctly. You can not add an expired card '. 281 - 'as a payment method.'); 282 - break; 283 - default: 284 - $message = $provider->getCreatePaymentMethodErrorMessage( 285 - $client_error); 286 - if (!$message) { 287 - $message = pht( 288 - "There was an unexpected error ('%s') processing payment ". 289 - "information.", 290 - $client_error); 291 - 292 - phlog($message); 293 - } 294 - break; 295 - } 296 - 297 - $errors[$client_error] = $message; 298 - } 299 - 300 - return $errors; 301 - } 302 - 303 - }
+14 -5
src/applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php
··· 26 26 27 27 $account = $method->getAccount(); 28 28 $account_id = $account->getID(); 29 - $account_uri = $this->getApplicationURI("/account/billing/{$account_id}/"); 29 + $account_uri = $account->getPaymentMethodsURI(); 30 30 31 31 if ($request->isFormPost()) { 32 + $xactions = array(); 32 33 33 - // TODO: ApplicationTransactions!!!! 34 - $method 35 - ->setStatus(PhortunePaymentMethod::STATUS_DISABLED) 36 - ->save(); 34 + $xactions[] = $method->getApplicationTransactionTemplate() 35 + ->setTransactionType( 36 + PhortunePaymentMethodStatusTransaction::TRANSACTIONTYPE) 37 + ->setNewValue(PhortunePaymentMethod::STATUS_DISABLED); 38 + 39 + $editor = id(new PhortunePaymentMethodEditor()) 40 + ->setActor($viewer) 41 + ->setContentSourceFromRequest($request) 42 + ->setContinueOnNoEffect(true) 43 + ->setContinueOnMissingFields(true); 44 + 45 + $editor->applyTransactions($method, $xactions); 37 46 38 47 return id(new AphrontRedirectResponse())->setURI($account_uri); 39 48 }
+29 -17
src/applications/phortune/controller/payment/PhortunePaymentMethodEditController.php src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodEditController.php
··· 20 20 return new Aphront404Response(); 21 21 } 22 22 23 + $next_uri = $method->getURI(); 24 + 23 25 $account = $method->getAccount(); 24 - $account_uri = $this->getApplicationURI($account->getID().'/'); 26 + $v_name = $method->getName(); 25 27 26 28 if ($request->isFormPost()) { 29 + $v_name = $request->getStr('name'); 27 30 28 - $name = $request->getStr('name'); 31 + $xactions = array(); 32 + 33 + $xactions[] = $method->getApplicationTransactionTemplate() 34 + ->setTransactionType( 35 + PhortunePaymentMethodNameTransaction::TRANSACTIONTYPE) 36 + ->setNewValue($v_name); 29 37 30 - // TODO: Use ApplicationTransactions 38 + $editor = id(new PhortunePaymentMethodEditor()) 39 + ->setActor($viewer) 40 + ->setContentSourceFromRequest($request) 41 + ->setContinueOnNoEffect(true) 42 + ->setContinueOnMissingFields(true); 31 43 32 - $method->setName($name); 33 - $method->save(); 44 + $editor->applyTransactions($method, $xactions); 34 45 35 - return id(new AphrontRedirectResponse())->setURI($account_uri); 46 + return id(new AphrontRedirectResponse())->setURI($next_uri); 36 47 } 37 48 38 49 $provider = $method->buildPaymentProvider(); 39 50 40 51 $form = id(new AphrontFormView()) 41 - ->setUser($viewer) 52 + ->setViewer($viewer) 42 53 ->appendChild( 43 54 id(new AphrontFormTextControl()) 44 55 ->setLabel(pht('Name')) ··· 54 65 ->setValue($method->getDisplayExpires())) 55 66 ->appendChild( 56 67 id(new AphrontFormSubmitControl()) 57 - ->addCancelButton($account_uri) 68 + ->addCancelButton($next_uri) 58 69 ->setValue(pht('Save Changes'))); 59 70 60 71 $box = id(new PHUIObjectBoxView()) ··· 62 73 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 63 74 ->setForm($form); 64 75 65 - $crumbs = $this->buildApplicationCrumbs(); 66 - $crumbs->addTextCrumb($account->getName(), $account_uri); 67 - $crumbs->addTextCrumb($method->getDisplayName()); 68 - $crumbs->addTextCrumb(pht('Edit')); 69 - $crumbs->setBorder(true); 76 + $crumbs = $this->buildApplicationCrumbs() 77 + ->addTextCrumb($account->getName(), $account->getURI()) 78 + ->addTextCrumb(pht('Payment Methods'), $account->getPaymentMethodsURI()) 79 + ->addTextCrumb($method->getObjectName(), $method->getURI()) 80 + ->addTextCrumb(pht('Edit')) 81 + ->setBorder(true); 70 82 71 83 $header = id(new PHUIHeaderView()) 72 84 ->setHeader(pht('Edit Payment Method')) ··· 74 86 75 87 $view = id(new PHUITwoColumnView()) 76 88 ->setHeader($header) 77 - ->setFooter(array( 78 - $box, 79 - )); 89 + ->setFooter( 90 + array( 91 + $box, 92 + )); 80 93 81 94 return $this->newPage() 82 95 ->setTitle(pht('Edit Payment Method')) 83 96 ->setCrumbs($crumbs) 84 97 ->appendChild($view); 85 - 86 98 } 87 99 88 100 }
+462
src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodCreateController.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentMethodCreateController 4 + extends PhortuneController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + 9 + $account_id = $request->getURIData('accountID'); 10 + $account = id(new PhortuneAccountQuery()) 11 + ->setViewer($viewer) 12 + ->withIDs(array($account_id)) 13 + ->requireCapabilities( 14 + array( 15 + PhabricatorPolicyCapability::CAN_VIEW, 16 + PhabricatorPolicyCapability::CAN_EDIT, 17 + )) 18 + ->executeOne(); 19 + if (!$account) { 20 + return new Aphront404Response(); 21 + } 22 + 23 + $cart_id = $request->getInt('cartID'); 24 + $subscription_id = $request->getInt('subscriptionID'); 25 + $merchant_id = $request->getInt('merchantID'); 26 + 27 + if ($cart_id) { 28 + $cart = id(new PhortuneCartQuery()) 29 + ->setViewer($viewer) 30 + ->withAccountPHIDs(array($account->getPHID())) 31 + ->withIDs(array($cart_id)) 32 + ->executeOne(); 33 + if (!$cart) { 34 + return new Aphront404Response(); 35 + } 36 + 37 + $subscription_phid = $cart->getSubscriptionPHID(); 38 + if ($subscription_phid) { 39 + $subscription = id(new PhortuneSubscriptionQuery()) 40 + ->setViewer($viewer) 41 + ->withAccountPHIDs(array($account->getPHID())) 42 + ->withPHIDs(array($subscription_phid)) 43 + ->executeOne(); 44 + if (!$subscription) { 45 + return new Aphront404Response(); 46 + } 47 + } else { 48 + $subscription = null; 49 + } 50 + 51 + $merchant = $cart->getMerchant(); 52 + 53 + $cart_id = $cart->getID(); 54 + $subscription_id = null; 55 + $merchant_id = null; 56 + 57 + $next_uri = $cart->getCheckoutURI(); 58 + } else if ($subscription_id) { 59 + $subscription = id(new PhortuneSubscriptionQuery()) 60 + ->setViewer($viewer) 61 + ->withAccountPHIDs(array($account->getPHID())) 62 + ->withIDs(array($subscription_id)) 63 + ->executeOne(); 64 + if (!$subscription) { 65 + return new Aphront404Response(); 66 + } 67 + 68 + $cart = null; 69 + $merchant = $subscription->getMerchant(); 70 + 71 + $cart_id = null; 72 + $subscription_id = $subscription->getID(); 73 + $merchant_id = null; 74 + 75 + $next_uri = $subscription->getURI(); 76 + } else if ($merchant_id) { 77 + $merchant_phids = $account->getMerchantPHIDs(); 78 + if ($merchant_phids) { 79 + $merchant = id(new PhortuneMerchantQuery()) 80 + ->setViewer($viewer) 81 + ->withIDs(array($merchant_id)) 82 + ->withPHIDs($merchant_phids) 83 + ->executeOne(); 84 + } else { 85 + $merchant = null; 86 + } 87 + 88 + if (!$merchant) { 89 + return new Aphront404Response(); 90 + } 91 + 92 + $cart = null; 93 + $subscription = null; 94 + 95 + $cart_id = null; 96 + $subscription_id = null; 97 + $merchant_id = $merchant->getID(); 98 + 99 + $next_uri = $account->getPaymentMethodsURI(); 100 + } else { 101 + $next_uri = $account->getPaymentMethodsURI(); 102 + 103 + $merchant_phids = $account->getMerchantPHIDs(); 104 + if ($merchant_phids) { 105 + $merchants = id(new PhortuneMerchantQuery()) 106 + ->setViewer($viewer) 107 + ->withPHIDs($merchant_phids) 108 + ->needProfileImage(true) 109 + ->execute(); 110 + } else { 111 + $merchants = array(); 112 + } 113 + 114 + if (!$merchants) { 115 + return $this->newDialog() 116 + ->setTitle(pht('No Merchants')) 117 + ->appendParagraph( 118 + pht( 119 + 'You have not established a relationship with any merchants '. 120 + 'yet. Create an order or subscription before adding payment '. 121 + 'methods.')) 122 + ->addCancelButton($next_uri); 123 + } 124 + 125 + // If there's more than one merchant, ask the user to pick which one they 126 + // want to pay. If there's only one, just pick it for them. 127 + if (count($merchants) > 1) { 128 + $menu = $this->newMerchantMenu($merchants); 129 + 130 + $form = id(new AphrontFormView()) 131 + ->appendInstructions( 132 + pht( 133 + 'Choose the merchant you want to pay.')); 134 + 135 + return $this->newDialog() 136 + ->setTitle(pht('Choose a Merchant')) 137 + ->appendForm($form) 138 + ->appendChild($menu) 139 + ->addCancelButton($next_uri); 140 + } 141 + 142 + $cart = null; 143 + $subscription = null; 144 + $merchant = head($merchants); 145 + 146 + $cart_id = null; 147 + $subscription_id = null; 148 + $merchant_id = $merchant->getID(); 149 + } 150 + 151 + $providers = $this->loadCreatePaymentMethodProvidersForMerchant($merchant); 152 + if (!$providers) { 153 + throw new Exception( 154 + pht( 155 + 'There are no payment providers enabled that can add payment '. 156 + 'methods.')); 157 + } 158 + 159 + $state_params = array( 160 + 'cartID' => $cart_id, 161 + 'subscriptionID' => $subscription_id, 162 + 'merchantID' => $merchant_id, 163 + ); 164 + $state_params = array_filter($state_params); 165 + 166 + $state_uri = new PhutilURI($request->getRequestURI()); 167 + foreach ($state_params as $key => $value) { 168 + $state_uri->replaceQueryParam($key, $value); 169 + } 170 + 171 + $provider_id = $request->getInt('providerID'); 172 + if (isset($providers[$provider_id])) { 173 + $provider = $providers[$provider_id]; 174 + } else { 175 + // If there's more than one provider, ask the user to pick how they 176 + // want to pay. If there's only one, just pick it. 177 + if (count($providers) > 1) { 178 + $menu = $this->newProviderMenu($providers, $state_uri); 179 + 180 + return $this->newDialog() 181 + ->setTitle(pht('Choose a Payment Method')) 182 + ->appendChild($menu) 183 + ->addCancelButton($next_uri); 184 + } 185 + 186 + $provider = head($providers); 187 + } 188 + 189 + $provider_id = $provider->getProviderConfig()->getID(); 190 + 191 + $state_params['providerID'] = $provider_id; 192 + 193 + $errors = array(); 194 + $display_exception = null; 195 + if ($request->isFormPost() && $request->getBool('isProviderForm')) { 196 + $method = id(new PhortunePaymentMethod()) 197 + ->setAccountPHID($account->getPHID()) 198 + ->setAuthorPHID($viewer->getPHID()) 199 + ->setMerchantPHID($merchant->getPHID()) 200 + ->setProviderPHID($provider->getProviderConfig()->getPHID()) 201 + ->setStatus(PhortunePaymentMethod::STATUS_ACTIVE); 202 + 203 + // Limit the rate at which you can attempt to add payment methods. This 204 + // is intended as a line of defense against using Phortune to validate a 205 + // large list of stolen credit card numbers. 206 + 207 + PhabricatorSystemActionEngine::willTakeAction( 208 + array($viewer->getPHID()), 209 + new PhortuneAddPaymentMethodAction(), 210 + 1); 211 + 212 + if (!$errors) { 213 + $errors = $this->processClientErrors( 214 + $provider, 215 + $request->getStr('errors')); 216 + } 217 + 218 + if (!$errors) { 219 + $client_token_raw = $request->getStr('token'); 220 + $client_token = null; 221 + try { 222 + $client_token = phutil_json_decode($client_token_raw); 223 + } catch (PhutilJSONParserException $ex) { 224 + $errors[] = pht( 225 + 'There was an error decoding token information submitted by the '. 226 + 'client. Expected a JSON-encoded token dictionary, received: %s.', 227 + nonempty($client_token_raw, pht('nothing'))); 228 + } 229 + 230 + if (!$provider->validateCreatePaymentMethodToken($client_token)) { 231 + $errors[] = pht( 232 + 'There was an error with the payment token submitted by the '. 233 + 'client. Expected a valid dictionary, received: %s.', 234 + $client_token_raw); 235 + } 236 + 237 + if (!$errors) { 238 + try { 239 + $provider->createPaymentMethodFromRequest( 240 + $request, 241 + $method, 242 + $client_token); 243 + } catch (PhortuneDisplayException $exception) { 244 + $display_exception = $exception; 245 + } catch (Exception $ex) { 246 + $errors = array( 247 + pht('There was an error adding this payment method:'), 248 + $ex->getMessage(), 249 + ); 250 + } 251 + } 252 + } 253 + 254 + if (!$errors && !$display_exception) { 255 + $xactions = array(); 256 + 257 + $xactions[] = $method->getApplicationTransactionTemplate() 258 + ->setTransactionType(PhabricatorTransactions::TYPE_CREATE) 259 + ->setNewValue(true); 260 + 261 + $editor = id(new PhortunePaymentMethodEditor()) 262 + ->setActor($viewer) 263 + ->setContentSourceFromRequest($request) 264 + ->setContinueOnNoEffect(true) 265 + ->setContinueOnMissingFields(true); 266 + 267 + $editor->applyTransactions($method, $xactions); 268 + 269 + $next_uri = new PhutilURI($next_uri); 270 + 271 + // If we added this method on a cart flow, return to the cart to 272 + // checkout with this payment method selected. 273 + if ($cart_id) { 274 + $next_uri->replaceQueryParam('paymentMethodID', $method->getID()); 275 + } 276 + 277 + return id(new AphrontRedirectResponse())->setURI($next_uri); 278 + } else { 279 + if ($display_exception) { 280 + $dialog_body = $display_exception->getView(); 281 + } else { 282 + $dialog_body = id(new PHUIInfoView()) 283 + ->setErrors($errors); 284 + } 285 + 286 + return $this->newDialog() 287 + ->setTitle(pht('Error Adding Payment Method')) 288 + ->appendChild($dialog_body) 289 + ->addCancelButton($request->getRequestURI()); 290 + } 291 + } 292 + 293 + $form = $provider->renderCreatePaymentMethodForm($request, $errors); 294 + 295 + $form 296 + ->setViewer($viewer) 297 + ->setAction($request->getPath()) 298 + ->setWorkflow(true) 299 + ->addHiddenInput('isProviderForm', true) 300 + ->appendChild( 301 + id(new AphrontFormSubmitControl()) 302 + ->setValue(pht('Add Payment Method')) 303 + ->addCancelButton($next_uri)); 304 + 305 + foreach ($state_params as $key => $value) { 306 + $form->addHiddenInput($key, $value); 307 + } 308 + 309 + $box = id(new PHUIObjectBoxView()) 310 + ->setHeaderText(pht('Method')) 311 + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 312 + ->setForm($form); 313 + 314 + $crumbs = $this->buildApplicationCrumbs() 315 + ->addTextCrumb(pht('Add Payment Method')) 316 + ->setBorder(true); 317 + 318 + $header = id(new PHUIHeaderView()) 319 + ->setHeader(pht('Add Payment Method')) 320 + ->setHeaderIcon('fa-plus-square'); 321 + 322 + $view = id(new PHUITwoColumnView()) 323 + ->setHeader($header) 324 + ->setFooter( 325 + array( 326 + $box, 327 + )); 328 + 329 + return $this->newPage() 330 + ->setTitle($provider->getPaymentMethodDescription()) 331 + ->setCrumbs($crumbs) 332 + ->appendChild($view); 333 + 334 + } 335 + 336 + private function processClientErrors( 337 + PhortunePaymentProvider $provider, 338 + $client_errors_raw) { 339 + 340 + $errors = array(); 341 + 342 + $client_errors = null; 343 + try { 344 + $client_errors = phutil_json_decode($client_errors_raw); 345 + } catch (PhutilJSONParserException $ex) { 346 + $errors[] = pht( 347 + 'There was an error decoding error information submitted by the '. 348 + 'client. Expected a JSON-encoded list of error codes, received: %s.', 349 + nonempty($client_errors_raw, pht('nothing'))); 350 + } 351 + 352 + foreach (array_unique($client_errors) as $key => $client_error) { 353 + $client_errors[$key] = $provider->translateCreatePaymentMethodErrorCode( 354 + $client_error); 355 + } 356 + 357 + foreach (array_unique($client_errors) as $client_error) { 358 + switch ($client_error) { 359 + case PhortuneErrCode::ERR_CC_INVALID_NUMBER: 360 + $message = pht( 361 + 'The card number you entered is not a valid card number. Check '. 362 + 'that you entered it correctly.'); 363 + break; 364 + case PhortuneErrCode::ERR_CC_INVALID_CVC: 365 + $message = pht( 366 + 'The CVC code you entered is not a valid CVC code. Check that '. 367 + 'you entered it correctly. The CVC code is a 3-digit or 4-digit '. 368 + 'numeric code which usually appears on the back of the card.'); 369 + break; 370 + case PhortuneErrCode::ERR_CC_INVALID_EXPIRY: 371 + $message = pht( 372 + 'The card expiration date is not a valid expiration date. Check '. 373 + 'that you entered it correctly. You can not add an expired card '. 374 + 'as a payment method.'); 375 + break; 376 + default: 377 + $message = $provider->getCreatePaymentMethodErrorMessage( 378 + $client_error); 379 + if (!$message) { 380 + $message = pht( 381 + "There was an unexpected error ('%s') processing payment ". 382 + "information.", 383 + $client_error); 384 + 385 + phlog($message); 386 + } 387 + break; 388 + } 389 + 390 + $errors[$client_error] = $message; 391 + } 392 + 393 + return $errors; 394 + } 395 + 396 + private function newMerchantMenu(array $merchants) { 397 + assert_instances_of($merchants, 'PhortuneMerchant'); 398 + 399 + $request = $this->getRequest(); 400 + $viewer = $this->getViewer(); 401 + 402 + $menu = id(new PHUIObjectItemListView()) 403 + ->setUser($viewer) 404 + ->setBig(true) 405 + ->setFlush(true); 406 + 407 + foreach ($merchants as $merchant) { 408 + $merchant_uri = id(new PhutilURI($request->getRequestURI())) 409 + ->replaceQueryParam('merchantID', $merchant->getID()); 410 + 411 + $item = id(new PHUIObjectItemView()) 412 + ->setObjectName($merchant->getObjectName()) 413 + ->setHeader($merchant->getName()) 414 + ->setHref($merchant_uri) 415 + ->setClickable(true) 416 + ->setImageURI($merchant->getProfileImageURI()); 417 + 418 + $menu->addItem($item); 419 + } 420 + 421 + return $menu; 422 + } 423 + 424 + private function newProviderMenu(array $providers, PhutilURI $state_uri) { 425 + assert_instances_of($providers, 'PhortunePaymentProvider'); 426 + 427 + $request = $this->getRequest(); 428 + $viewer = $this->getViewer(); 429 + 430 + $menu = id(new PHUIObjectItemListView()) 431 + ->setUser($viewer) 432 + ->setBig(true) 433 + ->setFlush(true); 434 + 435 + foreach ($providers as $provider) { 436 + $provider_id = $provider->getProviderConfig()->getID(); 437 + 438 + $provider_uri = id(clone $state_uri) 439 + ->replaceQueryParam('providerID', $provider_id); 440 + 441 + $description = $provider->getPaymentMethodDescription(); 442 + $icon_uri = $provider->getPaymentMethodIcon(); 443 + $details = $provider->getPaymentMethodProviderDescription(); 444 + 445 + $icon = id(new PHUIIconView()) 446 + ->setSpriteSheet(PHUIIconView::SPRITE_LOGIN) 447 + ->setSpriteIcon($icon_uri); 448 + 449 + $item = id(new PHUIObjectItemView()) 450 + ->setHeader($description) 451 + ->setHref($provider_uri) 452 + ->setClickable(true) 453 + ->addAttribute($details) 454 + ->setImageIcon($icon); 455 + 456 + $menu->addItem($item); 457 + } 458 + 459 + return $menu; 460 + } 461 + 462 + }
+18
src/applications/phortune/editor/PhortunePaymentMethodEditor.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentMethodEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorPhortuneApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Phortune Payment Methods'); 12 + } 13 + 14 + public function getCreateObjectTitle($author, $object) { 15 + return pht('%s created this payment method.', $author); 16 + } 17 + 18 + }
+10
src/applications/phortune/query/PhortunePaymentMethodTransactionQuery.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentMethodTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new PhortunePaymentMethodTransaction(); 8 + } 9 + 10 + }
+29 -17
src/applications/phortune/query/PhortuneSubscriptionQuery.php
··· 8 8 private $accountPHIDs; 9 9 private $merchantPHIDs; 10 10 private $statuses; 11 + private $paymentMethodPHIDs; 11 12 12 13 private $needTriggers; 13 14 ··· 36 37 return $this; 37 38 } 38 39 40 + public function withPaymentMethodPHIDs(array $method_phids) { 41 + $this->paymentMethodPHIDs = $method_phids; 42 + return $this; 43 + } 44 + 39 45 public function needTriggers($need_triggers) { 40 46 $this->needTriggers = $need_triggers; 41 47 return $this; 42 48 } 43 49 44 - protected function loadPage() { 45 - $table = new PhortuneSubscription(); 46 - $conn = $table->establishConnection('r'); 47 - 48 - $rows = queryfx_all( 49 - $conn, 50 - 'SELECT subscription.* FROM %T subscription %Q %Q %Q', 51 - $table->getTableName(), 52 - $this->buildWhereClause($conn), 53 - $this->buildOrderClause($conn), 54 - $this->buildLimitClause($conn)); 50 + public function newResultObject() { 51 + return new PhortuneSubscription(); 52 + } 55 53 56 - return $table->loadAllFromArray($rows); 54 + protected function loadPage() { 55 + return $this->loadStandardPage($this->newResultObject()); 57 56 } 58 57 59 58 protected function willFilterPage(array $subscriptions) { ··· 67 66 $account = idx($accounts, $subscription->getAccountPHID()); 68 67 if (!$account) { 69 68 unset($subscriptions[$key]); 69 + $this->didRejectResult($subscription); 70 70 continue; 71 71 } 72 72 $subscription->attachAccount($account); ··· 86 86 $merchant = idx($merchants, $subscription->getMerchantPHID()); 87 87 if (!$merchant) { 88 88 unset($subscriptions[$key]); 89 + $this->didRejectResult($subscription); 89 90 continue; 90 91 } 91 92 $subscription->attachMerchant($merchant); ··· 112 113 $implementation = idx($implementations, $ref); 113 114 if (!$implementation) { 114 115 unset($subscriptions[$key]); 116 + $this->didRejectResult($subscription); 115 117 continue; 116 118 } 117 119 $subscription->attachImplementation($implementation); ··· 133 135 $trigger = idx($triggers, $subscription->getTriggerPHID()); 134 136 if (!$trigger) { 135 137 unset($subscriptions[$key]); 138 + $this->didRejectResult($subscription); 136 139 continue; 137 140 } 138 141 $subscription->attachTrigger($trigger); ··· 142 145 return $subscriptions; 143 146 } 144 147 145 - protected function buildWhereClause(AphrontDatabaseConnection $conn) { 146 - $where = array(); 147 - 148 - $where[] = $this->buildPagingClause($conn); 148 + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { 149 + $where = parent::buildWhereClauseParts($conn); 149 150 150 151 if ($this->ids !== null) { 151 152 $where[] = qsprintf( ··· 182 183 $this->statuses); 183 184 } 184 185 185 - return $this->formatWhereClause($conn, $where); 186 + if ($this->paymentMethodPHIDs !== null) { 187 + $where[] = qsprintf( 188 + $conn, 189 + 'subscription.defaultPaymentMethodPHID IN (%Ls)', 190 + $this->paymentMethodPHIDs); 191 + } 192 + 193 + return $where; 194 + } 195 + 196 + protected function getPrimaryTableAlias() { 197 + return 'subscription'; 186 198 } 187 199 188 200 public function getQueryApplicationClass() {
-13
src/applications/phortune/query/PhortuneSubscriptionSearchEngine.php
··· 125 125 return parent::buildSavedQueryFromBuiltin($query_key); 126 126 } 127 127 128 - protected function getRequiredHandlePHIDsForResultList( 129 - array $subscriptions, 130 - PhabricatorSavedQuery $query) { 131 - $phids = array(); 132 - foreach ($subscriptions as $subscription) { 133 - $phids[] = $subscription->getPHID(); 134 - $phids[] = $subscription->getMerchantPHID(); 135 - $phids[] = $subscription->getAuthorPHID(); 136 - } 137 - return $phids; 138 - } 139 - 140 128 protected function renderResultList( 141 129 array $subscriptions, 142 130 PhabricatorSavedQuery $query, ··· 147 135 148 136 $table = id(new PhortuneSubscriptionTableView()) 149 137 ->setUser($viewer) 150 - ->setHandles($handles) 151 138 ->setSubscriptions($subscriptions); 152 139 153 140 $merchant = $this->getMerchant();
+6
src/applications/phortune/storage/PhortuneAccount.php
··· 117 117 $this->getID()); 118 118 } 119 119 120 + public function getPaymentMethodsURI() { 121 + return urisprintf( 122 + '/phortune/account/%d/methods/', 123 + $this->getID()); 124 + } 125 + 120 126 public function attachMerchantPHIDs(array $merchant_phids) { 121 127 $this->merchantPHIDs = $merchant_phids; 122 128 return $this;
+4
src/applications/phortune/storage/PhortuneMerchant.php
··· 70 70 return $this->assertAttached($this->profileImageFile); 71 71 } 72 72 73 + public function getObjectName() { 74 + return pht('Merchant %d', $this->getID()); 75 + } 76 + 73 77 74 78 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 75 79
+25 -1
src/applications/phortune/storage/PhortunePaymentMethod.php
··· 9 9 implements 10 10 PhabricatorPolicyInterface, 11 11 PhabricatorExtendedPolicyInterface, 12 - PhabricatorPolicyCodexInterface { 12 + PhabricatorPolicyCodexInterface, 13 + PhabricatorApplicationTransactionInterface { 13 14 14 15 const STATUS_ACTIVE = 'payment:active'; 15 16 const STATUS_DISABLED = 'payment:disabled'; ··· 138 139 139 140 public function isActive() { 140 141 return ($this->getStatus() === self::STATUS_ACTIVE); 142 + } 143 + 144 + public function getURI() { 145 + return urisprintf( 146 + '/phortune/account/%d/methods/%d/', 147 + $this->getAccount()->getID(), 148 + $this->getID()); 149 + } 150 + 151 + public function getObjectName() { 152 + return pht('Payment Method %d', $this->getID()); 153 + } 154 + 155 + 156 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 157 + 158 + 159 + public function getApplicationTransactionEditor() { 160 + return new PhortunePaymentMethodEditor(); 161 + } 162 + 163 + public function getApplicationTransactionTemplate() { 164 + return new PhortunePaymentMethodTransaction(); 141 165 } 142 166 143 167
+18
src/applications/phortune/storage/PhortunePaymentMethodTransaction.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentMethodTransaction 4 + extends PhabricatorModularTransaction { 5 + 6 + public function getApplicationName() { 7 + return 'phortune'; 8 + } 9 + 10 + public function getApplicationTransactionType() { 11 + return PhortunePaymentMethodPHIDType::TYPECONST; 12 + } 13 + 14 + public function getBaseTransactionClass() { 15 + return 'PhortunePaymentMethodTransactionType'; 16 + } 17 + 18 + }
+8 -12
src/applications/phortune/view/PhortuneSubscriptionTableView.php
··· 3 3 final class PhortuneSubscriptionTableView extends AphrontView { 4 4 5 5 private $subscriptions; 6 - private $handles; 7 6 private $isMerchantView; 8 7 private $notice; 9 - 10 - public function setHandles(array $handles) { 11 - $this->handles = $handles; 12 - return $this; 13 - } 14 - 15 - public function getHandles() { 16 - return $this->handles; 17 - } 18 8 19 9 public function setSubscriptions(array $subscriptions) { 20 10 $this->subscriptions = $subscriptions; ··· 40 30 } 41 31 42 32 public function render() { 33 + return $this->newTableView(); 34 + } 35 + 36 + public function newTableView() { 43 37 $subscriptions = $this->getSubscriptions(); 44 - $handles = $this->getHandles(); 45 - $viewer = $this->getUser(); 38 + $viewer = $this->getViewer(); 39 + 40 + $phids = mpull($subscriptions, 'getPHID'); 41 + $handles = $viewer->loadHandles($phids); 46 42 47 43 $rows = array(); 48 44 $rowc = array();
+39
src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodNameTransaction.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentMethodNameTransaction 4 + extends PhortunePaymentMethodTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'name'; 7 + 8 + public function generateOldValue($object) { 9 + return $object->getName(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setName($value); 14 + } 15 + 16 + public function getTitle() { 17 + $old_value = $this->getOldValue(); 18 + $new_value = $this->getNewValue(); 19 + 20 + if (strlen($old_value) && strlen($new_value)) { 21 + return pht( 22 + '%s renamed this payment method from %s to %s.', 23 + $this->renderAuthor(), 24 + $this->renderOldValue(), 25 + $this->renderNewValue()); 26 + } else if (strlen($new_value)) { 27 + return pht( 28 + '%s set the name of this payment method to %s.', 29 + $this->renderAuthor(), 30 + $this->renderNewValue()); 31 + } else { 32 + return pht( 33 + '%s removed the name of this payment method (was: %s).', 34 + $this->renderAuthor(), 35 + $this->renderOldValue()); 36 + } 37 + } 38 + 39 + }
+22
src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodStatusTransaction.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentMethodStatusTransaction 4 + extends PhortunePaymentMethodTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'status'; 7 + 8 + public function generateOldValue($object) { 9 + return $object->getStatus(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setStatus($value); 14 + } 15 + 16 + public function getTitle() { 17 + return pht( 18 + '%s changed the status of this payment method.', 19 + $this->renderAuthor()); 20 + } 21 + 22 + }
+4
src/applications/phortune/xaction/paymentmethod/PhortunePaymentMethodTransactionType.php
··· 1 + <?php 2 + 3 + abstract class PhortunePaymentMethodTransactionType 4 + extends PhabricatorModularTransactionType {}
-9
webroot/rsrc/css/application/phortune/phortune.css
··· 7 7 height: 34px; 8 8 } 9 9 10 - .phortune-payment-method-list { 11 - margin: 8px 24px 0; 12 - } 13 - 14 - .phortune-payment-method-list button { 15 - margin: 4px 0; 16 - width: 100%; 17 - } 18 - 19 10 .phortune-payment-onetime-list { 20 11 width: 280px; 21 12 }