Page MenuHomePhabricator

D10002.id24041.diff
No OneTemporary

D10002.id24041.diff

diff --git a/resources/sql/autopatches/20140721.phortune.3.charge.sql b/resources/sql/autopatches/20140721.phortune.3.charge.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20140721.phortune.3.charge.sql
@@ -0,0 +1,16 @@
+CREATE TABLE {$NAMESPACE}_phortune.phortune_charge (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ accountPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ cartPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ paymentMethodPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ amountInCents INT NOT NULL,
+ status VARCHAR(32) NOT NULL COLLATE utf8_bin,
+ metadata LONGTEXT NOT NULL COLLATE utf8_bin,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_phid` (phid),
+ KEY `key_cart` (cartPHID),
+ KEY `key_account` (accountPHID)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
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
@@ -2498,6 +2498,7 @@
'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php',
'PhortuneCartQuery' => 'applications/phortune/query/PhortuneCartQuery.php',
'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php',
+ 'PhortuneChargeQuery' => 'applications/phortune/query/PhortuneChargeQuery.php',
'PhortuneConstants' => 'applications/phortune/constants/PhortuneConstants.php',
'PhortuneController' => 'applications/phortune/controller/PhortuneController.php',
'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php',
@@ -5382,8 +5383,13 @@
'PhabricatorPolicyInterface',
),
'PhortuneCartQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
- 'PhortuneCharge' => 'PhortuneDAO',
+ 'PhortuneCharge' => array(
+ 'PhortuneDAO',
+ 'PhabricatorPolicyInterface',
+ ),
+ 'PhortuneChargeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneController' => 'PhabricatorController',
+ 'PhortuneCurrency' => 'Phobject',
'PhortuneCurrencyTestCase' => 'PhabricatorTestCase',
'PhortuneDAO' => 'PhabricatorLiskDAO',
'PhortuneErrCode' => 'PhortuneConstants',
diff --git a/src/applications/phortune/controller/PhortuneAccountBuyController.php b/src/applications/phortune/controller/PhortuneAccountBuyController.php
--- a/src/applications/phortune/controller/PhortuneAccountBuyController.php
+++ b/src/applications/phortune/controller/PhortuneAccountBuyController.php
@@ -11,10 +11,10 @@
public function processRequest() {
$request = $this->getRequest();
- $user = $request->getUser();
+ $viewer = $request->getUser();
$cart = id(new PhortuneCartQuery())
- ->setViewer($user)
+ ->setViewer($viewer)
->withIDs(array($this->id))
->needPurchases(true)
->executeOne();
@@ -25,6 +25,56 @@
$account = $cart->getAccount();
$account_uri = $this->getApplicationURI($account->getID().'/');
+ $methods = id(new PhortunePaymentMethodQuery())
+ ->setViewer($viewer)
+ ->withAccountPHIDs(array($account->getPHID()))
+ ->withStatus(PhortunePaymentMethodQuery::STATUS_OPEN)
+ ->execute();
+
+ $e_method = null;
+ $errors = array();
+
+ if ($request->isFormPost()) {
+
+ // Require CAN_EDIT on the cart to actually make purchases.
+
+ PhabricatorPolicyFilter::requireCapability(
+ $viewer,
+ $cart,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $method_id = $request->getInt('paymentMethodID');
+ $method = idx($methods, $method_id);
+ if (!$method) {
+ $e_method = pht('Required');
+ $errors[] = pht('You must choose a payment method.');
+ }
+
+ if (!$errors) {
+ $provider = $method->buildPaymentProvider();
+
+ $charge = id(new PhortuneCharge())
+ ->setAccountPHID($account->getPHID())
+ ->setCartPHID($cart->getPHID())
+ ->setAuthorPHID($viewer->getPHID())
+ ->setPaymentMethodPHID($method->getPHID())
+ ->setAmountInCents($cart->getTotalPriceInCents())
+ ->setStatus(PhortuneCharge::STATUS_PENDING);
+
+ $charge->openTransaction();
+ $charge->save();
+
+ // TODO: We should be setting some kind of status on the cart here.
+ $cart->save();
+ $charge->saveTransaction();
+
+ $provider->applyCharge($method, $charge);
+
+ throw new Exception('Executed a charge! Your money is gone forever!');
+ }
+ }
+
+
$rows = array();
$total = 0;
foreach ($cart->getPurchases() as $purchase) {
@@ -66,20 +116,11 @@
$cart_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Your Cart'))
+ ->setFormErrors($errors)
->appendChild($table);
$title = pht('Buy Stuff');
-
- $methods = id(new PhortunePaymentMethodQuery())
- ->setViewer($user)
- ->withAccountPHIDs(array($account->getPHID()))
- ->withStatus(PhortunePaymentMethodQuery::STATUS_OPEN)
- ->execute();
-
- $method_control = id(new AphrontFormRadioButtonControl())
- ->setLabel(pht('Payment Method'));
-
if (!$methods) {
$method_control = id(new AphrontFormStaticControl())
->setLabel(pht('Payment Method'))
@@ -98,11 +139,13 @@
}
}
+ $method_control->setError($e_method);
+
$payment_method_uri = $this->getApplicationURI(
$account->getID().'/paymentmethod/edit/');
$form = id(new AphrontFormView())
- ->setUser($user)
+ ->setUser($viewer)
->appendChild($method_control);
$add_providers = PhortunePaymentProvider::getProvidersForAddPaymentMethod();
@@ -137,7 +180,7 @@
$one_time_options[] = $provider->renderOneTimePaymentButton(
$account,
$cart,
- $user);
+ $viewer);
}
$provider_form = new PHUIFormLayoutView();
diff --git a/src/applications/phortune/controller/PhortuneAccountViewController.php b/src/applications/phortune/controller/PhortuneAccountViewController.php
--- a/src/applications/phortune/controller/PhortuneAccountViewController.php
+++ b/src/applications/phortune/controller/PhortuneAccountViewController.php
@@ -56,6 +56,7 @@
$payment_methods = $this->buildPaymentMethodsSection($account);
$purchase_history = $this->buildPurchaseHistorySection($account);
+ $charge_history = $this->buildChargeHistorySection($account);
$account_history = $this->buildAccountHistorySection($account);
$object_box = id(new PHUIObjectBoxView())
@@ -68,6 +69,7 @@
$object_box,
$payment_methods,
$purchase_history,
+ $charge_history,
$account_history,
),
array(
@@ -141,6 +143,56 @@
->setHeader($header);
}
+ private function buildChargeHistorySection(PhortuneAccount $account) {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $charges = id(new PhortuneChargeQuery())
+ ->setViewer($viewer)
+ ->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);
+ }
+
private function buildAccountHistorySection(PhortuneAccount $account) {
$request = $this->getRequest();
$user = $request->getUser();
diff --git a/src/applications/phortune/currency/PhortuneCurrency.php b/src/applications/phortune/currency/PhortuneCurrency.php
--- a/src/applications/phortune/currency/PhortuneCurrency.php
+++ b/src/applications/phortune/currency/PhortuneCurrency.php
@@ -1,6 +1,6 @@
<?php
-final class PhortuneCurrency {
+final class PhortuneCurrency extends Phobject {
private $value;
private $currency;
@@ -52,6 +52,18 @@
return $obj;
}
+ public static function newFromList(array $list) {
+ assert_instances_of($list, 'PhortuneCurrency');
+
+ $total = 0;
+ foreach ($list as $item) {
+ // TODO: This should check for integer overflows, etc.
+ $total += $item->getValue();
+ }
+
+ return PhortuneCurrency::newFromUSDCents($total);
+ }
+
public static function newFromUSDCents($cents) {
if (!is_int($cents)) {
throw new Exception(
diff --git a/src/applications/phortune/provider/PhortunePaymentProvider.php b/src/applications/phortune/provider/PhortunePaymentProvider.php
--- a/src/applications/phortune/provider/PhortunePaymentProvider.php
+++ b/src/applications/phortune/provider/PhortunePaymentProvider.php
@@ -97,6 +97,19 @@
abstract public function canHandlePaymentMethod(
PhortunePaymentMethod $method);
+ final public function applyCharge(
+ PhortunePaymentMethod $payment_method,
+ PhortuneCharge $charge) {
+
+ $charge->setStatus(PhortuneCharge::STATUS_CHARGING);
+ $charge->save();
+
+ $this->executeCharge($payment_method, $charge);
+
+ $charge->setStatus(PhortuneCharge::STATUS_CHARGED);
+ $charge->save();
+ }
+
abstract protected function executeCharge(
PhortunePaymentMethod $payment_method,
PhortuneCharge $charge);
diff --git a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php
--- a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php
+++ b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php
@@ -40,6 +40,9 @@
PhortunePaymentMethod $method,
PhortuneCharge $charge) {
+ $root = dirname(phutil_get_library_root('phabricator'));
+ require_once $root.'/externals/stripe-php/lib/Stripe.php';
+
$secret_key = $this->getSecretKey();
$params = array(
'amount' => $charge->getAmountInCents(),
diff --git a/src/applications/phortune/query/PhortuneCartQuery.php b/src/applications/phortune/query/PhortuneCartQuery.php
--- a/src/applications/phortune/query/PhortuneCartQuery.php
+++ b/src/applications/phortune/query/PhortuneCartQuery.php
@@ -49,6 +49,7 @@
$account = idx($accounts, $cart->getAccountPHID());
if (!$account) {
unset($carts[$key]);
+ continue;
}
$cart->attachAccount($account);
}
diff --git a/src/applications/phortune/query/PhortunePurchaseQuery.php b/src/applications/phortune/query/PhortuneChargeQuery.php
copy from src/applications/phortune/query/PhortunePurchaseQuery.php
copy to src/applications/phortune/query/PhortuneChargeQuery.php
--- a/src/applications/phortune/query/PhortunePurchaseQuery.php
+++ b/src/applications/phortune/query/PhortuneChargeQuery.php
@@ -1,11 +1,11 @@
<?php
-final class PhortunePurchaseQuery
+final class PhortuneChargeQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
- private $cartPHIDs;
+ private $accountPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
@@ -17,18 +17,18 @@
return $this;
}
- public function withCartPHIDs(array $cart_phids) {
- $this->cartPHIDs = $cart_phids;
+ public function withAccountPHIDs(array $account_phids) {
+ $this->accountPHIDs = $account_phids;
return $this;
}
protected function loadPage() {
- $table = new PhortunePurchase();
+ $table = new PhortuneCharge();
$conn = $table->establishConnection('r');
$rows = queryfx_all(
$conn,
- 'SELECT purchase.* FROM %T purchase %Q %Q %Q',
+ 'SELECT charge.* FROM %T charge %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn),
$this->buildOrderClause($conn),
@@ -37,22 +37,24 @@
return $table->loadAllFromArray($rows);
}
- protected function willFilterPage(array $purchases) {
- $carts = id(new PhabricatorObjectQuery())
+ protected function willFilterPage(array $charges) {
+ $accounts = id(new PhortuneAccountQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
- ->withPHIDs(mpull($purchases, 'getCartPHID'))
+ ->withPHIDs(mpull($charges, 'getAccountPHID'))
->execute();
+ $accounts = mpull($accounts, null, 'getPHID');
- foreach ($purchases as $key => $purchase) {
- $cart = idx($carts, $purchase->getCartPHID());
- if (!$cart) {
- unset($purchases[$key]);
+ foreach ($charges as $key => $charge) {
+ $account = idx($accounts, $charge->getAccountPHID());
+ if (!$account) {
+ unset($charges[$key]);
+ continue;
}
- $purchase->attachCart($cart);
+ $charge->attachAccount($account);
}
- return $purchases;
+ return $charges;
}
private function buildWhereClause(AphrontDatabaseConnection $conn) {
@@ -63,22 +65,22 @@
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
- 'purchase.id IN (%Ld)',
+ 'charge.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
- 'purchase.phid IN (%Ls)',
+ 'charge.phid IN (%Ls)',
$this->phids);
}
- if ($this->cartPHIDs !== null) {
+ if ($this->accountPHIDs !== null) {
$where[] = qsprintf(
$conn,
- 'purchase.cartPHID IN (%Ls)',
- $this->cartPHIDs);
+ 'charge.accountPHID IN (%Ls)',
+ $this->accountPHIDs);
}
return $this->formatWhereClause($where);
diff --git a/src/applications/phortune/query/PhortunePurchaseQuery.php b/src/applications/phortune/query/PhortunePurchaseQuery.php
--- a/src/applications/phortune/query/PhortunePurchaseQuery.php
+++ b/src/applications/phortune/query/PhortunePurchaseQuery.php
@@ -38,16 +38,18 @@
}
protected function willFilterPage(array $purchases) {
- $carts = id(new PhabricatorObjectQuery())
+ $carts = id(new PhortuneCartQuery())
->setViewer($this->getViewer())
->setParentQuery($this)
->withPHIDs(mpull($purchases, 'getCartPHID'))
->execute();
+ $carts = mpull($carts, null, 'getPHID');
foreach ($purchases as $key => $purchase) {
$cart = idx($carts, $purchase->getCartPHID());
if (!$cart) {
unset($purchases[$key]);
+ continue;
}
$purchase->attachCart($cart);
}
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
@@ -47,6 +47,16 @@
return $this->assertAttached($this->account);
}
+ public function getTotalPriceInCents() {
+ $prices = array();
+ foreach ($this->getPurchases() as $purchase) {
+ $prices[] = PhortuneCurrency::newFromUSDCents(
+ $purchase->getTotalPriceInCents());
+ }
+
+ return PhortuneCurrency::newFromList($prices)->getValue();
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
diff --git a/src/applications/phortune/storage/PhortuneCharge.php b/src/applications/phortune/storage/PhortuneCharge.php
--- a/src/applications/phortune/storage/PhortuneCharge.php
+++ b/src/applications/phortune/storage/PhortuneCharge.php
@@ -2,12 +2,12 @@
/**
* A charge is a charge (or credit) against an account and represents an actual
- * transfer of funds. Each charge is normally associated with a product, but a
- * product may have multiple charges. For example, a subscription may have
- * monthly charges, or a product may have a failed charge followed by a
- * successful charge.
+ * transfer of funds. Each charge is normally associated with a cart, but a
+ * cart may have multiple charges. For example, a product may have a failed
+ * charge followed by a successful charge.
*/
-final class PhortuneCharge extends PhortuneDAO {
+final class PhortuneCharge extends PhortuneDAO
+ implements PhabricatorPolicyInterface {
const STATUS_PENDING = 'charge:pending';
const STATUS_AUTHORIZED = 'charge:authorized';
@@ -16,12 +16,15 @@
const STATUS_FAILED = 'charge:failed';
protected $accountPHID;
- protected $purchasePHID;
+ protected $authorPHID;
+ protected $cartPHID;
protected $paymentMethodPHID;
protected $amountInCents;
protected $status;
protected $metadata = array();
+ private $account = self::ATTACHABLE;
+
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
@@ -36,4 +39,49 @@
PhabricatorPHIDConstants::PHID_TYPE_CHRG);
}
+ protected function didReadData() {
+ // The payment processing code is strict about types.
+ $this->amountInCents = (int)$this->amountInCents;
+ }
+
+ public function getMetadataValue($key, $default = null) {
+ return idx($this->metadata, $key, $default);
+ }
+
+ public function setMetadataValue($key, $value) {
+ $this->metadata[$key] = $value;
+ return $this;
+ }
+
+ public function getAccount() {
+ return $this->assertAttached($this->account);
+ }
+
+ public function attachAccount(PhortuneAccount $account) {
+ $this->account = $account;
+ return $this;
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ );
+ }
+
+ public function getPolicy($capability) {
+ return $this->getAccount()->getPolicy($capability);
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return $this->getAccount()->hasAutomaticCapability($capability, $viewer);
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return pht('Charges inherit the policies of the associated account.');
+ }
+
}

File Metadata

Mime Type
text/plain
Expires
Sun, Apr 6, 1:42 AM (6 d, 2 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7733834
Default Alt Text
D10002.id24041.diff (18 KB)

Event Timeline