Page MenuHomePhabricator

D12478.diff
No OneTemporary

D12478.diff

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

Mime Type
text/plain
Expires
May 9 2024, 7:09 AM (4 w, 4 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6274351
Default Alt Text
D12478.diff (21 KB)

Event Timeline