Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php b/src/applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php
index 962bb88f0c..e1053e74a1 100644
--- a/src/applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php
+++ b/src/applications/phortune/management/PhabricatorPhortuneManagementInvoiceWorkflow.php
@@ -1,165 +1,166 @@
<?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,
+ 'trigger.this-epoch' => $next_time,
+ 'manual' => true,
),
array(
'objectPHID' => $subscription->getPHID(),
));
return 0;
}
}
diff --git a/src/applications/phortune/worker/PhortuneSubscriptionWorker.php b/src/applications/phortune/worker/PhortuneSubscriptionWorker.php
index 233eb5dccb..23cab0386f 100644
--- a/src/applications/phortune/worker/PhortuneSubscriptionWorker.php
+++ b/src/applications/phortune/worker/PhortuneSubscriptionWorker.php
@@ -1,215 +1,217 @@
<?php
final class PhortuneSubscriptionWorker extends PhabricatorWorker {
protected function doWork() {
$subscription = $this->loadSubscription();
$range = $this->getBillingPeriodRange($subscription);
list($last_epoch, $next_epoch) = $range;
$should_invoice = $subscription->shouldInvoiceForBillingPeriod(
$last_epoch,
$next_epoch);
if (!$should_invoice) {
return;
}
$account = $subscription->getAccount();
$merchant = $subscription->getMerchant();
$viewer = PhabricatorUser::getOmnipotentUser();
$product = id(new PhortuneProductQuery())
->setViewer($viewer)
->withClassAndRef('PhortuneSubscriptionProduct', $subscription->getPHID())
->executeOne();
$cart_implementation = id(new PhortuneSubscriptionCart())
->setSubscription($subscription);
// TODO: This isn't really ideal. It would be better to use an application
// actor than the original author of the subscription. In particular, if
// someone initiates a subscription, adds some other account managers, and
// later leaves the company, they'll continue "acting" here indefinitely.
// However, for now, some of the stuff later in the pipeline requires a
// valid actor with a real PHID. The subscription should eventually be
// able to create these invoices "as" the application it is acting on
// behalf of.
$actor = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($subscription->getAuthorPHID()))
->executeOne();
if (!$actor) {
throw new Exception(pht('Failed to load actor to bill subscription!'));
}
$cart = $account->newCart($actor, $cart_implementation, $merchant);
$purchase = $cart->newPurchase($actor, $product);
$currency = $subscription->getCostForBillingPeriodAsCurrency(
$last_epoch,
$next_epoch);
$purchase
->setBasePriceAsCurrency($currency)
->setMetadataValue('subscriptionPHID', $subscription->getPHID())
->setMetadataValue('epoch.start', $last_epoch)
->setMetadataValue('epoch.end', $next_epoch)
->save();
$cart->setSubscriptionPHID($subscription->getPHID());
$cart->activateCart();
try {
$issues = $this->chargeSubscription($actor, $subscription, $cart);
} catch (Exception $ex) {
$issues = array(
pht(
'There was a technical error while trying to automatically bill '.
'this subscription: %s',
$ex),
);
}
if (!$issues) {
// We're all done; charging the cart sends a billing email as a side
// effect.
return;
}
// We're shoving this through the CartEditor because it has all the logic
// for sending mail about carts. This doesn't really affect the state of
// the cart, but reduces the amount of code duplication.
$xactions = array();
$xactions[] = id(new PhortuneCartTransaction())
->setTransactionType(PhortuneCartTransaction::TYPE_INVOICED)
->setNewValue(true);
$content_source = PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_PHORTUNE,
array());
$acting_phid = id(new PhabricatorPhortuneApplication())->getPHID();
$editor = id(new PhortuneCartEditor())
->setActor($viewer)
->setActingAsPHID($acting_phid)
->setContentSource($content_source)
->setContinueOnMissingFields(true)
->setInvoiceIssues($issues)
->applyTransactions($cart, $xactions);
}
private function chargeSubscription(
PhabricatorUser $viewer,
PhortuneSubscription $subscription,
PhortuneCart $cart) {
$issues = array();
if (!$subscription->getDefaultPaymentMethodPHID()) {
$issues[] = pht(
'There is no payment method associated with this subscription, so '.
'it could not be billed automatically. Add a default payment method '.
'to enable automatic billing.');
return $issues;
}
$method = id(new PhortunePaymentMethodQuery())
->setViewer($viewer)
->withPHIDs(array($subscription->getDefaultPaymentMethodPHID()))
->executeOne();
if (!$method) {
$issues[] = pht(
'The payment method associated with this subscription is invalid '.
'or out of date, so it could not be automatically billed. Update '.
'the default payment method to enable automatic billing.');
return $issues;
}
$provider = $method->buildPaymentProvider();
$charge = $cart->willApplyCharge($viewer, $provider, $method);
try {
$provider->applyCharge($method, $charge);
} catch (Exception $ex) {
$cart->didFailCharge($charge);
$issues[] = pht(
'Automatic billing failed: %s',
$ex->getMessage());
return $issues;
}
$cart->didApplyCharge($charge);
}
/**
* 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');
+ $this_epoch = idx($data, 'trigger.this-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.'));
+ if (empty($data['manual'])) {
+ 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);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Mon, Jul 28, 2:22 AM (1 w, 19 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
186354
Default Alt Text
(13 KB)

Event Timeline