Kind of generate a bill for users
Summary: Ref T6881. This generates a product, purchase and invoice for users, and there's sort of some UI for them. Stuff it doesn't do yet: - Try to autobill when we have a CC; - actually tell the user they should pay it; - ask the application for anything like "how much should we charge", or tell the application anything like "the user paid". However, these work: - You can //technically// pay the invoices. - You can see the invoices you paid in the past. Test Plan: Used `bin/phriction invoice` to double-bill myself over and over again. Paid one of the invoices. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6881 Differential Revision: https://secure.phabricator.com/D11580
This commit is contained in:
		| @@ -0,0 +1,2 @@ | |||||||
|  | ALTER TABLE {$NAMESPACE}_phortune.phortune_cart | ||||||
|  |   ADD subscriptionPHID VARBINARY(64); | ||||||
							
								
								
									
										2
									
								
								resources/sql/autopatches/20150130.phortune.2.subkey.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								resources/sql/autopatches/20150130.phortune.2.subkey.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | ALTER TABLE {$NAMESPACE}_phortune.phortune_cart | ||||||
|  |   ADD KEY `key_subscription` (subscriptionPHID); | ||||||
| @@ -2811,9 +2811,11 @@ phutil_register_library_map(array( | |||||||
|     'PhortuneSchemaSpec' => 'applications/phortune/storage/PhortuneSchemaSpec.php', |     'PhortuneSchemaSpec' => 'applications/phortune/storage/PhortuneSchemaSpec.php', | ||||||
|     'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php', |     'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php', | ||||||
|     'PhortuneSubscription' => 'applications/phortune/storage/PhortuneSubscription.php', |     'PhortuneSubscription' => 'applications/phortune/storage/PhortuneSubscription.php', | ||||||
|  |     'PhortuneSubscriptionCart' => 'applications/phortune/cart/PhortuneSubscriptionCart.php', | ||||||
|     'PhortuneSubscriptionImplementation' => 'applications/phortune/subscription/PhortuneSubscriptionImplementation.php', |     'PhortuneSubscriptionImplementation' => 'applications/phortune/subscription/PhortuneSubscriptionImplementation.php', | ||||||
|     'PhortuneSubscriptionListController' => 'applications/phortune/controller/PhortuneSubscriptionListController.php', |     'PhortuneSubscriptionListController' => 'applications/phortune/controller/PhortuneSubscriptionListController.php', | ||||||
|     'PhortuneSubscriptionPHIDType' => 'applications/phortune/phid/PhortuneSubscriptionPHIDType.php', |     'PhortuneSubscriptionPHIDType' => 'applications/phortune/phid/PhortuneSubscriptionPHIDType.php', | ||||||
|  |     'PhortuneSubscriptionProduct' => 'applications/phortune/product/PhortuneSubscriptionProduct.php', | ||||||
|     'PhortuneSubscriptionQuery' => 'applications/phortune/query/PhortuneSubscriptionQuery.php', |     'PhortuneSubscriptionQuery' => 'applications/phortune/query/PhortuneSubscriptionQuery.php', | ||||||
|     'PhortuneSubscriptionSearchEngine' => 'applications/phortune/query/PhortuneSubscriptionSearchEngine.php', |     'PhortuneSubscriptionSearchEngine' => 'applications/phortune/query/PhortuneSubscriptionSearchEngine.php', | ||||||
|     'PhortuneSubscriptionTableView' => 'applications/phortune/view/PhortuneSubscriptionTableView.php', |     'PhortuneSubscriptionTableView' => 'applications/phortune/view/PhortuneSubscriptionTableView.php', | ||||||
| @@ -6169,8 +6171,10 @@ phutil_register_library_map(array( | |||||||
|       'PhortuneDAO', |       'PhortuneDAO', | ||||||
|       'PhabricatorPolicyInterface', |       'PhabricatorPolicyInterface', | ||||||
|     ), |     ), | ||||||
|  |     'PhortuneSubscriptionCart' => 'PhortuneCartImplementation', | ||||||
|     'PhortuneSubscriptionListController' => 'PhortuneController', |     'PhortuneSubscriptionListController' => 'PhortuneController', | ||||||
|     'PhortuneSubscriptionPHIDType' => 'PhabricatorPHIDType', |     'PhortuneSubscriptionPHIDType' => 'PhabricatorPHIDType', | ||||||
|  |     'PhortuneSubscriptionProduct' => 'PhortuneProductImplementation', | ||||||
|     'PhortuneSubscriptionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', |     'PhortuneSubscriptionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', | ||||||
|     'PhortuneSubscriptionSearchEngine' => 'PhabricatorApplicationSearchEngine', |     'PhortuneSubscriptionSearchEngine' => 'PhabricatorApplicationSearchEngine', | ||||||
|     'PhortuneSubscriptionTableView' => 'AphrontView', |     'PhortuneSubscriptionTableView' => 'AphrontView', | ||||||
|   | |||||||
| @@ -50,6 +50,8 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { | |||||||
|               => 'PhortuneSubscriptionListController', |               => 'PhortuneSubscriptionListController', | ||||||
|             'view/(?P<id>\d+)/' |             'view/(?P<id>\d+)/' | ||||||
|               => 'PhortuneSubscriptionViewController', |               => 'PhortuneSubscriptionViewController', | ||||||
|  |             'order/(?P<subscriptionID>\d+)/' | ||||||
|  |               => 'PhortuneCartListController', | ||||||
|           ), |           ), | ||||||
|           'charge/(?:query/(?P<queryKey>[^/]+)/)?' |           'charge/(?:query/(?P<queryKey>[^/]+)/)?' | ||||||
|             => 'PhortuneChargeListController', |             => 'PhortuneChargeListController', | ||||||
| @@ -90,6 +92,8 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication { | |||||||
|               => 'PhortuneSubscriptionListController', |               => 'PhortuneSubscriptionListController', | ||||||
|             'view/(?P<id>\d+)/' |             'view/(?P<id>\d+)/' | ||||||
|               => 'PhortuneSubscriptionViewController', |               => 'PhortuneSubscriptionViewController', | ||||||
|  |             'order/(?P<subscriptionID>\d+)/' | ||||||
|  |               => 'PhortuneCartListController', | ||||||
|           ), |           ), | ||||||
|           '(?P<id>\d+)/' => 'PhortuneMerchantViewController', |           '(?P<id>\d+)/' => 'PhortuneMerchantViewController', | ||||||
|         ), |         ), | ||||||
|   | |||||||
							
								
								
									
										89
									
								
								src/applications/phortune/cart/PhortuneSubscriptionCart.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/applications/phortune/cart/PhortuneSubscriptionCart.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class PhortuneSubscriptionCart | ||||||
|  |    extends PhortuneCartImplementation { | ||||||
|  |  | ||||||
|  |   private $subscriptionPHID; | ||||||
|  |   private $subscription; | ||||||
|  |  | ||||||
|  |   public function setSubscriptionPHID($subscription_phid) { | ||||||
|  |     $this->subscriptionPHID = $subscription_phid; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getSubscriptionPHID() { | ||||||
|  |     return $this->subscriptionPHID; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function setSubscription(PhortuneSubscription $subscription) { | ||||||
|  |     $this->subscription = $subscription; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getSubscription() { | ||||||
|  |     return $this->subscription; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getName(PhortuneCart $cart) { | ||||||
|  |     return pht('Subscription'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function willCreateCart( | ||||||
|  |     PhabricatorUser $viewer, | ||||||
|  |     PhortuneCart $cart) { | ||||||
|  |  | ||||||
|  |     $subscription = $this->getSubscription(); | ||||||
|  |     if (!$subscription) { | ||||||
|  |       throw new Exception( | ||||||
|  |         pht('Call setSubscription() before building a cart!')); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $cart->setMetadataValue('subscriptionPHID', $subscription->getPHID()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function loadImplementationsForCarts( | ||||||
|  |     PhabricatorUser $viewer, | ||||||
|  |     array $carts) { | ||||||
|  |  | ||||||
|  |     $phids = array(); | ||||||
|  |     foreach ($carts as $cart) { | ||||||
|  |       $phids[] = $cart->getMetadataValue('subscriptionPHID'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $subscriptions = id(new PhortuneSubscriptionQuery()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->withPHIDs($phids) | ||||||
|  |       ->execute(); | ||||||
|  |     $subscriptions = mpull($subscriptions, null, 'getPHID'); | ||||||
|  |  | ||||||
|  |     $objects = array(); | ||||||
|  |     foreach ($carts as $key => $cart) { | ||||||
|  |       $subscription_phid = $cart->getMetadataValue('subscriptionPHID'); | ||||||
|  |       $subscription = idx($subscriptions, $subscription_phid); | ||||||
|  |       if (!$subscription) { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       $object = id(new PhortuneSubscriptionCart()) | ||||||
|  |         ->setSubscriptionPHID($subscription_phid) | ||||||
|  |         ->setSubscription($subscription); | ||||||
|  |  | ||||||
|  |       $objects[$key] = $object; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $objects; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getCancelURI(PhortuneCart $cart) { | ||||||
|  |     return $this->getSubscription()->getURI(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getDoneURI(PhortuneCart $cart) { | ||||||
|  |     return $this->getSubscription()->getURI(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getDoneActionName(PhortuneCart $cart) { | ||||||
|  |     return pht('Return to Subscription'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -3,29 +3,35 @@ | |||||||
| final class PhortuneCartListController | final class PhortuneCartListController | ||||||
|   extends PhortuneController { |   extends PhortuneController { | ||||||
|  |  | ||||||
|   private $accountID; |  | ||||||
|   private $merchantID; |  | ||||||
|   private $queryKey; |  | ||||||
|  |  | ||||||
|   private $merchant; |   private $merchant; | ||||||
|   private $account; |   private $account; | ||||||
|  |   private $subscription; | ||||||
|  |  | ||||||
|   public function willProcessRequest(array $data) { |   public function handleRequest(AphrontRequest $request) { | ||||||
|     $this->merchantID = idx($data, 'merchantID'); |     $viewer = $this->getViewer(); | ||||||
|     $this->accountID = idx($data, 'accountID'); |  | ||||||
|     $this->queryKey = idx($data, 'queryKey'); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public function processRequest() { |     $merchant_id = $request->getURIData('merchantID'); | ||||||
|     $request = $this->getRequest(); |     $account_id = $request->getURIData('accountID'); | ||||||
|     $viewer = $request->getUser(); |     $subscription_id = $request->getURIData('subscriptionID'); | ||||||
|  |  | ||||||
|     $engine = new PhortuneCartSearchEngine(); |     $engine = new PhortuneCartSearchEngine(); | ||||||
|  |  | ||||||
|     if ($this->merchantID) { |     if ($subscription_id) { | ||||||
|  |       $subscription = id(new PhortuneSubscriptionQuery()) | ||||||
|  |         ->setViewer($viewer) | ||||||
|  |         ->withIDs(array($subscription_id)) | ||||||
|  |         ->executeOne(); | ||||||
|  |       if (!$subscription) { | ||||||
|  |         return new Aphront404Response(); | ||||||
|  |       } | ||||||
|  |       $this->subscription = $subscription; | ||||||
|  |       $engine->setSubscription($subscription); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if ($merchant_id) { | ||||||
|       $merchant = id(new PhortuneMerchantQuery()) |       $merchant = id(new PhortuneMerchantQuery()) | ||||||
|         ->setViewer($viewer) |         ->setViewer($viewer) | ||||||
|         ->withIDs(array($this->merchantID)) |         ->withIDs(array($merchant_id)) | ||||||
|         ->requireCapabilities( |         ->requireCapabilities( | ||||||
|           array( |           array( | ||||||
|             PhabricatorPolicyCapability::CAN_VIEW, |             PhabricatorPolicyCapability::CAN_VIEW, | ||||||
| @@ -37,10 +43,10 @@ final class PhortuneCartListController | |||||||
|       } |       } | ||||||
|       $this->merchant = $merchant; |       $this->merchant = $merchant; | ||||||
|       $engine->setMerchant($merchant); |       $engine->setMerchant($merchant); | ||||||
|     } else if ($this->accountID) { |     } else if ($account_id) { | ||||||
|       $account = id(new PhortuneAccountQuery()) |       $account = id(new PhortuneAccountQuery()) | ||||||
|         ->setViewer($viewer) |         ->setViewer($viewer) | ||||||
|         ->withIDs(array($this->accountID)) |         ->withIDs(array($account_id)) | ||||||
|         ->requireCapabilities( |         ->requireCapabilities( | ||||||
|           array( |           array( | ||||||
|             PhabricatorPolicyCapability::CAN_VIEW, |             PhabricatorPolicyCapability::CAN_VIEW, | ||||||
| @@ -57,7 +63,7 @@ final class PhortuneCartListController | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     $controller = id(new PhabricatorApplicationSearchController()) |     $controller = id(new PhabricatorApplicationSearchController()) | ||||||
|       ->setQueryKey($this->queryKey) |       ->setQueryKey($request->getURIData('queryKey')) | ||||||
|       ->setSearchEngine($engine) |       ->setSearchEngine($engine) | ||||||
|       ->setNavigation($this->buildSideNavView()); |       ->setNavigation($this->buildSideNavView()); | ||||||
|  |  | ||||||
| @@ -82,27 +88,40 @@ final class PhortuneCartListController | |||||||
|   protected function buildApplicationCrumbs() { |   protected function buildApplicationCrumbs() { | ||||||
|     $crumbs = parent::buildApplicationCrumbs(); |     $crumbs = parent::buildApplicationCrumbs(); | ||||||
|  |  | ||||||
|  |     $subscription = $this->subscription; | ||||||
|  |  | ||||||
|     $merchant = $this->merchant; |     $merchant = $this->merchant; | ||||||
|     if ($merchant) { |     if ($merchant) { | ||||||
|       $id = $merchant->getID(); |       $id = $merchant->getID(); | ||||||
|       $crumbs->addTextCrumb( |       $this->addMerchantCrumb($crumbs, $merchant); | ||||||
|         $merchant->getName(), |       if (!$subscription) { | ||||||
|         $this->getApplicationURI("merchant/{$id}/")); |  | ||||||
|         $crumbs->addTextCrumb( |         $crumbs->addTextCrumb( | ||||||
|           pht('Orders'), |           pht('Orders'), | ||||||
|           $this->getApplicationURI("merchant/orders/{$id}/")); |           $this->getApplicationURI("merchant/orders/{$id}/")); | ||||||
|       } |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     $account = $this->account; |     $account = $this->account; | ||||||
|     if ($account) { |     if ($account) { | ||||||
|       $id = $account->getID(); |       $id = $account->getID(); | ||||||
|       $crumbs->addTextCrumb( |       $this->addAccountCrumb($crumbs, $account); | ||||||
|         $account->getName(), |       if (!$subscription) { | ||||||
|         $this->getApplicationURI("{$id}/")); |  | ||||||
|         $crumbs->addTextCrumb( |         $crumbs->addTextCrumb( | ||||||
|           pht('Orders'), |           pht('Orders'), | ||||||
|           $this->getApplicationURI("{$id}/order/")); |           $this->getApplicationURI("{$id}/order/")); | ||||||
|       } |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if ($subscription) { | ||||||
|  |       if ($merchant) { | ||||||
|  |         $subscription_uri = $subscription->getMerchantURI(); | ||||||
|  |       } else { | ||||||
|  |         $subscription_uri = $subscription->getURI(); | ||||||
|  |       } | ||||||
|  |       $crumbs->addTextCrumb( | ||||||
|  |         $subscription->getSubscriptionName(), | ||||||
|  |         $subscription_uri); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return $crumbs; |     return $crumbs; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -15,6 +15,8 @@ final class PhortuneSubscriptionViewController extends PhortuneController { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     $is_merchant = (bool)$request->getURIData('merchantID'); |     $is_merchant = (bool)$request->getURIData('merchantID'); | ||||||
|  |     $merchant = $subscription->getMerchant(); | ||||||
|  |     $account = $subscription->getAccount(); | ||||||
|  |  | ||||||
|     $title = pht('Subscription: %s', $subscription->getSubscriptionName()); |     $title = pht('Subscription: %s', $subscription->getSubscriptionName()); | ||||||
|  |  | ||||||
| @@ -27,9 +29,9 @@ final class PhortuneSubscriptionViewController extends PhortuneController { | |||||||
|  |  | ||||||
|     $crumbs = $this->buildApplicationCrumbs(); |     $crumbs = $this->buildApplicationCrumbs(); | ||||||
|     if ($is_merchant) { |     if ($is_merchant) { | ||||||
|       $this->addMerchantCrumb($crumbs, $subscription->getMerchant()); |       $this->addMerchantCrumb($crumbs, $merchant); | ||||||
|     } else { |     } else { | ||||||
|       $this->addAccountCrumb($crumbs, $subscription->getAccount()); |       $this->addAccountCrumb($crumbs, $account); | ||||||
|     } |     } | ||||||
|     $crumbs->addTextCrumb(pht('Subscription %d', $subscription->getID())); |     $crumbs->addTextCrumb(pht('Subscription %d', $subscription->getID())); | ||||||
|  |  | ||||||
| @@ -46,10 +48,66 @@ final class PhortuneSubscriptionViewController extends PhortuneController { | |||||||
|       ->setHeader($header) |       ->setHeader($header) | ||||||
|       ->addPropertyList($properties); |       ->addPropertyList($properties); | ||||||
|  |  | ||||||
|  |     $carts = 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, | ||||||
|  |         )) | ||||||
|  |       ->execute(); | ||||||
|  |  | ||||||
|  |     $phids = array(); | ||||||
|  |     foreach ($carts as $cart) { | ||||||
|  |       $phids[] = $cart->getPHID(); | ||||||
|  |       foreach ($cart->getPurchases() as $purchase) { | ||||||
|  |         $phids[] = $purchase->getPHID(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     $handles = $this->loadViewerHandles($phids); | ||||||
|  |  | ||||||
|  |     $invoice_table = id(new PhortuneOrderTableView()) | ||||||
|  |       ->setUser($viewer) | ||||||
|  |       ->setCarts($carts) | ||||||
|  |       ->setHandles($handles); | ||||||
|  |  | ||||||
|  |     $account_id = $account->getID(); | ||||||
|  |     $merchant_id = $merchant->getID(); | ||||||
|  |     $subscription_id = $subscription->getID(); | ||||||
|  |  | ||||||
|  |     if ($is_merchant) { | ||||||
|  |       $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('Recent Invoices')) | ||||||
|  |       ->addActionLink( | ||||||
|  |         id(new PHUIButtonView()) | ||||||
|  |           ->setTag('a') | ||||||
|  |           ->setIcon( | ||||||
|  |             id(new PHUIIconView()) | ||||||
|  |               ->setIconFont('fa-list')) | ||||||
|  |           ->setHref($invoices_uri) | ||||||
|  |           ->setText(pht('View All Invoices'))); | ||||||
|  |  | ||||||
|  |     $invoice_box = id(new PHUIObjectBoxView()) | ||||||
|  |       ->setHeader($invoice_header) | ||||||
|  |       ->appendChild($invoice_table); | ||||||
|  |  | ||||||
|     return $this->buildApplicationPage( |     return $this->buildApplicationPage( | ||||||
|       array( |       array( | ||||||
|         $crumbs, |         $crumbs, | ||||||
|         $object_box, |         $object_box, | ||||||
|  |         $invoice_box, | ||||||
|       ), |       ), | ||||||
|       array( |       array( | ||||||
|         'title' => $title, |         'title' => $title, | ||||||
|   | |||||||
| @@ -0,0 +1,90 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class PhortuneSubscriptionProduct | ||||||
|  |   extends  PhortuneProductImplementation { | ||||||
|  |  | ||||||
|  |   private $viewer; | ||||||
|  |   private $subscriptionPHID; | ||||||
|  |   private $subscription; | ||||||
|  |  | ||||||
|  |   public function setSubscriptionPHID($subscription_phid) { | ||||||
|  |     $this->subscriptionPHID = $subscription_phid; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getSubscriptionPHID() { | ||||||
|  |     return $this->subscriptionPHID; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function setSubscription(PhortuneSubscription $subscription) { | ||||||
|  |     $this->subscription = $subscription; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getSubscription() { | ||||||
|  |     return $this->subscription; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function setViewer(PhabricatorUser $viewer) { | ||||||
|  |     $this->viewer = $viewer; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getViewer() { | ||||||
|  |     return $this->viewer; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getRef() { | ||||||
|  |     return $this->getSubscriptionPHID(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getName(PhortuneProduct $product) { | ||||||
|  |     return $this->getSubscription()->getSubscriptionName(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getPriceAsCurrency(PhortuneProduct $product) { | ||||||
|  |     return PhortuneCurrency::newEmptyCurrency(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function didPurchaseProduct( | ||||||
|  |     PhortuneProduct $product, | ||||||
|  |     PhortunePurchase $purchase) { | ||||||
|  |     // TODO: Callback the subscription. | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function didRefundProduct( | ||||||
|  |     PhortuneProduct $product, | ||||||
|  |     PhortunePurchase $purchase, | ||||||
|  |     PhortuneCurrency $amount) { | ||||||
|  |     // TODO: Callback the subscription. | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function loadImplementationsForRefs( | ||||||
|  |     PhabricatorUser $viewer, | ||||||
|  |     array $refs) { | ||||||
|  |  | ||||||
|  |     $subscriptions = id(new PhortuneSubscriptionQuery()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->withPHIDs($refs) | ||||||
|  |       ->execute(); | ||||||
|  |     $subscriptions = mpull($subscriptions, null, 'getPHID'); | ||||||
|  |  | ||||||
|  |     $objects = array(); | ||||||
|  |     foreach ($refs as $ref) { | ||||||
|  |       $subscription = idx($subscriptions, $ref); | ||||||
|  |       if (!$subscription) { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       $objects[] = id(new PhortuneSubscriptionProduct()) | ||||||
|  |         ->setViewer($viewer) | ||||||
|  |         ->setSubscriptionPHID($ref) | ||||||
|  |         ->setSubscription($subscription); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $objects; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -7,6 +7,7 @@ final class PhortuneCartQuery | |||||||
|   private $phids; |   private $phids; | ||||||
|   private $accountPHIDs; |   private $accountPHIDs; | ||||||
|   private $merchantPHIDs; |   private $merchantPHIDs; | ||||||
|  |   private $subscriptionPHIDs; | ||||||
|   private $statuses; |   private $statuses; | ||||||
|  |  | ||||||
|   private $needPurchases; |   private $needPurchases; | ||||||
| @@ -31,6 +32,11 @@ final class PhortuneCartQuery | |||||||
|     return $this; |     return $this; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public function withSubscriptionPHIDs(array $subscription_phids) { | ||||||
|  |     $this->subscriptionPHIDs = $subscription_phids; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   public function withStatuses(array $statuses) { |   public function withStatuses(array $statuses) { | ||||||
|     $this->statuses = $statuses; |     $this->statuses = $statuses; | ||||||
|     return $this; |     return $this; | ||||||
| @@ -158,6 +164,13 @@ final class PhortuneCartQuery | |||||||
|         $this->merchantPHIDs); |         $this->merchantPHIDs); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if ($this->subscriptionPHIDs !== null) { | ||||||
|  |       $where[] = qsprintf( | ||||||
|  |         $conn, | ||||||
|  |         'cart.subscriptionPHID IN (%Ls)', | ||||||
|  |         $this->subscriptionPHIDs); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if ($this->statuses !== null) { |     if ($this->statuses !== null) { | ||||||
|       $where[] = qsprintf( |       $where[] = qsprintf( | ||||||
|         $conn, |         $conn, | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ final class PhortuneCartSearchEngine | |||||||
|  |  | ||||||
|   private $merchant; |   private $merchant; | ||||||
|   private $account; |   private $account; | ||||||
|  |   private $subscription; | ||||||
|  |  | ||||||
|   public function setAccount(PhortuneAccount $account) { |   public function setAccount(PhortuneAccount $account) { | ||||||
|     $this->account = $account; |     $this->account = $account; | ||||||
| @@ -24,6 +25,15 @@ final class PhortuneCartSearchEngine | |||||||
|     return $this->merchant; |     return $this->merchant; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public function setSubscription(PhortuneSubscription $subscription) { | ||||||
|  |     $this->subscription = $subscription; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getSubscription() { | ||||||
|  |     return $this->subscription; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   public function getResultTypeDescription() { |   public function getResultTypeDescription() { | ||||||
|     return pht('Phortune Orders'); |     return pht('Phortune Orders'); | ||||||
|   } |   } | ||||||
| @@ -83,6 +93,11 @@ final class PhortuneCartSearchEngine | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     $subscription = $this->getSubscription(); | ||||||
|  |     if ($subscription) { | ||||||
|  |       $query->withSubscriptionPHIDs(array($subscription->getPHID())); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return $query; |     return $query; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ final class PhortuneCart extends PhortuneDAO | |||||||
|   protected $accountPHID; |   protected $accountPHID; | ||||||
|   protected $authorPHID; |   protected $authorPHID; | ||||||
|   protected $merchantPHID; |   protected $merchantPHID; | ||||||
|  |   protected $subscriptionPHID; | ||||||
|   protected $cartClass; |   protected $cartClass; | ||||||
|   protected $status; |   protected $status; | ||||||
|   protected $metadata = array(); |   protected $metadata = array(); | ||||||
| @@ -518,6 +519,7 @@ final class PhortuneCart extends PhortuneDAO | |||||||
|         'status' => 'text32', |         'status' => 'text32', | ||||||
|         'cartClass' => 'text128', |         'cartClass' => 'text128', | ||||||
|         'mailKey' => 'bytes20', |         'mailKey' => 'bytes20', | ||||||
|  |         'subscriptionPHID' => 'phid?', | ||||||
|       ), |       ), | ||||||
|       self::CONFIG_KEY_SCHEMA => array( |       self::CONFIG_KEY_SCHEMA => array( | ||||||
|         'key_account' => array( |         'key_account' => array( | ||||||
| @@ -526,6 +528,9 @@ final class PhortuneCart extends PhortuneDAO | |||||||
|         'key_merchant' => array( |         'key_merchant' => array( | ||||||
|           'columns' => array('merchantPHID'), |           'columns' => array('merchantPHID'), | ||||||
|         ), |         ), | ||||||
|  |         'key_subscription' => array( | ||||||
|  |           'columns' => array('subscriptionPHID'), | ||||||
|  |         ), | ||||||
|       ), |       ), | ||||||
|     ) + parent::getConfiguration(); |     ) + parent::getConfiguration(); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -8,8 +8,55 @@ final class PhortuneSubscriptionWorker extends PhabricatorWorker { | |||||||
|     $range = $this->getBillingPeriodRange($subscription); |     $range = $this->getBillingPeriodRange($subscription); | ||||||
|     list($last_epoch, $next_epoch) = $range; |     list($last_epoch, $next_epoch) = $range; | ||||||
|  |  | ||||||
|     // TODO: Actual billing. |     $account = $subscription->getAccount(); | ||||||
|     echo "Bill from {$last_epoch} to {$next_epoch}.\n"; |     $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); | ||||||
|  |  | ||||||
|  |     // TODO: Consider allowing subscriptions to cost an amount other than one | ||||||
|  |     // dollar and twenty-three cents. | ||||||
|  |     $currency = PhortuneCurrency::newFromUserInput($actor, '1.23 USD'); | ||||||
|  |  | ||||||
|  |     $purchase | ||||||
|  |       ->setBasePriceAsCurrency($currency) | ||||||
|  |       ->setMetadataValue('subscriptionPHID', $subscription->getPHID()) | ||||||
|  |       ->save(); | ||||||
|  |  | ||||||
|  |     $cart->setSubscriptionPHID($subscription->getPHID()); | ||||||
|  |     $cart->activateCart(); | ||||||
|  |  | ||||||
|  |     // TODO: Autocharge this, etc.; this is still mostly faked up. | ||||||
|  |     echo 'Okay, made a cart here: '; | ||||||
|  |     echo $cart->getCheckoutURI()."\n\n"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 epriestley
					epriestley