1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-27 01:02:42 +01:00

Phortune v0

Summary:
Ref T2787. This does very little so far, but makes inroads on accounts and billing. This is mostly just modeled on what Stripe looks like. The objects are:

  - **Account**: Has one or more authorized users, who can make manage the account. An example might be "Phacility", and the three of us would be able to manage it. A user may be associated with more than one account (e.g., a corporate account and a personal account) but the UI tries to simplify the common case of a single account.
  - **Payment Method**: Something we can get sweet sweet money from; for now, a credit card registered with Stripe. Payment methods are associated with an account.
  - **Product**: A good (one time charge) or service (recurring charge). This might be "t-shirt" or "enterprise plan" or "hourly support" or whatever else.
  - **Purchase**: Represents a user purchasing a Product for an Account, using a Payment Method. e.g., you bought a shirt, or started a plan, or purchased support.
  - **Charge**: Actual charges against payment methods. A Purchase can create more than one charge if it's a plan, or if the first charge fails and we re-bill.

This doesn't fully account for stuff like coupons/discounts yet but they should fit into the model without any issues.

This only implements `Account`, and that only partially.

Test Plan: {F37531}

Reviewers: chad, btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2787

Differential Revision: https://secure.phabricator.com/D5435
This commit is contained in:
epriestley 2013-03-28 09:10:34 -07:00
parent 696498934c
commit 960ac3b2a6
22 changed files with 897 additions and 7 deletions

View file

@ -0,0 +1,46 @@
CREATE TABLE {$NAMESPACE}_phortune.phortune_account (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
name VARCHAR(255) NOT NULL,
balanceInCents BIGINT NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (phid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_phortune.phortune_accounttransaction (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
commentPHID VARCHAR(64) COLLATE utf8_bin,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
newValue LONGTEXT NOT NULL COLLATE utf8_bin,
contentSource LONGTEXT 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_object` (objectPHID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_phortune.edge (
src VARCHAR(64) NOT NULL COLLATE utf8_bin,
type INT UNSIGNED NOT NULL COLLATE utf8_bin,
dst VARCHAR(64) NOT NULL COLLATE utf8_bin,
dateCreated INT UNSIGNED NOT NULL,
seq INT UNSIGNED NOT NULL,
dataID INT UNSIGNED,
PRIMARY KEY (src, type, dst),
KEY (src, type, dateCreated, seq)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE {$NAMESPACE}_phortune.edgedata (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
data LONGTEXT NOT NULL COLLATE utf8_bin
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

View file

@ -702,6 +702,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationPhame' => 'applications/phame/application/PhabricatorApplicationPhame.php', 'PhabricatorApplicationPhame' => 'applications/phame/application/PhabricatorApplicationPhame.php',
'PhabricatorApplicationPhlux' => 'applications/phlux/application/PhabricatorApplicationPhlux.php', 'PhabricatorApplicationPhlux' => 'applications/phlux/application/PhabricatorApplicationPhlux.php',
'PhabricatorApplicationPholio' => 'applications/pholio/application/PhabricatorApplicationPholio.php', 'PhabricatorApplicationPholio' => 'applications/pholio/application/PhabricatorApplicationPholio.php',
'PhabricatorApplicationPhortune' => 'applications/phortune/application/PhabricatorApplicationPhortune.php',
'PhabricatorApplicationPhriction' => 'applications/phriction/application/PhabricatorApplicationPhriction.php', 'PhabricatorApplicationPhriction' => 'applications/phriction/application/PhabricatorApplicationPhriction.php',
'PhabricatorApplicationPonder' => 'applications/ponder/application/PhabricatorApplicationPonder.php', 'PhabricatorApplicationPonder' => 'applications/ponder/application/PhabricatorApplicationPonder.php',
'PhabricatorApplicationProject' => 'applications/project/application/PhabricatorApplicationProject.php', 'PhabricatorApplicationProject' => 'applications/project/application/PhabricatorApplicationProject.php',
@ -1527,7 +1528,22 @@ phutil_register_library_map(array(
'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php', 'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php',
'PholioTransactionType' => 'applications/pholio/constants/PholioTransactionType.php', 'PholioTransactionType' => 'applications/pholio/constants/PholioTransactionType.php',
'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php',
'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php',
'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php',
'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php',
'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php',
'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php',
'PhortuneAccountViewController' => 'applications/phortune/controller/PhortuneAccountViewController.php',
'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php',
'PhortuneController' => 'applications/phortune/controller/PhortuneController.php',
'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php',
'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php',
'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php', 'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php',
'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php',
'PhortunePaymentMethodListController' => 'applications/phortune/controller/PhortunePaymentMethodListController.php',
'PhortunePaymentMethodViewController' => 'applications/phortune/controller/PhortunePaymentMethodViewController.php',
'PhortuneProduct' => 'applications/phortune/storage/PhortuneProduct.php',
'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php',
'PhortuneStripeBaseController' => 'applications/phortune/stripe/controller/PhortuneStripeBaseController.php', 'PhortuneStripeBaseController' => 'applications/phortune/stripe/controller/PhortuneStripeBaseController.php',
'PhortuneStripePaymentFormView' => 'applications/phortune/stripe/view/PhortuneStripePaymentFormView.php', 'PhortuneStripePaymentFormView' => 'applications/phortune/stripe/view/PhortuneStripePaymentFormView.php',
'PhortuneStripeTestPaymentFormController' => 'applications/phortune/stripe/controller/PhortuneStripeTestPaymentFormController.php', 'PhortuneStripeTestPaymentFormController' => 'applications/phortune/stripe/controller/PhortuneStripeTestPaymentFormController.php',
@ -2328,6 +2344,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationPhame' => 'PhabricatorApplication', 'PhabricatorApplicationPhame' => 'PhabricatorApplication',
'PhabricatorApplicationPhlux' => 'PhabricatorApplication', 'PhabricatorApplicationPhlux' => 'PhabricatorApplication',
'PhabricatorApplicationPholio' => 'PhabricatorApplication', 'PhabricatorApplicationPholio' => 'PhabricatorApplication',
'PhabricatorApplicationPhortune' => 'PhabricatorApplication',
'PhabricatorApplicationPhriction' => 'PhabricatorApplication', 'PhabricatorApplicationPhriction' => 'PhabricatorApplication',
'PhabricatorApplicationPonder' => 'PhabricatorApplication', 'PhabricatorApplicationPonder' => 'PhabricatorApplication',
'PhabricatorApplicationProject' => 'PhabricatorApplication', 'PhabricatorApplicationProject' => 'PhabricatorApplication',
@ -3157,7 +3174,30 @@ phutil_register_library_map(array(
'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PholioTransactionType' => 'PholioConstants', 'PholioTransactionType' => 'PholioConstants',
'PholioTransactionView' => 'PhabricatorApplicationTransactionView', 'PholioTransactionView' => 'PhabricatorApplicationTransactionView',
'PhortuneAccount' =>
array(
0 => 'PhortuneDAO',
1 => 'PhabricatorPolicyInterface',
),
'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction',
'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortuneAccountViewController' => 'PhortuneController',
'PhortuneCharge' => 'PhortuneDAO',
'PhortuneController' => 'PhabricatorController',
'PhortuneDAO' => 'PhabricatorLiskDAO',
'PhortuneLandingController' => 'PhortuneController',
'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl',
'PhortunePaymentMethod' =>
array(
0 => 'PhortuneDAO',
1 => 'PhabricatorPolicyInterface',
),
'PhortunePaymentMethodListController' => 'PhabricatorController',
'PhortunePaymentMethodViewController' => 'PhabricatorController',
'PhortuneProduct' => 'PhortuneDAO',
'PhortunePurchase' => 'PhortuneDAO',
'PhortuneStripeBaseController' => 'PhabricatorController', 'PhortuneStripeBaseController' => 'PhabricatorController',
'PhortuneStripePaymentFormView' => 'AphrontView', 'PhortuneStripePaymentFormView' => 'AphrontView',
'PhortuneStripeTestPaymentFormController' => 'PhortuneStripeBaseController', 'PhortuneStripeTestPaymentFormController' => 'PhortuneStripeBaseController',

View file

@ -104,12 +104,6 @@ class AphrontDefaultApplicationConfiguration
'clear/' => 'PhabricatorNotificationClearController', 'clear/' => 'PhabricatorNotificationClearController',
), ),
'/phortune/' => array(
'stripe/' => array(
'testpaymentform/' => 'PhortuneStripeTestPaymentFormController',
),
),
'/debug/' => 'PhabricatorDebugController', '/debug/' => 'PhabricatorDebugController',
); );
} }

View file

@ -33,6 +33,11 @@ final class PhabricatorPHIDConstants {
const PHID_TYPE_CONF = 'CONF'; const PHID_TYPE_CONF = 'CONF';
const PHID_TYPE_CONP = 'CONP'; const PHID_TYPE_CONP = 'CONP';
const PHID_TYPE_PVAR = 'PVAR'; const PHID_TYPE_PVAR = 'PVAR';
const PHID_TYPE_ACNT = 'ACNT';
const PHID_TYPE_PDCT = 'PDCT';
const PHID_TYPE_PRCH = 'PRCH';
const PHID_TYPE_PAYM = 'PAYM';
const PHID_TYPE_CHRG = 'CHRG';
const PHID_TYPE_XACT = 'XACT'; const PHID_TYPE_XACT = 'XACT';
const PHID_TYPE_XCMT = 'XCMT'; const PHID_TYPE_XCMT = 'XCMT';

View file

@ -0,0 +1,53 @@
<?php
final class PhabricatorApplicationPhortune extends PhabricatorApplication {
public function getBaseURI() {
return '/phortune/';
}
public function getShortDescription() {
return pht('Account and Billing');
}
public function getIconName() {
return 'phortune';
}
public function getTitleGlyph() {
return "\xE2\x9C\x98";
}
public function getApplicationGroup() {
return self::GROUP_UTILITIES;
}
public function isBeta() {
return true;
}
public function getRoutes() {
return array(
'/phortune/' => array(
'' => 'PhortuneLandingController',
'(?P<accountID>\d+)/' => array(
'' => 'PhortuneAccountViewController',
),
'account/' => array(
'' => 'PhortuneAccountListController',
'edit/(?:(?P<id>\d+)/)?' => 'PhortuneAccountEditController',
),
'paymentmethod/' => array(
'' => 'PhortunePaymentMethodListController',
'view/(?P<id>\d+)/' => 'PhortunePaymentMethodViewController',
'edit/(?:(?P<id>\d+)/)?' => 'PhortunePaymentMethodEditController',
),
'stripe/' => array(
'testpaymentform/' => 'PhortuneStripeTestPaymentFormController',
),
),
);
}
}

View file

@ -0,0 +1,131 @@
<?php
final class PhortuneAccountViewController extends PhortuneController {
private $accountID;
public function willProcessRequest(array $data) {
$this->accountID = $data['accountID'];
}
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();
}
$title = $account->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Account'))
->setHref($request->getRequestURI()));
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$actions = id(new PhabricatorActionListView())
->setUser($user)
->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Account'))
->setIcon('edit')
->setHref('#')
->setDisabled(true))
->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Members'))
->setIcon('transcript')
->setHref('#')
->setDisabled(true));
$properties = id(new PhabricatorPropertyListView())
->setObject($account)
->setUser($user);
$properties->addProperty(pht('Balance'), $account->getBalanceInCents());
$payment_methods = $this->buildPaymentMethodsSection($account);
$account_history = $this->buildAccountHistorySection($account);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$payment_methods,
$account_history,
),
array(
'title' => $title,
'device' => true,
'dust' => true,
));
}
private function buildPaymentMethodsSection(PhortuneAccount $account) {
$request = $this->getRequest();
$user = $request->getUser();
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Payment Methods'));
$id = $account->getID();
$add_uri = $this->getApplicationURI($id.'/paymentmethod/edit/');
$actions = id(new PhabricatorActionListView())
->setUser($user)
->addAction(
id(new PhabricatorActionView())
->setName(pht('Add Payment Method'))
->setIcon('new')
->setHref($add_uri));
$list = id(new PhabricatorObjectItemListView())
->setUser($user)
->setNoDataString(
pht('No payment methods associated with this account.'));
return array(
$header,
$actions,
$list,
);
}
private function buildAccountHistorySection(PhortuneAccount $account) {
$request = $this->getRequest();
$user = $request->getUser();
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Account History'));
$xactions = id(new PhortuneAccountTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($account->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setTransactions($xactions)
->setMarkupEngine($engine);
return array(
$header,
$xaction_view,
);
}
}

View file

@ -0,0 +1,45 @@
<?php
abstract class PhortuneController extends PhabricatorController {
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 = new PhortuneAccount();
$editor = id(new PhortuneAccountEditor())
->setActor($user)
->setContentSource(
PhabricatorContentSource::newForSource(
PhabricatorContentSource::SOURCE_WEB,
array(
'ip' => $request->getRemoteAddr(),
)));
// We create an account for you the first time you visit Phortune.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$editor->applyTransactions($account, $xactions);
unset($unguarded);
return $account;
}
}

View file

@ -0,0 +1,29 @@
<?php
final class PhortuneLandingController extends PhortuneController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$accounts = id(new PhortuneAccountQuery())
->setViewer($user)
->withMemberPHIDs(array($user->getPHID()))
->execute();
if (!$accounts) {
$account = $this->createUserAccount($user);
$accounts = array($account);
}
if (count($accounts) == 1) {
$account = head($accounts);
$next_uri = $this->getApplicationURI($account->getID().'/');
} else {
$next_uri = $this->getApplicationURI('account/');
}
return id(new AphrontRedirectResponse())->setURI($next_uri);
}
}

View file

@ -0,0 +1,23 @@
<?php
final class PhortunePaymentMethodListController extends PhabricatorController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$title = pht('Payment Methods');
$crumbs = $this->buildApplicationCrumbs();
return $this->buildApplicationPage(
array(
$crumbs,
),
array(
'title' => $title,
'device' => true,
'dust' => true,
));
}
}

View file

@ -0,0 +1,21 @@
<?php
final class PhortunePaymentMethodViewController extends PhabricatorController {
public function processRequest() {
$title = '...';
$crumbs = $this->buildApplicationCrumbs();
return $this->buildApplicationPage(
array(
$crumbs,
),
array(
'title' => $title,
'device' => true,
'dust' => true,
));
}
}

View file

@ -0,0 +1,62 @@
<?php
final class PhortuneAccountEditor
extends PhabricatorApplicationTransactionEditor {
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = PhabricatorTransactions::TYPE_EDGE;
$types[] = PhortuneAccountTransaction::TYPE_NAME;
return $types;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhortuneAccountTransaction::TYPE_NAME:
return $object->getName();
}
return parent::getCustomTransactionOldValue($object, $xaction);
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhortuneAccountTransaction::TYPE_NAME:
return $xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhortuneAccountTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
return;
case PhabricatorTransactions::TYPE_EDGE:
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhortuneAccountTransaction::TYPE_NAME:
return;
case PhabricatorTransactions::TYPE_EDGE:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
}

View file

@ -0,0 +1,102 @@
<?php
final class PhortuneAccountQuery
extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids;
private $phids;
private $memberPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
return $this;
}
public function withPHIDs(array $phids) {
$this->phids = $phids;
return $this;
}
public function withMemberPHIDs(array $phids) {
$this->memberPHIDs = $phids;
return $this;
}
protected function loadPage() {
$table = new PhortuneAccount();
$conn = $table->establishConnection('r');
$rows = queryfx_all(
$conn,
'SELECT a.* FROM %T a %Q %Q %Q %Q',
$table->getTableName(),
$this->buildJoinClause($conn),
$this->buildWhereClause($conn),
$this->buildOrderClause($conn),
$this->buildLimitClause($conn));
return $table->loadAllFromArray($rows);
}
protected function willFilterPage(array $accounts) {
if (!$accounts) {
return array();
}
$query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(mpull($accounts, 'getPHID'))
->withEdgeTypes(array(PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER));
$query->execute();
foreach ($accounts as $account) {
$member_phids = $query->getDestinationPHIDs(array($account->getPHID()));
$account->attachMemberPHIDs($member_phids);
}
return $accounts;
}
private function buildWhereClause(AphrontDatabaseConnection $conn) {
$where = array();
$where[] = $this->buildPagingClause($conn);
if ($this->ids) {
$where[] = qsprintf(
$conn,
'a.id IN (%Ld)',
$this->ids);
}
if ($this->phids) {
$where[] = qsprintf(
$conn,
'a.phid IN (%Ls)',
$this->phids);
}
if ($this->memberPHIDs) {
$where[] = qsprintf(
$conn,
'm.dst IN (%Ls)',
$this->memberPHIDs);
}
return $this->formatWhereClause($where);
}
private function buildJoinClause(AphrontDatabaseConnection $conn) {
$joins = array();
if ($this->memberPHIDs) {
$joins[] = qsprintf(
$conn,
'LEFT JOIN %T m ON a.phid = m.src AND m.type = %d',
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER);
}
return implode(' ', $joins);
}
}

View file

@ -0,0 +1,10 @@
<?php
final class PhortuneAccountTransactionQuery
extends PhabricatorApplicationTransactionQuery {
protected function getTemplateApplicationTransaction() {
return new PhortuneAccountTransaction();
}
}

View file

@ -0,0 +1,60 @@
<?php
/**
* An account represents a purchasing entity. An account may have multiple users
* on it (e.g., several employees of a company have access to the company
* account), and a user may have several accounts (e.g., a company account and
* a personal account).
*/
final class PhortuneAccount extends PhortuneDAO
implements PhabricatorPolicyInterface {
protected $name;
protected $balanceInCents = 0;
private $memberPHIDs;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_ACNT);
}
public function getMemberPHIDs() {
if ($this->memberPHIDs === null) {
throw new Exception("Call attachMemberPHIDs() before getMemberPHIDs()!");
}
return $this->memberPHIDs;
}
public function attachMemberPHIDs(array $phids) {
$this->memberPHIDs = $phids;
return $this;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return false;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
$members = array_fuse($this->getMemberPHIDs());
return isset($members[$viewer->getPHID()]);
}
}

View file

@ -0,0 +1,74 @@
<?php
final class PhortuneAccountTransaction
extends PhabricatorApplicationTransaction {
const TYPE_NAME = 'phortune:name';
public function getApplicationName() {
return 'phortune';
}
public function getApplicationTransactionType() {
return PhabricatorPHIDConstants::PHID_TYPE_ACNT;
}
public function getApplicationTransactionCommentObject() {
return null;
}
public function getApplicationObjectTypeName() {
return pht('account');
}
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 account.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s renamed this account from "%s" to "%s".',
$this->renderHandleLink($author_phid),
$old,
$new);
}
break;
case PhabricatorTransactions::TYPE_EDGE:
switch ($this->getMetadataValue('edge:type')) {
case PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER:
$add = array_diff(array_keys($new), array_keys($old));
$rem = array_diff(array_keys($old), array_keys($new));
if ($add && $rem) {
return pht(
'%s changed account members, added %s; removed %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleList($add),
$this->renderHandleList($rem));
} else if ($add) {
return pht(
'%s added account members: %s',
$this->renderHandleLink($author_phid),
$this->renderHandleList($add));
} else {
return pht(
'%s removed account members: %s',
$this->renderHandleLink($author_phid),
$this->renderHandleList($add));
}
break;
}
break;
}
return parent::getTitle();
}
}

View file

@ -0,0 +1,38 @@
<?php
/**
* 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.
*/
final class PhortuneCharge extends PhortuneDAO {
const STATUS_PENDING = 'charge:pending';
const STATUS_AUTHORIZED = 'charge:authorized';
const STATUS_CHARGED = 'charge:charged';
const STATUS_FAILED = 'charge:failed';
protected $accountPHID;
protected $purchasePHID;
protected $paymentMethodPHID;
protected $amountInCents;
protected $status;
protected $metadata;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_CHRG);
}
}

View file

@ -0,0 +1,9 @@
<?php
abstract class PhortuneDAO extends PhabricatorLiskDAO {
public function getApplicationName() {
return 'phortune';
}
}

View file

@ -0,0 +1,64 @@
<?php
/**
* A payment method is a credit card; it is associated with an account and
* charges can be made against it.
*/
final class PhortunePaymentMethod extends PhortuneDAO
implements PhabricatorPolicyInterface {
protected $name;
protected $accountPHID;
protected $authorPHID;
protected $metadata;
private $account;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_PAYM);
}
public function attachAccount(PhortuneAccount $account) {
$this->account = $account;
return $this;
}
public function getAccount() {
if (!$this->account) {
throw new Exception("Call attachAccount() before getAccount()!");
}
return $this->account;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
);
}
public function getPolicy($capability) {
return $this->getAccount()->getPolicy($capability);
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return $this->getAccount()->hasAutomaticCapability(
$capability,
$viewer);
}
}

View file

@ -0,0 +1,34 @@
<?php
/**
* A product is something users can purchase. It may be a one-time purchase,
* or a plan which is billed monthly.
*/
final class PhortuneProduct extends PhortuneDAO {
const TYPE_BILL_ONCE = 'phortune:thing';
const TYPE_BILL_PLAN = 'phortune:plan';
protected $productName;
protected $priceInCents;
protected $billingIntervalInMonths;
protected $trialPeriodInDays;
protected $metadata;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_PDCT);
}
}

View file

@ -0,0 +1,35 @@
<?php
/**
* A purchase represents a user buying something or a subscription to a plan.
*/
final class PhortunePurchase extends PhortuneDAO {
const STATUS_PROCESSING = 'purchase:processing';
const STATUS_ACTIVE = 'purchase:active';
const STATUS_CANCELED = 'purchase:canceled';
const STATUS_DELIVERED = 'purchase:delivered';
const STATUS_FAILED = 'purchase:failed';
protected $productPHID;
protected $accountPHID;
protected $authorPHID;
protected $paymentMethodPHID;
protected $quantity;
protected $status;
public function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
self::CONFIG_SERIALIZATION => array(
'metadata' => self::SERIALIZATION_JSON,
),
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
PhabricatorPHIDConstants::PHID_TYPE_PRCH);
}
}

View file

@ -42,8 +42,12 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
const TYPE_OBJECT_HAS_FILE = 25; const TYPE_OBJECT_HAS_FILE = 25;
const TYPE_FILE_HAS_OBJECT = 26; const TYPE_FILE_HAS_OBJECT = 26;
const TYPE_ACCOUNT_HAS_MEMBER = 27;
const TYPE_MEMBER_HAS_ACCOUNT = 28;
const TYPE_TEST_NO_CYCLE = 9000; const TYPE_TEST_NO_CYCLE = 9000;
public static function getInverse($edge_type) { public static function getInverse($edge_type) {
static $map = array( static $map = array(
self::TYPE_TASK_HAS_COMMIT => self::TYPE_COMMIT_HAS_TASK, self::TYPE_TASK_HAS_COMMIT => self::TYPE_COMMIT_HAS_TASK,
@ -84,6 +88,9 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
self::TYPE_OBJECT_HAS_FILE => self::TYPE_FILE_HAS_OBJECT, self::TYPE_OBJECT_HAS_FILE => self::TYPE_FILE_HAS_OBJECT,
self::TYPE_FILE_HAS_OBJECT => self::TYPE_OBJECT_HAS_FILE, self::TYPE_FILE_HAS_OBJECT => self::TYPE_OBJECT_HAS_FILE,
self::TYPE_ACCOUNT_HAS_MEMBER => self::TYPE_MEMBER_HAS_ACCOUNT,
self::TYPE_MEMBER_HAS_ACCOUNT => self::TYPE_ACCOUNT_HAS_MEMBER,
); );
return idx($map, $edge_type); return idx($map, $edge_type);
@ -117,7 +124,7 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
PhabricatorPHIDConstants::PHID_TYPE_MCRO => 'PhabricatorFileImageMacro', PhabricatorPHIDConstants::PHID_TYPE_MCRO => 'PhabricatorFileImageMacro',
PhabricatorPHIDConstants::PHID_TYPE_CONP => 'ConpherenceThread', PhabricatorPHIDConstants::PHID_TYPE_CONP => 'ConpherenceThread',
PhabricatorPHIDConstants::PHID_TYPE_WIKI => 'PhrictionDocument', PhabricatorPHIDConstants::PHID_TYPE_WIKI => 'PhrictionDocument',
PhabricatorPHIDConstants::PHID_TYPE_ACNT => 'PhortuneAccount',
); );
$class = idx($class_map, $phid_type); $class = idx($class_map, $phid_type);

View file

@ -179,6 +179,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'db', 'type' => 'db',
'name' => 'phlux', 'name' => 'phlux',
), ),
'db.phortune' => array(
'type' => 'db',
'name' => 'phortune',
),
'0000.legacy.sql' => array( '0000.legacy.sql' => array(
'type' => 'sql', 'type' => 'sql',
'name' => $this->getPatchPath('0000.legacy.sql'), 'name' => $this->getPatchPath('0000.legacy.sql'),
@ -1198,6 +1202,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql', 'type' => 'sql',
'name' => $this->getPatchPath('20130310.xactionmeta.sql'), 'name' => $this->getPatchPath('20130310.xactionmeta.sql'),
), ),
'20130322.phortune.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130322.phortune.sql'),
),
); );
} }