Page MenuHomePhabricator

D20721.diff
No OneTemporary

D20721.diff

diff --git a/resources/sql/autopatches/20190816.subscription.01.xaction.sql b/resources/sql/autopatches/20190816.subscription.01.xaction.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20190816.subscription.01.xaction.sql
@@ -0,0 +1,19 @@
+CREATE TABLE {$NAMESPACE}_phortune.phortune_subscriptiontransaction (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARBINARY(64) NOT NULL,
+ authorPHID VARBINARY(64) NOT NULL,
+ objectPHID VARBINARY(64) NOT NULL,
+ viewPolicy VARBINARY(64) NOT NULL,
+ editPolicy VARBINARY(64) NOT NULL,
+ commentPHID VARBINARY(64) DEFAULT NULL,
+ commentVersion INT UNSIGNED NOT NULL,
+ transactionType VARCHAR(32) NOT NULL,
+ oldValue LONGTEXT NOT NULL,
+ newValue LONGTEXT NOT NULL,
+ contentSource LONGTEXT NOT NULL,
+ metadata LONGTEXT NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_phid` (`phid`),
+ KEY `key_object` (`objectPHID`)
+) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -5249,11 +5249,13 @@
'PhortuneAccountOrdersController' => 'applications/phortune/controller/account/PhortuneAccountOrdersController.php',
'PhortuneAccountOverviewController' => 'applications/phortune/controller/account/PhortuneAccountOverviewController.php',
'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php',
- 'PhortuneAccountPaymentMethodListController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php',
+ 'PhortuneAccountPaymentMethodController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php',
'PhortuneAccountPaymentMethodViewController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodViewController.php',
'PhortuneAccountProfileController' => 'applications/phortune/controller/account/PhortuneAccountProfileController.php',
'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php',
+ 'PhortuneAccountSubscriptionAutopayController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionAutopayController.php',
'PhortuneAccountSubscriptionController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionController.php',
+ 'PhortuneAccountSubscriptionViewController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php',
'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php',
'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php',
'PhortuneAccountTransactionType' => 'applications/phortune/xaction/PhortuneAccountTransactionType.php',
@@ -5361,16 +5363,21 @@
'PhortuneSchemaSpec' => 'applications/phortune/storage/PhortuneSchemaSpec.php',
'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php',
'PhortuneSubscription' => 'applications/phortune/storage/PhortuneSubscription.php',
+ 'PhortuneSubscriptionAutopayTransaction' => 'applications/phortune/xaction/subscription/PhortuneSubscriptionAutopayTransaction.php',
'PhortuneSubscriptionCart' => 'applications/phortune/cart/PhortuneSubscriptionCart.php',
'PhortuneSubscriptionEditController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionEditController.php',
+ 'PhortuneSubscriptionEditor' => 'applications/phortune/editor/PhortuneSubscriptionEditor.php',
'PhortuneSubscriptionImplementation' => 'applications/phortune/subscription/PhortuneSubscriptionImplementation.php',
'PhortuneSubscriptionListController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionListController.php',
'PhortuneSubscriptionPHIDType' => 'applications/phortune/phid/PhortuneSubscriptionPHIDType.php',
+ 'PhortuneSubscriptionPolicyCodex' => 'applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php',
'PhortuneSubscriptionProduct' => 'applications/phortune/product/PhortuneSubscriptionProduct.php',
'PhortuneSubscriptionQuery' => 'applications/phortune/query/PhortuneSubscriptionQuery.php',
'PhortuneSubscriptionSearchEngine' => 'applications/phortune/query/PhortuneSubscriptionSearchEngine.php',
'PhortuneSubscriptionTableView' => 'applications/phortune/view/PhortuneSubscriptionTableView.php',
- 'PhortuneSubscriptionViewController' => 'applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php',
+ 'PhortuneSubscriptionTransaction' => 'applications/phortune/storage/PhortuneSubscriptionTransaction.php',
+ 'PhortuneSubscriptionTransactionQuery' => 'applications/phortune/query/PhortuneSubscriptionTransactionQuery.php',
+ 'PhortuneSubscriptionTransactionType' => 'applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php',
'PhortuneSubscriptionWorker' => 'applications/phortune/worker/PhortuneSubscriptionWorker.php',
'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php',
'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php',
@@ -11812,11 +11819,13 @@
'PhortuneAccountOrdersController' => 'PhortuneAccountProfileController',
'PhortuneAccountOverviewController' => 'PhortuneAccountProfileController',
'PhortuneAccountPHIDType' => 'PhabricatorPHIDType',
- 'PhortuneAccountPaymentMethodListController' => 'PhortuneAccountProfileController',
+ 'PhortuneAccountPaymentMethodController' => 'PhortuneAccountProfileController',
'PhortuneAccountPaymentMethodViewController' => 'PhortuneAccountController',
'PhortuneAccountProfileController' => 'PhortuneAccountController',
'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhortuneAccountSubscriptionAutopayController' => 'PhortuneAccountController',
'PhortuneAccountSubscriptionController' => 'PhortuneAccountProfileController',
+ 'PhortuneAccountSubscriptionViewController' => 'PhortuneAccountController',
'PhortuneAccountTransaction' => 'PhabricatorModularTransaction',
'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortuneAccountTransactionType' => 'PhabricatorModularTransactionType',
@@ -11953,17 +11962,25 @@
'PhortuneSubscription' => array(
'PhortuneDAO',
'PhabricatorPolicyInterface',
+ 'PhabricatorExtendedPolicyInterface',
+ 'PhabricatorPolicyCodexInterface',
+ 'PhabricatorApplicationTransactionInterface',
),
+ 'PhortuneSubscriptionAutopayTransaction' => 'PhortuneSubscriptionTransactionType',
'PhortuneSubscriptionCart' => 'PhortuneCartImplementation',
'PhortuneSubscriptionEditController' => 'PhortuneController',
+ 'PhortuneSubscriptionEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortuneSubscriptionImplementation' => 'Phobject',
'PhortuneSubscriptionListController' => 'PhortuneController',
'PhortuneSubscriptionPHIDType' => 'PhabricatorPHIDType',
+ 'PhortuneSubscriptionPolicyCodex' => 'PhabricatorPolicyCodex',
'PhortuneSubscriptionProduct' => 'PhortuneProductImplementation',
'PhortuneSubscriptionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneSubscriptionSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhortuneSubscriptionTableView' => 'AphrontView',
- 'PhortuneSubscriptionViewController' => 'PhortuneController',
+ 'PhortuneSubscriptionTransaction' => 'PhabricatorModularTransaction',
+ 'PhortuneSubscriptionTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'PhortuneSubscriptionTransactionType' => 'PhabricatorModularTransactionType',
'PhortuneSubscriptionWorker' => 'PhabricatorWorker',
'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider',
'PhragmentBrowseController' => 'PhragmentController',
diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php
--- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php
+++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php
@@ -43,9 +43,7 @@
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhortuneSubscriptionListController',
'view/(?P<id>\d+)/'
- => 'PhortuneSubscriptionViewController',
- 'edit/(?P<id>\d+)/'
- => 'PhortuneSubscriptionEditController',
+ => 'PhortuneAccountSubscriptionViewController',
'order/(?P<subscriptionID>\d+)/'
=> 'PhortuneCartListController',
),
@@ -73,12 +71,18 @@
'(?P<accountID>\d+)/' => array(
'details/' => 'PhortuneAccountDetailsController',
'methods/' => array(
- '' => 'PhortuneAccountPaymentMethodListController',
+ '' => 'PhortuneAccountPaymentMethodController',
'(?P<id>\d+)/' => 'PhortuneAccountPaymentMethodViewController',
),
'orders/' => 'PhortuneAccountOrdersController',
'charges/' => 'PhortuneAccountChargesController',
- 'subscriptions/' => 'PhortuneAccountSubscriptionController',
+ 'subscriptions/' => array(
+ '' => 'PhortuneAccountSubscriptionController',
+ '(?P<subscriptionID>\d+)/' => array(
+ 'autopay/(?P<methodID>\d+)/'
+ => 'PhortuneAccountSubscriptionAutopayController',
+ ),
+ ),
'managers/' => array(
'' => 'PhortuneAccountManagersController',
'add/' => 'PhortuneAccountAddManagerController',
@@ -124,7 +128,7 @@
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhortuneSubscriptionListController',
'view/(?P<id>\d+)/'
- => 'PhortuneSubscriptionViewController',
+ => 'PhortuneAccountSubscriptionViewController',
'order/(?P<subscriptionID>\d+)/'
=> 'PhortuneCartListController',
),
diff --git a/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php b/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php
--- a/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php
+++ b/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php
@@ -12,6 +12,7 @@
->setCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
))
->setIsActive(true)
->setDescription(
diff --git a/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php b/src/applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php
copy from src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php
copy to src/applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php
--- a/src/applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php
+++ b/src/applications/phortune/codex/PhortuneSubscriptionPolicyCodex.php
@@ -1,6 +1,6 @@
<?php
-final class PhortunePaymentMethodPolicyCodex
+final class PhortuneSubscriptionPolicyCodex
extends PhabricatorPolicyCodex {
public function getPolicySpecialRuleDescriptions() {
@@ -12,11 +12,12 @@
->setCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
))
->setIsActive(true)
->setDescription(
pht(
- 'Account members may view and edit payment methods.'));
+ 'Account members may view and edit subscriptions.'));
$rules[] = $this->newRule()
->setCapabilities(
@@ -27,7 +28,7 @@
->setDescription(
pht(
'Merchants you have a relationship with may view associated '.
- 'payment methods.'));
+ 'subscriptions.'));
return $rules;
}
diff --git a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php
rename from src/applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php
rename to src/applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php
--- a/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php
+++ b/src/applications/phortune/controller/account/PhortuneAccountPaymentMethodController.php
@@ -1,6 +1,6 @@
<?php
-final class PhortuneAccountPaymentMethodListController
+final class PhortuneAccountPaymentMethodController
extends PhortuneAccountProfileController {
protected function shouldRequireAccountEditCapability() {
diff --git a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionAutopayController.php b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionAutopayController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionAutopayController.php
@@ -0,0 +1,137 @@
+<?php
+
+final class PhortuneAccountSubscriptionAutopayController
+ extends PhortuneAccountController {
+
+ protected function shouldRequireAccountEditCapability() {
+ return true;
+ }
+
+ protected function handleAccountRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+ $account = $this->getAccount();
+
+ $subscription = id(new PhortuneSubscriptionQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($request->getURIData('subscriptionID')))
+ ->withAccountPHIDs(array($account->getPHID()))
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->executeOne();
+ if (!$subscription) {
+ return new Aphront404Response();
+ }
+
+ $method = id(new PhortunePaymentMethodQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($request->getURIData('methodID')))
+ ->withAccountPHIDs(array($subscription->getAccountPHID()))
+ ->withMerchantPHIDs(array($subscription->getMerchantPHID()))
+ ->withStatuses(
+ array(
+ PhortunePaymentMethod::STATUS_ACTIVE,
+ ))
+ ->executeOne();
+ if (!$method) {
+ return new Aphront404Response();
+ }
+
+ $next_uri = $subscription->getURI();
+
+ $autopay_phid = $subscription->getDefaultPaymentMethodPHID();
+ $is_stop = ($autopay_phid === $method->getPHID());
+
+ if ($request->isFormOrHisecPost()) {
+ if ($is_stop) {
+ $new_phid = null;
+ } else {
+ $new_phid = $method->getPHID();
+ }
+
+ $xactions = array();
+
+ $xactions[] = $subscription->getApplicationTransactionTemplate()
+ ->setTransactionType(
+ PhortuneSubscriptionAutopayTransaction::TRANSACTIONTYPE)
+ ->setNewValue($new_phid);
+
+ $editor = $subscription->getApplicationTransactionEditor()
+ ->setActor($viewer)
+ ->setContentSourceFromRequest($request)
+ ->setContinueOnNoEffect(true)
+ ->setContinueOnMissingFields(true)
+ ->setCancelURI($next_uri);
+
+ $editor->applyTransactions($subscription, $xactions);
+
+ return id(new AphrontRedirectResponse())->setURI($next_uri);
+ }
+
+ $method_phid = $method->getPHID();
+ $subscription_phid = $subscription->getPHID();
+
+ $handles = $viewer->loadHandles(
+ array(
+ $method_phid,
+ $subscription_phid,
+ ));
+
+ $method_handle = $handles[$method_phid];
+ $subscription_handle = $handles[$subscription_phid];
+
+ $method_display = $method_handle->renderLink();
+ $method_display = phutil_tag(
+ 'strong',
+ array(),
+ $method_display);
+
+ $subscription_display = $subscription_handle->renderLink();
+ $subscription_display = phutil_tag(
+ 'strong',
+ array(),
+ $subscription_display);
+
+ $body = array();
+ if ($is_stop) {
+ $title = pht('Stop Autopay');
+
+ $body[] = pht(
+ 'Remove %s as the automatic payment method for subscription %s?',
+ $method_display,
+ $subscription_display);
+
+ $body[] = pht(
+ 'This payment method will no longer be charged automatically.');
+
+ $submit = pht('Stop Autopay');
+ } else {
+ $title = pht('Start Autopay');
+
+ $body[] = pht(
+ 'Set %s as the automatic payment method for subscription %s?',
+ $method_display,
+ $subscription_display);
+
+ $body[] = pht(
+ 'This payment method will be used to automatically pay future '.
+ 'charges.');
+
+ $submit = pht('Start Autopay');
+ }
+
+ $dialog = $this->newDialog()
+ ->setTitle($title)
+ ->addCancelButton($next_uri)
+ ->addSubmitButton($submit);
+
+ foreach ($body as $graph) {
+ $dialog->appendParagraph($graph);
+ }
+
+ return $dialog;
+ }
+
+}
diff --git a/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/controller/account/PhortuneAccountSubscriptionViewController.php
@@ -0,0 +1,338 @@
+<?php
+
+final class PhortuneAccountSubscriptionViewController
+ extends PhortuneAccountController {
+
+ protected function shouldRequireAccountEditCapability() {
+ return false;
+ }
+
+ protected function handleAccountRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+
+ $subscription = id(new PhortuneSubscriptionQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($request->getURIData('id')))
+ ->needTriggers(true)
+ ->executeOne();
+ if (!$subscription) {
+ return new Aphront404Response();
+ }
+
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $subscription,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $merchant = $subscription->getMerchant();
+ $account = $subscription->getAccount();
+
+ $account_id = $account->getID();
+ $subscription_id = $subscription->getID();
+
+ $title = $subscription->getSubscriptionFullName();
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader($title)
+ ->setHeaderIcon('fa-retweet');
+
+ $edit_uri = $subscription->getEditURI();
+
+ $crumbs = $this->buildApplicationCrumbs()
+ ->addTextCrumb($subscription->getSubscriptionCrumbName())
+ ->setBorder(true);
+
+ $properties = id(new PHUIPropertyListView())
+ ->setUser($viewer);
+
+ $next_invoice = $subscription->getTrigger()->getNextEventPrediction();
+ $properties->addProperty(
+ pht('Next Invoice'),
+ phabricator_datetime($next_invoice, $viewer));
+
+ $autopay = $this->newAutopayView($subscription);
+
+ $details = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Subscription Details'))
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+ ->addPropertyList($properties);
+
+ $due_box = $this->buildDueInvoices($subscription);
+ $invoice_box = $this->buildPastInvoices($subscription);
+
+ $timeline = $this->buildTransactionTimeline(
+ $subscription,
+ new PhortuneSubscriptionTransactionQuery());
+ $timeline->setShouldTerminate(true);
+
+ $view = id(new PHUITwoColumnView())
+ ->setHeader($header)
+ ->setFooter(
+ array(
+ $details,
+ $autopay,
+ $due_box,
+ $invoice_box,
+ $timeline,
+ ));
+
+ return $this->newPage()
+ ->setTitle($title)
+ ->setCrumbs($crumbs)
+ ->appendChild($view);
+ }
+
+ private function buildDueInvoices(PhortuneSubscription $subscription) {
+ $viewer = $this->getViewer();
+
+ $invoices = id(new PhortuneCartQuery())
+ ->setViewer($viewer)
+ ->withSubscriptionPHIDs(array($subscription->getPHID()))
+ ->needPurchases(true)
+ ->withInvoices(true)
+ ->execute();
+
+ $phids = array();
+ foreach ($invoices as $invoice) {
+ $phids[] = $invoice->getPHID();
+ $phids[] = $invoice->getMerchantPHID();
+ foreach ($invoice->getPurchases() as $purchase) {
+ $phids[] = $purchase->getPHID();
+ }
+ }
+ $handles = $this->loadViewerHandles($phids);
+
+ $invoice_table = id(new PhortuneOrderTableView())
+ ->setUser($viewer)
+ ->setCarts($invoices)
+ ->setIsInvoices(true)
+ ->setHandles($handles);
+
+ $invoice_header = id(new PHUIHeaderView())
+ ->setHeader(pht('Invoices Due'));
+
+ return id(new PHUIObjectBoxView())
+ ->setHeader($invoice_header)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+ ->appendChild($invoice_table);
+ }
+
+ private function buildPastInvoices(PhortuneSubscription $subscription) {
+ $viewer = $this->getViewer();
+
+ $invoices = id(new PhortuneCartQuery())
+ ->setViewer($viewer)
+ ->withSubscriptionPHIDs(array($subscription->getPHID()))
+ ->needPurchases(true)
+ ->withStatuses(
+ array(
+ PhortuneCart::STATUS_PURCHASING,
+ PhortuneCart::STATUS_CHARGED,
+ PhortuneCart::STATUS_HOLD,
+ PhortuneCart::STATUS_REVIEW,
+ PhortuneCart::STATUS_PURCHASED,
+ ))
+ ->setLimit(50)
+ ->execute();
+
+ $phids = array();
+ foreach ($invoices as $invoice) {
+ $phids[] = $invoice->getPHID();
+ foreach ($invoice->getPurchases() as $purchase) {
+ $phids[] = $purchase->getPHID();
+ }
+ }
+ $handles = $this->loadViewerHandles($phids);
+
+ $invoice_table = id(new PhortuneOrderTableView())
+ ->setUser($viewer)
+ ->setCarts($invoices)
+ ->setHandles($handles);
+
+ $account = $subscription->getAccount();
+ $merchant = $subscription->getMerchant();
+
+ $account_id = $account->getID();
+ $merchant_id = $merchant->getID();
+ $subscription_id = $subscription->getID();
+
+ $invoices_uri = $this->getApplicationURI(
+ "{$account_id}/subscription/order/{$subscription_id}/");
+
+ $invoice_header = id(new PHUIHeaderView())
+ ->setHeader(pht('Past Invoices'))
+ ->addActionLink(
+ id(new PHUIButtonView())
+ ->setTag('a')
+ ->setIcon('fa-list')
+ ->setHref($invoices_uri)
+ ->setText(pht('View All Invoices')));
+
+ return id(new PHUIObjectBoxView())
+ ->setHeader($invoice_header)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+ ->appendChild($invoice_table);
+ }
+
+ private function newAutopayView(PhortuneSubscription $subscription) {
+ $viewer = $this->getViewer();
+ $account = $subscription->getAccount();
+
+ $add_method_uri = urisprintf(
+ '/phortune/account/%d/card/new/?subscriptionID=%s',
+ $account->getID(),
+ $subscription->getID());
+ $add_method_uri = $this->getApplicationURI($add_method_uri);
+
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $subscription,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $methods = id(new PhortunePaymentMethodQuery())
+ ->setViewer($viewer)
+ ->withAccountPHIDs(array($subscription->getAccountPHID()))
+ ->withMerchantPHIDs(array($subscription->getMerchantPHID()))
+ ->withStatuses(
+ array(
+ PhortunePaymentMethod::STATUS_ACTIVE,
+ ))
+ ->execute();
+ $methods = mpull($methods, null, 'getPHID');
+
+ $autopay_phid = $subscription->getDefaultPaymentMethodPHID();
+ $autopay_method = idx($methods, $autopay_phid);
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader(pht('Autopay'))
+ ->addActionLink(
+ id(new PHUIButtonView())
+ ->setTag('a')
+ ->setIcon('fa-plus')
+ ->setHref($add_method_uri)
+ ->setText(pht('Add Payment Method'))
+ ->setWorkflow(!$can_edit)
+ ->setDisabled(!$can_edit));
+
+ $methods = array_select_keys($methods, array($autopay_phid)) + $methods;
+
+ $rows = array();
+ $rowc = array();
+ foreach ($methods as $method) {
+ $is_autopay = ($autopay_method === $method);
+
+ $remove_uri = urisprintf(
+ '/card/%d/disable/?subscriptionID=%d',
+ $method->getID(),
+ $subscription->getID());
+ $remove_uri = $this->getApplicationURI($remove_uri);
+
+ $autopay_uri = urisprintf(
+ '/account/%d/subscriptions/%d/autopay/%d/',
+ $account->getID(),
+ $subscription->getID(),
+ $method->getID());
+ $autopay_uri = $this->getApplicationURI($autopay_uri);
+
+ $remove_button = id(new PHUIButtonView())
+ ->setTag('a')
+ ->setColor('grey')
+ ->setIcon('fa-times')
+ ->setText(pht('Delete'))
+ ->setHref($remove_uri)
+ ->setWorkflow(true)
+ ->setDisabled(!$can_edit);
+
+ if ($is_autopay) {
+ $autopay_button = id(new PHUIButtonView())
+ ->setColor('red')
+ ->setIcon('fa-times')
+ ->setText(pht('Stop Autopay'));
+ } else {
+ if ($autopay_method) {
+ $make_color = 'grey';
+ } else {
+ $make_color = 'green';
+ }
+
+ $autopay_button = id(new PHUIButtonView())
+ ->setColor($make_color)
+ ->setIcon('fa-retweet')
+ ->setText(pht('Start Autopay'));
+ }
+
+ $autopay_button
+ ->setTag('a')
+ ->setHref($autopay_uri)
+ ->setWorkflow(true)
+ ->setDisabled(!$can_edit);
+
+ $rows[] = array(
+ $method->getID(),
+ phutil_tag(
+ 'a',
+ array(
+ 'href' => $method->getURI(),
+ ),
+ $method->getFullDisplayName()),
+ $method->getDisplayExpires(),
+ $autopay_button,
+ $remove_button,
+ );
+
+ if ($is_autopay) {
+ $rowc[] = 'highlighted';
+ } else {
+ $rowc[] = null;
+ }
+ }
+
+ $method_table = id(new AphrontTableView($rows))
+ ->setHeaders(
+ array(
+ pht('ID'),
+ pht('Payment Method'),
+ pht('Expires'),
+ null,
+ null,
+ ))
+ ->setRowClasses($rowc)
+ ->setColumnClasses(
+ array(
+ null,
+ 'pri wide',
+ null,
+ 'right',
+ null,
+ ));
+
+ if (!$autopay_method) {
+ $method_table->setNotice(
+ array(
+ id(new PHUIIconView())->setIcon('fa-warning yellow'),
+ ' ',
+ pht('Autopay is not currently configured for this subscription.'),
+ ));
+ } else {
+ $method_table->setNotice(
+ array(
+ id(new PHUIIconView())->setIcon('fa-check green'),
+ ' ',
+ pht(
+ 'Autopay is configured using %s.',
+ phutil_tag(
+ 'a',
+ array(
+ 'href' => $autopay_method->getURI(),
+ ),
+ $autopay_method->getFullDisplayName())),
+ ));
+ }
+
+ return id(new PHUIObjectBoxView())
+ ->setHeader($header)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+ ->setTable($method_table);
+ }
+
+}
diff --git a/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php b/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php
--- a/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php
+++ b/src/applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php
@@ -24,6 +24,21 @@
return new Aphront400Response();
}
+ $subscription_id = $request->getInt('subscriptionID');
+ if ($subscription_id) {
+ $subscription = id(new PhortuneSubscriptionQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($subscription_id))
+ ->withAccountPHIDs(array($method->getAccountPHID()))
+ ->withMerchantPHIDs(array($method->getMerchantPHID()))
+ ->executeOne();
+ if (!$subscription) {
+ return new Aphront404Response();
+ }
+ } else {
+ $subscription = null;
+ }
+
$account = $method->getAccount();
$account_id = $account->getID();
$account_uri = $account->getPaymentMethodsURI();
@@ -44,18 +59,32 @@
$editor->applyTransactions($method, $xactions);
- return id(new AphrontRedirectResponse())->setURI($account_uri);
+ if ($subscription) {
+ $next_uri = $subscription->getURI();
+ } else {
+ $next_uri = $account_uri;
+ }
+
+ return id(new AphrontRedirectResponse())->setURI($next_uri);
}
+ $method_phid = $method->getPHID();
+ $handles = $viewer->loadHandles(
+ array(
+ $method_phid,
+ ));
+
+ $method_handle = $handles[$method_phid];
+ $method_display = $method_handle->renderLink();
+ $method_display = phutil_tag('strong', array(), $method_display);
+
return $this->newDialog()
->setTitle(pht('Remove Payment Method'))
+ ->addHiddenInput('subscriptionID', $subscription_id)
->appendParagraph(
pht(
- 'Remove the payment method "%s" from your account?',
- phutil_tag(
- 'strong',
- array(),
- $method->getFullDisplayName())))
+ 'Remove the payment method %s from your account?',
+ $method_display))
->appendParagraph(
pht(
'You will no longer be able to make payments using this payment '.
diff --git a/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php b/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php
deleted file mode 100644
--- a/src/applications/phortune/controller/subscription/PhortuneSubscriptionViewController.php
+++ /dev/null
@@ -1,224 +0,0 @@
-<?php
-
-final class PhortuneSubscriptionViewController extends PhortuneController {
-
- public function handleRequest(AphrontRequest $request) {
- $viewer = $this->getViewer();
-
- $authority = $this->loadMerchantAuthority();
-
- $subscription_query = id(new PhortuneSubscriptionQuery())
- ->setViewer($viewer)
- ->withIDs(array($request->getURIData('id')))
- ->needTriggers(true);
-
- if ($authority) {
- $subscription_query->withMerchantPHIDs(array($authority->getPHID()));
- }
-
- $subscription = $subscription_query->executeOne();
- if (!$subscription) {
- return new Aphront404Response();
- }
-
- $can_edit = PhabricatorPolicyFilter::hasCapability(
- $viewer,
- $subscription,
- PhabricatorPolicyCapability::CAN_EDIT);
-
- $merchant = $subscription->getMerchant();
- $account = $subscription->getAccount();
-
- $account_id = $account->getID();
- $subscription_id = $subscription->getID();
-
- $title = $subscription->getSubscriptionFullName();
-
- $header = id(new PHUIHeaderView())
- ->setHeader($title)
- ->setHeaderIcon('fa-calendar-o');
-
- $curtain = $this->newCurtainView($subscription);
- $edit_uri = $subscription->getEditURI();
-
- $curtain->addAction(
- id(new PhabricatorActionView())
- ->setIcon('fa-credit-card')
- ->setName(pht('Manage Autopay'))
- ->setHref($edit_uri)
- ->setDisabled(!$can_edit)
- ->setWorkflow(!$can_edit));
-
- $crumbs = $this->buildApplicationCrumbs();
- if ($authority) {
- $this->addMerchantCrumb($crumbs, $merchant);
- } else {
- $this->addAccountCrumb($crumbs, $account);
- }
- $crumbs->addTextCrumb($subscription->getSubscriptionCrumbName());
- $crumbs->setBorder(true);
-
- $properties = id(new PHUIPropertyListView())
- ->setUser($viewer);
-
- $next_invoice = $subscription->getTrigger()->getNextEventPrediction();
- $properties->addProperty(
- pht('Next Invoice'),
- phabricator_datetime($next_invoice, $viewer));
-
- $default_method = $subscription->getDefaultPaymentMethodPHID();
- if ($default_method) {
- $method = id(new PhortunePaymentMethodQuery())
- ->setViewer($viewer)
- ->withPHIDs(array($default_method))
- ->withStatuses(
- array(
- PhortunePaymentMethod::STATUS_ACTIVE,
- ))
- ->executeOne();
- if ($method) {
- $handles = $this->loadViewerHandles(array($default_method));
- $autopay_method = $handles[$default_method]->renderLink();
- } else {
- $autopay_method = phutil_tag(
- 'em',
- array(),
- pht('<Deleted Payment Method>'));
- }
- } else {
- $autopay_method = phutil_tag(
- 'em',
- array(),
- pht('No Autopay Method Configured'));
- }
-
- $properties->addProperty(
- pht('Autopay With'),
- $autopay_method);
-
- $details = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Details'))
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->addPropertyList($properties);
-
- $due_box = $this->buildDueInvoices($subscription, $authority);
- $invoice_box = $this->buildPastInvoices($subscription, $authority);
-
- $view = id(new PHUITwoColumnView())
- ->setHeader($header)
- ->setCurtain($curtain)
- ->setMainColumn(array(
- $details,
- $due_box,
- $invoice_box,
- ));
-
- return $this->newPage()
- ->setTitle($title)
- ->setCrumbs($crumbs)
- ->appendChild($view);
- }
-
- private function buildDueInvoices(
- PhortuneSubscription $subscription,
- $authority) {
- $viewer = $this->getViewer();
-
- $invoices = id(new PhortuneCartQuery())
- ->setViewer($viewer)
- ->withSubscriptionPHIDs(array($subscription->getPHID()))
- ->needPurchases(true)
- ->withInvoices(true)
- ->execute();
-
- $phids = array();
- foreach ($invoices as $invoice) {
- $phids[] = $invoice->getPHID();
- $phids[] = $invoice->getMerchantPHID();
- foreach ($invoice->getPurchases() as $purchase) {
- $phids[] = $purchase->getPHID();
- }
- }
- $handles = $this->loadViewerHandles($phids);
-
- $invoice_table = id(new PhortuneOrderTableView())
- ->setUser($viewer)
- ->setCarts($invoices)
- ->setIsInvoices(true)
- ->setIsMerchantView((bool)$authority)
- ->setHandles($handles);
-
- $invoice_header = id(new PHUIHeaderView())
- ->setHeader(pht('Invoices Due'));
-
- return id(new PHUIObjectBoxView())
- ->setHeader($invoice_header)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($invoice_table);
- }
-
- private function buildPastInvoices(
- PhortuneSubscription $subscription,
- $authority) {
- $viewer = $this->getViewer();
-
- $invoices = id(new PhortuneCartQuery())
- ->setViewer($viewer)
- ->withSubscriptionPHIDs(array($subscription->getPHID()))
- ->needPurchases(true)
- ->withStatuses(
- array(
- PhortuneCart::STATUS_PURCHASING,
- PhortuneCart::STATUS_CHARGED,
- PhortuneCart::STATUS_HOLD,
- PhortuneCart::STATUS_REVIEW,
- PhortuneCart::STATUS_PURCHASED,
- ))
- ->setLimit(50)
- ->execute();
-
- $phids = array();
- foreach ($invoices as $invoice) {
- $phids[] = $invoice->getPHID();
- foreach ($invoice->getPurchases() as $purchase) {
- $phids[] = $purchase->getPHID();
- }
- }
- $handles = $this->loadViewerHandles($phids);
-
- $invoice_table = id(new PhortuneOrderTableView())
- ->setUser($viewer)
- ->setCarts($invoices)
- ->setHandles($handles);
-
- $account = $subscription->getAccount();
- $merchant = $subscription->getMerchant();
-
- $account_id = $account->getID();
- $merchant_id = $merchant->getID();
- $subscription_id = $subscription->getID();
-
- if ($authority) {
- $invoices_uri = $this->getApplicationURI(
- "merchant/{$merchant_id}/subscription/order/{$subscription_id}/");
- } else {
- $invoices_uri = $this->getApplicationURI(
- "{$account_id}/subscription/order/{$subscription_id}/");
- }
-
- $invoice_header = id(new PHUIHeaderView())
- ->setHeader(pht('Past Invoices'))
- ->addActionLink(
- id(new PHUIButtonView())
- ->setTag('a')
- ->setIcon('fa-list')
- ->setHref($invoices_uri)
- ->setText(pht('View All Invoices')));
-
- return id(new PHUIObjectBoxView())
- ->setHeader($invoice_header)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($invoice_table);
- }
-
-}
diff --git a/src/applications/phortune/editor/PhortuneSubscriptionEditor.php b/src/applications/phortune/editor/PhortuneSubscriptionEditor.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/editor/PhortuneSubscriptionEditor.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PhortuneSubscriptionEditor
+ extends PhabricatorApplicationTransactionEditor {
+
+ public function getEditorApplicationClass() {
+ return 'PhabricatorPhortuneApplication';
+ }
+
+ public function getEditorObjectsDescription() {
+ return pht('Phortune Subscriptions');
+ }
+
+ public function getCreateObjectTitle($author, $object) {
+ return pht('%s created this subscription.', $author);
+ }
+
+}
diff --git a/src/applications/phortune/phid/PhortunePaymentMethodPHIDType.php b/src/applications/phortune/phid/PhortunePaymentMethodPHIDType.php
--- a/src/applications/phortune/phid/PhortunePaymentMethodPHIDType.php
+++ b/src/applications/phortune/phid/PhortunePaymentMethodPHIDType.php
@@ -32,9 +32,9 @@
foreach ($handles as $phid => $handle) {
$method = $objects[$phid];
- $id = $method->getID();
-
- $handle->setName($method->getFullDisplayName());
+ $handle
+ ->setName($method->getFullDisplayName())
+ ->setURI($method->getURI());
}
}
diff --git a/src/applications/phortune/phid/PhortuneSubscriptionPHIDType.php b/src/applications/phortune/phid/PhortuneSubscriptionPHIDType.php
--- a/src/applications/phortune/phid/PhortuneSubscriptionPHIDType.php
+++ b/src/applications/phortune/phid/PhortuneSubscriptionPHIDType.php
@@ -32,11 +32,9 @@
foreach ($handles as $phid => $handle) {
$subscription = $objects[$phid];
- $id = $subscription->getID();
-
- $handle->setName($subscription->getSubscriptionName());
- $handle->setURI($subscription->getURI());
-
+ $handle
+ ->setName($subscription->getSubscriptionName())
+ ->setURI($subscription->getURI());
}
}
diff --git a/src/applications/phortune/query/PhortuneSubscriptionTransactionQuery.php b/src/applications/phortune/query/PhortuneSubscriptionTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/query/PhortuneSubscriptionTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhortuneSubscriptionTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ public function getTemplateApplicationTransaction() {
+ return new PhortuneSubscriptionTransaction();
+ }
+
+}
diff --git a/src/applications/phortune/storage/PhortunePaymentMethod.php b/src/applications/phortune/storage/PhortunePaymentMethod.php
--- a/src/applications/phortune/storage/PhortunePaymentMethod.php
+++ b/src/applications/phortune/storage/PhortunePaymentMethod.php
@@ -180,7 +180,6 @@
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
-
// See T13366. If you can edit the merchant associated with this payment
// method, you can view the payment method.
if ($capability === PhabricatorPolicyCapability::CAN_VIEW) {
diff --git a/src/applications/phortune/storage/PhortuneSubscription.php b/src/applications/phortune/storage/PhortuneSubscription.php
--- a/src/applications/phortune/storage/PhortuneSubscription.php
+++ b/src/applications/phortune/storage/PhortuneSubscription.php
@@ -3,8 +3,13 @@
/**
* A subscription bills users regularly.
*/
-final class PhortuneSubscription extends PhortuneDAO
- implements PhabricatorPolicyInterface {
+final class PhortuneSubscription
+ extends PhortuneDAO
+ implements
+ PhabricatorPolicyInterface,
+ PhabricatorExtendedPolicyInterface,
+ PhabricatorPolicyCodexInterface,
+ PhabricatorApplicationTransactionInterface {
const STATUS_ACTIVE = 'active';
const STATUS_CANCELLED = 'cancelled';
@@ -55,9 +60,8 @@
) + parent::getConfiguration();
}
- public function generatePHID() {
- return PhabricatorPHID::generateNewPHID(
- PhortuneSubscriptionPHIDType::TYPECONST);
+ public function getPHIDType() {
+ return PhortuneSubscriptionPHIDType::TYPECONST;
}
public static function initializeNewSubscription(
@@ -245,6 +249,16 @@
$purchase);
}
+/* -( PhabricatorApplicationTransactionInterface )------------------------- */
+
+
+ public function getApplicationTransactionEditor() {
+ return new PhortuneSubscriptionEditor();
+ }
+
+ public function getApplicationTransactionTemplate() {
+ return new PhortuneSubscriptionTransaction();
+ }
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@@ -257,26 +271,17 @@
}
public function getPolicy($capability) {
- // NOTE: Both view and edit use the account's edit policy. We punch a hole
- // through this for merchants, below.
- return $this
- ->getAccount()
- ->getPolicy(PhabricatorPolicyCapability::CAN_EDIT);
+ return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
- if ($this->getAccount()->hasAutomaticCapability($capability, $viewer)) {
- return true;
- }
-
- // If the viewer controls the merchant this subscription bills to, they can
- // view the subscription.
- if ($capability == PhabricatorPolicyCapability::CAN_VIEW) {
- $can_admin = PhabricatorPolicyFilter::hasCapability(
- $viewer,
- $this->getMerchant(),
- PhabricatorPolicyCapability::CAN_EDIT);
- if ($can_admin) {
+ // See T13366. If you can edit the merchant associated with this
+ // subscription, you can view the subscription.
+ if ($capability === PhabricatorPolicyCapability::CAN_VIEW) {
+ $any_edit = PhortuneMerchantQuery::canViewersEditMerchants(
+ array($viewer->getPHID()),
+ array($this->getMerchantPHID()));
+ if ($any_edit) {
return true;
}
}
@@ -284,12 +289,31 @@
return false;
}
- public function describeAutomaticCapability($capability) {
+
+/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
+
+
+ public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
+ if ($this->hasAutomaticCapability($capability, $viewer)) {
+ return array();
+ }
+
+ // See T13366. For blanket view and edit permissions on all subscriptions,
+ // you must be able to edit the associated account.
return array(
- pht('Subscriptions inherit the policies of the associated account.'),
- pht(
- 'The merchant you are subscribed with can review and manage the '.
- 'subscription.'),
+ array(
+ $this->getAccount(),
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ),
);
}
+
+
+/* -( PhabricatorPolicyCodexInterface )------------------------------------ */
+
+
+ public function newPolicyCodex() {
+ return new PhortuneSubscriptionPolicyCodex();
+ }
+
}
diff --git a/src/applications/phortune/storage/PhortuneSubscriptionTransaction.php b/src/applications/phortune/storage/PhortuneSubscriptionTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/storage/PhortuneSubscriptionTransaction.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PhortuneSubscriptionTransaction
+ extends PhabricatorModularTransaction {
+
+ public function getApplicationName() {
+ return 'phortune';
+ }
+
+ public function getApplicationTransactionType() {
+ return PhortuneSubscriptionPHIDType::TYPECONST;
+ }
+
+ public function getBaseTransactionClass() {
+ return 'PhortuneSubscriptionTransactionType';
+ }
+
+}
diff --git a/src/applications/phortune/xaction/subscription/PhortuneSubscriptionAutopayTransaction.php b/src/applications/phortune/xaction/subscription/PhortuneSubscriptionAutopayTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/xaction/subscription/PhortuneSubscriptionAutopayTransaction.php
@@ -0,0 +1,41 @@
+<?php
+
+final class PhortuneSubscriptionAutopayTransaction
+ extends PhortuneSubscriptionTransactionType {
+
+ const TRANSACTIONTYPE = 'autopay';
+
+ public function generateOldValue($object) {
+ return $object->getDefaultPaymentMethodPHID();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setDefaultPaymentMethodPHID($value);
+ }
+
+ public function getTitle() {
+ $old_phid = $this->getOldValue();
+ $new_phid = $this->getNewValue();
+
+ if ($old_phid && $new_phid) {
+ return pht(
+ '%s changed the automatic payment method for this subscription.',
+ $this->renderAuthor());
+ } else if ($new_phid) {
+ return pht(
+ '%s configured an automatic payment method for this subscription.',
+ $this->renderAuthor());
+ } else {
+ return pht(
+ '%s stopped automatic payments for this subscription.',
+ $this->renderAuthor());
+ }
+ }
+
+ public function shouldTryMFA(
+ $object,
+ PhabricatorApplicationTransaction $xaction) {
+ return true;
+ }
+
+}
diff --git a/src/applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php b/src/applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/xaction/subscription/PhortuneSubscriptionTransactionType.php
@@ -0,0 +1,4 @@
+<?php
+
+abstract class PhortuneSubscriptionTransactionType
+ extends PhabricatorModularTransactionType {}

File Metadata

Mime Type
text/plain
Expires
Mon, Nov 25, 11:43 PM (15 m, 23 s)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6787398
Default Alt Text
D20721.diff (45 KB)

Event Timeline