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

Update Phortune subscriptions for modern infrastructure

Summary:
Depends on D20720. Ref T13366.

- Use modern policies and policy interfaces.
- Use new merchant authority cache.
- Add (some) transactions.
- Move MFA from pre-upgrade-gate to post-one-shot-check.
- Simplify the autopay workflow.
- Use the "reloading arrows" icon for subscriptions more consistently.

Test Plan: As a merchant-authority and account-authority, viewed, edited, and changed autopay for subscriptions.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13366

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

+746 -277
+19
resources/sql/autopatches/20190816.subscription.01.xaction.sql
··· 1 + CREATE TABLE {$NAMESPACE}_phortune.phortune_subscriptiontransaction ( 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};
+21 -4
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 - 'PhortuneAccountPaymentMethodListController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php', 5252 + 'PhortuneAccountPaymentMethodController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php', 5253 5253 'PhortuneAccountPaymentMethodViewController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodViewController.php', 5254 5254 'PhortuneAccountProfileController' => 'applications/phortune/controller/account/PhortuneAccountProfileController.php', 5255 5255 'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php', 5256 + 'PhortuneAccountSubscriptionAutopayController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionAutopayController.php', 5256 5257 'PhortuneAccountSubscriptionController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionController.php', 5258 + 'PhortuneAccountSubscriptionViewController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php', 5257 5259 'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php', 5258 5260 'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php', 5259 5261 'PhortuneAccountTransactionType' => 'applications/phortune/xaction/PhortuneAccountTransactionType.php', ··· 5361 5363 'PhortuneSchemaSpec' => 'applications/phortune/storage/PhortuneSchemaSpec.php', 5362 5364 'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php', 5363 5365 'PhortuneSubscription' => 'applications/phortune/storage/PhortuneSubscription.php', 5366 + 'PhortuneSubscriptionAutopayTransaction' => 'applications/phortune/xaction/subscription/PhortuneSubscriptionAutopayTransaction.php', 5364 5367 'PhortuneSubscriptionCart' => 'applications/phortune/cart/PhortuneSubscriptionCart.php', 5365 5368 'PhortuneSubscriptionEditController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php', 5369 + 'PhortuneSubscriptionEditor' => 'applications/phortune/editor/PhortuneSubscriptionEditor.php', 5366 5370 'PhortuneSubscriptionImplementation' => 'applications/phortune/subscription/PhortuneSubscriptionImplementation.php', 5367 5371 'PhortuneSubscriptionListController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionListController.php', 5368 5372 'PhortuneSubscriptionPHIDType' => 'applications/phortune/phid/PhortuneSubscriptionPHIDType.php', 5373 + 'PhortuneSubscriptionPolicyCodex' => 'applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php', 5369 5374 'PhortuneSubscriptionProduct' => 'applications/phortune/product/PhortuneSubscriptionProduct.php', 5370 5375 'PhortuneSubscriptionQuery' => 'applications/phortune/query/PhortuneSubscriptionQuery.php', 5371 5376 'PhortuneSubscriptionSearchEngine' => 'applications/phortune/query/PhortuneSubscriptionSearchEngine.php', 5372 5377 'PhortuneSubscriptionTableView' => 'applications/phortune/view/PhortuneSubscriptionTableView.php', 5373 - 'PhortuneSubscriptionViewController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php', 5378 + 'PhortuneSubscriptionTransaction' => 'applications/phortune/storage/PhortuneSubscriptionTransaction.php', 5379 + 'PhortuneSubscriptionTransactionQuery' => 'applications/phortune/query/PhortuneSubscriptionTransactionQuery.php', 5380 + 'PhortuneSubscriptionTransactionType' => 'applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php', 5374 5381 'PhortuneSubscriptionWorker' => 'applications/phortune/worker/PhortuneSubscriptionWorker.php', 5375 5382 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', 5376 5383 'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php', ··· 11812 11819 'PhortuneAccountOrdersController' => 'PhortuneAccountProfileController', 11813 11820 'PhortuneAccountOverviewController' => 'PhortuneAccountProfileController', 11814 11821 'PhortuneAccountPHIDType' => 'PhabricatorPHIDType', 11815 - 'PhortuneAccountPaymentMethodListController' => 'PhortuneAccountProfileController', 11822 + 'PhortuneAccountPaymentMethodController' => 'PhortuneAccountProfileController', 11816 11823 'PhortuneAccountPaymentMethodViewController' => 'PhortuneAccountController', 11817 11824 'PhortuneAccountProfileController' => 'PhortuneAccountController', 11818 11825 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 11826 + 'PhortuneAccountSubscriptionAutopayController' => 'PhortuneAccountController', 11819 11827 'PhortuneAccountSubscriptionController' => 'PhortuneAccountProfileController', 11828 + 'PhortuneAccountSubscriptionViewController' => 'PhortuneAccountController', 11820 11829 'PhortuneAccountTransaction' => 'PhabricatorModularTransaction', 11821 11830 'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 11822 11831 'PhortuneAccountTransactionType' => 'PhabricatorModularTransactionType', ··· 11953 11962 'PhortuneSubscription' => array( 11954 11963 'PhortuneDAO', 11955 11964 'PhabricatorPolicyInterface', 11965 + 'PhabricatorExtendedPolicyInterface', 11966 + 'PhabricatorPolicyCodexInterface', 11967 + 'PhabricatorApplicationTransactionInterface', 11956 11968 ), 11969 + 'PhortuneSubscriptionAutopayTransaction' => 'PhortuneSubscriptionTransactionType', 11957 11970 'PhortuneSubscriptionCart' => 'PhortuneCartImplementation', 11958 11971 'PhortuneSubscriptionEditController' => 'PhortuneController', 11972 + 'PhortuneSubscriptionEditor' => 'PhabricatorApplicationTransactionEditor', 11959 11973 'PhortuneSubscriptionImplementation' => 'Phobject', 11960 11974 'PhortuneSubscriptionListController' => 'PhortuneController', 11961 11975 'PhortuneSubscriptionPHIDType' => 'PhabricatorPHIDType', 11976 + 'PhortuneSubscriptionPolicyCodex' => 'PhabricatorPolicyCodex', 11962 11977 'PhortuneSubscriptionProduct' => 'PhortuneProductImplementation', 11963 11978 'PhortuneSubscriptionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 11964 11979 'PhortuneSubscriptionSearchEngine' => 'PhabricatorApplicationSearchEngine', 11965 11980 'PhortuneSubscriptionTableView' => 'AphrontView', 11966 - 'PhortuneSubscriptionViewController' => 'PhortuneController', 11981 + 'PhortuneSubscriptionTransaction' => 'PhabricatorModularTransaction', 11982 + 'PhortuneSubscriptionTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 11983 + 'PhortuneSubscriptionTransactionType' => 'PhabricatorModularTransactionType', 11967 11984 'PhortuneSubscriptionWorker' => 'PhabricatorWorker', 11968 11985 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', 11969 11986 'PhragmentBrowseController' => 'PhragmentController',
+10 -6
src/applications/phortune/application/PhabricatorPhortuneApplication.php
··· 43 43 '(?:query/(?P<queryKey>[^/]+)/)?' 44 44 => 'PhortuneSubscriptionListController', 45 45 'view/(?P<id>\d+)/' 46 - => 'PhortuneSubscriptionViewController', 47 - 'edit/(?P<id>\d+)/' 48 - => 'PhortuneSubscriptionEditController', 46 + => 'PhortuneAccountSubscriptionViewController', 49 47 'order/(?P<subscriptionID>\d+)/' 50 48 => 'PhortuneCartListController', 51 49 ), ··· 73 71 '(?P<accountID>\d+)/' => array( 74 72 'details/' => 'PhortuneAccountDetailsController', 75 73 'methods/' => array( 76 - '' => 'PhortuneAccountPaymentMethodListController', 74 + '' => 'PhortuneAccountPaymentMethodController', 77 75 '(?P<id>\d+)/' => 'PhortuneAccountPaymentMethodViewController', 78 76 ), 79 77 'orders/' => 'PhortuneAccountOrdersController', 80 78 'charges/' => 'PhortuneAccountChargesController', 81 - 'subscriptions/' => 'PhortuneAccountSubscriptionController', 79 + 'subscriptions/' => array( 80 + '' => 'PhortuneAccountSubscriptionController', 81 + '(?P<subscriptionID>\d+)/' => array( 82 + 'autopay/(?P<methodID>\d+)/' 83 + => 'PhortuneAccountSubscriptionAutopayController', 84 + ), 85 + ), 82 86 'managers/' => array( 83 87 '' => 'PhortuneAccountManagersController', 84 88 'add/' => 'PhortuneAccountAddManagerController', ··· 124 128 '(?:query/(?P<queryKey>[^/]+)/)?' 125 129 => 'PhortuneSubscriptionListController', 126 130 'view/(?P<id>\d+)/' 127 - => 'PhortuneSubscriptionViewController', 131 + => 'PhortuneAccountSubscriptionViewController', 128 132 'order/(?P<subscriptionID>\d+)/' 129 133 => 'PhortuneCartListController', 130 134 ),
+1
src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php
··· 12 12 ->setCapabilities( 13 13 array( 14 14 PhabricatorPolicyCapability::CAN_VIEW, 15 + PhabricatorPolicyCapability::CAN_EDIT, 15 16 )) 16 17 ->setIsActive(true) 17 18 ->setDescription(
+36
src/applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php
··· 1 + <?php 2 + 3 + final class PhortuneSubscriptionPolicyCodex 4 + extends PhabricatorPolicyCodex { 5 + 6 + public function getPolicySpecialRuleDescriptions() { 7 + $object = $this->getObject(); 8 + 9 + $rules = array(); 10 + 11 + $rules[] = $this->newRule() 12 + ->setCapabilities( 13 + array( 14 + PhabricatorPolicyCapability::CAN_VIEW, 15 + PhabricatorPolicyCapability::CAN_EDIT, 16 + )) 17 + ->setIsActive(true) 18 + ->setDescription( 19 + pht( 20 + 'Account members may view and edit subscriptions.')); 21 + 22 + $rules[] = $this->newRule() 23 + ->setCapabilities( 24 + array( 25 + PhabricatorPolicyCapability::CAN_VIEW, 26 + )) 27 + ->setIsActive(true) 28 + ->setDescription( 29 + pht( 30 + 'Merchants you have a relationship with may view associated '. 31 + 'subscriptions.')); 32 + 33 + return $rules; 34 + } 35 + 36 + }
+1 -1
src/applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php src/applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php
··· 1 1 <?php 2 2 3 - final class PhortuneAccountPaymentMethodListController 3 + final class PhortuneAccountPaymentMethodController 4 4 extends PhortuneAccountProfileController { 5 5 6 6 protected function shouldRequireAccountEditCapability() {
+137
src/applications/phortune/controller/account/PhortuneAccountSubscriptionAutopayController.php
··· 1 + <?php 2 + 3 + final class PhortuneAccountSubscriptionAutopayController 4 + extends PhortuneAccountController { 5 + 6 + protected function shouldRequireAccountEditCapability() { 7 + return true; 8 + } 9 + 10 + protected function handleAccountRequest(AphrontRequest $request) { 11 + $viewer = $this->getViewer(); 12 + $account = $this->getAccount(); 13 + 14 + $subscription = id(new PhortuneSubscriptionQuery()) 15 + ->setViewer($viewer) 16 + ->withIDs(array($request->getURIData('subscriptionID'))) 17 + ->withAccountPHIDs(array($account->getPHID())) 18 + ->requireCapabilities( 19 + array( 20 + PhabricatorPolicyCapability::CAN_VIEW, 21 + PhabricatorPolicyCapability::CAN_EDIT, 22 + )) 23 + ->executeOne(); 24 + if (!$subscription) { 25 + return new Aphront404Response(); 26 + } 27 + 28 + $method = id(new PhortunePaymentMethodQuery()) 29 + ->setViewer($viewer) 30 + ->withIDs(array($request->getURIData('methodID'))) 31 + ->withAccountPHIDs(array($subscription->getAccountPHID())) 32 + ->withMerchantPHIDs(array($subscription->getMerchantPHID())) 33 + ->withStatuses( 34 + array( 35 + PhortunePaymentMethod::STATUS_ACTIVE, 36 + )) 37 + ->executeOne(); 38 + if (!$method) { 39 + return new Aphront404Response(); 40 + } 41 + 42 + $next_uri = $subscription->getURI(); 43 + 44 + $autopay_phid = $subscription->getDefaultPaymentMethodPHID(); 45 + $is_stop = ($autopay_phid === $method->getPHID()); 46 + 47 + if ($request->isFormOrHisecPost()) { 48 + if ($is_stop) { 49 + $new_phid = null; 50 + } else { 51 + $new_phid = $method->getPHID(); 52 + } 53 + 54 + $xactions = array(); 55 + 56 + $xactions[] = $subscription->getApplicationTransactionTemplate() 57 + ->setTransactionType( 58 + PhortuneSubscriptionAutopayTransaction::TRANSACTIONTYPE) 59 + ->setNewValue($new_phid); 60 + 61 + $editor = $subscription->getApplicationTransactionEditor() 62 + ->setActor($viewer) 63 + ->setContentSourceFromRequest($request) 64 + ->setContinueOnNoEffect(true) 65 + ->setContinueOnMissingFields(true) 66 + ->setCancelURI($next_uri); 67 + 68 + $editor->applyTransactions($subscription, $xactions); 69 + 70 + return id(new AphrontRedirectResponse())->setURI($next_uri); 71 + } 72 + 73 + $method_phid = $method->getPHID(); 74 + $subscription_phid = $subscription->getPHID(); 75 + 76 + $handles = $viewer->loadHandles( 77 + array( 78 + $method_phid, 79 + $subscription_phid, 80 + )); 81 + 82 + $method_handle = $handles[$method_phid]; 83 + $subscription_handle = $handles[$subscription_phid]; 84 + 85 + $method_display = $method_handle->renderLink(); 86 + $method_display = phutil_tag( 87 + 'strong', 88 + array(), 89 + $method_display); 90 + 91 + $subscription_display = $subscription_handle->renderLink(); 92 + $subscription_display = phutil_tag( 93 + 'strong', 94 + array(), 95 + $subscription_display); 96 + 97 + $body = array(); 98 + if ($is_stop) { 99 + $title = pht('Stop Autopay'); 100 + 101 + $body[] = pht( 102 + 'Remove %s as the automatic payment method for subscription %s?', 103 + $method_display, 104 + $subscription_display); 105 + 106 + $body[] = pht( 107 + 'This payment method will no longer be charged automatically.'); 108 + 109 + $submit = pht('Stop Autopay'); 110 + } else { 111 + $title = pht('Start Autopay'); 112 + 113 + $body[] = pht( 114 + 'Set %s as the automatic payment method for subscription %s?', 115 + $method_display, 116 + $subscription_display); 117 + 118 + $body[] = pht( 119 + 'This payment method will be used to automatically pay future '. 120 + 'charges.'); 121 + 122 + $submit = pht('Start Autopay'); 123 + } 124 + 125 + $dialog = $this->newDialog() 126 + ->setTitle($title) 127 + ->addCancelButton($next_uri) 128 + ->addSubmitButton($submit); 129 + 130 + foreach ($body as $graph) { 131 + $dialog->appendParagraph($graph); 132 + } 133 + 134 + return $dialog; 135 + } 136 + 137 + }
+338
src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php
··· 1 + <?php 2 + 3 + final class PhortuneAccountSubscriptionViewController 4 + extends PhortuneAccountController { 5 + 6 + protected function shouldRequireAccountEditCapability() { 7 + return false; 8 + } 9 + 10 + protected function handleAccountRequest(AphrontRequest $request) { 11 + $viewer = $this->getViewer(); 12 + 13 + $subscription = id(new PhortuneSubscriptionQuery()) 14 + ->setViewer($viewer) 15 + ->withIDs(array($request->getURIData('id'))) 16 + ->needTriggers(true) 17 + ->executeOne(); 18 + if (!$subscription) { 19 + return new Aphront404Response(); 20 + } 21 + 22 + $can_edit = PhabricatorPolicyFilter::hasCapability( 23 + $viewer, 24 + $subscription, 25 + PhabricatorPolicyCapability::CAN_EDIT); 26 + 27 + $merchant = $subscription->getMerchant(); 28 + $account = $subscription->getAccount(); 29 + 30 + $account_id = $account->getID(); 31 + $subscription_id = $subscription->getID(); 32 + 33 + $title = $subscription->getSubscriptionFullName(); 34 + 35 + $header = id(new PHUIHeaderView()) 36 + ->setHeader($title) 37 + ->setHeaderIcon('fa-retweet'); 38 + 39 + $edit_uri = $subscription->getEditURI(); 40 + 41 + $crumbs = $this->buildApplicationCrumbs() 42 + ->addTextCrumb($subscription->getSubscriptionCrumbName()) 43 + ->setBorder(true); 44 + 45 + $properties = id(new PHUIPropertyListView()) 46 + ->setUser($viewer); 47 + 48 + $next_invoice = $subscription->getTrigger()->getNextEventPrediction(); 49 + $properties->addProperty( 50 + pht('Next Invoice'), 51 + phabricator_datetime($next_invoice, $viewer)); 52 + 53 + $autopay = $this->newAutopayView($subscription); 54 + 55 + $details = id(new PHUIObjectBoxView()) 56 + ->setHeaderText(pht('Subscription Details')) 57 + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 58 + ->addPropertyList($properties); 59 + 60 + $due_box = $this->buildDueInvoices($subscription); 61 + $invoice_box = $this->buildPastInvoices($subscription); 62 + 63 + $timeline = $this->buildTransactionTimeline( 64 + $subscription, 65 + new PhortuneSubscriptionTransactionQuery()); 66 + $timeline->setShouldTerminate(true); 67 + 68 + $view = id(new PHUITwoColumnView()) 69 + ->setHeader($header) 70 + ->setFooter( 71 + array( 72 + $details, 73 + $autopay, 74 + $due_box, 75 + $invoice_box, 76 + $timeline, 77 + )); 78 + 79 + return $this->newPage() 80 + ->setTitle($title) 81 + ->setCrumbs($crumbs) 82 + ->appendChild($view); 83 + } 84 + 85 + private function buildDueInvoices(PhortuneSubscription $subscription) { 86 + $viewer = $this->getViewer(); 87 + 88 + $invoices = id(new PhortuneCartQuery()) 89 + ->setViewer($viewer) 90 + ->withSubscriptionPHIDs(array($subscription->getPHID())) 91 + ->needPurchases(true) 92 + ->withInvoices(true) 93 + ->execute(); 94 + 95 + $phids = array(); 96 + foreach ($invoices as $invoice) { 97 + $phids[] = $invoice->getPHID(); 98 + $phids[] = $invoice->getMerchantPHID(); 99 + foreach ($invoice->getPurchases() as $purchase) { 100 + $phids[] = $purchase->getPHID(); 101 + } 102 + } 103 + $handles = $this->loadViewerHandles($phids); 104 + 105 + $invoice_table = id(new PhortuneOrderTableView()) 106 + ->setUser($viewer) 107 + ->setCarts($invoices) 108 + ->setIsInvoices(true) 109 + ->setHandles($handles); 110 + 111 + $invoice_header = id(new PHUIHeaderView()) 112 + ->setHeader(pht('Invoices Due')); 113 + 114 + return id(new PHUIObjectBoxView()) 115 + ->setHeader($invoice_header) 116 + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 117 + ->appendChild($invoice_table); 118 + } 119 + 120 + private function buildPastInvoices(PhortuneSubscription $subscription) { 121 + $viewer = $this->getViewer(); 122 + 123 + $invoices = id(new PhortuneCartQuery()) 124 + ->setViewer($viewer) 125 + ->withSubscriptionPHIDs(array($subscription->getPHID())) 126 + ->needPurchases(true) 127 + ->withStatuses( 128 + array( 129 + PhortuneCart::STATUS_PURCHASING, 130 + PhortuneCart::STATUS_CHARGED, 131 + PhortuneCart::STATUS_HOLD, 132 + PhortuneCart::STATUS_REVIEW, 133 + PhortuneCart::STATUS_PURCHASED, 134 + )) 135 + ->setLimit(50) 136 + ->execute(); 137 + 138 + $phids = array(); 139 + foreach ($invoices as $invoice) { 140 + $phids[] = $invoice->getPHID(); 141 + foreach ($invoice->getPurchases() as $purchase) { 142 + $phids[] = $purchase->getPHID(); 143 + } 144 + } 145 + $handles = $this->loadViewerHandles($phids); 146 + 147 + $invoice_table = id(new PhortuneOrderTableView()) 148 + ->setUser($viewer) 149 + ->setCarts($invoices) 150 + ->setHandles($handles); 151 + 152 + $account = $subscription->getAccount(); 153 + $merchant = $subscription->getMerchant(); 154 + 155 + $account_id = $account->getID(); 156 + $merchant_id = $merchant->getID(); 157 + $subscription_id = $subscription->getID(); 158 + 159 + $invoices_uri = $this->getApplicationURI( 160 + "{$account_id}/subscription/order/{$subscription_id}/"); 161 + 162 + $invoice_header = id(new PHUIHeaderView()) 163 + ->setHeader(pht('Past Invoices')) 164 + ->addActionLink( 165 + id(new PHUIButtonView()) 166 + ->setTag('a') 167 + ->setIcon('fa-list') 168 + ->setHref($invoices_uri) 169 + ->setText(pht('View All Invoices'))); 170 + 171 + return id(new PHUIObjectBoxView()) 172 + ->setHeader($invoice_header) 173 + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 174 + ->appendChild($invoice_table); 175 + } 176 + 177 + private function newAutopayView(PhortuneSubscription $subscription) { 178 + $viewer = $this->getViewer(); 179 + $account = $subscription->getAccount(); 180 + 181 + $add_method_uri = urisprintf( 182 + '/phortune/account/%d/card/new/?subscriptionID=%s', 183 + $account->getID(), 184 + $subscription->getID()); 185 + $add_method_uri = $this->getApplicationURI($add_method_uri); 186 + 187 + $can_edit = PhabricatorPolicyFilter::hasCapability( 188 + $viewer, 189 + $subscription, 190 + PhabricatorPolicyCapability::CAN_EDIT); 191 + 192 + $methods = id(new PhortunePaymentMethodQuery()) 193 + ->setViewer($viewer) 194 + ->withAccountPHIDs(array($subscription->getAccountPHID())) 195 + ->withMerchantPHIDs(array($subscription->getMerchantPHID())) 196 + ->withStatuses( 197 + array( 198 + PhortunePaymentMethod::STATUS_ACTIVE, 199 + )) 200 + ->execute(); 201 + $methods = mpull($methods, null, 'getPHID'); 202 + 203 + $autopay_phid = $subscription->getDefaultPaymentMethodPHID(); 204 + $autopay_method = idx($methods, $autopay_phid); 205 + 206 + $header = id(new PHUIHeaderView()) 207 + ->setHeader(pht('Autopay')) 208 + ->addActionLink( 209 + id(new PHUIButtonView()) 210 + ->setTag('a') 211 + ->setIcon('fa-plus') 212 + ->setHref($add_method_uri) 213 + ->setText(pht('Add Payment Method')) 214 + ->setWorkflow(!$can_edit) 215 + ->setDisabled(!$can_edit)); 216 + 217 + $methods = array_select_keys($methods, array($autopay_phid)) + $methods; 218 + 219 + $rows = array(); 220 + $rowc = array(); 221 + foreach ($methods as $method) { 222 + $is_autopay = ($autopay_method === $method); 223 + 224 + $remove_uri = urisprintf( 225 + '/card/%d/disable/?subscriptionID=%d', 226 + $method->getID(), 227 + $subscription->getID()); 228 + $remove_uri = $this->getApplicationURI($remove_uri); 229 + 230 + $autopay_uri = urisprintf( 231 + '/account/%d/subscriptions/%d/autopay/%d/', 232 + $account->getID(), 233 + $subscription->getID(), 234 + $method->getID()); 235 + $autopay_uri = $this->getApplicationURI($autopay_uri); 236 + 237 + $remove_button = id(new PHUIButtonView()) 238 + ->setTag('a') 239 + ->setColor('grey') 240 + ->setIcon('fa-times') 241 + ->setText(pht('Delete')) 242 + ->setHref($remove_uri) 243 + ->setWorkflow(true) 244 + ->setDisabled(!$can_edit); 245 + 246 + if ($is_autopay) { 247 + $autopay_button = id(new PHUIButtonView()) 248 + ->setColor('red') 249 + ->setIcon('fa-times') 250 + ->setText(pht('Stop Autopay')); 251 + } else { 252 + if ($autopay_method) { 253 + $make_color = 'grey'; 254 + } else { 255 + $make_color = 'green'; 256 + } 257 + 258 + $autopay_button = id(new PHUIButtonView()) 259 + ->setColor($make_color) 260 + ->setIcon('fa-retweet') 261 + ->setText(pht('Start Autopay')); 262 + } 263 + 264 + $autopay_button 265 + ->setTag('a') 266 + ->setHref($autopay_uri) 267 + ->setWorkflow(true) 268 + ->setDisabled(!$can_edit); 269 + 270 + $rows[] = array( 271 + $method->getID(), 272 + phutil_tag( 273 + 'a', 274 + array( 275 + 'href' => $method->getURI(), 276 + ), 277 + $method->getFullDisplayName()), 278 + $method->getDisplayExpires(), 279 + $autopay_button, 280 + $remove_button, 281 + ); 282 + 283 + if ($is_autopay) { 284 + $rowc[] = 'highlighted'; 285 + } else { 286 + $rowc[] = null; 287 + } 288 + } 289 + 290 + $method_table = id(new AphrontTableView($rows)) 291 + ->setHeaders( 292 + array( 293 + pht('ID'), 294 + pht('Payment Method'), 295 + pht('Expires'), 296 + null, 297 + null, 298 + )) 299 + ->setRowClasses($rowc) 300 + ->setColumnClasses( 301 + array( 302 + null, 303 + 'pri wide', 304 + null, 305 + 'right', 306 + null, 307 + )); 308 + 309 + if (!$autopay_method) { 310 + $method_table->setNotice( 311 + array( 312 + id(new PHUIIconView())->setIcon('fa-warning yellow'), 313 + ' ', 314 + pht('Autopay is not currently configured for this subscription.'), 315 + )); 316 + } else { 317 + $method_table->setNotice( 318 + array( 319 + id(new PHUIIconView())->setIcon('fa-check green'), 320 + ' ', 321 + pht( 322 + 'Autopay is configured using %s.', 323 + phutil_tag( 324 + 'a', 325 + array( 326 + 'href' => $autopay_method->getURI(), 327 + ), 328 + $autopay_method->getFullDisplayName())), 329 + )); 330 + } 331 + 332 + return id(new PHUIObjectBoxView()) 333 + ->setHeader($header) 334 + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 335 + ->setTable($method_table); 336 + } 337 + 338 + }
+35 -6
src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php
··· 24 24 return new Aphront400Response(); 25 25 } 26 26 27 + $subscription_id = $request->getInt('subscriptionID'); 28 + if ($subscription_id) { 29 + $subscription = id(new PhortuneSubscriptionQuery()) 30 + ->setViewer($viewer) 31 + ->withIDs(array($subscription_id)) 32 + ->withAccountPHIDs(array($method->getAccountPHID())) 33 + ->withMerchantPHIDs(array($method->getMerchantPHID())) 34 + ->executeOne(); 35 + if (!$subscription) { 36 + return new Aphront404Response(); 37 + } 38 + } else { 39 + $subscription = null; 40 + } 41 + 27 42 $account = $method->getAccount(); 28 43 $account_id = $account->getID(); 29 44 $account_uri = $account->getPaymentMethodsURI(); ··· 44 59 45 60 $editor->applyTransactions($method, $xactions); 46 61 47 - return id(new AphrontRedirectResponse())->setURI($account_uri); 62 + if ($subscription) { 63 + $next_uri = $subscription->getURI(); 64 + } else { 65 + $next_uri = $account_uri; 66 + } 67 + 68 + return id(new AphrontRedirectResponse())->setURI($next_uri); 48 69 } 49 70 71 + $method_phid = $method->getPHID(); 72 + $handles = $viewer->loadHandles( 73 + array( 74 + $method_phid, 75 + )); 76 + 77 + $method_handle = $handles[$method_phid]; 78 + $method_display = $method_handle->renderLink(); 79 + $method_display = phutil_tag('strong', array(), $method_display); 80 + 50 81 return $this->newDialog() 51 82 ->setTitle(pht('Remove Payment Method')) 83 + ->addHiddenInput('subscriptionID', $subscription_id) 52 84 ->appendParagraph( 53 85 pht( 54 - 'Remove the payment method "%s" from your account?', 55 - phutil_tag( 56 - 'strong', 57 - array(), 58 - $method->getFullDisplayName()))) 86 + 'Remove the payment method %s from your account?', 87 + $method_display)) 59 88 ->appendParagraph( 60 89 pht( 61 90 'You will no longer be able to make payments using this payment '.
-224
src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php
··· 1 - <?php 2 - 3 - final class PhortuneSubscriptionViewController extends PhortuneController { 4 - 5 - public function handleRequest(AphrontRequest $request) { 6 - $viewer = $this->getViewer(); 7 - 8 - $authority = $this->loadMerchantAuthority(); 9 - 10 - $subscription_query = id(new PhortuneSubscriptionQuery()) 11 - ->setViewer($viewer) 12 - ->withIDs(array($request->getURIData('id'))) 13 - ->needTriggers(true); 14 - 15 - if ($authority) { 16 - $subscription_query->withMerchantPHIDs(array($authority->getPHID())); 17 - } 18 - 19 - $subscription = $subscription_query->executeOne(); 20 - if (!$subscription) { 21 - return new Aphront404Response(); 22 - } 23 - 24 - $can_edit = PhabricatorPolicyFilter::hasCapability( 25 - $viewer, 26 - $subscription, 27 - PhabricatorPolicyCapability::CAN_EDIT); 28 - 29 - $merchant = $subscription->getMerchant(); 30 - $account = $subscription->getAccount(); 31 - 32 - $account_id = $account->getID(); 33 - $subscription_id = $subscription->getID(); 34 - 35 - $title = $subscription->getSubscriptionFullName(); 36 - 37 - $header = id(new PHUIHeaderView()) 38 - ->setHeader($title) 39 - ->setHeaderIcon('fa-calendar-o'); 40 - 41 - $curtain = $this->newCurtainView($subscription); 42 - $edit_uri = $subscription->getEditURI(); 43 - 44 - $curtain->addAction( 45 - id(new PhabricatorActionView()) 46 - ->setIcon('fa-credit-card') 47 - ->setName(pht('Manage Autopay')) 48 - ->setHref($edit_uri) 49 - ->setDisabled(!$can_edit) 50 - ->setWorkflow(!$can_edit)); 51 - 52 - $crumbs = $this->buildApplicationCrumbs(); 53 - if ($authority) { 54 - $this->addMerchantCrumb($crumbs, $merchant); 55 - } else { 56 - $this->addAccountCrumb($crumbs, $account); 57 - } 58 - $crumbs->addTextCrumb($subscription->getSubscriptionCrumbName()); 59 - $crumbs->setBorder(true); 60 - 61 - $properties = id(new PHUIPropertyListView()) 62 - ->setUser($viewer); 63 - 64 - $next_invoice = $subscription->getTrigger()->getNextEventPrediction(); 65 - $properties->addProperty( 66 - pht('Next Invoice'), 67 - phabricator_datetime($next_invoice, $viewer)); 68 - 69 - $default_method = $subscription->getDefaultPaymentMethodPHID(); 70 - if ($default_method) { 71 - $method = id(new PhortunePaymentMethodQuery()) 72 - ->setViewer($viewer) 73 - ->withPHIDs(array($default_method)) 74 - ->withStatuses( 75 - array( 76 - PhortunePaymentMethod::STATUS_ACTIVE, 77 - )) 78 - ->executeOne(); 79 - if ($method) { 80 - $handles = $this->loadViewerHandles(array($default_method)); 81 - $autopay_method = $handles[$default_method]->renderLink(); 82 - } else { 83 - $autopay_method = phutil_tag( 84 - 'em', 85 - array(), 86 - pht('<Deleted Payment Method>')); 87 - } 88 - } else { 89 - $autopay_method = phutil_tag( 90 - 'em', 91 - array(), 92 - pht('No Autopay Method Configured')); 93 - } 94 - 95 - $properties->addProperty( 96 - pht('Autopay With'), 97 - $autopay_method); 98 - 99 - $details = id(new PHUIObjectBoxView()) 100 - ->setHeaderText(pht('Details')) 101 - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 102 - ->addPropertyList($properties); 103 - 104 - $due_box = $this->buildDueInvoices($subscription, $authority); 105 - $invoice_box = $this->buildPastInvoices($subscription, $authority); 106 - 107 - $view = id(new PHUITwoColumnView()) 108 - ->setHeader($header) 109 - ->setCurtain($curtain) 110 - ->setMainColumn(array( 111 - $details, 112 - $due_box, 113 - $invoice_box, 114 - )); 115 - 116 - return $this->newPage() 117 - ->setTitle($title) 118 - ->setCrumbs($crumbs) 119 - ->appendChild($view); 120 - } 121 - 122 - private function buildDueInvoices( 123 - PhortuneSubscription $subscription, 124 - $authority) { 125 - $viewer = $this->getViewer(); 126 - 127 - $invoices = id(new PhortuneCartQuery()) 128 - ->setViewer($viewer) 129 - ->withSubscriptionPHIDs(array($subscription->getPHID())) 130 - ->needPurchases(true) 131 - ->withInvoices(true) 132 - ->execute(); 133 - 134 - $phids = array(); 135 - foreach ($invoices as $invoice) { 136 - $phids[] = $invoice->getPHID(); 137 - $phids[] = $invoice->getMerchantPHID(); 138 - foreach ($invoice->getPurchases() as $purchase) { 139 - $phids[] = $purchase->getPHID(); 140 - } 141 - } 142 - $handles = $this->loadViewerHandles($phids); 143 - 144 - $invoice_table = id(new PhortuneOrderTableView()) 145 - ->setUser($viewer) 146 - ->setCarts($invoices) 147 - ->setIsInvoices(true) 148 - ->setIsMerchantView((bool)$authority) 149 - ->setHandles($handles); 150 - 151 - $invoice_header = id(new PHUIHeaderView()) 152 - ->setHeader(pht('Invoices Due')); 153 - 154 - return id(new PHUIObjectBoxView()) 155 - ->setHeader($invoice_header) 156 - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 157 - ->appendChild($invoice_table); 158 - } 159 - 160 - private function buildPastInvoices( 161 - PhortuneSubscription $subscription, 162 - $authority) { 163 - $viewer = $this->getViewer(); 164 - 165 - $invoices = id(new PhortuneCartQuery()) 166 - ->setViewer($viewer) 167 - ->withSubscriptionPHIDs(array($subscription->getPHID())) 168 - ->needPurchases(true) 169 - ->withStatuses( 170 - array( 171 - PhortuneCart::STATUS_PURCHASING, 172 - PhortuneCart::STATUS_CHARGED, 173 - PhortuneCart::STATUS_HOLD, 174 - PhortuneCart::STATUS_REVIEW, 175 - PhortuneCart::STATUS_PURCHASED, 176 - )) 177 - ->setLimit(50) 178 - ->execute(); 179 - 180 - $phids = array(); 181 - foreach ($invoices as $invoice) { 182 - $phids[] = $invoice->getPHID(); 183 - foreach ($invoice->getPurchases() as $purchase) { 184 - $phids[] = $purchase->getPHID(); 185 - } 186 - } 187 - $handles = $this->loadViewerHandles($phids); 188 - 189 - $invoice_table = id(new PhortuneOrderTableView()) 190 - ->setUser($viewer) 191 - ->setCarts($invoices) 192 - ->setHandles($handles); 193 - 194 - $account = $subscription->getAccount(); 195 - $merchant = $subscription->getMerchant(); 196 - 197 - $account_id = $account->getID(); 198 - $merchant_id = $merchant->getID(); 199 - $subscription_id = $subscription->getID(); 200 - 201 - if ($authority) { 202 - $invoices_uri = $this->getApplicationURI( 203 - "merchant/{$merchant_id}/subscription/order/{$subscription_id}/"); 204 - } else { 205 - $invoices_uri = $this->getApplicationURI( 206 - "{$account_id}/subscription/order/{$subscription_id}/"); 207 - } 208 - 209 - $invoice_header = id(new PHUIHeaderView()) 210 - ->setHeader(pht('Past Invoices')) 211 - ->addActionLink( 212 - id(new PHUIButtonView()) 213 - ->setTag('a') 214 - ->setIcon('fa-list') 215 - ->setHref($invoices_uri) 216 - ->setText(pht('View All Invoices'))); 217 - 218 - return id(new PHUIObjectBoxView()) 219 - ->setHeader($invoice_header) 220 - ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 221 - ->appendChild($invoice_table); 222 - } 223 - 224 - }
+18
src/applications/phortune/editor/PhortuneSubscriptionEditor.php
··· 1 + <?php 2 + 3 + final class PhortuneSubscriptionEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorPhortuneApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Phortune Subscriptions'); 12 + } 13 + 14 + public function getCreateObjectTitle($author, $object) { 15 + return pht('%s created this subscription.', $author); 16 + } 17 + 18 + }
+3 -3
src/applications/phortune/phid/PhortunePaymentMethodPHIDType.php
··· 32 32 foreach ($handles as $phid => $handle) { 33 33 $method = $objects[$phid]; 34 34 35 - $id = $method->getID(); 36 - 37 - $handle->setName($method->getFullDisplayName()); 35 + $handle 36 + ->setName($method->getFullDisplayName()) 37 + ->setURI($method->getURI()); 38 38 } 39 39 } 40 40
+3 -5
src/applications/phortune/phid/PhortuneSubscriptionPHIDType.php
··· 32 32 foreach ($handles as $phid => $handle) { 33 33 $subscription = $objects[$phid]; 34 34 35 - $id = $subscription->getID(); 36 - 37 - $handle->setName($subscription->getSubscriptionName()); 38 - $handle->setURI($subscription->getURI()); 39 - 35 + $handle 36 + ->setName($subscription->getSubscriptionName()) 37 + ->setURI($subscription->getURI()); 40 38 } 41 39 } 42 40
+10
src/applications/phortune/query/PhortuneSubscriptionTransactionQuery.php
··· 1 + <?php 2 + 3 + final class PhortuneSubscriptionTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new PhortuneSubscriptionTransaction(); 8 + } 9 + 10 + }
-1
src/applications/phortune/storage/PhortunePaymentMethod.php
··· 180 180 } 181 181 182 182 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 183 - 184 183 // See T13366. If you can edit the merchant associated with this payment 185 184 // method, you can view the payment method. 186 185 if ($capability === PhabricatorPolicyCapability::CAN_VIEW) {
+51 -27
src/applications/phortune/storage/PhortuneSubscription.php
··· 3 3 /** 4 4 * A subscription bills users regularly. 5 5 */ 6 - final class PhortuneSubscription extends PhortuneDAO 7 - implements PhabricatorPolicyInterface { 6 + final class PhortuneSubscription 7 + extends PhortuneDAO 8 + implements 9 + PhabricatorPolicyInterface, 10 + PhabricatorExtendedPolicyInterface, 11 + PhabricatorPolicyCodexInterface, 12 + PhabricatorApplicationTransactionInterface { 8 13 9 14 const STATUS_ACTIVE = 'active'; 10 15 const STATUS_CANCELLED = 'cancelled'; ··· 55 60 ) + parent::getConfiguration(); 56 61 } 57 62 58 - public function generatePHID() { 59 - return PhabricatorPHID::generateNewPHID( 60 - PhortuneSubscriptionPHIDType::TYPECONST); 63 + public function getPHIDType() { 64 + return PhortuneSubscriptionPHIDType::TYPECONST; 61 65 } 62 66 63 67 public static function initializeNewSubscription( ··· 245 249 $purchase); 246 250 } 247 251 252 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 253 + 254 + 255 + public function getApplicationTransactionEditor() { 256 + return new PhortuneSubscriptionEditor(); 257 + } 258 + 259 + public function getApplicationTransactionTemplate() { 260 + return new PhortuneSubscriptionTransaction(); 261 + } 248 262 249 263 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 250 264 ··· 257 271 } 258 272 259 273 public function getPolicy($capability) { 260 - // NOTE: Both view and edit use the account's edit policy. We punch a hole 261 - // through this for merchants, below. 262 - return $this 263 - ->getAccount() 264 - ->getPolicy(PhabricatorPolicyCapability::CAN_EDIT); 274 + return PhabricatorPolicies::getMostOpenPolicy(); 265 275 } 266 276 267 277 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 268 - if ($this->getAccount()->hasAutomaticCapability($capability, $viewer)) { 269 - return true; 270 - } 271 - 272 - // If the viewer controls the merchant this subscription bills to, they can 273 - // view the subscription. 274 - if ($capability == PhabricatorPolicyCapability::CAN_VIEW) { 275 - $can_admin = PhabricatorPolicyFilter::hasCapability( 276 - $viewer, 277 - $this->getMerchant(), 278 - PhabricatorPolicyCapability::CAN_EDIT); 279 - if ($can_admin) { 278 + // See T13366. If you can edit the merchant associated with this 279 + // subscription, you can view the subscription. 280 + if ($capability === PhabricatorPolicyCapability::CAN_VIEW) { 281 + $any_edit = PhortuneMerchantQuery::canViewersEditMerchants( 282 + array($viewer->getPHID()), 283 + array($this->getMerchantPHID())); 284 + if ($any_edit) { 280 285 return true; 281 286 } 282 287 } ··· 284 289 return false; 285 290 } 286 291 287 - public function describeAutomaticCapability($capability) { 292 + 293 + /* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ 294 + 295 + 296 + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { 297 + if ($this->hasAutomaticCapability($capability, $viewer)) { 298 + return array(); 299 + } 300 + 301 + // See T13366. For blanket view and edit permissions on all subscriptions, 302 + // you must be able to edit the associated account. 288 303 return array( 289 - pht('Subscriptions inherit the policies of the associated account.'), 290 - pht( 291 - 'The merchant you are subscribed with can review and manage the '. 292 - 'subscription.'), 304 + array( 305 + $this->getAccount(), 306 + PhabricatorPolicyCapability::CAN_EDIT, 307 + ), 293 308 ); 294 309 } 310 + 311 + 312 + /* -( PhabricatorPolicyCodexInterface )------------------------------------ */ 313 + 314 + 315 + public function newPolicyCodex() { 316 + return new PhortuneSubscriptionPolicyCodex(); 317 + } 318 + 295 319 }
+18
src/applications/phortune/storage/PhortuneSubscriptionTransaction.php
··· 1 + <?php 2 + 3 + final class PhortuneSubscriptionTransaction 4 + extends PhabricatorModularTransaction { 5 + 6 + public function getApplicationName() { 7 + return 'phortune'; 8 + } 9 + 10 + public function getApplicationTransactionType() { 11 + return PhortuneSubscriptionPHIDType::TYPECONST; 12 + } 13 + 14 + public function getBaseTransactionClass() { 15 + return 'PhortuneSubscriptionTransactionType'; 16 + } 17 + 18 + }
+41
src/applications/phortune/xaction/subscription/PhortuneSubscriptionAutopayTransaction.php
··· 1 + <?php 2 + 3 + final class PhortuneSubscriptionAutopayTransaction 4 + extends PhortuneSubscriptionTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'autopay'; 7 + 8 + public function generateOldValue($object) { 9 + return $object->getDefaultPaymentMethodPHID(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setDefaultPaymentMethodPHID($value); 14 + } 15 + 16 + public function getTitle() { 17 + $old_phid = $this->getOldValue(); 18 + $new_phid = $this->getNewValue(); 19 + 20 + if ($old_phid && $new_phid) { 21 + return pht( 22 + '%s changed the automatic payment method for this subscription.', 23 + $this->renderAuthor()); 24 + } else if ($new_phid) { 25 + return pht( 26 + '%s configured an automatic payment method for this subscription.', 27 + $this->renderAuthor()); 28 + } else { 29 + return pht( 30 + '%s stopped automatic payments for this subscription.', 31 + $this->renderAuthor()); 32 + } 33 + } 34 + 35 + public function shouldTryMFA( 36 + $object, 37 + PhabricatorApplicationTransaction $xaction) { 38 + return true; 39 + } 40 + 41 + }
+4
src/applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php
··· 1 + <?php 2 + 3 + abstract class PhortuneSubscriptionTransactionType 4 + extends PhabricatorModularTransactionType {}