1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-25 16:22:43 +01:00

Phortune Cart Status, some one-time support

Summary:
Ref T2787. Carts need a status so we can tell if they've been purchased. Also kind of get WePay working as a one-time provider, and let charges not have a methodPHID (they won't for one-time providers).

All the status stuff is still super crazy rough and you can do things like start a checkout, add a bunch of stuff to your cart, complete the checkout, and have Phabricator think you paid for all the stuff you added. But this is fine for now since you can't actually edit carts, and also none of this is at all usable anyway. I'll refine some of the workflows in future diffs, for now I'm just getting things hooked up and technically working.

Test Plan:
  - Purcahsed a cart and got a sort of status/done screen instead of a "your money is gone" exception.
  - Went through the WePay flow and got a successful test checkout.

Reviewers: btrahan, chad

Reviewed By: chad

Subscribers: epriestley

Maniphest Tasks: T2787

Differential Revision: https://secure.phabricator.com/D10003
This commit is contained in:
epriestley 2014-07-23 10:36:25 -07:00
parent 4c0f15b94b
commit d6eb1c67e7
18 changed files with 274 additions and 105 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phortune.phortune_cart
ADD status VARCHAR(32) NOT NULL COLLATE utf8_bin;

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_phortune.phortune_cart
SET status = 'cart:ready' WHERE status = '';

View file

@ -0,0 +1,3 @@
ALTER TABLE {$NAMESPACE}_phortune.phortune_charge
ADD paymentProviderKey VARCHAR(128) NOT NULL COLLATE utf8_bin
AFTER cartPHID;

View file

@ -0,0 +1,4 @@
/* Make this nullable to support one-time providers. */
ALTER TABLE {$NAMESPACE}_phortune.phortune_charge
CHANGE paymentMethodPHID paymentMethodPHID VARCHAR(64) COLLATE utf8_bin;

View file

@ -2488,7 +2488,6 @@ phutil_register_library_map(array(
'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php',
'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php',
'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php',
'PhortuneAccountBuyController' => 'applications/phortune/controller/PhortuneAccountBuyController.php',
'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php',
'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php',
'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php',
@ -2496,7 +2495,10 @@ phutil_register_library_map(array(
'PhortuneAccountViewController' => 'applications/phortune/controller/PhortuneAccountViewController.php',
'PhortuneBalancedPaymentProvider' => 'applications/phortune/provider/PhortuneBalancedPaymentProvider.php',
'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php',
'PhortuneCartCheckoutController' => 'applications/phortune/controller/PhortuneCartCheckoutController.php',
'PhortuneCartController' => 'applications/phortune/controller/PhortuneCartController.php',
'PhortuneCartQuery' => 'applications/phortune/query/PhortuneCartQuery.php',
'PhortuneCartViewController' => 'applications/phortune/controller/PhortuneCartViewController.php',
'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php',
'PhortuneChargeQuery' => 'applications/phortune/query/PhortuneChargeQuery.php',
'PhortuneConstants' => 'applications/phortune/constants/PhortuneConstants.php',
@ -5371,7 +5373,6 @@ phutil_register_library_map(array(
'PhortuneDAO',
'PhabricatorPolicyInterface',
),
'PhortuneAccountBuyController' => 'PhortuneController',
'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction',
@ -5382,7 +5383,10 @@ phutil_register_library_map(array(
'PhortuneDAO',
'PhabricatorPolicyInterface',
),
'PhortuneCartCheckoutController' => 'PhortuneCartController',
'PhortuneCartController' => 'PhortuneController',
'PhortuneCartQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneCartViewController' => 'PhortuneCartController',
'PhortuneCharge' => array(
'PhortuneDAO',
'PhabricatorPolicyInterface',

View file

@ -41,7 +41,10 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication {
),
'buy/(?P<productID>\d+)/' => 'PhortuneProductPurchaseController',
),
'cart/(?P<id>\d+)/' => 'PhortuneAccountBuyController',
'cart/(?P<id>\d+)/' => array(
'' => 'PhortuneCartViewController',
'checkout/' => 'PhortuneCartCheckoutController',
),
'account/' => array(
'' => 'PhortuneAccountListController',
'edit/(?:(?P<id>\d+)/)?' => 'PhortuneAccountEditController',

View file

@ -152,45 +152,7 @@ final class PhortuneAccountViewController extends PhortuneController {
->withAccountPHIDs(array($account->getPHID()))
->execute();
$rows = array();
foreach ($charges as $charge) {
$rows[] = array(
$charge->getID(),
$charge->getCartPHID(),
$charge->getPaymentMethodPHID(),
PhortuneCurrency::newFromUSDCents($charge->getAmountInCents())
->formatForDisplay(),
$charge->getStatus(),
phabricator_datetime($charge->getDateCreated(), $viewer),
);
}
$charge_table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Charge ID'),
pht('Cart'),
pht('Method'),
pht('Amount'),
pht('Status'),
pht('Created'),
))
->setColumnClasses(
array(
'',
'',
'',
'wide right',
'',
'',
));
$header = id(new PHUIHeaderView())
->setHeader(pht('Charge History'));
return id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($charge_table);
return $this->buildChargesTable($charges);
}
private function buildAccountHistorySection(PhortuneAccount $account) {

View file

@ -1,7 +1,7 @@
<?php
final class PhortuneAccountBuyController
extends PhortuneController {
final class PhortuneCartCheckoutController
extends PhortuneCartController {
private $id;
@ -64,60 +64,23 @@ final class PhortuneAccountBuyController
$charge->openTransaction();
$charge->save();
// TODO: We should be setting some kind of status on the cart here.
$cart->setStatus(PhortuneCart::STATUS_PURCHASING);
$cart->save();
$charge->saveTransaction();
$provider->applyCharge($method, $charge);
throw new Exception('Executed a charge! Your money is gone forever!');
$cart->setStatus(PhortuneCart::STATUS_PURCHASED);
$cart->save();
$view_uri = $this->getApplicationURI('cart/'.$cart->getID().'/');
return id(new AphrontRedirectResponse())->setURI($view_uri);
}
}
$rows = array();
$total = 0;
foreach ($cart->getPurchases() as $purchase) {
$rows[] = array(
pht('A Purchase'),
PhortuneCurrency::newFromUSDCents($purchase->getBasePriceInCents())
->formatForDisplay(),
$purchase->getQuantity(),
PhortuneCurrency::newFromUSDCents($purchase->getTotalPriceInCents())
->formatForDisplay(),
);
$total += $purchase->getTotalPriceInCents();
}
$rows[] = array(
phutil_tag('strong', array(), pht('Total')),
'',
'',
phutil_tag('strong', array(),
PhortuneCurrency::newFromUSDCents($total)->formatForDisplay()),
);
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Item'),
pht('Price'),
pht('Qty.'),
pht('Total'),
));
$table->setColumnClasses(
array(
'wide',
'right',
'right',
'right',
));
$cart_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Your Cart'))
->setFormErrors($errors)
->appendChild($table);
$cart_box = $this->buildCartContents($cart);
$cart_box->setFormErrors($errors);
$title = pht('Buy Stuff');

View file

@ -0,0 +1,52 @@
<?php
abstract class PhortuneCartController
extends PhortuneController {
protected function buildCartContents(PhortuneCart $cart) {
$rows = array();
$total = 0;
foreach ($cart->getPurchases() as $purchase) {
$rows[] = array(
pht('A Purchase'),
PhortuneCurrency::newFromUSDCents($purchase->getBasePriceInCents())
->formatForDisplay(),
$purchase->getQuantity(),
PhortuneCurrency::newFromUSDCents($purchase->getTotalPriceInCents())
->formatForDisplay(),
);
$total += $purchase->getTotalPriceInCents();
}
$rows[] = array(
phutil_tag('strong', array(), pht('Total')),
'',
'',
phutil_tag('strong', array(),
PhortuneCurrency::newFromUSDCents($total)->formatForDisplay()),
);
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Item'),
pht('Price'),
pht('Qty.'),
pht('Total'),
));
$table->setColumnClasses(
array(
'wide',
'right',
'right',
'right',
));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Cart Contents'))
->appendChild($table);
}
}

View file

@ -0,0 +1,50 @@
<?php
final class PhortuneCartViewController
extends PhortuneCartController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$cart = id(new PhortuneCartQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->needPurchases(true)
->executeOne();
if (!$cart) {
return new Aphront404Response();
}
$cart_box = $this->buildCartContents($cart);
$charges = id(new PhortuneChargeQuery())
->setViewer($viewer)
->withCartPHIDs(array($cart->getPHID()))
->execute();
$charges_table = $this->buildChargesTable($charges);
$account = $cart->getAccount();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Cart'));
return $this->buildApplicationPage(
array(
$crumbs,
$cart_box,
$charges_table,
),
array(
'title' => pht('Cart'),
));
}
}

View file

@ -52,5 +52,52 @@ abstract class PhortuneController extends PhabricatorController {
return $account;
}
protected function buildChargesTable(array $charges) {
$request = $this->getRequest();
$viewer = $request->getUser();
$rows = array();
foreach ($charges as $charge) {
$rows[] = array(
$charge->getID(),
$charge->getCartPHID(),
$charge->getPaymentProviderKey(),
$charge->getPaymentMethodPHID(),
PhortuneCurrency::newFromUSDCents($charge->getAmountInCents())
->formatForDisplay(),
$charge->getStatus(),
phabricator_datetime($charge->getDateCreated(), $viewer),
);
}
$charge_table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Charge ID'),
pht('Cart'),
pht('Provider'),
pht('Method'),
pht('Amount'),
pht('Status'),
pht('Created'),
))
->setColumnClasses(
array(
'',
'',
'',
'',
'wide right',
'',
'',
));
$header = id(new PHUIHeaderView())
->setHeader(pht('Charge History'));
return id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($charge_table);
}
}

View file

@ -39,6 +39,7 @@ final class PhortuneProductPurchaseController
$cart = new PhortuneCart();
$cart->openTransaction();
$cart->setStatus(PhortuneCart::STATUS_READY);
$cart->setAccountPHID($account->getPHID());
$cart->setAuthorPHID($user->getPHID());
$cart->save();
@ -57,7 +58,8 @@ final class PhortuneProductPurchaseController
$cart->saveTransaction();
$cart_uri = $this->getApplicationURI('/cart/'.$cart->getID().'/');
$cart_id = $cart->getID();
$cart_uri = $this->getApplicationURI('/cart/'.$cart_id.'/checkout/');
return id(new AphrontRedirectResponse())->setURI($cart_uri);
}

View file

@ -56,7 +56,19 @@ final class PhortuneProviderController extends PhortuneController {
public function loadCart($id) {
return id(new PhortuneCart());
$request = $this->getRequest();
$viewer = $request->getUser();
return id(new PhortuneCartQuery())
->setViewer($viewer)
->needPurchases(true)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
}
}

View file

@ -121,7 +121,7 @@ final class PhortunePaypalPaymentProvider extends PhortunePaymentProvider {
'cartID' => $cart->getID(),
));
$total_in_cents = $cart->getTotalInCents();
$total_in_cents = $cart->getTotalPriceInCents();
$price = PhortuneCurrency::newFromUSDCents($total_in_cents);
$result = $this

View file

@ -111,11 +111,15 @@ final class PhortuneWePayPaymentProvider extends PhortunePaymentProvider {
PhortuneProviderController $controller,
AphrontRequest $request) {
$viewer = $request->getUser();
$cart = $controller->loadCart($request->getInt('cartID'));
if (!$cart) {
return new Aphront404Response();
}
$cart_uri = '/phortune/cart/'.$cart->getID().'/';
$root = dirname(phutil_get_library_root('phabricator'));
require_once $root.'/externals/wepay/wepay.php';
@ -139,7 +143,7 @@ final class PhortuneWePayPaymentProvider extends PhortunePaymentProvider {
'cartID' => $cart->getID(),
));
$total_in_cents = $cart->getTotalInCents();
$total_in_cents = $cart->getTotalPriceInCents();
$price = PhortuneCurrency::newFromUSDCents($total_in_cents);
$params = array(
@ -153,7 +157,12 @@ final class PhortuneWePayPaymentProvider extends PhortunePaymentProvider {
'fee_payer' => 'Payee',
'redirect_uri' => $return_uri,
'fallback_uri' => $cancel_uri,
'auto_capture' => false,
// NOTE: If we don't `auto_capture`, we might get a result back in
// either an "authorized" or a "reserved" state. We can't capture
// an "authorized" result, so just autocapture.
'auto_capture' => true,
'require_shipping' => 0,
'shipping_fee' => 0,
'charge_tax' => 0,
@ -163,18 +172,57 @@ final class PhortuneWePayPaymentProvider extends PhortunePaymentProvider {
$result = $wepay->request('checkout/create', $params);
// NOTE: We might want to store "$result->checkout_id" on the Cart.
// TODO: We must store "$result->checkout_id" on the Cart since the
// user might not end up back here. Really this needs a bunch of junk.
$uri = new PhutilURI($result->checkout_uri);
return id(new AphrontRedirectResponse())->setURI($uri);
case 'charge':
$checkout_id = $request->getInt('checkout_id');
$params = array(
'checkout_id' => $checkout_id,
);
// NOTE: We get $_REQUEST['checkout_id'] here, but our parameters are
// dropped so we should stop depending on them or shove them into the
// URI.
$checkout = $wepay->request('checkout', $params);
if ($checkout->reference_id != $cart->getPHID()) {
throw new Exception(
pht('Checkout reference ID does not match cart PHID!'));
}
var_dump($_REQUEST);
break;
switch ($checkout->state) {
case 'authorized':
case 'reserved':
case 'captured':
break;
default:
throw new Exception(
pht(
'Checkout is in bad state "%s"!',
$result->state));
}
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$charge = id(new PhortuneCharge())
->setAmountInCents((int)$checkout->gross * 100)
->setAccountPHID($cart->getAccount()->getPHID())
->setAuthorPHID($viewer->getPHID())
->setPaymentProviderKey($this->getProviderKey())
->setCartPHID($cart->getPHID())
->setStatus(PhortuneCharge::STATUS_CHARGING)
->save();
$cart->openTransaction();
$charge->setStatus(PhortuneCharge::STATUS_CHARGED);
$charge->save();
$cart->setStatus(PhortuneCart::STATUS_PURCHASED);
$cart->save();
$cart->saveTransaction();
unset($unguarded);
return id(new AphrontRedirectResponse())->setURI($cart_uri);
case 'cancel':
var_dump($_REQUEST);
break;

View file

@ -6,6 +6,7 @@ final class PhortuneChargeQuery
private $ids;
private $phids;
private $accountPHIDs;
private $cartPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -22,6 +23,11 @@ final class PhortuneChargeQuery
return $this;
}
public function withCartPHIDs(array $cart_phids) {
$this->cartPHIDs = $cart_phids;
return $this;
}
protected function loadPage() {
$table = new PhortuneCharge();
$conn = $table->establishConnection('r');
@ -83,6 +89,13 @@ final class PhortuneChargeQuery
$this->accountPHIDs);
}
if ($this->cartPHIDs !== null) {
$where[] = qsprintf(
$conn,
'charge.cartPHID IN (%Ls)',
$this->cartPHIDs);
}
return $this->formatWhereClause($where);
}

View file

@ -3,8 +3,13 @@
final class PhortuneCart extends PhortuneDAO
implements PhabricatorPolicyInterface {
const STATUS_READY = 'cart:ready';
const STATUS_PURCHASING = 'cart:purchasing';
const STATUS_PURCHASED = 'cart:purchased';
protected $accountPHID;
protected $authorPHID;
protected $status;
protected $metadata;
private $account = self::ATTACHABLE;
@ -24,10 +29,6 @@ final class PhortuneCart extends PhortuneDAO
PhabricatorPHIDConstants::PHID_TYPE_CART);
}
public function getTotalInCents() {
return 123;
}
public function attachPurchases(array $purchases) {
assert_instances_of($purchases, 'PhortunePurchase');
$this->purchases = $purchases;

View file

@ -18,6 +18,7 @@ final class PhortuneCharge extends PhortuneDAO
protected $accountPHID;
protected $authorPHID;
protected $cartPHID;
protected $paymentProviderKey;
protected $paymentMethodPHID;
protected $amountInCents;
protected $status;