Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15379320
D12478.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
21 KB
Referenced Files
None
Subscribers
None
D12478.diff
View Options
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
@@ -2817,6 +2817,8 @@
'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php',
'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php',
'PhortuneAccountViewController' => 'applications/phortune/controller/PhortuneAccountViewController.php',
+ 'PhortuneAdHocCart' => 'applications/phortune/cart/PhortuneAdHocCart.php',
+ 'PhortuneAdHocProduct' => 'applications/phortune/product/PhortuneAdHocProduct.php',
'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php',
'PhortuneCartAcceptController' => 'applications/phortune/controller/PhortuneCartAcceptController.php',
'PhortuneCartCancelController' => 'applications/phortune/controller/PhortuneCartCancelController.php',
@@ -2856,6 +2858,7 @@
'PhortuneMerchantEditController' => 'applications/phortune/controller/PhortuneMerchantEditController.php',
'PhortuneMerchantEditor' => 'applications/phortune/editor/PhortuneMerchantEditor.php',
'PhortuneMerchantHasMemberEdgeType' => 'applications/phortune/edge/PhortuneMerchantHasMemberEdgeType.php',
+ 'PhortuneMerchantInvoiceCreateController' => 'applications/phortune/controller/PhortuneMerchantInvoiceCreateController.php',
'PhortuneMerchantListController' => 'applications/phortune/controller/PhortuneMerchantListController.php',
'PhortuneMerchantPHIDType' => 'applications/phortune/phid/PhortuneMerchantPHIDType.php',
'PhortuneMerchantQuery' => 'applications/phortune/query/PhortuneMerchantQuery.php',
@@ -6265,6 +6268,8 @@
'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction',
'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortuneAccountViewController' => 'PhortuneController',
+ 'PhortuneAdHocCart' => 'PhortuneCartImplementation',
+ 'PhortuneAdHocProduct' => 'PhortuneProductImplementation',
'PhortuneCart' => array(
'PhortuneDAO',
'PhabricatorApplicationTransactionInterface',
@@ -6312,6 +6317,7 @@
'PhortuneMerchantEditController' => 'PhortuneMerchantController',
'PhortuneMerchantEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortuneMerchantHasMemberEdgeType' => 'PhabricatorEdgeType',
+ 'PhortuneMerchantInvoiceCreateController' => 'PhortuneMerchantController',
'PhortuneMerchantListController' => 'PhortuneMerchantController',
'PhortuneMerchantPHIDType' => 'PhabricatorPHIDType',
'PhortuneMerchantQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
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
@@ -84,19 +84,24 @@
'edit/(?:(?P<id>\d+)/)?' => 'PhortuneMerchantEditController',
'orders/(?P<merchantID>\d+)/(?:query/(?P<queryKey>[^/]+)/)?'
=> 'PhortuneCartListController',
- '(?P<merchantID>\d+)/cart/(?P<id>\d+)/' => array(
- '' => 'PhortuneCartViewController',
- '(?P<action>cancel|refund)/' => 'PhortuneCartCancelController',
- 'update/' => 'PhortuneCartUpdateController',
- 'accept/' => 'PhortuneCartAcceptController',
- ),
- '(?P<merchantID>\d+)/subscription/' => array(
- '(?:query/(?P<queryKey>[^/]+)/)?'
- => 'PhortuneSubscriptionListController',
- 'view/(?P<id>\d+)/'
- => 'PhortuneSubscriptionViewController',
- 'order/(?P<subscriptionID>\d+)/'
- => 'PhortuneCartListController',
+ '(?P<merchantID>\d+)/' => array(
+ 'cart/(?P<id>\d+)/' => array(
+ '' => 'PhortuneCartViewController',
+ '(?P<action>cancel|refund)/' => 'PhortuneCartCancelController',
+ 'update/' => 'PhortuneCartUpdateController',
+ 'accept/' => 'PhortuneCartAcceptController',
+ ),
+ 'subscription/' => array(
+ '(?:query/(?P<queryKey>[^/]+)/)?'
+ => 'PhortuneSubscriptionListController',
+ 'view/(?P<id>\d+)/'
+ => 'PhortuneSubscriptionViewController',
+ 'order/(?P<subscriptionID>\d+)/'
+ => 'PhortuneCartListController',
+ ),
+ 'invoice/' => array(
+ 'new/' => 'PhortuneMerchantInvoiceCreateController',
+ ),
),
'(?P<id>\d+)/' => 'PhortuneMerchantViewController',
),
diff --git a/src/applications/phortune/cart/PhortuneAdHocCart.php b/src/applications/phortune/cart/PhortuneAdHocCart.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/cart/PhortuneAdHocCart.php
@@ -0,0 +1,39 @@
+<?php
+
+final class PhortuneAdHocCart extends PhortuneCartImplementation {
+
+ public function loadImplementationsForCarts(
+ PhabricatorUser $viewer,
+ array $carts) {
+
+ $results = array();
+ foreach ($carts as $key => $cart) {
+ $results[$key] = new PhortuneAdHocCart();
+ }
+
+ return $results;
+ }
+
+ public function getName(PhortuneCart $cart) {
+ return $cart->getMetadataValue('adhoc.title');
+ }
+
+ public function getDescription(PhortuneCart $cart) {
+ return $cart->getMetadataValue('adhoc.description');
+ }
+
+ public function getCancelURI(PhortuneCart $cart) {
+ return null;
+ }
+
+ public function getDoneURI(PhortuneCart $cart) {
+ return null;
+ }
+
+ public function willCreateCart(
+ PhabricatorUser $viewer,
+ PhortuneCart $cart) {
+ return;
+ }
+
+}
diff --git a/src/applications/phortune/cart/PhortuneCartImplementation.php b/src/applications/phortune/cart/PhortuneCartImplementation.php
--- a/src/applications/phortune/cart/PhortuneCartImplementation.php
+++ b/src/applications/phortune/cart/PhortuneCartImplementation.php
@@ -16,6 +16,10 @@
abstract public function getCancelURI(PhortuneCart $cart);
abstract public function getDoneURI(PhortuneCart $cart);
+ public function getDescription(PhortuneCart $cart) {
+ return null;
+ }
+
public function getDoneActionName(PhortuneCart $cart) {
return pht('Return to Application');
}
diff --git a/src/applications/phortune/controller/PhortuneCartCheckoutController.php b/src/applications/phortune/controller/PhortuneCartCheckoutController.php
--- a/src/applications/phortune/controller/PhortuneCartCheckoutController.php
+++ b/src/applications/phortune/controller/PhortuneCartCheckoutController.php
@@ -209,6 +209,8 @@
->appendChild($form)
->appendChild($provider_form);
+ $description_box = $this->renderCartDescription($cart);
+
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Checkout'));
$crumbs->addTextCrumb($title);
@@ -217,6 +219,7 @@
array(
$crumbs,
$cart_box,
+ $description_box,
$payment_box,
),
array(
diff --git a/src/applications/phortune/controller/PhortuneCartController.php b/src/applications/phortune/controller/PhortuneCartController.php
--- a/src/applications/phortune/controller/PhortuneCartController.php
+++ b/src/applications/phortune/controller/PhortuneCartController.php
@@ -42,4 +42,26 @@
return $table;
}
+ protected function renderCartDescription(PhortuneCart $cart) {
+ $description = $cart->getDescription();
+ if (!strlen($description)) {
+ return null;
+ }
+
+ $output = PhabricatorMarkupEngine::renderOneObject(
+ id(new PhabricatorMarkupOneOff())
+ ->setPreserveLinebreaks(true)
+ ->setContent($description),
+ 'default',
+ $this->getViewer());
+
+ $box = id(new PHUIBoxView())
+ ->addMargin(PHUI::MARGIN_LARGE)
+ ->appendChild($output);
+
+ return id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Description'))
+ ->appendChild($box);
+ }
+
}
diff --git a/src/applications/phortune/controller/PhortuneCartViewController.php b/src/applications/phortune/controller/PhortuneCartViewController.php
--- a/src/applications/phortune/controller/PhortuneCartViewController.php
+++ b/src/applications/phortune/controller/PhortuneCartViewController.php
@@ -15,11 +15,16 @@
$authority = $this->loadMerchantAuthority();
- $cart = id(new PhortuneCartQuery())
+ $query = id(new PhortuneCartQuery())
->setViewer($viewer)
->withIDs(array($this->id))
- ->needPurchases(true)
- ->executeOne();
+ ->needPurchases(true);
+
+ if ($authority) {
+ $query->withMerchantPHIDs(array($authority->getPHID()));
+ }
+
+ $cart = $query->executeOne();
if (!$cart) {
return new Aphront404Response();
}
@@ -35,6 +40,31 @@
$error_view = null;
$resume_uri = null;
switch ($cart->getStatus()) {
+ case PhortuneCart::STATUS_READY:
+ if ($authority && $request->getStr('invoice')) {
+ // We arrived here by following the ad-hoc invoice workflow, and
+ // are acting with merchant authority.
+
+ $checkout_uri = PhabricatorEnv::getURI($cart->getCheckoutURI());
+
+ $invoice_message = array(
+ pht(
+ 'Manual invoices do not automatically notify recipients yet. '.
+ 'Send the payer this checkout link:'),
+ ' ',
+ phutil_tag(
+ 'a',
+ array(
+ 'href' => $checkout_uri,
+ ),
+ $checkout_uri),
+ );
+
+ $error_view = id(new PHUIInfoView())
+ ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
+ ->setErrors(array($invoice_message));
+ }
+ break;
case PhortuneCart::STATUS_PURCHASING:
if ($can_edit) {
$resume_uri = $cart->getMetadataValue('provider.checkoutURI');
@@ -86,7 +116,6 @@
$error_view = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->appendChild(pht('This purchase has been completed.'));
-
break;
}
@@ -126,6 +155,8 @@
$cart_box->setInfoView($error_view);
}
+ $description = $this->renderCartDescription($cart);
+
$charges = id(new PhortuneChargeQuery())
->setViewer($viewer)
->withCartPHIDs(array($cart->getPHID()))
@@ -171,6 +202,7 @@
array(
$crumbs,
$cart_box,
+ $description,
$charges,
$timeline,
),
diff --git a/src/applications/phortune/controller/PhortuneMerchantInvoiceCreateController.php b/src/applications/phortune/controller/PhortuneMerchantInvoiceCreateController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/controller/PhortuneMerchantInvoiceCreateController.php
@@ -0,0 +1,245 @@
+<?php
+
+final class PhortuneMerchantInvoiceCreateController
+ extends PhortuneMerchantController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getUser();
+
+ $merchant = $this->loadMerchantAuthority();
+ if (!$merchant) {
+ return new Aphront404Response();
+ }
+
+ $merchant_id = $merchant->getID();
+ $cancel_uri = $this->getApplicationURI("/merchant/{$merchant_id}/");
+
+ // Load the user to invoice, or prompt the viewer to select one.
+ $target_user = null;
+ $user_phid = head($request->getArr('userPHID'));
+ if (!$user_phid) {
+ $user_phid = $request->getStr('userPHID');
+ }
+ if ($user_phid) {
+ $target_user = id(new PhabricatorPeopleQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($user_phid))
+ ->executeOne();
+ }
+
+ if (!$target_user) {
+ $form = id(new AphrontFormView())
+ ->setUser($viewer)
+ ->appendRemarkupInstructions(pht('Choose a user to invoice.'))
+ ->appendControl(
+ id(new AphrontFormTokenizerControl())
+ ->setLabel(pht('User'))
+ ->setDatasource(new PhabricatorPeopleDatasource())
+ ->setName('userPHID')
+ ->setLimit(1));
+
+ return $this->newDialog()
+ ->setTitle(pht('Choose User'))
+ ->appendForm($form)
+ ->addCancelButton($cancel_uri)
+ ->addSubmitButton(pht('Continue'));
+ }
+
+ // Load the account to invoice, or prompt the viewer to select one.
+ $target_account = null;
+ $account_phid = $request->getStr('accountPHID');
+ if ($account_phid) {
+ $target_account = id(new PhortuneAccountQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($account_phid))
+ ->withMemberPHIDs(array($target_user->getPHID()))
+ ->executeOne();
+ }
+
+ if (!$target_account) {
+ $accounts = PhortuneAccountQuery::loadAccountsForUser(
+ $target_user,
+ PhabricatorContentSource::newFromRequest($request));
+
+ $form = id(new AphrontFormView())
+ ->setUser($viewer)
+ ->addHiddenInput('userPHID', $target_user->getPHID())
+ ->appendRemarkupInstructions(pht('Choose which account to invoice.'))
+ ->appendControl(
+ id(new AphrontFormMarkupControl())
+ ->setLabel(pht('User'))
+ ->setValue($viewer->renderHandle($target_user->getPHID())))
+ ->appendControl(
+ id(new AphrontFormSelectControl())
+ ->setLabel(pht('Account'))
+ ->setName('accountPHID')
+ ->setValue($account_phid)
+ ->setOptions(mpull($accounts, 'getName', 'getPHID')));
+
+ return $this->newDialog()
+ ->setTitle(pht('Choose Account'))
+ ->appendForm($form)
+ ->addCancelButton($cancel_uri)
+ ->addSubmitButton(pht('Continue'));
+ }
+
+
+ // Now we build the actual invoice.
+ $title = pht('New Invoice');
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addTextCrumb($merchant->getName());
+
+ $v_title = $request->getStr('title');
+ $e_title = true;
+
+ $v_name = $request->getStr('name');
+ $e_name = true;
+
+ $v_cost = $request->getStr('cost');
+ $e_cost = true;
+
+ $v_desc = $request->getStr('description');
+
+ $v_quantity = 1;
+ $e_quantity = null;
+
+ $errors = array();
+ if ($request->isFormPost() && $request->getStr('invoice')) {
+ $v_quantity = $request->getStr('quantity');
+
+ $e_title = null;
+ $e_name = null;
+ $e_cost = null;
+ $e_quantity = null;
+
+ if (!strlen($v_title)) {
+ $e_title = pht('Required');
+ $errors[] = pht('You must title this invoice.');
+ }
+
+ if (!strlen($v_name)) {
+ $e_name = pht('Required');
+ $errors[] = pht('You must provide a name for this purchase.');
+ }
+
+ if (!strlen($v_cost)) {
+ $e_cost = pht('Required');
+ $errors[] = pht('You must provide a cost for this purchase.');
+ } else {
+ try {
+ $v_currency = PhortuneCurrency::newFromUserInput(
+ $viewer,
+ $v_cost);
+ } catch (Exception $ex) {
+ $errors[] = $ex->getMessage();
+ $e_cost = pht('Invalid');
+ }
+ }
+
+ if ((int)$v_quantity <= 0) {
+ $e_quantity = pht('Invalid');
+ $errors[] = pht('Quantity must be a positive integer.');
+ }
+
+ if (!$errors) {
+ $unique = Filesystem::readRandomCharacters(16);
+
+ $product = id(new PhortuneProductQuery())
+ ->setViewer($target_user)
+ ->withClassAndRef('PhortuneAdHocProduct', $unique)
+ ->executeOne();
+
+ $cart_implementation = new PhortuneAdHocCart();
+
+ $cart = $target_account->newCart(
+ $target_user,
+ $cart_implementation,
+ $merchant);
+
+ $cart
+ ->setMetadataValue('adhoc.title', $v_title)
+ ->setMetadataValue('adhoc.description', $v_desc);
+
+ $purchase = $cart->newPurchase($target_user, $product)
+ ->setBasePriceAsCurrency($v_currency)
+ ->setQuantity((int)$v_quantity)
+ ->setMetadataValue('adhoc.name', $v_name)
+ ->save();
+
+ // TODO: Actually mark these as invoices. Right now, there's no easy
+ // way to do that.
+
+ $cart->activateCart();
+ $cart_id = $cart->getID();
+
+ $uri = "/merchant/{$merchant_id}/cart/{$cart_id}/?invoice=true";
+ $uri = $this->getApplicationURI($uri);
+
+ return id(new AphrontRedirectResponse())->setURI($uri);
+ }
+ }
+
+ $form = id(new AphrontFormView())
+ ->setUser($viewer)
+ ->addHiddenInput('userPHID', $target_user->getPHID())
+ ->addHiddenInput('accountPHID', $target_account->getPHID())
+ ->addHiddenInput('invoice', true)
+ ->appendControl(
+ id(new AphrontFormMarkupControl())
+ ->setLabel(pht('User'))
+ ->setValue($viewer->renderHandle($target_user->getPHID())))
+ ->appendControl(
+ id(new AphrontFormMarkupControl())
+ ->setLabel(pht('Account'))
+ ->setValue($viewer->renderHandle($target_account->getPHID())))
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel(pht('Invoice Title'))
+ ->setName('title')
+ ->setValue($v_title)
+ ->setError($e_title))
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel(pht('Purchase Name'))
+ ->setName('name')
+ ->setValue($v_name)
+ ->setError($e_name))
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel(pht('Purchase Cost'))
+ ->setName('cost')
+ ->setValue($v_cost)
+ ->setError($e_cost))
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel(pht('Quantity'))
+ ->setName('quantity')
+ ->setValue($v_quantity)
+ ->setError($e_quantity))
+ ->appendChild(
+ id(new AphrontFormTextAreaControl())
+ ->setLabel(pht('Invoice Description'))
+ ->setName('description')
+ ->setValue($v_desc))
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->addCancelButton($cancel_uri)
+ ->setValue(pht('Send Invoice')));
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('New Invoice'))
+ ->setFormErrors($errors)
+ ->appendChild($form);
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $box,
+ ),
+ array(
+ 'title' => $title,
+ ));
+ }
+
+}
diff --git a/src/applications/phortune/controller/PhortuneMerchantViewController.php b/src/applications/phortune/controller/PhortuneMerchantViewController.php
--- a/src/applications/phortune/controller/PhortuneMerchantViewController.php
+++ b/src/applications/phortune/controller/PhortuneMerchantViewController.php
@@ -192,6 +192,15 @@
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
+
+ $view->addAction(
+ id(new PhabricatorActionView())
+ ->setName(pht('New Invoice'))
+ ->setIcon('fa-fax')
+ ->setHref($this->getApplicationURI("merchant/{$id}/invoice/new/"))
+ ->setDisabled(!$can_edit)
+ ->setWorkflow(!$can_edit));
+
return $view;
}
diff --git a/src/applications/phortune/product/PhortuneAdHocProduct.php b/src/applications/phortune/product/PhortuneAdHocProduct.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/product/PhortuneAdHocProduct.php
@@ -0,0 +1,42 @@
+<?php
+
+final class PhortuneAdHocProduct extends PhortuneProductImplementation {
+
+ private $ref;
+
+ public function loadImplementationsForRefs(
+ PhabricatorUser $viewer,
+ array $refs) {
+
+ $results = array();
+ foreach ($refs as $key => $ref) {
+ $product = new PhortuneAdHocProduct();
+ $product->ref = $ref;
+ $results[$key] = $product;
+ }
+
+ return $results;
+ }
+
+ public function getRef() {
+ return $this->ref;
+ }
+
+ public function getName(PhortuneProduct $product) {
+ return pht('Ad-Hoc Product');
+ }
+
+ public function getPurchaseName(
+ PhortuneProduct $product,
+ PhortunePurchase $purchase) {
+
+ return coalesce(
+ $purchase->getMetadataValue('adhoc.name'),
+ $this->getName($product));
+ }
+
+ public function getPriceAsCurrency(PhortuneProduct $product) {
+ return PhortuneCurrency::newEmptyCurrency();
+ }
+
+}
diff --git a/src/applications/phortune/storage/PhortuneCart.php b/src/applications/phortune/storage/PhortuneCart.php
--- a/src/applications/phortune/storage/PhortuneCart.php
+++ b/src/applications/phortune/storage/PhortuneCart.php
@@ -453,6 +453,10 @@
return $this->getImplementation()->getCancelURI($this);
}
+ public function getDescription() {
+ return $this->getImplementation()->getDescription($this);
+ }
+
public function getDetailURI(PhortuneMerchant $authority = null) {
if ($authority) {
$prefix = 'merchant/'.$authority->getID().'/';
diff --git a/src/applications/phortune/storage/PhortunePurchase.php b/src/applications/phortune/storage/PhortunePurchase.php
--- a/src/applications/phortune/storage/PhortunePurchase.php
+++ b/src/applications/phortune/storage/PhortunePurchase.php
@@ -92,7 +92,14 @@
}
public function getTotalPriceAsCurrency() {
- return $this->getBasePriceAsCurrency();
+ $base = $this->getBasePriceAsCurrency();
+
+ $price = PhortuneCurrency::newEmptyCurrency();
+ for ($ii = 0; $ii < $this->getQuantity(); $ii++) {
+ $price = $price->add($base);
+ }
+
+ return $price;
}
public function getMetadataValue($key, $default = null) {
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Mar 14, 8:12 PM (3 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7669184
Default Alt Text
D12478.diff (21 KB)
Attached To
Mode
D12478: Support basic ad-hoc invoices in Phortune
Attached
Detach File
Event Timeline
Log In to Comment