diff --git a/resources/sql/autopatches/20141005.phortuneproduct.sql b/resources/sql/autopatches/20141005.phortuneproduct.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20141005.phortuneproduct.sql
@@ -0,0 +1,22 @@
+DROP TABLE {$NAMESPACE}_phortune.phortune_producttransaction;
+
+ALTER TABLE {$NAMESPACE}_phortune.phortune_product
+  DROP productName;
+
+ALTER TABLE {$NAMESPACE}_phortune.phortune_product
+  DROP priceAsCurrency;
+
+ALTER TABLE {$NAMESPACE}_phortune.phortune_product
+  ADD productClassKey BINARY(12) NOT NULL;
+
+ALTER TABLE {$NAMESPACE}_phortune.phortune_product
+  ADD productClass VARCHAR(128) NOT NULL COLLATE utf8_bin;
+
+ALTER TABLE {$NAMESPACE}_phortune.phortune_product
+  ADD productRefKey BINARY(12) NOT NULL;
+
+ALTER TABLE {$NAMESPACE}_phortune.phortune_product
+  ADD productRef VARCHAR(128) NOT NULL COLLATE utf8_bin;
+
+ALTER TABLE {$NAMESPACE}_phortune.phortune_product
+  ADD UNIQUE KEY `key_product` (productClassKey, productRefKey);
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
@@ -669,6 +669,7 @@
     'FundBackerEditor' => 'applications/fund/editor/FundBackerEditor.php',
     'FundBackerListController' => 'applications/fund/controller/FundBackerListController.php',
     'FundBackerPHIDType' => 'applications/fund/phid/FundBackerPHIDType.php',
+    'FundBackerProduct' => 'applications/fund/phortune/FundBackerProduct.php',
     'FundBackerQuery' => 'applications/fund/query/FundBackerQuery.php',
     'FundBackerSearchEngine' => 'applications/fund/query/FundBackerSearchEngine.php',
     'FundBackerTransaction' => 'applications/fund/storage/FundBackerTransaction.php',
@@ -2580,13 +2581,9 @@
     'PhortunePaymentProviderTestCase' => 'applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php',
     'PhortunePaypalPaymentProvider' => 'applications/phortune/provider/PhortunePaypalPaymentProvider.php',
     'PhortuneProduct' => 'applications/phortune/storage/PhortuneProduct.php',
-    'PhortuneProductEditController' => 'applications/phortune/controller/PhortuneProductEditController.php',
-    'PhortuneProductEditor' => 'applications/phortune/editor/PhortuneProductEditor.php',
+    'PhortuneProductImplementation' => 'applications/phortune/product/PhortuneProductImplementation.php',
     'PhortuneProductListController' => 'applications/phortune/controller/PhortuneProductListController.php',
-    'PhortuneProductPurchaseController' => 'applications/phortune/controller/PhortuneProductPurchaseController.php',
     'PhortuneProductQuery' => 'applications/phortune/query/PhortuneProductQuery.php',
-    'PhortuneProductTransaction' => 'applications/phortune/storage/PhortuneProductTransaction.php',
-    'PhortuneProductTransactionQuery' => 'applications/phortune/query/PhortuneProductTransactionQuery.php',
     'PhortuneProductViewController' => 'applications/phortune/controller/PhortuneProductViewController.php',
     'PhortuneProviderController' => 'applications/phortune/controller/PhortuneProviderController.php',
     'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php',
@@ -3520,6 +3517,7 @@
     'FundBackerEditor' => 'PhabricatorApplicationTransactionEditor',
     'FundBackerListController' => 'FundController',
     'FundBackerPHIDType' => 'PhabricatorPHIDType',
+    'FundBackerProduct' => 'PhortuneProductImplementation',
     'FundBackerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'FundBackerSearchEngine' => 'PhabricatorApplicationSearchEngine',
     'FundBackerTransaction' => 'PhabricatorApplicationTransaction',
@@ -5616,13 +5614,8 @@
       'PhortuneDAO',
       'PhabricatorPolicyInterface',
     ),
-    'PhortuneProductEditController' => 'PhabricatorController',
-    'PhortuneProductEditor' => 'PhabricatorApplicationTransactionEditor',
     'PhortuneProductListController' => 'PhabricatorController',
-    'PhortuneProductPurchaseController' => 'PhortuneController',
     'PhortuneProductQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
-    'PhortuneProductTransaction' => 'PhabricatorApplicationTransaction',
-    'PhortuneProductTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
     'PhortuneProductViewController' => 'PhortuneController',
     'PhortuneProviderController' => 'PhortuneController',
     'PhortunePurchase' => array(
diff --git a/src/applications/fund/controller/FundInitiativeBackController.php b/src/applications/fund/controller/FundInitiativeBackController.php
--- a/src/applications/fund/controller/FundInitiativeBackController.php
+++ b/src/applications/fund/controller/FundInitiativeBackController.php
@@ -60,7 +60,19 @@
           ->setAmountAsCurrency($currency)
           ->save();
 
-        // TODO: Here, we'd create a purchase and cart.
+        $product = id(new PhortuneProductQuery())
+          ->setViewer($viewer)
+          ->withClassAndRef('FundBackerProduct', $initiative->getPHID())
+          ->executeOne();
+
+        $account = PhortuneAccountQuery::loadActiveAccountForUser(
+          $viewer,
+          PhabricatorContentSource::newFromRequest($request));
+
+        $cart = $account->newCart($viewer);
+
+        $purchase = $cart->newPurchase($viewer, $product);
+        $purchase->setBasePriceAsCurrency($currency)->save();
 
         $xactions = array();
 
@@ -74,9 +86,8 @@
 
         $editor->applyTransactions($backer, $xactions);
 
-        // TODO: Here, we'd ship the user into Phortune.
-
-        return id(new AphrontRedirectResponse())->setURI($initiative_uri);
+        return id(new AphrontRedirectResponse())
+          ->setURI($cart->getCheckoutURI());
       }
     }
 
diff --git a/src/applications/fund/phortune/FundBackerProduct.php b/src/applications/fund/phortune/FundBackerProduct.php
new file mode 100644
--- /dev/null
+++ b/src/applications/fund/phortune/FundBackerProduct.php
@@ -0,0 +1,64 @@
+<?php
+
+final class FundBackerProduct extends PhortuneProductImplementation {
+
+  private $initiativePHID;
+  private $initiative;
+
+  public function getRef() {
+    return $this->getInitiativePHID();
+  }
+
+  public function getName(PhortuneProduct $product) {
+    return pht('Back Initiative %s', $this->initiativePHID);
+  }
+
+  public function getPriceAsCurrency(PhortuneProduct $product) {
+    return PhortuneCurrency::newEmptyCurrency();
+  }
+
+  public function setInitiativePHID($initiative_phid) {
+    $this->initiativePHID = $initiative_phid;
+    return $this;
+  }
+
+  public function getInitiativePHID() {
+    return $this->initiativePHID;
+  }
+
+  public function setInitiative(FundInitiative $initiative) {
+    $this->initiative = $initiative;
+    return $this;
+  }
+
+  public function getInitiative() {
+    return $this->initiative;
+  }
+
+  public function loadImplementationsForRefs(
+    PhabricatorUser $viewer,
+    array $refs) {
+
+    $initiatives = id(new FundInitiativeQuery())
+      ->setViewer($viewer)
+      ->withPHIDs($refs)
+      ->execute();
+    $initiatives = mpull($initiatives, null, 'getPHID');
+
+    $objects = array();
+    foreach ($refs as $ref) {
+      $object = id(new FundBackerProduct())
+        ->setInitiativePHID($ref);
+
+      $initiative = idx($initiatives, $ref);
+      if ($initiative) {
+        $object->setInitiative($initiative);
+      }
+
+      $objects[] = $object;
+    }
+
+    return $objects;
+  }
+
+}
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
@@ -58,9 +58,6 @@
           'view/(?P<id>\d+)/' => 'PhortuneProductViewController',
           'edit/(?:(?P<id>\d+)/)?' => 'PhortuneProductEditController',
         ),
-        'purchase/(?P<id>\d+)/' => array(
-          '' => 'PhortunePurchaseViewController',
-        ),
         'provider/(?P<digest>[^/]+)/(?P<action>[^/]+)/'
           => 'PhortuneProviderController',
       ),
diff --git a/src/applications/phortune/controller/PhortuneController.php b/src/applications/phortune/controller/PhortuneController.php
--- a/src/applications/phortune/controller/PhortuneController.php
+++ b/src/applications/phortune/controller/PhortuneController.php
@@ -3,53 +3,9 @@
 abstract class PhortuneController extends PhabricatorController {
 
   protected function loadActiveAccount(PhabricatorUser $user) {
-    $accounts = id(new PhortuneAccountQuery())
-      ->setViewer($user)
-      ->withMemberPHIDs(array($user->getPHID()))
-      ->execute();
-
-    if (!$accounts) {
-      return $this->createUserAccount($user);
-    } else if (count($accounts) == 1) {
-      return head($accounts);
-    } else {
-      throw new Exception('TODO: No account selection yet.');
-    }
-  }
-
-  protected function createUserAccount(PhabricatorUser $user) {
-    $request = $this->getRequest();
-
-    $xactions = array();
-    $xactions[] = id(new PhortuneAccountTransaction())
-      ->setTransactionType(PhortuneAccountTransaction::TYPE_NAME)
-      ->setNewValue(pht('Account (%s)', $user->getUserName()));
-
-    $xactions[] = id(new PhortuneAccountTransaction())
-      ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
-      ->setMetadataValue(
-        'edge:type',
-        PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER)
-      ->setNewValue(
-        array(
-          '=' => array($user->getPHID() => $user->getPHID()),
-        ));
-
-    $account = id(new PhortuneAccount())
-      ->attachMemberPHIDs(array());
-
-    $editor = id(new PhortuneAccountEditor())
-      ->setActor($user)
-      ->setContentSourceFromRequest($request);
-
-    // We create an account for you the first time you visit Phortune.
-    $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
-
-      $editor->applyTransactions($account, $xactions);
-
-    unset($unguarded);
-
-    return $account;
+    return PhortuneAccountQuery::loadActiveAccountForUser(
+      $user,
+      PhabricatorContentSource::newFromRequest($this->getRequest()));
   }
 
   protected function buildChargesTable(array $charges, $show_cart = true) {
diff --git a/src/applications/phortune/controller/PhortuneProductEditController.php b/src/applications/phortune/controller/PhortuneProductEditController.php
deleted file mode 100644
--- a/src/applications/phortune/controller/PhortuneProductEditController.php
+++ /dev/null
@@ -1,133 +0,0 @@
-<?php
-
-final class PhortuneProductEditController extends PhabricatorController {
-
-  private $productID;
-
-  public function willProcessRequest(array $data) {
-    $this->productID = idx($data, 'id');
-  }
-
-  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 = PhortuneProduct::initializeNewProduct();
-      $is_create = true;
-      $cancel_uri = $this->getApplicationURI('product/');
-    }
-
-    $v_name = $product->getProductName();
-    $v_price = $product->getPriceAsCurrency()->formatForDisplay();
-    $display_price = $v_price;
-
-    $e_name = true;
-    $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;
-      }
-
-      $display_price = $request->getStr('price');
-      try {
-        $v_price = PhortuneCurrency::newFromUserInput($user, $display_price)
-          ->serializeForStorage();
-        $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_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 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,
-      ));
-  }
-
-}
diff --git a/src/applications/phortune/controller/PhortuneProductPurchaseController.php b/src/applications/phortune/controller/PhortuneProductPurchaseController.php
deleted file mode 100644
--- a/src/applications/phortune/controller/PhortuneProductPurchaseController.php
+++ /dev/null
@@ -1,71 +0,0 @@
-<?php
-
-final class PhortuneProductPurchaseController
-  extends PhortuneController {
-
-  private $accountID;
-  private $productID;
-
-  public function willProcessRequest(array $data) {
-    $this->accountID = $data['accountID'];
-    $this->productID = $data['productID'];
-  }
-
-  public function processRequest() {
-    $request = $this->getRequest();
-    $user = $request->getUser();
-
-    $account = id(new PhortuneAccountQuery())
-      ->setViewer($user)
-      ->withIDs(array($this->accountID))
-      ->executeOne();
-    if (!$account) {
-      return new Aphront404Response();
-    }
-
-    $account_uri = $this->getApplicationURI($account->getID().'/');
-
-    $product = id(new PhortuneProductQuery())
-      ->setViewer($user)
-      ->withIDs(array($this->productID))
-      ->executeOne();
-    if (!$product) {
-      return new Aphront404Response();
-    }
-
-    if ($request->isFormPost()) {
-      // TODO: Use ApplicationTransations.
-
-      $cart = new PhortuneCart();
-      $cart->openTransaction();
-
-        $cart->setStatus(PhortuneCart::STATUS_READY);
-        $cart->setAccountPHID($account->getPHID());
-        $cart->setAuthorPHID($user->getPHID());
-        $cart->save();
-
-        $purchase = new PhortunePurchase();
-        $purchase->setProductPHID($product->getPHID());
-        $purchase->setAccountPHID($account->getPHID());
-        $purchase->setAuthorPHID($user->getPHID());
-        $purchase->setCartPHID($cart->getPHID());
-        $purchase->setBasePriceAsCurrency($product->getPriceAsCurrency());
-        $purchase->setQuantity(1);
-
-        $purchase->setStatus(PhortunePurchase::STATUS_PENDING);
-        $purchase->save();
-
-      $cart->saveTransaction();
-
-      $cart_id = $cart->getID();
-      $cart_uri = $this->getApplicationURI('/cart/'.$cart_id.'/checkout/');
-      return id(new AphrontRedirectResponse())->setURI($cart_uri);
-    }
-
-    return $this->newDialog()
-      ->setTitle(pht('Purchase Product'))
-      ->appendParagraph(pht('Really purchase this stuff?'))
-      ->addSubmitButton(pht('Checkout'))
-      ->addCancelButton($account_uri);
-  }
-}
diff --git a/src/applications/phortune/controller/PhortuneProductViewController.php b/src/applications/phortune/controller/PhortuneProductViewController.php
--- a/src/applications/phortune/controller/PhortuneProductViewController.php
+++ b/src/applications/phortune/controller/PhortuneProductViewController.php
@@ -34,19 +34,7 @@
 
     $actions = id(new PhabricatorActionListView())
       ->setUser($user)
-      ->setObjectURI($request->getRequestURI())
-      ->addAction(
-        id(new PhabricatorActionView())
-          ->setName(pht('Edit Product'))
-          ->setHref($edit_uri)
-          ->setIcon('fa-pencil'))
-      ->addAction(
-        id(new PhabricatorActionView())
-          ->setUser($user)
-          ->setName(pht('Purchase'))
-          ->setHref($cart_uri)
-          ->setIcon('fa-shopping-cart')
-          ->setWorkflow(true));
+      ->setObjectURI($request->getRequestURI());
 
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->setActionList($actions);
@@ -64,20 +52,6 @@
         pht('Price'),
         $product->getPriceAsCurrency()->formatForDisplay());
 
-    $xactions = id(new PhortuneProductTransactionQuery())
-      ->setViewer($user)
-      ->withObjectPHIDs(array($product->getPHID()))
-      ->execute();
-
-    $engine = id(new PhabricatorMarkupEngine())
-      ->setViewer($user);
-
-    $xaction_view = id(new PhabricatorApplicationTransactionView())
-      ->setUser($user)
-      ->setObjectPHID($product->getPHID())
-      ->setTransactions($xactions)
-      ->setMarkupEngine($engine);
-
     $object_box = id(new PHUIObjectBoxView())
       ->setHeader($header)
       ->addPropertyList($properties);
@@ -86,7 +60,6 @@
       array(
         $crumbs,
         $object_box,
-        $xaction_view,
       ),
       array(
         'title' => $title,
diff --git a/src/applications/phortune/editor/PhortuneProductEditor.php b/src/applications/phortune/editor/PhortuneProductEditor.php
deleted file mode 100644
--- a/src/applications/phortune/editor/PhortuneProductEditor.php
+++ /dev/null
@@ -1,74 +0,0 @@
-<?php
-
-
-final class PhortuneProductEditor
-  extends PhabricatorApplicationTransactionEditor {
-
-  public function getEditorApplicationClass() {
-    return 'PhabricatorPhortuneApplication';
-  }
-
-  public function getEditorObjectsDescription() {
-    return pht('Phortune Products');
-  }
-
-  public function getTransactionTypes() {
-    $types = parent::getTransactionTypes();
-
-    $types[] = PhortuneProductTransaction::TYPE_NAME;
-    $types[] = PhortuneProductTransaction::TYPE_PRICE;
-
-    return $types;
-  }
-
-
-  protected function getCustomTransactionOldValue(
-    PhabricatorLiskDAO $object,
-    PhabricatorApplicationTransaction $xaction) {
-    switch ($xaction->getTransactionType()) {
-      case PhortuneProductTransaction::TYPE_NAME:
-        return $object->getProductName();
-      case PhortuneProductTransaction::TYPE_PRICE:
-        return $object->getPriceAsCurrency()->serializeForStorage();
-    }
-    return parent::getCustomTransactionOldValue($object, $xaction);
-  }
-
-  protected function getCustomTransactionNewValue(
-    PhabricatorLiskDAO $object,
-    PhabricatorApplicationTransaction $xaction) {
-    switch ($xaction->getTransactionType()) {
-      case PhortuneProductTransaction::TYPE_NAME:
-      case PhortuneProductTransaction::TYPE_PRICE:
-        return $xaction->getNewValue();
-    }
-    return parent::getCustomTransactionNewValue($object, $xaction);
-  }
-
-  protected function applyCustomInternalTransaction(
-    PhabricatorLiskDAO $object,
-    PhabricatorApplicationTransaction $xaction) {
-    switch ($xaction->getTransactionType()) {
-      case PhortuneProductTransaction::TYPE_NAME:
-        $object->setProductName($xaction->getNewValue());
-        return;
-      case PhortuneProductTransaction::TYPE_PRICE:
-        $object->setPriceAsCurrency(
-          PhortuneCurrency::newFromString($xaction->getNewValue()));
-        return;
-    }
-    return parent::applyCustomInternalTransaction($object, $xaction);
-  }
-
-  protected function applyCustomExternalTransaction(
-    PhabricatorLiskDAO $object,
-    PhabricatorApplicationTransaction $xaction) {
-    switch ($xaction->getTransactionType()) {
-      case PhortuneProductTransaction::TYPE_NAME:
-      case PhortuneProductTransaction::TYPE_PRICE:
-        return;
-    }
-    return parent::applyCustomExternalTransaction($object, $xaction);
-  }
-
-}
diff --git a/src/applications/phortune/product/PhortuneProductImplementation.php b/src/applications/phortune/product/PhortuneProductImplementation.php
new file mode 100644
--- /dev/null
+++ b/src/applications/phortune/product/PhortuneProductImplementation.php
@@ -0,0 +1,13 @@
+<?php
+
+abstract class PhortuneProductImplementation {
+
+  abstract public function loadImplementationsForRefs(
+    PhabricatorUser $viewer,
+    array $refs);
+
+  abstract public function getRef();
+  abstract public function getName(PhortuneProduct $product);
+  abstract public function getPriceAsCurrency(PhortuneProduct $product);
+
+}
diff --git a/src/applications/phortune/query/PhortuneAccountQuery.php b/src/applications/phortune/query/PhortuneAccountQuery.php
--- a/src/applications/phortune/query/PhortuneAccountQuery.php
+++ b/src/applications/phortune/query/PhortuneAccountQuery.php
@@ -7,6 +7,24 @@
   private $phids;
   private $memberPHIDs;
 
+  public static function loadActiveAccountForUser(
+    PhabricatorUser $user,
+    PhabricatorContentSource $content_source) {
+
+    $accounts = id(new PhortuneAccountQuery())
+      ->setViewer($user)
+      ->withMemberPHIDs(array($user->getPHID()))
+      ->execute();
+
+    if (!$accounts) {
+      return PhortuneAccount::createNewAccount($user, $content_source);
+    } else if (count($accounts) == 1) {
+      return head($accounts);
+    } else {
+      throw new Exception('TODO: No account selection yet.');
+    }
+  }
+
   public function withIDs(array $ids) {
     $this->ids = $ids;
     return $this;
diff --git a/src/applications/phortune/query/PhortuneProductQuery.php b/src/applications/phortune/query/PhortuneProductQuery.php
--- a/src/applications/phortune/query/PhortuneProductQuery.php
+++ b/src/applications/phortune/query/PhortuneProductQuery.php
@@ -5,6 +5,7 @@
 
   private $ids;
   private $phids;
+  private $refMap;
 
   public function withIDs(array $ids) {
     $this->ids = $ids;
@@ -16,6 +17,11 @@
     return $this;
   }
 
+  public function withClassAndRef($class, $ref) {
+    $this->refMap = array($class => array($ref));
+    return $this;
+  }
+
   protected function loadPage() {
     $table = new PhortuneProduct();
     $conn = $table->establishConnection('r');
@@ -28,26 +34,80 @@
       $this->buildOrderClause($conn),
       $this->buildLimitClause($conn));
 
-    return $table->loadAllFromArray($rows);
+    $page = $table->loadAllFromArray($rows);
+
+    // NOTE: We're loading product implementations here, but also creating any
+    // products which do not yet exist.
+
+    $class_map = mgroup($page, 'getProductClass');
+    if ($this->refMap) {
+      $class_map += array_fill_keys(array_keys($this->refMap), array());
+    }
+
+    foreach ($class_map as $class => $products) {
+      $refs = mpull($products, null, 'getProductRef');
+      if (isset($this->refMap[$class])) {
+        $refs += array_fill_keys($this->refMap[$class], null);
+      }
+
+      $implementations = newv($class, array())->loadImplementationsForRefs(
+        $this->getViewer(),
+        array_keys($refs));
+      $implementations = mpull($implementations, null, 'getRef');
+
+      foreach ($implementations as $ref => $implementation) {
+        $product = idx($refs, $ref);
+        if ($product === null) {
+          // If this product does not exist yet, create it and add it to the
+          // result page.
+          $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+            $product = PhortuneProduct::initializeNewProduct()
+              ->setProductClass($class)
+              ->setProductRef($ref)
+              ->save();
+          unset($unguarded);
+
+          $page[] = $product;
+        }
+
+        $product->attachImplementation($implementation);
+      }
+    }
+
+    return $page;
   }
 
   private function buildWhereClause(AphrontDatabaseConnection $conn) {
     $where = array();
 
-    if ($this->ids) {
+    if ($this->ids !== null) {
       $where[] = qsprintf(
         $conn,
         'id IN (%Ld)',
         $this->ids);
     }
 
-    if ($this->phids) {
+    if ($this->phids !== null) {
       $where[] = qsprintf(
         $conn,
         'phid IN (%Ls)',
         $this->phids);
     }
 
+    if ($this->refMap !== null) {
+      $sql = array();
+      foreach ($this->refMap as $class => $refs) {
+        foreach ($refs as $ref) {
+          $sql[] = qsprintf(
+            $conn,
+            '(productClassKey = %s AND productRefKey = %s)',
+            PhabricatorHash::digestForIndex($class),
+            PhabricatorHash::digestForIndex($ref));
+        }
+      }
+      $where[] = implode(' OR ', $sql);
+    }
+
     $where[] = $this->buildPagingClause($conn);
 
     return $this->formatWhereClause($where);
diff --git a/src/applications/phortune/query/PhortuneProductTransactionQuery.php b/src/applications/phortune/query/PhortuneProductTransactionQuery.php
deleted file mode 100644
--- a/src/applications/phortune/query/PhortuneProductTransactionQuery.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-final class PhortuneProductTransactionQuery
-  extends PhabricatorApplicationTransactionQuery {
-
-  public function getTemplateApplicationTransaction() {
-    return new PhortuneProductTransaction();
-  }
-
-}
diff --git a/src/applications/phortune/storage/PhortuneAccount.php b/src/applications/phortune/storage/PhortuneAccount.php
--- a/src/applications/phortune/storage/PhortuneAccount.php
+++ b/src/applications/phortune/storage/PhortuneAccount.php
@@ -13,6 +13,54 @@
 
   private $memberPHIDs = self::ATTACHABLE;
 
+  public static function initializeNewAccount(PhabricatorUser $actor) {
+    $account = id(new PhortuneAccount());
+
+    $account->memberPHIDs = array();
+
+    return $account;
+  }
+
+  public static function createNewAccount(
+    PhabricatorUser $actor,
+    PhabricatorContentSource $content_source) {
+
+    $account = PhortuneAccount::initializeNewAccount($actor);
+
+    $xactions = array();
+    $xactions[] = id(new PhortuneAccountTransaction())
+      ->setTransactionType(PhortuneAccountTransaction::TYPE_NAME)
+      ->setNewValue(pht('Account (%s)', $actor->getUserName()));
+
+    $xactions[] = id(new PhortuneAccountTransaction())
+      ->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
+      ->setMetadataValue(
+        'edge:type',
+        PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER)
+      ->setNewValue(
+        array(
+          '=' => array($actor->getPHID() => $actor->getPHID()),
+        ));
+
+    $editor = id(new PhortuneAccountEditor())
+      ->setActor($actor)
+      ->setContentSource($content_source);
+
+    // We create an account for you the first time you visit Phortune.
+    $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
+
+      $editor->applyTransactions($account, $xactions);
+
+    unset($unguarded);
+
+    return $account;
+  }
+
+  public function newCart(PhabricatorUser $actor) {
+    return PhortuneCart::initializeNewCart($actor, $this)
+      ->save();
+  }
+
   public function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
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
@@ -15,6 +15,38 @@
   private $account = self::ATTACHABLE;
   private $purchases = self::ATTACHABLE;
 
+  public static function initializeNewCart(
+    PhabricatorUser $actor,
+    PhortuneAccount $account) {
+    $cart = id(new PhortuneCart())
+      ->setAuthorPHID($actor->getPHID())
+      ->setStatus(self::STATUS_READY)
+      ->setAccountPHID($account->getPHID());
+
+    $cart->account = $account;
+    $cart->purchases = array();
+
+    return $cart;
+  }
+
+  public function newPurchase(
+    PhabricatorUser $actor,
+    PhortuneProduct $product) {
+
+    $purchase = PhortunePurchase::initializeNewPurchase($actor, $product)
+      ->setAccountPHID($this->getAccount()->getPHID())
+      ->setCartPHID($this->getPHID())
+      ->save();
+
+    $this->purchases[] = $purchase;
+
+    return $purchase;
+  }
+
+  public function getCheckoutURI() {
+    return '/phortune/cart/'.$this->getID().'/checkout/';
+  }
+
   public function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
diff --git a/src/applications/phortune/storage/PhortuneProduct.php b/src/applications/phortune/storage/PhortuneProduct.php
--- a/src/applications/phortune/storage/PhortuneProduct.php
+++ b/src/applications/phortune/storage/PhortuneProduct.php
@@ -6,9 +6,13 @@
 final class PhortuneProduct extends PhortuneDAO
   implements PhabricatorPolicyInterface {
 
-  protected $productName;
-  protected $priceAsCurrency;
-  protected $metadata;
+  protected $productClassKey;
+  protected $productClass;
+  protected $productRefKey;
+  protected $productRef;
+  protected $metadata = array();
+
+  private $implementation = self::ATTACHABLE;
 
   public function getConfiguration() {
     return array(
@@ -16,15 +20,17 @@
       self::CONFIG_SERIALIZATION => array(
         'metadata' => self::SERIALIZATION_JSON,
       ),
-      self::CONFIG_APPLICATION_SERIALIZERS => array(
-        'priceAsCurrency' => new PhortuneCurrencySerializer(),
-      ),
       self::CONFIG_COLUMN_SCHEMA => array(
-        'productName' => 'text255',
-        'status' => 'text64',
-        'priceAsCurrency' => 'text64',
-        'billingIntervalInMonths' => 'uint32?',
-        'trialPeriodInDays' => 'uint32?',
+        'productClassKey' => 'bytes12',
+        'productClass' => 'text128',
+        'productRefKey' => 'bytes12',
+        'productRef' => 'text128',
+      ),
+      self::CONFIG_KEY_SCHEMA => array(
+        'key_product' => array(
+          'columns' => array('productClassKey', 'productRefKey'),
+          'unique' => true,
+        ),
       ),
     ) + parent::getConfiguration();
   }
@@ -35,8 +41,33 @@
   }
 
   public static function initializeNewProduct() {
-    return id(new PhortuneProduct())
-      ->setPriceAsCurrency(PhortuneCurrency::newEmptyCurrency());
+    return id(new PhortuneProduct());
+  }
+
+  public function attachImplementation(PhortuneProductImplementation $impl) {
+    $this->implementation = $impl;
+  }
+
+  public function getImplementation() {
+    return $this->assertAttached($this->implementation);
+  }
+
+  public function save() {
+    $this->productClassKey = PhabricatorHash::digestForIndex(
+      $this->productClass);
+
+    $this->productRefKey = PhabricatorHash::digestForIndex(
+      $this->productRef);
+
+    return parent::save();
+  }
+
+  public function getPriceAsCurrency() {
+    return $this->getImplementation()->getPriceAsCurrency($this);
+  }
+
+  public function getProductName() {
+    return $this->getImplementation()->getName($this);
   }
 
 
diff --git a/src/applications/phortune/storage/PhortuneProductTransaction.php b/src/applications/phortune/storage/PhortuneProductTransaction.php
deleted file mode 100644
--- a/src/applications/phortune/storage/PhortuneProductTransaction.php
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-
-final class PhortuneProductTransaction
-  extends PhabricatorApplicationTransaction {
-
-  const TYPE_NAME   = 'product:name';
-  const TYPE_PRICE  = 'product:price';
-
-  public function getApplicationName() {
-    return 'phortune';
-  }
-
-  public function getApplicationTransactionType() {
-    return PhabricatorPHIDConstants::PHID_TYPE_PDCT;
-  }
-
-  public function getApplicationTransactionCommentObject() {
-    return null;
-  }
-
-  public function getTitle() {
-    $author_phid = $this->getAuthorPHID();
-
-    $old = $this->getOldValue();
-    $new = $this->getNewValue();
-
-    switch ($this->getTransactionType()) {
-      case self::TYPE_NAME:
-        if ($old === null) {
-          return pht(
-            '%s created this product.',
-            $this->renderHandleLink($author_phid));
-        } else {
-          return pht(
-            '%s renamed this product from "%s" to "%s".',
-            $this->renderHandleLink($author_phid),
-            $old,
-            $new);
-        }
-        break;
-      case self::TYPE_PRICE:
-        if ($old === null) {
-          return pht(
-            '%s set product price to %s.',
-            $this->renderHandleLink($author_phid),
-            PhortuneCurrency::newFromString($new)
-              ->formatForDisplay());
-        } else {
-          return pht(
-            '%s changed product price from %s to %s.',
-            $this->renderHandleLink($author_phid),
-            PhortuneCurrency::newFromString($old)
-              ->formatForDisplay(),
-            PhortuneCurrency::newFromString($new)
-              ->formatForDisplay());
-        }
-        break;
-    }
-
-    return parent::getTitle();
-  }
-
-}
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
@@ -20,10 +20,21 @@
   protected $basePriceAsCurrency;
   protected $quantity;
   protected $status;
-  protected $metadata;
+  protected $metadata = array();
 
   private $cart = self::ATTACHABLE;
 
+  public static function initializeNewPurchase(
+    PhabricatorUser $actor,
+    PhortuneProduct $product) {
+    return id(new PhortunePurchase())
+      ->setAuthorPHID($actor->getPHID())
+      ->setProductPHID($product->getPHID())
+      ->setQuantity(1)
+      ->setStatus(self::STATUS_PENDING)
+      ->setBasePriceAsCurrency($product->getPriceAsCurrency());
+  }
+
   public function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,