mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 16:52:41 +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:
parent
696498934c
commit
960ac3b2a6
22 changed files with 897 additions and 7 deletions
46
resources/sql/patches/20130322.phortune.sql
Normal file
46
resources/sql/patches/20130322.phortune.sql
Normal 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;
|
||||
|
|
@ -702,6 +702,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationPhame' => 'applications/phame/application/PhabricatorApplicationPhame.php',
|
||||
'PhabricatorApplicationPhlux' => 'applications/phlux/application/PhabricatorApplicationPhlux.php',
|
||||
'PhabricatorApplicationPholio' => 'applications/pholio/application/PhabricatorApplicationPholio.php',
|
||||
'PhabricatorApplicationPhortune' => 'applications/phortune/application/PhabricatorApplicationPhortune.php',
|
||||
'PhabricatorApplicationPhriction' => 'applications/phriction/application/PhabricatorApplicationPhriction.php',
|
||||
'PhabricatorApplicationPonder' => 'applications/ponder/application/PhabricatorApplicationPonder.php',
|
||||
'PhabricatorApplicationProject' => 'applications/project/application/PhabricatorApplicationProject.php',
|
||||
|
@ -1527,7 +1528,22 @@ phutil_register_library_map(array(
|
|||
'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php',
|
||||
'PholioTransactionType' => 'applications/pholio/constants/PholioTransactionType.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',
|
||||
'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',
|
||||
'PhortuneStripePaymentFormView' => 'applications/phortune/stripe/view/PhortuneStripePaymentFormView.php',
|
||||
'PhortuneStripeTestPaymentFormController' => 'applications/phortune/stripe/controller/PhortuneStripeTestPaymentFormController.php',
|
||||
|
@ -2328,6 +2344,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationPhame' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationPhlux' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationPholio' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationPhortune' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationPhriction' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationPonder' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationProject' => 'PhabricatorApplication',
|
||||
|
@ -3157,7 +3174,30 @@ phutil_register_library_map(array(
|
|||
'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PholioTransactionType' => 'PholioConstants',
|
||||
'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',
|
||||
'PhortunePaymentMethod' =>
|
||||
array(
|
||||
0 => 'PhortuneDAO',
|
||||
1 => 'PhabricatorPolicyInterface',
|
||||
),
|
||||
'PhortunePaymentMethodListController' => 'PhabricatorController',
|
||||
'PhortunePaymentMethodViewController' => 'PhabricatorController',
|
||||
'PhortuneProduct' => 'PhortuneDAO',
|
||||
'PhortunePurchase' => 'PhortuneDAO',
|
||||
'PhortuneStripeBaseController' => 'PhabricatorController',
|
||||
'PhortuneStripePaymentFormView' => 'AphrontView',
|
||||
'PhortuneStripeTestPaymentFormController' => 'PhortuneStripeBaseController',
|
||||
|
|
|
@ -104,12 +104,6 @@ class AphrontDefaultApplicationConfiguration
|
|||
'clear/' => 'PhabricatorNotificationClearController',
|
||||
),
|
||||
|
||||
'/phortune/' => array(
|
||||
'stripe/' => array(
|
||||
'testpaymentform/' => 'PhortuneStripeTestPaymentFormController',
|
||||
),
|
||||
),
|
||||
|
||||
'/debug/' => 'PhabricatorDebugController',
|
||||
);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,11 @@ final class PhabricatorPHIDConstants {
|
|||
const PHID_TYPE_CONF = 'CONF';
|
||||
const PHID_TYPE_CONP = 'CONP';
|
||||
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_XCMT = 'XCMT';
|
||||
|
|
|
@ -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',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
45
src/applications/phortune/controller/PhortuneController.php
Normal file
45
src/applications/phortune/controller/PhortuneController.php
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
62
src/applications/phortune/editor/PhortuneAccountEditor.php
Normal file
62
src/applications/phortune/editor/PhortuneAccountEditor.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
102
src/applications/phortune/query/PhortuneAccountQuery.php
Normal file
102
src/applications/phortune/query/PhortuneAccountQuery.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class PhortuneAccountTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
protected function getTemplateApplicationTransaction() {
|
||||
return new PhortuneAccountTransaction();
|
||||
}
|
||||
|
||||
}
|
60
src/applications/phortune/storage/PhortuneAccount.php
Normal file
60
src/applications/phortune/storage/PhortuneAccount.php
Normal 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()]);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
38
src/applications/phortune/storage/PhortuneCharge.php
Normal file
38
src/applications/phortune/storage/PhortuneCharge.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
9
src/applications/phortune/storage/PhortuneDAO.php
Normal file
9
src/applications/phortune/storage/PhortuneDAO.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
abstract class PhortuneDAO extends PhabricatorLiskDAO {
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'phortune';
|
||||
}
|
||||
|
||||
}
|
64
src/applications/phortune/storage/PhortunePaymentMethod.php
Normal file
64
src/applications/phortune/storage/PhortunePaymentMethod.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
34
src/applications/phortune/storage/PhortuneProduct.php
Normal file
34
src/applications/phortune/storage/PhortuneProduct.php
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
35
src/applications/phortune/storage/PhortunePurchase.php
Normal file
35
src/applications/phortune/storage/PhortunePurchase.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -42,8 +42,12 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
|
|||
const TYPE_OBJECT_HAS_FILE = 25;
|
||||
const TYPE_FILE_HAS_OBJECT = 26;
|
||||
|
||||
const TYPE_ACCOUNT_HAS_MEMBER = 27;
|
||||
const TYPE_MEMBER_HAS_ACCOUNT = 28;
|
||||
|
||||
const TYPE_TEST_NO_CYCLE = 9000;
|
||||
|
||||
|
||||
public static function getInverse($edge_type) {
|
||||
static $map = array(
|
||||
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_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);
|
||||
|
@ -117,7 +124,7 @@ final class PhabricatorEdgeConfig extends PhabricatorEdgeConstants {
|
|||
PhabricatorPHIDConstants::PHID_TYPE_MCRO => 'PhabricatorFileImageMacro',
|
||||
PhabricatorPHIDConstants::PHID_TYPE_CONP => 'ConpherenceThread',
|
||||
PhabricatorPHIDConstants::PHID_TYPE_WIKI => 'PhrictionDocument',
|
||||
|
||||
PhabricatorPHIDConstants::PHID_TYPE_ACNT => 'PhortuneAccount',
|
||||
);
|
||||
|
||||
$class = idx($class_map, $phid_type);
|
||||
|
|
|
@ -179,6 +179,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'type' => 'db',
|
||||
'name' => 'phlux',
|
||||
),
|
||||
'db.phortune' => array(
|
||||
'type' => 'db',
|
||||
'name' => 'phortune',
|
||||
),
|
||||
'0000.legacy.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('0000.legacy.sql'),
|
||||
|
@ -1198,6 +1202,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20130310.xactionmeta.sql'),
|
||||
),
|
||||
'20130322.phortune.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20130322.phortune.sql'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue