forked from phacility/phabricator
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add some of a billing daemon skeleton
Summary: Ref T6881. This adds the worker, and a script to make it easier to test. It doesn't actually invoice anything. I'm intentionally allowing the script to double-bill since it makes testing way easier (by letting you bill the same period over and over again), and provides a tool for recovery if billing screws up. (This diff isn't very interesting, just trying to avoid a 5K-line diff at the end.) Test Plan: Used `bin/phortune invoice ...` to get the worker to print out some date ranges which it would theoretically invoice. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6881 Differential Revision: https://secure.phabricator.com/D11577
- Loading branch information
epriestley
committed
Jan 30, 2015
1 parent
a65244c
commit d804598
Showing
10 changed files
with
301 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../scripts/setup/manage_phortune.php |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
165 changes: 165 additions & 0 deletions
165
src/applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
|
||
} |
4 changes: 4 additions & 0 deletions
4
src/applications/phortune/management/PhabricatorPhortuneManagementWorkflow.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<?php | ||
|
||
abstract class PhabricatorPhortuneManagementWorkflow | ||
extends PhabricatorManagementWorkflow {} |
82 changes: 82 additions & 0 deletions
82
src/applications/phortune/worker/PhortuneSubscriptionWorker.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters