public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); $id = $request->getURIData('id'); $initiative = id(new FundInitiativeQuery())->setViewer($viewer)->withIDs(array($id))->executeOne(); if (!$initiative) { return new Aphront404Response(); } $merchant = id(new PhortuneMerchantQuery())->setViewer($viewer)->withPHIDs(array($initiative->getMerchantPHID()))->executeOne(); if (!$merchant) { return new Aphront404Response(); } $initiative_uri = '/' . $initiative->getMonogram(); if ($initiative->isClosed()) { return $this->newDialog()->setTitle(pht('Initiative Closed'))->appendParagraph(pht('You can not back a closed initiative.'))->addCancelButton($initiative_uri); } $accounts = PhortuneAccountQuery::loadAccountsForUser($viewer, PhabricatorContentSource::newFromRequest($request)); $v_amount = null; $e_amount = true; $v_account = head($accounts)->getPHID(); $errors = array(); if ($request->isFormPost()) { $v_amount = $request->getStr('amount'); $v_account = $request->getStr('accountPHID'); if (empty($accounts[$v_account])) { $errors[] = pht('You must specify an account.'); } else { $account = $accounts[$v_account]; } if (!strlen($v_amount)) { $errors[] = pht('You must specify how much money you want to contribute to the ' . 'initiative.'); $e_amount = pht('Required'); } else { try { $currency = PhortuneCurrency::newFromUserInput($viewer, $v_amount); $currency->assertInRange('1.00 USD', null); } catch (Exception $ex) { $errors[] = $ex->getMessage(); $e_amount = pht('Invalid'); } } if (!$errors) { $backer = FundBacker::initializeNewBacker($viewer)->setInitiativePHID($initiative->getPHID())->attachInitiative($initiative)->setAmountAsCurrency($currency)->save(); $product = id(new PhortuneProductQuery())->setViewer($viewer)->withClassAndRef('FundBackerProduct', $initiative->getPHID())->executeOne(); $cart_implementation = id(new FundBackerCart())->setInitiative($initiative); $cart = $account->newCart($viewer, $cart_implementation, $merchant); $purchase = $cart->newPurchase($viewer, $product); $purchase->setBasePriceAsCurrency($currency)->setMetadataValue('backerPHID', $backer->getPHID())->save(); $xactions = array(); $xactions[] = id(new FundBackerTransaction())->setTransactionType(FundBackerTransaction::TYPE_STATUS)->setNewValue(FundBacker::STATUS_IN_CART); $editor = id(new FundBackerEditor())->setActor($viewer)->setContentSourceFromRequest($request); $editor->applyTransactions($backer, $xactions); $cart->activateCart(); return id(new AphrontRedirectResponse())->setURI($cart->getCheckoutURI()); } } $form = id(new AphrontFormView())->setUser($viewer)->appendChild(id(new AphrontFormSelectControl())->setName('accountPHID')->setLabel(pht('Account'))->setValue($v_account)->setOptions(mpull($accounts, 'getName', 'getPHID')))->appendChild(id(new AphrontFormTextControl())->setName('amount')->setLabel(pht('Amount'))->setValue($v_amount)->setError($e_amount)); return $this->newDialog()->setTitle(pht('Back %s %s', $initiative->getMonogram(), $initiative->getName()))->setErrors($errors)->appendChild($form->buildLayoutView())->addCancelButton($initiative_uri)->addSubmitButton(pht('Continue')); }
public function testInvalidCurrencyFromUserInput() { $map = array('--1', '$$1', '1 JPY', 'buck fiddy', '1.2.3', '1 dollar'); $user = new PhabricatorUser(); foreach ($map as $input) { $caught = null; try { PhortuneCurrency::newFromUserInput($user, $input); } catch (Exception $ex) { $caught = $ex; } $this->assertTrue($caught instanceof Exception, "{$input}"); } }
public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $initiative = id(new FundInitiativeQuery())->setViewer($viewer)->withIDs(array($this->id))->executeOne(); if (!$initiative) { return new Aphront404Response(); } $initiative_uri = '/' . $initiative->getMonogram(); if ($initiative->isClosed()) { return $this->newDialog()->setTitle(pht('Initiative Closed'))->appendParagraph(pht('You can not back a closed initiative.'))->addCancelButton($initiative_uri); } $v_amount = null; $e_amount = true; $errors = array(); if ($request->isFormPost()) { $v_amount = $request->getStr('amount'); if (!strlen($v_amount)) { $errors[] = pht('You must specify how much money you want to contribute to the ' . 'initiative.'); $e_amount = pht('Required'); } else { try { $currency = PhortuneCurrency::newFromUserInput($viewer, $v_amount); } catch (Exception $ex) { $errors[] = $ex->getMessage(); $e_amount = pht('Invalid'); } } if (!$errors) { $backer = FundBacker::initializeNewBacker($viewer)->setInitiativePHID($initiative->getPHID())->attachInitiative($initiative)->setAmountInCents($currency->getValue())->save(); // TODO: Here, we'd create a purchase and cart. $xactions = array(); $xactions[] = id(new FundBackerTransaction())->setTransactionType(FundBackerTransaction::TYPE_STATUS)->setNewValue(FundBacker::STATUS_IN_CART); $editor = id(new FundBackerEditor())->setActor($viewer)->setContentSourceFromRequest($request); $editor->applyTransactions($backer, $xactions); // TODO: Here, we'd ship the user into Phortune. return id(new AphrontRedirectResponse())->setURI($initiative_uri); } } $form = id(new AphrontFormView())->setUser($viewer)->appendChild(id(new AphrontFormTextControl())->setName('amount')->setLabel(pht('Amount'))->setValue($v_amount)->setError($e_amount)); return $this->newDialog()->setTitle(pht('Back Initiative'))->setErrors($errors)->appendChild($form->buildLayoutView())->addCancelButton($initiative_uri)->addSubmitButton(pht('Continue')); }
public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $authority = $this->loadMerchantAuthority(); $cart_query = id(new PhortuneCartQuery())->setViewer($viewer)->withIDs(array($this->id))->needPurchases(true); if ($authority) { $cart_query->withMerchantPHIDs(array($authority->getPHID())); } $cart = $cart_query->executeOne(); if (!$cart) { return new Aphront404Response(); } switch ($this->action) { case 'cancel': // You must be able to edit the account to cancel an order. PhabricatorPolicyFilter::requireCapability($viewer, $cart->getAccount(), PhabricatorPolicyCapability::CAN_EDIT); $is_refund = false; break; case 'refund': // You must be able to control the merchant to refund an order. PhabricatorPolicyFilter::requireCapability($viewer, $cart->getMerchant(), PhabricatorPolicyCapability::CAN_EDIT); $is_refund = true; break; default: return new Aphront404Response(); } $cancel_uri = $cart->getDetailURI($authority); $merchant = $cart->getMerchant(); try { if ($is_refund) { $title = pht('Unable to Refund Order'); $cart->assertCanRefundOrder(); } else { $title = pht('Unable to Cancel Order'); $cart->assertCanCancelOrder(); } } catch (Exception $ex) { return $this->newDialog()->setTitle($title)->appendChild($ex->getMessage())->addCancelButton($cancel_uri); } $charges = id(new PhortuneChargeQuery())->setViewer($viewer)->withCartPHIDs(array($cart->getPHID()))->withStatuses(array(PhortuneCharge::STATUS_HOLD, PhortuneCharge::STATUS_CHARGED))->execute(); $amounts = mpull($charges, 'getAmountAsCurrency'); $maximum = PhortuneCurrency::newFromList($amounts); $v_refund = $maximum->formatForDisplay(); $errors = array(); $e_refund = true; if ($request->isFormPost()) { if ($is_refund) { try { $refund = PhortuneCurrency::newFromUserInput($viewer, $request->getStr('refund')); $refund->assertInRange('0.00 USD', $maximum->formatForDisplay()); } catch (Exception $ex) { $errors[] = $ex->getMessage(); $e_refund = pht('Invalid'); } } else { $refund = $maximum; } if (!$errors) { $charges = msort($charges, 'getID'); $charges = array_reverse($charges); if ($charges) { $providers = id(new PhortunePaymentProviderConfigQuery())->setViewer($viewer)->withPHIDs(mpull($charges, 'getProviderPHID'))->execute(); $providers = mpull($providers, null, 'getPHID'); } else { $providers = array(); } foreach ($charges as $charge) { $refundable = $charge->getAmountRefundableAsCurrency(); if (!$refundable->isPositive()) { // This charge is a refund, or has already been fully refunded. continue; } if ($refund->isGreaterThan($refundable)) { $refund_amount = $refundable; } else { $refund_amount = $refund; } $provider_config = idx($providers, $charge->getProviderPHID()); if (!$provider_config) { throw new Exception(pht('Unable to load provider for charge!')); } $provider = $provider_config->buildProvider(); $refund_charge = $cart->willRefundCharge($viewer, $provider, $charge, $refund_amount); $refunded = false; try { $provider->refundCharge($charge, $refund_charge); $refunded = true; } catch (Exception $ex) { phlog($ex); $cart->didFailRefund($charge, $refund_charge); } if ($refunded) { $cart->didRefundCharge($charge, $refund_charge); $refund = $refund->subtract($refund_amount); } if (!$refund->isPositive()) { break; } } if ($refund->isPositive()) { throw new Exception(pht('Unable to refund some charges!')); } // TODO: If every HOLD and CHARGING transaction has been fully refunded // and we're in a HOLD, REVIEW, PURCHASING or CHARGED cart state we // probably need to kick the cart back to READY here (or maybe kill // it if it was in REVIEW)? return id(new AphrontRedirectResponse())->setURI($cancel_uri); } } if ($is_refund) { $title = pht('Refund Order?'); $body = pht('Really refund this order?'); $button = pht('Refund Order'); $cancel_text = pht('Cancel'); $form = id(new AphrontFormView())->setUser($viewer)->appendChild(id(new AphrontFormTextControl())->setName('refund')->setLabel(pht('Amount'))->setError($e_refund)->setValue($v_refund)); $form = $form->buildLayoutView(); } else { $title = pht('Cancel Order?'); $body = pht('Really cancel this order? Any payment will be refunded.'); $button = pht('Cancel Order'); // Don't give the user a "Cancel" button in response to a "Cancel?" // prompt, as it's confusing. $cancel_text = pht('Do Not Cancel Order'); $form = null; } return $this->newDialog()->setTitle($title)->setErrors($errors)->appendChild($body)->appendChild($form)->addSubmitButton($button)->addCancelButton($cancel_uri, $cancel_text); }
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(); $cart->setIsInvoice(1)->save(); $cart->activateCart(); $cart_id = $cart->getID(); $uri = "/merchant/{$merchant_id}/cart/{$cart_id}/"; $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)->setForm($form); return $this->buildApplicationPage(array($crumbs, $box), array('title' => $title)); }
public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if ($this->productID) { $product = id(new PhortuneProductQuery())->setViewer($user)->withIDs(array($this->productID))->executeOne(); if (!$product) { return new Aphront404Response(); } $is_create = false; $cancel_uri = $this->getApplicationURI('product/view/' . $this->productID . '/'); } else { $product = new PhortuneProduct(); $is_create = true; $cancel_uri = $this->getApplicationURI('product/'); } $v_name = $product->getProductName(); $v_type = $product->getProductType(); $v_price = (int) $product->getPriceInCents(); $display_price = PhortuneCurrency::newFromUSDCents($v_price)->formatForDisplay(); $e_name = true; $e_type = null; $e_price = true; $errors = array(); if ($request->isFormPost()) { $v_name = $request->getStr('name'); if (!strlen($v_name)) { $e_name = pht('Required'); $errors[] = pht('Product must have a name.'); } else { $e_name = null; } if ($is_create) { $v_type = $request->getStr('type'); $type_map = PhortuneProduct::getTypeMap(); if (empty($type_map[$v_type])) { $e_type = pht('Invalid'); $errors[] = pht('Product type is invalid.'); } else { $e_type = null; } } $display_price = $request->getStr('price'); try { $v_price = PhortuneCurrency::newFromUserInput($user, $display_price)->getValue(); $e_price = null; } catch (Exception $ex) { $errors[] = pht('Price should be formatted as: $1.23'); $e_price = pht('Invalid'); } if (!$errors) { $xactions = array(); $xactions[] = id(new PhortuneProductTransaction())->setTransactionType(PhortuneProductTransaction::TYPE_NAME)->setNewValue($v_name); $xactions[] = id(new PhortuneProductTransaction())->setTransactionType(PhortuneProductTransaction::TYPE_TYPE)->setNewValue($v_type); $xactions[] = id(new PhortuneProductTransaction())->setTransactionType(PhortuneProductTransaction::TYPE_PRICE)->setNewValue($v_price); $editor = id(new PhortuneProductEditor())->setActor($user)->setContinueOnNoEffect(true)->setContentSourceFromRequest($request); $editor->applyTransactions($product, $xactions); return id(new AphrontRedirectResponse())->setURI($this->getApplicationURI('product/view/' . $product->getID() . '/')); } } if ($errors) { $errors = id(new AphrontErrorView())->setErrors($errors); } $form = id(new AphrontFormView())->setUser($user)->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Name'))->setName('name')->setValue($v_name)->setError($e_name))->appendChild(id(new AphrontFormSelectControl())->setLabel(pht('Type'))->setName('type')->setValue($v_type)->setError($e_type)->setOptions(PhortuneProduct::getTypeMap())->setDisabled(!$is_create))->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Price'))->setName('price')->setValue($display_price)->setError($e_price))->appendChild(id(new AphrontFormSubmitControl())->setValue($is_create ? pht('Create Product') : pht('Save Product'))->addCancelButton($cancel_uri)); $title = pht('Edit Product'); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Products'), $this->getApplicationURI('product/')); $crumbs->addTextCrumb($is_create ? pht('Create') : pht('Edit'), $request->getRequestURI()); $box = id(new PHUIObjectBoxView())->setHeaderText(pht('Edit Product'))->appendChild($form); return $this->buildApplicationPage(array($crumbs, $box), array('title' => $title)); }