@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+897 -7
+46
resources/sql/patches/20130322.phortune.sql
··· 1 + CREATE TABLE {$NAMESPACE}_phortune.phortune_account ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + name VARCHAR(255) NOT NULL, 5 + balanceInCents BIGINT NOT NULL, 6 + dateCreated INT UNSIGNED NOT NULL, 7 + dateModified INT UNSIGNED NOT NULL, 8 + UNIQUE KEY `key_phid` (phid) 9 + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 10 + 11 + CREATE TABLE {$NAMESPACE}_phortune.phortune_accounttransaction ( 12 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 13 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 14 + authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 15 + objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 16 + viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 17 + editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin, 18 + commentPHID VARCHAR(64) COLLATE utf8_bin, 19 + commentVersion INT UNSIGNED NOT NULL, 20 + transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin, 21 + oldValue LONGTEXT NOT NULL COLLATE utf8_bin, 22 + newValue LONGTEXT NOT NULL COLLATE utf8_bin, 23 + contentSource LONGTEXT NOT NULL COLLATE utf8_bin, 24 + metadata LONGTEXT NOT NULL COLLATE utf8_bin, 25 + dateCreated INT UNSIGNED NOT NULL, 26 + dateModified INT UNSIGNED NOT NULL, 27 + UNIQUE KEY `key_phid` (phid), 28 + KEY `key_object` (objectPHID) 29 + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 30 + 31 + CREATE TABLE {$NAMESPACE}_phortune.edge ( 32 + src VARCHAR(64) NOT NULL COLLATE utf8_bin, 33 + type INT UNSIGNED NOT NULL COLLATE utf8_bin, 34 + dst VARCHAR(64) NOT NULL COLLATE utf8_bin, 35 + dateCreated INT UNSIGNED NOT NULL, 36 + seq INT UNSIGNED NOT NULL, 37 + dataID INT UNSIGNED, 38 + PRIMARY KEY (src, type, dst), 39 + KEY (src, type, dateCreated, seq) 40 + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 41 + 42 + CREATE TABLE {$NAMESPACE}_phortune.edgedata ( 43 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 44 + data LONGTEXT NOT NULL COLLATE utf8_bin 45 + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 46 +
+40
src/__phutil_library_map__.php
··· 702 702 'PhabricatorApplicationPhame' => 'applications/phame/application/PhabricatorApplicationPhame.php', 703 703 'PhabricatorApplicationPhlux' => 'applications/phlux/application/PhabricatorApplicationPhlux.php', 704 704 'PhabricatorApplicationPholio' => 'applications/pholio/application/PhabricatorApplicationPholio.php', 705 + 'PhabricatorApplicationPhortune' => 'applications/phortune/application/PhabricatorApplicationPhortune.php', 705 706 'PhabricatorApplicationPhriction' => 'applications/phriction/application/PhabricatorApplicationPhriction.php', 706 707 'PhabricatorApplicationPonder' => 'applications/ponder/application/PhabricatorApplicationPonder.php', 707 708 'PhabricatorApplicationProject' => 'applications/project/application/PhabricatorApplicationProject.php', ··· 1527 1528 'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php', 1528 1529 'PholioTransactionType' => 'applications/pholio/constants/PholioTransactionType.php', 1529 1530 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', 1531 + 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', 1532 + 'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php', 1533 + 'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php', 1534 + 'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php', 1535 + 'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php', 1536 + 'PhortuneAccountViewController' => 'applications/phortune/controller/PhortuneAccountViewController.php', 1537 + 'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php', 1538 + 'PhortuneController' => 'applications/phortune/controller/PhortuneController.php', 1539 + 'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php', 1540 + 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', 1530 1541 'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php', 1542 + 'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php', 1543 + 'PhortunePaymentMethodListController' => 'applications/phortune/controller/PhortunePaymentMethodListController.php', 1544 + 'PhortunePaymentMethodViewController' => 'applications/phortune/controller/PhortunePaymentMethodViewController.php', 1545 + 'PhortuneProduct' => 'applications/phortune/storage/PhortuneProduct.php', 1546 + 'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php', 1531 1547 'PhortuneStripeBaseController' => 'applications/phortune/stripe/controller/PhortuneStripeBaseController.php', 1532 1548 'PhortuneStripePaymentFormView' => 'applications/phortune/stripe/view/PhortuneStripePaymentFormView.php', 1533 1549 'PhortuneStripeTestPaymentFormController' => 'applications/phortune/stripe/controller/PhortuneStripeTestPaymentFormController.php', ··· 2328 2344 'PhabricatorApplicationPhame' => 'PhabricatorApplication', 2329 2345 'PhabricatorApplicationPhlux' => 'PhabricatorApplication', 2330 2346 'PhabricatorApplicationPholio' => 'PhabricatorApplication', 2347 + 'PhabricatorApplicationPhortune' => 'PhabricatorApplication', 2331 2348 'PhabricatorApplicationPhriction' => 'PhabricatorApplication', 2332 2349 'PhabricatorApplicationPonder' => 'PhabricatorApplication', 2333 2350 'PhabricatorApplicationProject' => 'PhabricatorApplication', ··· 3157 3174 'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3158 3175 'PholioTransactionType' => 'PholioConstants', 3159 3176 'PholioTransactionView' => 'PhabricatorApplicationTransactionView', 3177 + 'PhortuneAccount' => 3178 + array( 3179 + 0 => 'PhortuneDAO', 3180 + 1 => 'PhabricatorPolicyInterface', 3181 + ), 3182 + 'PhortuneAccountEditor' => 'PhabricatorApplicationTransactionEditor', 3183 + 'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3184 + 'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction', 3185 + 'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3186 + 'PhortuneAccountViewController' => 'PhortuneController', 3187 + 'PhortuneCharge' => 'PhortuneDAO', 3188 + 'PhortuneController' => 'PhabricatorController', 3189 + 'PhortuneDAO' => 'PhabricatorLiskDAO', 3190 + 'PhortuneLandingController' => 'PhortuneController', 3160 3191 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', 3192 + 'PhortunePaymentMethod' => 3193 + array( 3194 + 0 => 'PhortuneDAO', 3195 + 1 => 'PhabricatorPolicyInterface', 3196 + ), 3197 + 'PhortunePaymentMethodListController' => 'PhabricatorController', 3198 + 'PhortunePaymentMethodViewController' => 'PhabricatorController', 3199 + 'PhortuneProduct' => 'PhortuneDAO', 3200 + 'PhortunePurchase' => 'PhortuneDAO', 3161 3201 'PhortuneStripeBaseController' => 'PhabricatorController', 3162 3202 'PhortuneStripePaymentFormView' => 'AphrontView', 3163 3203 'PhortuneStripeTestPaymentFormController' => 'PhortuneStripeBaseController',
-6
src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
··· 104 104 'clear/' => 'PhabricatorNotificationClearController', 105 105 ), 106 106 107 - '/phortune/' => array( 108 - 'stripe/' => array( 109 - 'testpaymentform/' => 'PhortuneStripeTestPaymentFormController', 110 - ), 111 - ), 112 - 113 107 '/debug/' => 'PhabricatorDebugController', 114 108 ); 115 109 }
+5
src/applications/phid/PhabricatorPHIDConstants.php
··· 33 33 const PHID_TYPE_CONF = 'CONF'; 34 34 const PHID_TYPE_CONP = 'CONP'; 35 35 const PHID_TYPE_PVAR = 'PVAR'; 36 + const PHID_TYPE_ACNT = 'ACNT'; 37 + const PHID_TYPE_PDCT = 'PDCT'; 38 + const PHID_TYPE_PRCH = 'PRCH'; 39 + const PHID_TYPE_PAYM = 'PAYM'; 40 + const PHID_TYPE_CHRG = 'CHRG'; 36 41 37 42 const PHID_TYPE_XACT = 'XACT'; 38 43 const PHID_TYPE_XCMT = 'XCMT';
+53
src/applications/phortune/application/PhabricatorApplicationPhortune.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationPhortune extends PhabricatorApplication { 4 + 5 + public function getBaseURI() { 6 + return '/phortune/'; 7 + } 8 + 9 + public function getShortDescription() { 10 + return pht('Account and Billing'); 11 + } 12 + 13 + public function getIconName() { 14 + return 'phortune'; 15 + } 16 + 17 + public function getTitleGlyph() { 18 + return "\xE2\x9C\x98"; 19 + } 20 + 21 + public function getApplicationGroup() { 22 + return self::GROUP_UTILITIES; 23 + } 24 + 25 + public function isBeta() { 26 + return true; 27 + } 28 + 29 + public function getRoutes() { 30 + return array( 31 + '/phortune/' => array( 32 + '' => 'PhortuneLandingController', 33 + '(?P<accountID>\d+)/' => array( 34 + '' => 'PhortuneAccountViewController', 35 + ), 36 + 37 + 'account/' => array( 38 + '' => 'PhortuneAccountListController', 39 + 'edit/(?:(?P<id>\d+)/)?' => 'PhortuneAccountEditController', 40 + ), 41 + 'paymentmethod/' => array( 42 + '' => 'PhortunePaymentMethodListController', 43 + 'view/(?P<id>\d+)/' => 'PhortunePaymentMethodViewController', 44 + 'edit/(?:(?P<id>\d+)/)?' => 'PhortunePaymentMethodEditController', 45 + ), 46 + 'stripe/' => array( 47 + 'testpaymentform/' => 'PhortuneStripeTestPaymentFormController', 48 + ), 49 + ), 50 + ); 51 + } 52 + 53 + }
+131
src/applications/phortune/controller/PhortuneAccountViewController.php
··· 1 + <?php 2 + 3 + final class PhortuneAccountViewController extends PhortuneController { 4 + 5 + private $accountID; 6 + 7 + public function willProcessRequest(array $data) { 8 + $this->accountID = $data['accountID']; 9 + } 10 + 11 + public function processRequest() { 12 + $request = $this->getRequest(); 13 + $user = $request->getUser(); 14 + 15 + $account = id(new PhortuneAccountQuery()) 16 + ->setViewer($user) 17 + ->withIDs(array($this->accountID)) 18 + ->executeOne(); 19 + 20 + if (!$account) { 21 + return new Aphront404Response(); 22 + } 23 + 24 + $title = $account->getName(); 25 + 26 + $crumbs = $this->buildApplicationCrumbs(); 27 + $crumbs->addCrumb( 28 + id(new PhabricatorCrumbView()) 29 + ->setName(pht('Account')) 30 + ->setHref($request->getRequestURI())); 31 + 32 + $header = id(new PhabricatorHeaderView()) 33 + ->setHeader($title); 34 + 35 + $actions = id(new PhabricatorActionListView()) 36 + ->setUser($user) 37 + ->addAction( 38 + id(new PhabricatorActionView()) 39 + ->setName(pht('Edit Account')) 40 + ->setIcon('edit') 41 + ->setHref('#') 42 + ->setDisabled(true)) 43 + ->addAction( 44 + id(new PhabricatorActionView()) 45 + ->setName(pht('Edit Members')) 46 + ->setIcon('transcript') 47 + ->setHref('#') 48 + ->setDisabled(true)); 49 + 50 + $properties = id(new PhabricatorPropertyListView()) 51 + ->setObject($account) 52 + ->setUser($user); 53 + 54 + $properties->addProperty(pht('Balance'), $account->getBalanceInCents()); 55 + 56 + $payment_methods = $this->buildPaymentMethodsSection($account); 57 + $account_history = $this->buildAccountHistorySection($account); 58 + 59 + return $this->buildApplicationPage( 60 + array( 61 + $crumbs, 62 + $header, 63 + $actions, 64 + $properties, 65 + $payment_methods, 66 + $account_history, 67 + ), 68 + array( 69 + 'title' => $title, 70 + 'device' => true, 71 + 'dust' => true, 72 + )); 73 + } 74 + 75 + private function buildPaymentMethodsSection(PhortuneAccount $account) { 76 + $request = $this->getRequest(); 77 + $user = $request->getUser(); 78 + 79 + $header = id(new PhabricatorHeaderView()) 80 + ->setHeader(pht('Payment Methods')); 81 + 82 + $id = $account->getID(); 83 + $add_uri = $this->getApplicationURI($id.'/paymentmethod/edit/'); 84 + 85 + $actions = id(new PhabricatorActionListView()) 86 + ->setUser($user) 87 + ->addAction( 88 + id(new PhabricatorActionView()) 89 + ->setName(pht('Add Payment Method')) 90 + ->setIcon('new') 91 + ->setHref($add_uri)); 92 + 93 + $list = id(new PhabricatorObjectItemListView()) 94 + ->setUser($user) 95 + ->setNoDataString( 96 + pht('No payment methods associated with this account.')); 97 + 98 + return array( 99 + $header, 100 + $actions, 101 + $list, 102 + ); 103 + } 104 + 105 + private function buildAccountHistorySection(PhortuneAccount $account) { 106 + $request = $this->getRequest(); 107 + $user = $request->getUser(); 108 + 109 + $header = id(new PhabricatorHeaderView()) 110 + ->setHeader(pht('Account History')); 111 + 112 + $xactions = id(new PhortuneAccountTransactionQuery()) 113 + ->setViewer($user) 114 + ->withObjectPHIDs(array($account->getPHID())) 115 + ->execute(); 116 + 117 + $engine = id(new PhabricatorMarkupEngine()) 118 + ->setViewer($user); 119 + 120 + $xaction_view = id(new PhabricatorApplicationTransactionView()) 121 + ->setUser($user) 122 + ->setTransactions($xactions) 123 + ->setMarkupEngine($engine); 124 + 125 + return array( 126 + $header, 127 + $xaction_view, 128 + ); 129 + } 130 + 131 + }
+45
src/applications/phortune/controller/PhortuneController.php
··· 1 + <?php 2 + 3 + abstract class PhortuneController extends PhabricatorController { 4 + 5 + protected function createUserAccount(PhabricatorUser $user) { 6 + $request = $this->getRequest(); 7 + 8 + $xactions = array(); 9 + $xactions[] = id(new PhortuneAccountTransaction()) 10 + ->setTransactionType(PhortuneAccountTransaction::TYPE_NAME) 11 + ->setNewValue(pht('Account (%s)', $user->getUserName())); 12 + 13 + $xactions[] = id(new PhortuneAccountTransaction()) 14 + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 15 + ->setMetadataValue( 16 + 'edge:type', 17 + PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER) 18 + ->setNewValue( 19 + array( 20 + '=' => array($user->getPHID() => $user->getPHID()), 21 + )); 22 + 23 + $account = new PhortuneAccount(); 24 + 25 + $editor = id(new PhortuneAccountEditor()) 26 + ->setActor($user) 27 + ->setContentSource( 28 + PhabricatorContentSource::newForSource( 29 + PhabricatorContentSource::SOURCE_WEB, 30 + array( 31 + 'ip' => $request->getRemoteAddr(), 32 + ))); 33 + 34 + // We create an account for you the first time you visit Phortune. 35 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 36 + 37 + $editor->applyTransactions($account, $xactions); 38 + 39 + unset($unguarded); 40 + 41 + return $account; 42 + } 43 + 44 + 45 + }
+29
src/applications/phortune/controller/PhortuneLandingController.php
··· 1 + <?php 2 + 3 + final class PhortuneLandingController extends PhortuneController { 4 + 5 + public function processRequest() { 6 + $request = $this->getRequest(); 7 + $user = $request->getUser(); 8 + 9 + $accounts = id(new PhortuneAccountQuery()) 10 + ->setViewer($user) 11 + ->withMemberPHIDs(array($user->getPHID())) 12 + ->execute(); 13 + 14 + if (!$accounts) { 15 + $account = $this->createUserAccount($user); 16 + $accounts = array($account); 17 + } 18 + 19 + if (count($accounts) == 1) { 20 + $account = head($accounts); 21 + $next_uri = $this->getApplicationURI($account->getID().'/'); 22 + } else { 23 + $next_uri = $this->getApplicationURI('account/'); 24 + } 25 + 26 + return id(new AphrontRedirectResponse())->setURI($next_uri); 27 + } 28 + 29 + }
+23
src/applications/phortune/controller/PhortunePaymentMethodListController.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentMethodListController extends PhabricatorController { 4 + 5 + public function processRequest() { 6 + $request = $this->getRequest(); 7 + $user = $request->getUser(); 8 + 9 + $title = pht('Payment Methods'); 10 + $crumbs = $this->buildApplicationCrumbs(); 11 + 12 + return $this->buildApplicationPage( 13 + array( 14 + $crumbs, 15 + ), 16 + array( 17 + 'title' => $title, 18 + 'device' => true, 19 + 'dust' => true, 20 + )); 21 + } 22 + 23 + }
+21
src/applications/phortune/controller/PhortunePaymentMethodViewController.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentMethodViewController extends PhabricatorController { 4 + 5 + public function processRequest() { 6 + 7 + $title = '...'; 8 + $crumbs = $this->buildApplicationCrumbs(); 9 + 10 + return $this->buildApplicationPage( 11 + array( 12 + $crumbs, 13 + ), 14 + array( 15 + 'title' => $title, 16 + 'device' => true, 17 + 'dust' => true, 18 + )); 19 + } 20 + 21 + }
+62
src/applications/phortune/editor/PhortuneAccountEditor.php
··· 1 + <?php 2 + 3 + 4 + final class PhortuneAccountEditor 5 + extends PhabricatorApplicationTransactionEditor { 6 + 7 + public function getTransactionTypes() { 8 + $types = parent::getTransactionTypes(); 9 + 10 + $types[] = PhabricatorTransactions::TYPE_EDGE; 11 + $types[] = PhortuneAccountTransaction::TYPE_NAME; 12 + 13 + return $types; 14 + } 15 + 16 + 17 + protected function getCustomTransactionOldValue( 18 + PhabricatorLiskDAO $object, 19 + PhabricatorApplicationTransaction $xaction) { 20 + switch ($xaction->getTransactionType()) { 21 + case PhortuneAccountTransaction::TYPE_NAME: 22 + return $object->getName(); 23 + } 24 + return parent::getCustomTransactionOldValue($object, $xaction); 25 + } 26 + 27 + protected function getCustomTransactionNewValue( 28 + PhabricatorLiskDAO $object, 29 + PhabricatorApplicationTransaction $xaction) { 30 + switch ($xaction->getTransactionType()) { 31 + case PhortuneAccountTransaction::TYPE_NAME: 32 + return $xaction->getNewValue(); 33 + } 34 + return parent::getCustomTransactionNewValue($object, $xaction); 35 + } 36 + 37 + protected function applyCustomInternalTransaction( 38 + PhabricatorLiskDAO $object, 39 + PhabricatorApplicationTransaction $xaction) { 40 + switch ($xaction->getTransactionType()) { 41 + case PhortuneAccountTransaction::TYPE_NAME: 42 + $object->setName($xaction->getNewValue()); 43 + return; 44 + case PhabricatorTransactions::TYPE_EDGE: 45 + return; 46 + } 47 + return parent::applyCustomInternalTransaction($object, $xaction); 48 + } 49 + 50 + protected function applyCustomExternalTransaction( 51 + PhabricatorLiskDAO $object, 52 + PhabricatorApplicationTransaction $xaction) { 53 + switch ($xaction->getTransactionType()) { 54 + case PhortuneAccountTransaction::TYPE_NAME: 55 + return; 56 + case PhabricatorTransactions::TYPE_EDGE: 57 + return; 58 + } 59 + return parent::applyCustomExternalTransaction($object, $xaction); 60 + } 61 + 62 + }
+102
src/applications/phortune/query/PhortuneAccountQuery.php
··· 1 + <?php 2 + 3 + final class PhortuneAccountQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + private $memberPHIDs; 9 + 10 + public function withIDs(array $ids) { 11 + $this->ids = $ids; 12 + return $this; 13 + } 14 + 15 + public function withPHIDs(array $phids) { 16 + $this->phids = $phids; 17 + return $this; 18 + } 19 + 20 + public function withMemberPHIDs(array $phids) { 21 + $this->memberPHIDs = $phids; 22 + return $this; 23 + } 24 + 25 + protected function loadPage() { 26 + $table = new PhortuneAccount(); 27 + $conn = $table->establishConnection('r'); 28 + 29 + $rows = queryfx_all( 30 + $conn, 31 + 'SELECT a.* FROM %T a %Q %Q %Q %Q', 32 + $table->getTableName(), 33 + $this->buildJoinClause($conn), 34 + $this->buildWhereClause($conn), 35 + $this->buildOrderClause($conn), 36 + $this->buildLimitClause($conn)); 37 + 38 + return $table->loadAllFromArray($rows); 39 + } 40 + 41 + protected function willFilterPage(array $accounts) { 42 + if (!$accounts) { 43 + return array(); 44 + } 45 + 46 + $query = id(new PhabricatorEdgeQuery()) 47 + ->withSourcePHIDs(mpull($accounts, 'getPHID')) 48 + ->withEdgeTypes(array(PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER)); 49 + $query->execute(); 50 + 51 + foreach ($accounts as $account) { 52 + $member_phids = $query->getDestinationPHIDs(array($account->getPHID())); 53 + $account->attachMemberPHIDs($member_phids); 54 + } 55 + 56 + return $accounts; 57 + } 58 + 59 + private function buildWhereClause(AphrontDatabaseConnection $conn) { 60 + $where = array(); 61 + 62 + $where[] = $this->buildPagingClause($conn); 63 + 64 + if ($this->ids) { 65 + $where[] = qsprintf( 66 + $conn, 67 + 'a.id IN (%Ld)', 68 + $this->ids); 69 + } 70 + 71 + if ($this->phids) { 72 + $where[] = qsprintf( 73 + $conn, 74 + 'a.phid IN (%Ls)', 75 + $this->phids); 76 + } 77 + 78 + if ($this->memberPHIDs) { 79 + $where[] = qsprintf( 80 + $conn, 81 + 'm.dst IN (%Ls)', 82 + $this->memberPHIDs); 83 + } 84 + 85 + return $this->formatWhereClause($where); 86 + } 87 + 88 + private function buildJoinClause(AphrontDatabaseConnection $conn) { 89 + $joins = array(); 90 + 91 + if ($this->memberPHIDs) { 92 + $joins[] = qsprintf( 93 + $conn, 94 + 'LEFT JOIN %T m ON a.phid = m.src AND m.type = %d', 95 + PhabricatorEdgeConfig::TABLE_NAME_EDGE, 96 + PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER); 97 + } 98 + 99 + return implode(' ', $joins); 100 + } 101 + 102 + }
+10
src/applications/phortune/query/PhortuneAccountTransactionQuery.php
··· 1 + <?php 2 + 3 + final class PhortuneAccountTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + protected function getTemplateApplicationTransaction() { 7 + return new PhortuneAccountTransaction(); 8 + } 9 + 10 + }
+60
src/applications/phortune/storage/PhortuneAccount.php
··· 1 + <?php 2 + 3 + /** 4 + * An account represents a purchasing entity. An account may have multiple users 5 + * on it (e.g., several employees of a company have access to the company 6 + * account), and a user may have several accounts (e.g., a company account and 7 + * a personal account). 8 + */ 9 + final class PhortuneAccount extends PhortuneDAO 10 + implements PhabricatorPolicyInterface { 11 + 12 + protected $name; 13 + protected $balanceInCents = 0; 14 + 15 + private $memberPHIDs; 16 + 17 + public function getConfiguration() { 18 + return array( 19 + self::CONFIG_AUX_PHID => true, 20 + ) + parent::getConfiguration(); 21 + } 22 + 23 + public function generatePHID() { 24 + return PhabricatorPHID::generateNewPHID( 25 + PhabricatorPHIDConstants::PHID_TYPE_ACNT); 26 + } 27 + 28 + public function getMemberPHIDs() { 29 + if ($this->memberPHIDs === null) { 30 + throw new Exception("Call attachMemberPHIDs() before getMemberPHIDs()!"); 31 + } 32 + return $this->memberPHIDs; 33 + } 34 + 35 + public function attachMemberPHIDs(array $phids) { 36 + $this->memberPHIDs = $phids; 37 + return $this; 38 + } 39 + 40 + 41 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 42 + 43 + 44 + public function getCapabilities() { 45 + return array( 46 + PhabricatorPolicyCapability::CAN_VIEW, 47 + PhabricatorPolicyCapability::CAN_EDIT, 48 + ); 49 + } 50 + 51 + public function getPolicy($capability) { 52 + return false; 53 + } 54 + 55 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 56 + $members = array_fuse($this->getMemberPHIDs()); 57 + return isset($members[$viewer->getPHID()]); 58 + } 59 + 60 + }
+74
src/applications/phortune/storage/PhortuneAccountTransaction.php
··· 1 + <?php 2 + 3 + final class PhortuneAccountTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + const TYPE_NAME = 'phortune:name'; 7 + 8 + public function getApplicationName() { 9 + return 'phortune'; 10 + } 11 + 12 + public function getApplicationTransactionType() { 13 + return PhabricatorPHIDConstants::PHID_TYPE_ACNT; 14 + } 15 + 16 + public function getApplicationTransactionCommentObject() { 17 + return null; 18 + } 19 + 20 + public function getApplicationObjectTypeName() { 21 + return pht('account'); 22 + } 23 + 24 + public function getTitle() { 25 + $author_phid = $this->getAuthorPHID(); 26 + 27 + $old = $this->getOldValue(); 28 + $new = $this->getNewValue(); 29 + 30 + switch ($this->getTransactionType()) { 31 + case self::TYPE_NAME: 32 + if ($old === null) { 33 + return pht( 34 + '%s created this account.', 35 + $this->renderHandleLink($author_phid)); 36 + } else { 37 + return pht( 38 + '%s renamed this account from "%s" to "%s".', 39 + $this->renderHandleLink($author_phid), 40 + $old, 41 + $new); 42 + } 43 + break; 44 + case PhabricatorTransactions::TYPE_EDGE: 45 + switch ($this->getMetadataValue('edge:type')) { 46 + case PhabricatorEdgeConfig::TYPE_ACCOUNT_HAS_MEMBER: 47 + $add = array_diff(array_keys($new), array_keys($old)); 48 + $rem = array_diff(array_keys($old), array_keys($new)); 49 + if ($add && $rem) { 50 + return pht( 51 + '%s changed account members, added %s; removed %s.', 52 + $this->renderHandleLink($author_phid), 53 + $this->renderHandleList($add), 54 + $this->renderHandleList($rem)); 55 + } else if ($add) { 56 + return pht( 57 + '%s added account members: %s', 58 + $this->renderHandleLink($author_phid), 59 + $this->renderHandleList($add)); 60 + } else { 61 + return pht( 62 + '%s removed account members: %s', 63 + $this->renderHandleLink($author_phid), 64 + $this->renderHandleList($add)); 65 + } 66 + break; 67 + } 68 + break; 69 + } 70 + 71 + return parent::getTitle(); 72 + } 73 + 74 + }
+38
src/applications/phortune/storage/PhortuneCharge.php
··· 1 + <?php 2 + 3 + /** 4 + * A charge is a charge (or credit) against an account and represents an actual 5 + * transfer of funds. Each charge is normally associated with a product, but a 6 + * product may have multiple charges. For example, a subscription may have 7 + * monthly charges, or a product may have a failed charge followed by a 8 + * successful charge. 9 + */ 10 + final class PhortuneCharge extends PhortuneDAO { 11 + 12 + const STATUS_PENDING = 'charge:pending'; 13 + const STATUS_AUTHORIZED = 'charge:authorized'; 14 + const STATUS_CHARGED = 'charge:charged'; 15 + const STATUS_FAILED = 'charge:failed'; 16 + 17 + protected $accountPHID; 18 + protected $purchasePHID; 19 + protected $paymentMethodPHID; 20 + protected $amountInCents; 21 + protected $status; 22 + protected $metadata; 23 + 24 + public function getConfiguration() { 25 + return array( 26 + self::CONFIG_AUX_PHID => true, 27 + self::CONFIG_SERIALIZATION => array( 28 + 'metadata' => self::SERIALIZATION_JSON, 29 + ), 30 + ) + parent::getConfiguration(); 31 + } 32 + 33 + public function generatePHID() { 34 + return PhabricatorPHID::generateNewPHID( 35 + PhabricatorPHIDConstants::PHID_TYPE_CHRG); 36 + } 37 + 38 + }
+9
src/applications/phortune/storage/PhortuneDAO.php
··· 1 + <?php 2 + 3 + abstract class PhortuneDAO extends PhabricatorLiskDAO { 4 + 5 + public function getApplicationName() { 6 + return 'phortune'; 7 + } 8 + 9 + }
+64
src/applications/phortune/storage/PhortunePaymentMethod.php
··· 1 + <?php 2 + 3 + /** 4 + * A payment method is a credit card; it is associated with an account and 5 + * charges can be made against it. 6 + */ 7 + final class PhortunePaymentMethod extends PhortuneDAO 8 + implements PhabricatorPolicyInterface { 9 + 10 + protected $name; 11 + protected $accountPHID; 12 + protected $authorPHID; 13 + protected $metadata; 14 + 15 + private $account; 16 + 17 + public function getConfiguration() { 18 + return array( 19 + self::CONFIG_AUX_PHID => true, 20 + self::CONFIG_SERIALIZATION => array( 21 + 'metadata' => self::SERIALIZATION_JSON, 22 + ), 23 + ) + parent::getConfiguration(); 24 + } 25 + 26 + public function generatePHID() { 27 + return PhabricatorPHID::generateNewPHID( 28 + PhabricatorPHIDConstants::PHID_TYPE_PAYM); 29 + } 30 + 31 + public function attachAccount(PhortuneAccount $account) { 32 + $this->account = $account; 33 + return $this; 34 + } 35 + 36 + public function getAccount() { 37 + if (!$this->account) { 38 + throw new Exception("Call attachAccount() before getAccount()!"); 39 + } 40 + return $this->account; 41 + } 42 + 43 + 44 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 45 + 46 + 47 + public function getCapabilities() { 48 + return array( 49 + PhabricatorPolicyCapability::CAN_VIEW, 50 + PhabricatorPolicyCapability::CAN_EDIT, 51 + ); 52 + } 53 + 54 + public function getPolicy($capability) { 55 + return $this->getAccount()->getPolicy($capability); 56 + } 57 + 58 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 59 + return $this->getAccount()->hasAutomaticCapability( 60 + $capability, 61 + $viewer); 62 + } 63 + 64 + }
+34
src/applications/phortune/storage/PhortuneProduct.php
··· 1 + <?php 2 + 3 + /** 4 + * A product is something users can purchase. It may be a one-time purchase, 5 + * or a plan which is billed monthly. 6 + */ 7 + final class PhortuneProduct extends PhortuneDAO { 8 + 9 + const TYPE_BILL_ONCE = 'phortune:thing'; 10 + const TYPE_BILL_PLAN = 'phortune:plan'; 11 + 12 + protected $productName; 13 + protected $priceInCents; 14 + protected $billingIntervalInMonths; 15 + protected $trialPeriodInDays; 16 + protected $metadata; 17 + 18 + public function getConfiguration() { 19 + return array( 20 + self::CONFIG_AUX_PHID => true, 21 + self::CONFIG_SERIALIZATION => array( 22 + 'metadata' => self::SERIALIZATION_JSON, 23 + ), 24 + ) + parent::getConfiguration(); 25 + } 26 + 27 + public function generatePHID() { 28 + return PhabricatorPHID::generateNewPHID( 29 + PhabricatorPHIDConstants::PHID_TYPE_PDCT); 30 + } 31 + 32 + 33 + 34 + }
+35
src/applications/phortune/storage/PhortunePurchase.php
··· 1 + <?php 2 + 3 + /** 4 + * A purchase represents a user buying something or a subscription to a plan. 5 + */ 6 + final class PhortunePurchase extends PhortuneDAO { 7 + 8 + const STATUS_PROCESSING = 'purchase:processing'; 9 + const STATUS_ACTIVE = 'purchase:active'; 10 + const STATUS_CANCELED = 'purchase:canceled'; 11 + const STATUS_DELIVERED = 'purchase:delivered'; 12 + const STATUS_FAILED = 'purchase:failed'; 13 + 14 + protected $productPHID; 15 + protected $accountPHID; 16 + protected $authorPHID; 17 + protected $paymentMethodPHID; 18 + protected $quantity; 19 + protected $status; 20 + 21 + public function getConfiguration() { 22 + return array( 23 + self::CONFIG_AUX_PHID => true, 24 + self::CONFIG_SERIALIZATION => array( 25 + 'metadata' => self::SERIALIZATION_JSON, 26 + ), 27 + ) + parent::getConfiguration(); 28 + } 29 + 30 + public function generatePHID() { 31 + return PhabricatorPHID::generateNewPHID( 32 + PhabricatorPHIDConstants::PHID_TYPE_PRCH); 33 + } 34 + 35 + }
+8 -1
src/infrastructure/edges/constants/PhabricatorEdgeConfig.php
··· 42 42 const TYPE_OBJECT_HAS_FILE = 25; 43 43 const TYPE_FILE_HAS_OBJECT = 26; 44 44 45 + const TYPE_ACCOUNT_HAS_MEMBER = 27; 46 + const TYPE_MEMBER_HAS_ACCOUNT = 28; 47 + 45 48 const TYPE_TEST_NO_CYCLE = 9000; 49 + 46 50 47 51 public static function getInverse($edge_type) { 48 52 static $map = array( ··· 84 88 85 89 self::TYPE_OBJECT_HAS_FILE => self::TYPE_FILE_HAS_OBJECT, 86 90 self::TYPE_FILE_HAS_OBJECT => self::TYPE_OBJECT_HAS_FILE, 91 + 92 + self::TYPE_ACCOUNT_HAS_MEMBER => self::TYPE_MEMBER_HAS_ACCOUNT, 93 + self::TYPE_MEMBER_HAS_ACCOUNT => self::TYPE_ACCOUNT_HAS_MEMBER, 87 94 ); 88 95 89 96 return idx($map, $edge_type); ··· 117 124 PhabricatorPHIDConstants::PHID_TYPE_MCRO => 'PhabricatorFileImageMacro', 118 125 PhabricatorPHIDConstants::PHID_TYPE_CONP => 'ConpherenceThread', 119 126 PhabricatorPHIDConstants::PHID_TYPE_WIKI => 'PhrictionDocument', 120 - 127 + PhabricatorPHIDConstants::PHID_TYPE_ACNT => 'PhortuneAccount', 121 128 ); 122 129 123 130 $class = idx($class_map, $phid_type);
+8
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 179 179 'type' => 'db', 180 180 'name' => 'phlux', 181 181 ), 182 + 'db.phortune' => array( 183 + 'type' => 'db', 184 + 'name' => 'phortune', 185 + ), 182 186 '0000.legacy.sql' => array( 183 187 'type' => 'sql', 184 188 'name' => $this->getPatchPath('0000.legacy.sql'), ··· 1197 1201 '20130310.xactionmeta.sql' => array( 1198 1202 'type' => 'sql', 1199 1203 'name' => $this->getPatchPath('20130310.xactionmeta.sql'), 1204 + ), 1205 + '20130322.phortune.sql' => array( 1206 + 'type' => 'sql', 1207 + 'name' => $this->getPatchPath('20130322.phortune.sql'), 1200 1208 ), 1201 1209 ); 1202 1210 }