Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14478179
D11577.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
D11577.diff
View Options
diff --git a/bin/phortune b/bin/phortune
new file mode 120000
--- /dev/null
+++ b/bin/phortune
@@ -0,0 +1 @@
+../scripts/setup/manage_phortune.php
\ No newline at end of file
diff --git a/scripts/setup/manage_phortune.php b/scripts/setup/manage_phortune.php
new file mode 100755
--- /dev/null
+++ b/scripts/setup/manage_phortune.php
@@ -0,0 +1,21 @@
+#!/usr/bin/env php
+<?php
+
+$root = dirname(dirname(dirname(__FILE__)));
+require_once $root.'/scripts/__init_script__.php';
+
+$args = new PhutilArgumentParser($argv);
+$args->setTagline('manage billing');
+$args->setSynopsis(<<<EOSYNOPSIS
+**phortune** __command__ [__options__]
+ Manage billing.
+
+EOSYNOPSIS
+ );
+$args->parseStandardArguments();
+
+$workflows = id(new PhutilSymbolLoader())
+ ->setAncestorClass('PhabricatorPhortuneManagementWorkflow')
+ ->loadObjects();
+$workflows[] = new PhutilHelpArgumentWorkflow();
+$args->parseWorkflows($workflows);
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
@@ -2151,6 +2151,8 @@
'PhabricatorPholioConfigOptions' => 'applications/pholio/config/PhabricatorPholioConfigOptions.php',
'PhabricatorPholioMockTestDataGenerator' => 'applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php',
'PhabricatorPhortuneApplication' => 'applications/phortune/application/PhabricatorPhortuneApplication.php',
+ 'PhabricatorPhortuneManagementInvoiceWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php',
+ 'PhabricatorPhortuneManagementWorkflow' => 'applications/phortune/management/PhabricatorPhortuneManagementWorkflow.php',
'PhabricatorPhragmentApplication' => 'applications/phragment/application/PhabricatorPhragmentApplication.php',
'PhabricatorPhrequentApplication' => 'applications/phrequent/application/PhabricatorPhrequentApplication.php',
'PhabricatorPhrequentConfigOptions' => 'applications/phrequent/config/PhabricatorPhrequentConfigOptions.php',
@@ -2816,6 +2818,7 @@
'PhortuneSubscriptionSearchEngine' => 'applications/phortune/query/PhortuneSubscriptionSearchEngine.php',
'PhortuneSubscriptionTableView' => 'applications/phortune/view/PhortuneSubscriptionTableView.php',
'PhortuneSubscriptionViewController' => 'applications/phortune/controller/PhortuneSubscriptionViewController.php',
+ 'PhortuneSubscriptionWorker' => 'applications/phortune/worker/PhortuneSubscriptionWorker.php',
'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php',
'PhortuneWePayPaymentProvider' => 'applications/phortune/provider/PhortuneWePayPaymentProvider.php',
'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php',
@@ -5396,6 +5399,8 @@
'PhabricatorPholioConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPholioMockTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorPhortuneApplication' => 'PhabricatorApplication',
+ 'PhabricatorPhortuneManagementInvoiceWorkflow' => 'PhabricatorPhortuneManagementWorkflow',
+ 'PhabricatorPhortuneManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorPhragmentApplication' => 'PhabricatorApplication',
'PhabricatorPhrequentApplication' => 'PhabricatorApplication',
'PhabricatorPhrequentConfigOptions' => 'PhabricatorApplicationConfigOptions',
@@ -6170,6 +6175,7 @@
'PhortuneSubscriptionSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhortuneSubscriptionTableView' => 'AphrontView',
'PhortuneSubscriptionViewController' => 'PhortuneController',
+ 'PhortuneSubscriptionWorker' => 'PhabricatorWorker',
'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider',
'PhortuneWePayPaymentProvider' => 'PhortunePaymentProvider',
'PhragmentBrowseController' => 'PhragmentController',
diff --git a/src/applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php b/src/applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php
@@ -0,0 +1,165 @@
+<?php
+
+final class PhabricatorPhortuneManagementInvoiceWorkflow
+ extends PhabricatorPhortuneManagementWorkflow {
+
+ protected function didConstruct() {
+ $this
+ ->setName('invoice')
+ ->setSynopsis(
+ pht(
+ 'Invoices a subscription for a given billing period. This can '.
+ 'charge payment accounts twice.'))
+ ->setArguments(
+ array(
+ array(
+ 'name' => 'subscription',
+ 'param' => 'phid',
+ 'help' => pht('Subscription to invoice.'),
+ ),
+ array(
+ 'name' => 'now',
+ 'param' => 'time',
+ 'help' => pht(
+ 'Bill as though the current time is a specific time.'),
+ ),
+ array(
+ 'name' => 'last',
+ 'param' => 'time',
+ 'help' => pht('Set the start of the billing period.'),
+ ),
+ array(
+ 'name' => 'next',
+ 'param' => 'time',
+ 'help' => pht('Set the end of the billing period.'),
+ ),
+ array(
+ 'name' => 'auto-range',
+ 'help' => pht('Automatically use the current billing period.'),
+ ),
+ array(
+ 'name' => 'force',
+ 'help' => pht(
+ 'Skip the prompt warning you that this operation is '.
+ 'potentially dangerous.'),
+ ),
+ ));
+ }
+
+ public function execute(PhutilArgumentParser $args) {
+ $console = PhutilConsole::getConsole();
+ $viewer = $this->getViewer();
+
+ $subscription_phid = $args->getArg('subscription');
+ if (!$subscription_phid) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Specify which subscription to invoice with --subscription.'));
+ }
+
+ $subscription = id(new PhortuneSubscriptionQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($subscription_phid))
+ ->needTriggers(true)
+ ->executeOne();
+ if (!$subscription) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Unable to load subscription with PHID "%s".',
+ $subscription_phid));
+ }
+
+ $now = $args->getArg('now');
+ $now = $this->parseTimeArgument($now);
+ if (!$now) {
+ $now = PhabricatorTime::getNow();
+ }
+
+ $time_guard = PhabricatorTime::pushTime($now, date_default_timezone_get());
+
+ $console->writeOut(
+ "%s\n",
+ pht(
+ 'Set current time to %s.',
+ phabricator_datetime(PhabricatorTime::getNow(), $viewer)));
+
+ $auto_range = $args->getArg('auto-range');
+ $last_arg = $args->getArg('last');
+ $next_arg = $args->getARg('next');
+
+ if (!$auto_range && !$last_arg && !$next_arg) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Specify a billing range with --last and --next, or use '.
+ '--auto-range.'));
+ } else if (!$auto_range & (!$last_arg || !$next_arg)) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'When specifying --last or --next, you must specify both arguments '.
+ 'to define the beginning and end of the billing range.'));
+ } else if (!$auto_range && ($last_arg && $next_arg)) {
+ $last_time = $this->parseTimeArgument($args->getArg('last'));
+ $next_time = $this->parseTimeArgument($args->getArg('next'));
+ } else if ($auto_range && ($last_arg || $next_arg)) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Use either --auto-range or --last and --next to specify the '.
+ 'billing range, but not both.'));
+ } else {
+ $trigger = $subscription->getTrigger();
+ $event = $trigger->getEvent();
+ if (!$event) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Unable to calculate --auto-range, this subscription has not been '.
+ 'scheduled for billing yet. Wait for the trigger daemon to '.
+ 'schedule the subscription.'));
+ }
+ $last_time = $event->getLastEventEpoch();
+ $next_time = $event->getNextEventEpoch();
+ }
+
+ $console->writeOut(
+ "%s\n",
+ pht(
+ 'Preparing to invoice subscription "%s" from %s to %s.',
+ $subscription->getSubscriptionName(),
+ ($last_time
+ ? phabricator_datetime($last_time, $viewer)
+ : pht('subscription creation')),
+ phabricator_datetime($next_time, $viewer)));
+
+ PhabricatorWorker::setRunAllTasksInProcess(true);
+
+ if (!$args->getArg('force')) {
+ $console->writeOut(
+ "**<bg:yellow> %s </bg>**\n%s\n",
+ pht('WARNING'),
+ phutil_console_wrap(
+ pht(
+ 'Manually invoicing will double bill payment accounts if the '.
+ 'range overlaps an existing or future invoice. This script is '.
+ 'intended for testing and development, and should not be part '.
+ 'of routine billing operations. If you continue, you may '.
+ 'incorrectly overcharge customers.')));
+
+ if (!phutil_console_confirm(pht('Really invoice this subscription?'))) {
+ throw new Exception(pht('Declining to invoice.'));
+ }
+ }
+
+ PhabricatorWorker::scheduleTask(
+ 'PhortuneSubscriptionWorker',
+ array(
+ 'subscriptionPHID' => $subscription->getPHID(),
+ 'trigger.last-epoch' => $last_time,
+ 'trigger.next-epoch' => $next_time,
+ ),
+ array(
+ 'objectPHID' => $subscription->getPHID(),
+ ));
+
+ return 0;
+ }
+
+}
diff --git a/src/applications/phortune/management/PhabricatorPhortuneManagementWorkflow.php b/src/applications/phortune/management/PhabricatorPhortuneManagementWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/management/PhabricatorPhortuneManagementWorkflow.php
@@ -0,0 +1,4 @@
+<?php
+
+abstract class PhabricatorPhortuneManagementWorkflow
+ extends PhabricatorManagementWorkflow {}
diff --git a/src/applications/phortune/worker/PhortuneSubscriptionWorker.php b/src/applications/phortune/worker/PhortuneSubscriptionWorker.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/worker/PhortuneSubscriptionWorker.php
@@ -0,0 +1,82 @@
+<?php
+
+final class PhortuneSubscriptionWorker extends PhabricatorWorker {
+
+ protected function doWork() {
+ $subscription = $this->loadSubscription();
+
+ $range = $this->getBillingPeriodRange($subscription);
+ list($last_epoch, $next_epoch) = $range;
+
+ // TODO: Actual billing.
+ echo "Bill from {$last_epoch} to {$next_epoch}.\n";
+ }
+
+
+ /**
+ * Load the subscription to generate an invoice for.
+ *
+ * @return PhortuneSubscription The subscription to invoice.
+ */
+ private function loadSubscription() {
+ $viewer = PhabricatorUser::getOmnipotentUser();
+
+ $data = $this->getTaskData();
+ $subscription_phid = idx($data, 'subscriptionPHID');
+
+ $subscription = id(new PhortuneSubscriptionQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($subscription_phid))
+ ->executeOne();
+ if (!$subscription) {
+ throw new PhabricatorWorkerPermanentFailureException(
+ pht(
+ 'Failed to load subscription with PHID "%s".',
+ $subscription_phid));
+ }
+
+ return $subscription;
+ }
+
+
+ /**
+ * Get the start and end epoch timestamps for this billing period.
+ *
+ * @param PhortuneSubscription The subscription being billed.
+ * @return pair<int, int> Beginning and end of the billing range.
+ */
+ private function getBillingPeriodRange(PhortuneSubscription $subscription) {
+ $data = $this->getTaskData();
+
+ $last_epoch = idx($data, 'trigger.last-epoch');
+ if (!$last_epoch) {
+ // If this is the first time the subscription is firing, use the
+ // creation date as the start of the billing period.
+ $last_epoch = $subscription->getDateCreated();
+ }
+ $this_epoch = idx($data, 'trigger.next-epoch');
+
+ if (!$last_epoch || !$this_epoch) {
+ throw new PhabricatorWorkerPermanentFailureException(
+ pht(
+ 'Subscription is missing billing period information.'));
+ }
+
+ $period_length = ($this_epoch - $last_epoch);
+ if ($period_length <= 0) {
+ throw new PhabricatorWorkerPermanentFailureException(
+ pht(
+ 'Subscription has invalid billing period.'));
+ }
+
+ if (PhabricatorTime::getNow() < $this_epoch) {
+ throw new Exception(
+ pht(
+ 'Refusing to generate a subscription invoice for a billing period '.
+ 'which ends in the future.'));
+ }
+
+ return array($last_epoch, $this_epoch);
+ }
+
+}
diff --git a/src/infrastructure/daemon/workers/action/PhabricatorScheduleTaskTriggerAction.php b/src/infrastructure/daemon/workers/action/PhabricatorScheduleTaskTriggerAction.php
--- a/src/infrastructure/daemon/workers/action/PhabricatorScheduleTaskTriggerAction.php
+++ b/src/infrastructure/daemon/workers/action/PhabricatorScheduleTaskTriggerAction.php
@@ -38,7 +38,10 @@
public function execute($last_epoch, $this_epoch) {
PhabricatorWorker::scheduleTask(
$this->getProperty('class'),
- $this->getProperty('data'),
+ $this->getProperty('data') + array(
+ 'trigger.last-epoch' => $last_epoch,
+ 'trigger.this-epoch' => $this_epoch,
+ ),
$this->getProperty('options'));
}
diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php
--- a/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php
+++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementFireWorkflow.php
@@ -47,12 +47,12 @@
$triggers = $this->loadTriggers($args);
$now = $args->getArg('now');
- $now = $this->parseTime($now);
+ $now = $this->parseTimeArgument($now);
if (!$now) {
$now = PhabricatorTime::getNow();
}
- PhabricatorTime::pushTime($now, date_default_timezone_get());
+ $time_guard = PhabricatorTime::pushTime($now, date_default_timezone_get());
$console->writeOut(
"%s\n",
@@ -60,8 +60,8 @@
'Set current time to %s.',
phabricator_datetime(PhabricatorTime::getNow(), $viewer)));
- $last_time = $this->parseTime($args->getArg('last'));
- $next_time = $this->parseTime($args->getArg('next'));
+ $last_time = $this->parseTimeArgument($args->getArg('last'));
+ $next_time = $this->parseTimeArgument($args->getArg('next'));
PhabricatorWorker::setRunAllTasksInProcess(true);
@@ -84,7 +84,7 @@
$console->writeOut(
"%s\n",
pht(
- 'Trigger is not scheduled to execute. Use --at to simluate '.
+ 'Trigger is not scheduled to execute. Use --next to simluate '.
'a scheduled event.'));
continue;
} else {
diff --git a/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementWorkflow.php b/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementWorkflow.php
--- a/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementWorkflow.php
+++ b/src/infrastructure/daemon/workers/management/PhabricatorWorkerTriggerManagementWorkflow.php
@@ -42,17 +42,4 @@
return pht('Trigger %d', $trigger->getID());
}
- protected function parseTime($time) {
- if (!strlen($time)) {
- return null;
- }
-
- $epoch = strtotime($time);
- if ($epoch <= 0) {
- throw new PhutilArgumentUsageException(
- pht('Unable to parse time "%s".', $time));
- }
- return $epoch;
- }
-
}
diff --git a/src/infrastructure/management/PhabricatorManagementWorkflow.php b/src/infrastructure/management/PhabricatorManagementWorkflow.php
--- a/src/infrastructure/management/PhabricatorManagementWorkflow.php
+++ b/src/infrastructure/management/PhabricatorManagementWorkflow.php
@@ -13,4 +13,17 @@
return PhabricatorUser::getOmnipotentUser();
}
+ protected function parseTimeArgument($time) {
+ if (!strlen($time)) {
+ return null;
+ }
+
+ $epoch = strtotime($time);
+ if ($epoch <= 0) {
+ throw new PhutilArgumentUsageException(
+ pht('Unable to parse time "%s".', $time));
+ }
+ return $epoch;
+ }
+
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Dec 29, 4:45 PM (8 h, 48 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6943075
Default Alt Text
D11577.diff (16 KB)
Attached To
Mode
D11577: Add some of a billing daemon skeleton
Attached
Detach File
Event Timeline
Log In to Comment