1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-03 11:21:01 +01:00

Make Phortune payment methods transaction-oriented and always support "Add Payment Method"

Summary:
Depends on D20718. Ref T13366. Ref T13367.

  - Phortune payment methods currently do not use transactions; update them.
  - Give them a proper view page with a transaction log.
  - Add an "Add Payment Method" button which always works.
  - Show which subscriptions a payment method is associated with.
  - Get rid of the "Active" status indicator since we now treat "disabled" as "removed", to align with user expectation/intent.
  - Swap out of some of the super weird div-form-button UI into the new "big, clickable" UI for choice dialogs among a small number of options on a single dimension.

Test Plan:
  - As a mechant-authority and account-authority, created payment methods from carts, subscriptions, and accounts. Edited and viewed payment methods.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13367, T13366

Differential Revision: https://secure.phabricator.com/D20719
This commit is contained in:
epriestley 2019-08-16 10:36:30 -07:00
parent c4e0ac4d27
commit 201634848e
25 changed files with 906 additions and 424 deletions

View file

@ -92,7 +92,7 @@ return array(
'rsrc/css/application/pholio/pholio.css' => '88ef5ef1',
'rsrc/css/application/phortune/phortune-credit-card-form.css' => '3b9868a8',
'rsrc/css/application/phortune/phortune-invoice.css' => '4436b241',
'rsrc/css/application/phortune/phortune.css' => '12e8251a',
'rsrc/css/application/phortune/phortune.css' => '508a1a5e',
'rsrc/css/application/phrequent/phrequent.css' => 'bd79cc67',
'rsrc/css/application/phriction/phriction-document-css.css' => '03380da0',
'rsrc/css/application/policy/policy-edit.css' => '8794e2ed',
@ -810,7 +810,7 @@ return array(
'pholio-inline-comments-css' => '722b48c2',
'phortune-credit-card-form' => 'd12d214f',
'phortune-credit-card-form-css' => '3b9868a8',
'phortune-css' => '12e8251a',
'phortune-css' => '508a1a5e',
'phortune-invoice-css' => '4436b241',
'phrequent-css' => 'bd79cc67',
'phriction-document-css' => '03380da0',

View file

@ -0,0 +1,19 @@
CREATE TABLE {$NAMESPACE}_phortune.phortune_paymentmethodtransaction (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
phid VARBINARY(64) NOT NULL,
authorPHID VARBINARY(64) NOT NULL,
objectPHID VARBINARY(64) NOT NULL,
viewPolicy VARBINARY(64) NOT NULL,
editPolicy VARBINARY(64) NOT NULL,
commentPHID VARBINARY(64) DEFAULT NULL,
commentVersion INT UNSIGNED NOT NULL,
transactionType VARCHAR(32) NOT NULL,
oldValue LONGTEXT NOT NULL,
newValue LONGTEXT NOT NULL,
contentSource LONGTEXT NOT NULL,
metadata LONGTEXT NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
dateModified INT UNSIGNED NOT NULL,
UNIQUE KEY `key_phid` (`phid`),
KEY `key_object` (`objectPHID`)
) ENGINE=InnoDB DEFAULT CHARSET={$CHARSET} COLLATE {$COLLATE_TEXT};

View file

@ -5249,7 +5249,8 @@ phutil_register_library_map(array(
'PhortuneAccountOrdersController' => 'applications/phortune/controller/account/PhortuneAccountOrdersController.php',
'PhortuneAccountOverviewController' => 'applications/phortune/controller/account/PhortuneAccountOverviewController.php',
'PhortuneAccountPHIDType' => 'applications/phortune/phid/PhortuneAccountPHIDType.php',
'PhortuneAccountPaymentMethodsController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodsController.php',
'PhortuneAccountPaymentMethodListController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodListController.php',
'PhortuneAccountPaymentMethodViewController' => 'applications/phortune/controller/account/PhortuneAccountPaymentMethodViewController.php',
'PhortuneAccountProfileController' => 'applications/phortune/controller/account/PhortuneAccountProfileController.php',
'PhortuneAccountQuery' => 'applications/phortune/query/PhortuneAccountQuery.php',
'PhortuneAccountSubscriptionController' => 'applications/phortune/controller/account/PhortuneAccountSubscriptionController.php',
@ -5325,12 +5326,18 @@ phutil_register_library_map(array(
'PhortuneOrderTableView' => 'applications/phortune/view/PhortuneOrderTableView.php',
'PhortunePayPalPaymentProvider' => 'applications/phortune/provider/PhortunePayPalPaymentProvider.php',
'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php',
'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php',
'PhortunePaymentMethodDisableController' => 'applications/phortune/controller/payment/PhortunePaymentMethodDisableController.php',
'PhortunePaymentMethodEditController' => 'applications/phortune/controller/payment/PhortunePaymentMethodEditController.php',
'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodCreateController.php',
'PhortunePaymentMethodDisableController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodDisableController.php',
'PhortunePaymentMethodEditController' => 'applications/phortune/controller/paymentmethod/PhortunePaymentMethodEditController.php',
'PhortunePaymentMethodEditor' => 'applications/phortune/editor/PhortunePaymentMethodEditor.php',
'PhortunePaymentMethodNameTransaction' => 'applications/phortune/xaction/paymentmethod/PhortunePaymentMethodNameTransaction.php',
'PhortunePaymentMethodPHIDType' => 'applications/phortune/phid/PhortunePaymentMethodPHIDType.php',
'PhortunePaymentMethodPolicyCodex' => 'applications/phortune/codex/PhortunePaymentMethodPolicyCodex.php',
'PhortunePaymentMethodQuery' => 'applications/phortune/query/PhortunePaymentMethodQuery.php',
'PhortunePaymentMethodStatusTransaction' => 'applications/phortune/xaction/paymentmethod/PhortunePaymentMethodStatusTransaction.php',
'PhortunePaymentMethodTransaction' => 'applications/phortune/storage/PhortunePaymentMethodTransaction.php',
'PhortunePaymentMethodTransactionQuery' => 'applications/phortune/query/PhortunePaymentMethodTransactionQuery.php',
'PhortunePaymentMethodTransactionType' => 'applications/phortune/xaction/paymentmethod/PhortunePaymentMethodTransactionType.php',
'PhortunePaymentProvider' => 'applications/phortune/provider/PhortunePaymentProvider.php',
'PhortunePaymentProviderConfig' => 'applications/phortune/storage/PhortunePaymentProviderConfig.php',
'PhortunePaymentProviderConfigEditor' => 'applications/phortune/editor/PhortunePaymentProviderConfigEditor.php',
@ -11805,7 +11812,8 @@ phutil_register_library_map(array(
'PhortuneAccountOrdersController' => 'PhortuneAccountProfileController',
'PhortuneAccountOverviewController' => 'PhortuneAccountProfileController',
'PhortuneAccountPHIDType' => 'PhabricatorPHIDType',
'PhortuneAccountPaymentMethodsController' => 'PhortuneAccountProfileController',
'PhortuneAccountPaymentMethodListController' => 'PhortuneAccountProfileController',
'PhortuneAccountPaymentMethodViewController' => 'PhortuneAccountController',
'PhortuneAccountProfileController' => 'PhortuneAccountController',
'PhortuneAccountQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneAccountSubscriptionController' => 'PhortuneAccountProfileController',
@ -11896,13 +11904,20 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'PhabricatorPolicyCodexInterface',
'PhabricatorApplicationTransactionInterface',
),
'PhortunePaymentMethodCreateController' => 'PhortuneController',
'PhortunePaymentMethodDisableController' => 'PhortuneController',
'PhortunePaymentMethodEditController' => 'PhortuneController',
'PhortunePaymentMethodEditor' => 'PhabricatorApplicationTransactionEditor',
'PhortunePaymentMethodNameTransaction' => 'PhortunePaymentMethodTransactionType',
'PhortunePaymentMethodPHIDType' => 'PhabricatorPHIDType',
'PhortunePaymentMethodPolicyCodex' => 'PhabricatorPolicyCodex',
'PhortunePaymentMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortunePaymentMethodStatusTransaction' => 'PhortunePaymentMethodTransactionType',
'PhortunePaymentMethodTransaction' => 'PhabricatorModularTransaction',
'PhortunePaymentMethodTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortunePaymentMethodTransactionType' => 'PhabricatorModularTransactionType',
'PhortunePaymentProvider' => 'Phobject',
'PhortunePaymentProviderConfig' => array(
'PhortuneDAO',

View file

@ -72,7 +72,10 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication {
'(?P<accountID>\d+)/' => array(
'details/' => 'PhortuneAccountDetailsController',
'methods/' => 'PhortuneAccountPaymentMethodsController',
'methods/' => array(
'' => 'PhortuneAccountPaymentMethodListController',
'(?P<id>\d+)/' => 'PhortuneAccountPaymentMethodViewController',
),
'orders/' => 'PhortuneAccountOrdersController',
'charges/' => 'PhortuneAccountChargesController',
'subscriptions/' => 'PhortuneAccountSubscriptionController',

View file

@ -23,6 +23,10 @@ abstract class PhortuneAccountController
abstract protected function shouldRequireAccountEditCapability();
abstract protected function handleAccountRequest(AphrontRequest $request);
private function hasAccount() {
return (bool)$this->account;
}
final protected function getAccount() {
if ($this->account === null) {
throw new Exception(
@ -37,8 +41,10 @@ abstract class PhortuneAccountController
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
// If we hit a policy exception, we can make it here without finding
// an account.
if ($this->hasAccount()) {
$account = $this->getAccount();
if ($account) {
$crumbs->addTextCrumb($account->getName(), $account->getURI());
}

View file

@ -1,6 +1,6 @@
<?php
final class PhortuneAccountPaymentMethodsController
final class PhortuneAccountPaymentMethodListController
extends PhortuneAccountProfileController {
protected function shouldRequireAccountEditCapability() {
@ -46,15 +46,17 @@ final class PhortuneAccountPaymentMethodsController
$id = $account->getID();
// TODO: Allow adding a card here directly
$add = id(new PHUIButtonView())
->setTag('a')
->setText(pht('New Payment Method'))
->setText(pht('Add Payment Method'))
->setIcon('fa-plus')
->setHref($this->getApplicationURI("{$id}/card/new/"));
->setHref($this->getApplicationURI("{$id}/card/new/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit);
$header = id(new PHUIHeaderView())
->setHeader(pht('Payment Methods'));
->setHeader(pht('Payment Methods'))
->addActionLink($add);
$list = id(new PHUIObjectItemListView())
->setUser($viewer)
@ -74,39 +76,14 @@ final class PhortuneAccountPaymentMethodsController
foreach ($methods as $method) {
$id = $method->getID();
$item = new PHUIObjectItemView();
$item->setHeader($method->getFullDisplayName());
switch ($method->getStatus()) {
case PhortunePaymentMethod::STATUS_ACTIVE:
$item->setStatusIcon('fa-check green');
$disable_uri = $this->getApplicationURI('card/'.$id.'/disable/');
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-times')
->setHref($disable_uri)
->setDisabled(!$can_edit)
->setWorkflow(true));
break;
case PhortunePaymentMethod::STATUS_DISABLED:
$item->setStatusIcon('fa-ban lightbluetext');
$item->setDisabled(true);
break;
}
$item = id(new PHUIObjectItemView())
->setObjectName($method->getObjectName())
->setHeader($method->getFullDisplayName())
->setHref($method->getURI());
$provider = $method->buildPaymentProvider();
$item->addAttribute($provider->getPaymentMethodProviderDescription());
$edit_uri = $this->getApplicationURI('card/'.$id.'/edit/');
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-pencil')
->setHref($edit_uri)
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$list->addItem($item);
}

View file

@ -0,0 +1,154 @@
<?php
final class PhortuneAccountPaymentMethodViewController
extends PhortuneAccountController {
protected function shouldRequireAccountEditCapability() {
return false;
}
protected function handleAccountRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$account = $this->getAccount();
$method = id(new PhortunePaymentMethodQuery())
->setViewer($viewer)
->withAccountPHIDs(array($account->getPHID()))
->withIDs(array($request->getURIData('id')))
->withStatuses(
array(
PhortunePaymentMethod::STATUS_ACTIVE,
))
->executeOne();
if (!$method) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Payment Methods'), $account->getPaymentMethodsURI())
->addTextCrumb($method->getObjectName())
->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader($method->getFullDisplayName());
$details = $this->newDetailsView($method);
$timeline = $this->buildTransactionTimeline(
$method,
new PhortunePaymentMethodTransactionQuery());
$timeline->setShouldTerminate(true);
$autopay = $this->newAutopayView($method);
$curtain = $this->buildCurtainView($method);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(
array(
$details,
$autopay,
$timeline,
));
return $this->newPage()
->setTitle($method->getObjectName())
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildCurtainView(PhortunePaymentMethod $method) {
$viewer = $this->getViewer();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$method,
PhabricatorPolicyCapability::CAN_EDIT);
$edit_uri = $this->getApplicationURI(
urisprintf(
'card/%d/edit/',
$method->getID()));
$remove_uri = $this->getApplicationURI(
urisprintf(
'card/%d/disable/',
$method->getID()));
$curtain = $this->newCurtainView($method);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Payment Method'))
->setIcon('fa-pencil')
->setHref($edit_uri)
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Remove Payment Method'))
->setIcon('fa-times')
->setHref($remove_uri)
->setDisabled(!$can_edit)
->setWorkflow(true));
return $curtain;
}
private function newDetailsView(PhortunePaymentMethod $method) {
$viewer = $this->getViewer();
$merchant_phid = $method->getMerchantPHID();
$handles = $viewer->loadHandles(
array(
$merchant_phid,
));
$view = id(new PHUIPropertyListView())
->setUser($viewer);
if (strlen($method->getName())) {
$view->addProperty(pht('Name'), $method->getDisplayName());
}
$view->addProperty(pht('Summary'), $method->getSummary());
$view->addProperty(pht('Expires'), $method->getDisplayExpires());
$view->addProperty(
pht('Merchant'),
$handles[$merchant_phid]->renderLink());
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Payment Method Details'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addPropertyList($view);
}
private function newAutopayView(PhortunePaymentMethod $method) {
$viewer = $this->getViewer();
$subscriptions = id(new PhortuneSubscriptionQuery())
->setViewer($viewer)
->withPaymentMethodPHIDs(array($method->getPHID()))
->execute();
$table = id(new PhortuneSubscriptionTableView())
->setViewer($viewer)
->setSubscriptions($subscriptions)
->newTableView();
$table->setNoDataString(
pht(
'This payment method is not the default payment method for '.
'any subscriptions.'));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Autopay Subscriptions'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
}
}

View file

@ -47,11 +47,8 @@ final class PhortuneAccountSubscriptionController
->setLimit(25)
->execute();
$handles = $this->loadViewerHandles(mpull($subscriptions, 'getPHID'));
$table = id(new PhortuneSubscriptionTableView())
->setUser($viewer)
->setHandles($handles)
->setSubscriptions($subscriptions);
$header = id(new PHUIHeaderView())

View file

@ -1,303 +0,0 @@
<?php
final class PhortunePaymentMethodCreateController
extends PhortuneController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$account_id = $request->getURIData('accountID');
$account = id(new PhortuneAccountQuery())
->setViewer($viewer)
->withIDs(array($account_id))
->executeOne();
if (!$account) {
return new Aphront404Response();
}
$account_id = $account->getID();
$merchant = id(new PhortuneMerchantQuery())
->setViewer($viewer)
->withIDs(array($request->getInt('merchantID')))
->executeOne();
if (!$merchant) {
return new Aphront404Response();
}
$cart_id = $request->getInt('cartID');
$subscription_id = $request->getInt('subscriptionID');
if ($cart_id) {
$cancel_uri = $this->getApplicationURI("cart/{$cart_id}/checkout/");
} else if ($subscription_id) {
$cancel_uri = $this->getApplicationURI(
"{$account_id}/subscription/edit/{$subscription_id}/");
} else {
$cancel_uri = $this->getApplicationURI($account->getID().'/');
}
$providers = $this->loadCreatePaymentMethodProvidersForMerchant($merchant);
if (!$providers) {
throw new Exception(
pht(
'There are no payment providers enabled that can add payment '.
'methods.'));
}
if (count($providers) == 1) {
// If there's only one provider, always choose it.
$provider_id = head_key($providers);
} else {
$provider_id = $request->getInt('providerID');
if (empty($providers[$provider_id])) {
$choices = array();
foreach ($providers as $provider) {
$choices[] = $this->renderSelectProvider($provider);
}
$content = phutil_tag(
'div',
array(
'class' => 'phortune-payment-method-list',
),
$choices);
return $this->newDialog()
->setRenderDialogAsDiv(true)
->setTitle(pht('Add Payment Method'))
->appendParagraph(pht('Choose a payment method to add:'))
->appendChild($content)
->addCancelButton($cancel_uri);
}
}
$provider = $providers[$provider_id];
$errors = array();
$display_exception = null;
if ($request->isFormPost() && $request->getBool('isProviderForm')) {
$method = id(new PhortunePaymentMethod())
->setAccountPHID($account->getPHID())
->setAuthorPHID($viewer->getPHID())
->setMerchantPHID($merchant->getPHID())
->setProviderPHID($provider->getProviderConfig()->getPHID())
->setStatus(PhortunePaymentMethod::STATUS_ACTIVE);
// Limit the rate at which you can attempt to add payment methods. This
// is intended as a line of defense against using Phortune to validate a
// large list of stolen credit card numbers.
PhabricatorSystemActionEngine::willTakeAction(
array($viewer->getPHID()),
new PhortuneAddPaymentMethodAction(),
1);
if (!$errors) {
$errors = $this->processClientErrors(
$provider,
$request->getStr('errors'));
}
if (!$errors) {
$client_token_raw = $request->getStr('token');
$client_token = null;
try {
$client_token = phutil_json_decode($client_token_raw);
} catch (PhutilJSONParserException $ex) {
$errors[] = pht(
'There was an error decoding token information submitted by the '.
'client. Expected a JSON-encoded token dictionary, received: %s.',
nonempty($client_token_raw, pht('nothing')));
}
if (!$provider->validateCreatePaymentMethodToken($client_token)) {
$errors[] = pht(
'There was an error with the payment token submitted by the '.
'client. Expected a valid dictionary, received: %s.',
$client_token_raw);
}
if (!$errors) {
try {
$provider->createPaymentMethodFromRequest(
$request,
$method,
$client_token);
} catch (PhortuneDisplayException $exception) {
$display_exception = $exception;
} catch (Exception $ex) {
$errors = array(
pht('There was an error adding this payment method:'),
$ex->getMessage(),
);
}
}
}
if (!$errors && !$display_exception) {
$method->save();
// If we added this method on a cart flow, return to the cart to
// check out.
if ($cart_id) {
$next_uri = $this->getApplicationURI(
"cart/{$cart_id}/checkout/?paymentMethodID=".$method->getID());
} else if ($subscription_id) {
$next_uri = new PhutilURI($cancel_uri);
$next_uri->replaceQueryParam('added', true);
} else {
$account_uri = $this->getApplicationURI($account->getID().'/');
$next_uri = new PhutilURI($account_uri);
$next_uri->setFragment('payment');
}
return id(new AphrontRedirectResponse())->setURI($next_uri);
} else {
if ($display_exception) {
$dialog_body = $display_exception->getView();
} else {
$dialog_body = id(new PHUIInfoView())
->setErrors($errors);
}
return $this->newDialog()
->setTitle(pht('Error Adding Payment Method'))
->appendChild($dialog_body)
->addCancelButton($request->getRequestURI());
}
}
$form = $provider->renderCreatePaymentMethodForm($request, $errors);
$form
->setUser($viewer)
->setAction($request->getRequestURI())
->setWorkflow(true)
->addHiddenInput('providerID', $provider_id)
->addHiddenInput('cartID', $request->getInt('cartID'))
->addHiddenInput('subscriptionID', $request->getInt('subscriptionID'))
->addHiddenInput('isProviderForm', true)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Add Payment Method'))
->addCancelButton($cancel_uri));
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Method'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Add Payment Method'));
$crumbs->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader(pht('Add Payment Method'))
->setHeaderIcon('fa-plus-square');
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$box,
));
return $this->newPage()
->setTitle($provider->getPaymentMethodDescription())
->setCrumbs($crumbs)
->appendChild($view);
}
private function renderSelectProvider(
PhortunePaymentProvider $provider) {
$request = $this->getRequest();
$viewer = $request->getUser();
$description = $provider->getPaymentMethodDescription();
$icon_uri = $provider->getPaymentMethodIcon();
$details = $provider->getPaymentMethodProviderDescription();
$this->requireResource('phortune-css');
$icon = id(new PHUIIconView())
->setSpriteSheet(PHUIIconView::SPRITE_LOGIN)
->setSpriteIcon($provider->getPaymentMethodIcon());
$button = id(new PHUIButtonView())
->setSize(PHUIButtonView::BIG)
->setColor(PHUIButtonView::GREY)
->setIcon($icon)
->setText($description)
->setSubtext($details)
->setMetadata(array('disableWorkflow' => true));
$form = id(new AphrontFormView())
->setUser($viewer)
->setAction($request->getRequestURI())
->addHiddenInput('providerID', $provider->getProviderConfig()->getID())
->appendChild($button);
return $form;
}
private function processClientErrors(
PhortunePaymentProvider $provider,
$client_errors_raw) {
$errors = array();
$client_errors = null;
try {
$client_errors = phutil_json_decode($client_errors_raw);
} catch (PhutilJSONParserException $ex) {
$errors[] = pht(
'There was an error decoding error information submitted by the '.
'client. Expected a JSON-encoded list of error codes, received: %s.',
nonempty($client_errors_raw, pht('nothing')));
}
foreach (array_unique($client_errors) as $key => $client_error) {
$client_errors[$key] = $provider->translateCreatePaymentMethodErrorCode(
$client_error);
}
foreach (array_unique($client_errors) as $client_error) {
switch ($client_error) {
case PhortuneErrCode::ERR_CC_INVALID_NUMBER:
$message = pht(
'The card number you entered is not a valid card number. Check '.
'that you entered it correctly.');
break;
case PhortuneErrCode::ERR_CC_INVALID_CVC:
$message = pht(
'The CVC code you entered is not a valid CVC code. Check that '.
'you entered it correctly. The CVC code is a 3-digit or 4-digit '.
'numeric code which usually appears on the back of the card.');
break;
case PhortuneErrCode::ERR_CC_INVALID_EXPIRY:
$message = pht(
'The card expiration date is not a valid expiration date. Check '.
'that you entered it correctly. You can not add an expired card '.
'as a payment method.');
break;
default:
$message = $provider->getCreatePaymentMethodErrorMessage(
$client_error);
if (!$message) {
$message = pht(
"There was an unexpected error ('%s') processing payment ".
"information.",
$client_error);
phlog($message);
}
break;
}
$errors[$client_error] = $message;
}
return $errors;
}
}

View file

@ -0,0 +1,462 @@
<?php
final class PhortunePaymentMethodCreateController
extends PhortuneController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$account_id = $request->getURIData('accountID');
$account = id(new PhortuneAccountQuery())
->setViewer($viewer)
->withIDs(array($account_id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$account) {
return new Aphront404Response();
}
$cart_id = $request->getInt('cartID');
$subscription_id = $request->getInt('subscriptionID');
$merchant_id = $request->getInt('merchantID');
if ($cart_id) {
$cart = id(new PhortuneCartQuery())
->setViewer($viewer)
->withAccountPHIDs(array($account->getPHID()))
->withIDs(array($cart_id))
->executeOne();
if (!$cart) {
return new Aphront404Response();
}
$subscription_phid = $cart->getSubscriptionPHID();
if ($subscription_phid) {
$subscription = id(new PhortuneSubscriptionQuery())
->setViewer($viewer)
->withAccountPHIDs(array($account->getPHID()))
->withPHIDs(array($subscription_phid))
->executeOne();
if (!$subscription) {
return new Aphront404Response();
}
} else {
$subscription = null;
}
$merchant = $cart->getMerchant();
$cart_id = $cart->getID();
$subscription_id = null;
$merchant_id = null;
$next_uri = $cart->getCheckoutURI();
} else if ($subscription_id) {
$subscription = id(new PhortuneSubscriptionQuery())
->setViewer($viewer)
->withAccountPHIDs(array($account->getPHID()))
->withIDs(array($subscription_id))
->executeOne();
if (!$subscription) {
return new Aphront404Response();
}
$cart = null;
$merchant = $subscription->getMerchant();
$cart_id = null;
$subscription_id = $subscription->getID();
$merchant_id = null;
$next_uri = $subscription->getURI();
} else if ($merchant_id) {
$merchant_phids = $account->getMerchantPHIDs();
if ($merchant_phids) {
$merchant = id(new PhortuneMerchantQuery())
->setViewer($viewer)
->withIDs(array($merchant_id))
->withPHIDs($merchant_phids)
->executeOne();
} else {
$merchant = null;
}
if (!$merchant) {
return new Aphront404Response();
}
$cart = null;
$subscription = null;
$cart_id = null;
$subscription_id = null;
$merchant_id = $merchant->getID();
$next_uri = $account->getPaymentMethodsURI();
} else {
$next_uri = $account->getPaymentMethodsURI();
$merchant_phids = $account->getMerchantPHIDs();
if ($merchant_phids) {
$merchants = id(new PhortuneMerchantQuery())
->setViewer($viewer)
->withPHIDs($merchant_phids)
->needProfileImage(true)
->execute();
} else {
$merchants = array();
}
if (!$merchants) {
return $this->newDialog()
->setTitle(pht('No Merchants'))
->appendParagraph(
pht(
'You have not established a relationship with any merchants '.
'yet. Create an order or subscription before adding payment '.
'methods.'))
->addCancelButton($next_uri);
}
// If there's more than one merchant, ask the user to pick which one they
// want to pay. If there's only one, just pick it for them.
if (count($merchants) > 1) {
$menu = $this->newMerchantMenu($merchants);
$form = id(new AphrontFormView())
->appendInstructions(
pht(
'Choose the merchant you want to pay.'));
return $this->newDialog()
->setTitle(pht('Choose a Merchant'))
->appendForm($form)
->appendChild($menu)
->addCancelButton($next_uri);
}
$cart = null;
$subscription = null;
$merchant = head($merchants);
$cart_id = null;
$subscription_id = null;
$merchant_id = $merchant->getID();
}
$providers = $this->loadCreatePaymentMethodProvidersForMerchant($merchant);
if (!$providers) {
throw new Exception(
pht(
'There are no payment providers enabled that can add payment '.
'methods.'));
}
$state_params = array(
'cartID' => $cart_id,
'subscriptionID' => $subscription_id,
'merchantID' => $merchant_id,
);
$state_params = array_filter($state_params);
$state_uri = new PhutilURI($request->getRequestURI());
foreach ($state_params as $key => $value) {
$state_uri->replaceQueryParam($key, $value);
}
$provider_id = $request->getInt('providerID');
if (isset($providers[$provider_id])) {
$provider = $providers[$provider_id];
} else {
// If there's more than one provider, ask the user to pick how they
// want to pay. If there's only one, just pick it.
if (count($providers) > 1) {
$menu = $this->newProviderMenu($providers, $state_uri);
return $this->newDialog()
->setTitle(pht('Choose a Payment Method'))
->appendChild($menu)
->addCancelButton($next_uri);
}
$provider = head($providers);
}
$provider_id = $provider->getProviderConfig()->getID();
$state_params['providerID'] = $provider_id;
$errors = array();
$display_exception = null;
if ($request->isFormPost() && $request->getBool('isProviderForm')) {
$method = id(new PhortunePaymentMethod())
->setAccountPHID($account->getPHID())
->setAuthorPHID($viewer->getPHID())
->setMerchantPHID($merchant->getPHID())
->setProviderPHID($provider->getProviderConfig()->getPHID())
->setStatus(PhortunePaymentMethod::STATUS_ACTIVE);
// Limit the rate at which you can attempt to add payment methods. This
// is intended as a line of defense against using Phortune to validate a
// large list of stolen credit card numbers.
PhabricatorSystemActionEngine::willTakeAction(
array($viewer->getPHID()),
new PhortuneAddPaymentMethodAction(),
1);
if (!$errors) {
$errors = $this->processClientErrors(
$provider,
$request->getStr('errors'));
}
if (!$errors) {
$client_token_raw = $request->getStr('token');
$client_token = null;
try {
$client_token = phutil_json_decode($client_token_raw);
} catch (PhutilJSONParserException $ex) {
$errors[] = pht(
'There was an error decoding token information submitted by the '.
'client. Expected a JSON-encoded token dictionary, received: %s.',
nonempty($client_token_raw, pht('nothing')));
}
if (!$provider->validateCreatePaymentMethodToken($client_token)) {
$errors[] = pht(
'There was an error with the payment token submitted by the '.
'client. Expected a valid dictionary, received: %s.',
$client_token_raw);
}
if (!$errors) {
try {
$provider->createPaymentMethodFromRequest(
$request,
$method,
$client_token);
} catch (PhortuneDisplayException $exception) {
$display_exception = $exception;
} catch (Exception $ex) {
$errors = array(
pht('There was an error adding this payment method:'),
$ex->getMessage(),
);
}
}
}
if (!$errors && !$display_exception) {
$xactions = array();
$xactions[] = $method->getApplicationTransactionTemplate()
->setTransactionType(PhabricatorTransactions::TYPE_CREATE)
->setNewValue(true);
$editor = id(new PhortunePaymentMethodEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$editor->applyTransactions($method, $xactions);
$next_uri = new PhutilURI($next_uri);
// If we added this method on a cart flow, return to the cart to
// checkout with this payment method selected.
if ($cart_id) {
$next_uri->replaceQueryParam('paymentMethodID', $method->getID());
}
return id(new AphrontRedirectResponse())->setURI($next_uri);
} else {
if ($display_exception) {
$dialog_body = $display_exception->getView();
} else {
$dialog_body = id(new PHUIInfoView())
->setErrors($errors);
}
return $this->newDialog()
->setTitle(pht('Error Adding Payment Method'))
->appendChild($dialog_body)
->addCancelButton($request->getRequestURI());
}
}
$form = $provider->renderCreatePaymentMethodForm($request, $errors);
$form
->setViewer($viewer)
->setAction($request->getPath())
->setWorkflow(true)
->addHiddenInput('isProviderForm', true)
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Add Payment Method'))
->addCancelButton($next_uri));
foreach ($state_params as $key => $value) {
$form->addHiddenInput($key, $value);
}
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Method'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Add Payment Method'))
->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader(pht('Add Payment Method'))
->setHeaderIcon('fa-plus-square');
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(
array(
$box,
));
return $this->newPage()
->setTitle($provider->getPaymentMethodDescription())
->setCrumbs($crumbs)
->appendChild($view);
}
private function processClientErrors(
PhortunePaymentProvider $provider,
$client_errors_raw) {
$errors = array();
$client_errors = null;
try {
$client_errors = phutil_json_decode($client_errors_raw);
} catch (PhutilJSONParserException $ex) {
$errors[] = pht(
'There was an error decoding error information submitted by the '.
'client. Expected a JSON-encoded list of error codes, received: %s.',
nonempty($client_errors_raw, pht('nothing')));
}
foreach (array_unique($client_errors) as $key => $client_error) {
$client_errors[$key] = $provider->translateCreatePaymentMethodErrorCode(
$client_error);
}
foreach (array_unique($client_errors) as $client_error) {
switch ($client_error) {
case PhortuneErrCode::ERR_CC_INVALID_NUMBER:
$message = pht(
'The card number you entered is not a valid card number. Check '.
'that you entered it correctly.');
break;
case PhortuneErrCode::ERR_CC_INVALID_CVC:
$message = pht(
'The CVC code you entered is not a valid CVC code. Check that '.
'you entered it correctly. The CVC code is a 3-digit or 4-digit '.
'numeric code which usually appears on the back of the card.');
break;
case PhortuneErrCode::ERR_CC_INVALID_EXPIRY:
$message = pht(
'The card expiration date is not a valid expiration date. Check '.
'that you entered it correctly. You can not add an expired card '.
'as a payment method.');
break;
default:
$message = $provider->getCreatePaymentMethodErrorMessage(
$client_error);
if (!$message) {
$message = pht(
"There was an unexpected error ('%s') processing payment ".
"information.",
$client_error);
phlog($message);
}
break;
}
$errors[$client_error] = $message;
}
return $errors;
}
private function newMerchantMenu(array $merchants) {
assert_instances_of($merchants, 'PhortuneMerchant');
$request = $this->getRequest();
$viewer = $this->getViewer();
$menu = id(new PHUIObjectItemListView())
->setUser($viewer)
->setBig(true)
->setFlush(true);
foreach ($merchants as $merchant) {
$merchant_uri = id(new PhutilURI($request->getRequestURI()))
->replaceQueryParam('merchantID', $merchant->getID());
$item = id(new PHUIObjectItemView())
->setObjectName($merchant->getObjectName())
->setHeader($merchant->getName())
->setHref($merchant_uri)
->setClickable(true)
->setImageURI($merchant->getProfileImageURI());
$menu->addItem($item);
}
return $menu;
}
private function newProviderMenu(array $providers, PhutilURI $state_uri) {
assert_instances_of($providers, 'PhortunePaymentProvider');
$request = $this->getRequest();
$viewer = $this->getViewer();
$menu = id(new PHUIObjectItemListView())
->setUser($viewer)
->setBig(true)
->setFlush(true);
foreach ($providers as $provider) {
$provider_id = $provider->getProviderConfig()->getID();
$provider_uri = id(clone $state_uri)
->replaceQueryParam('providerID', $provider_id);
$description = $provider->getPaymentMethodDescription();
$icon_uri = $provider->getPaymentMethodIcon();
$details = $provider->getPaymentMethodProviderDescription();
$icon = id(new PHUIIconView())
->setSpriteSheet(PHUIIconView::SPRITE_LOGIN)
->setSpriteIcon($icon_uri);
$item = id(new PHUIObjectItemView())
->setHeader($description)
->setHref($provider_uri)
->setClickable(true)
->addAttribute($details)
->setImageIcon($icon);
$menu->addItem($item);
}
return $menu;
}
}

View file

@ -26,14 +26,23 @@ final class PhortunePaymentMethodDisableController
$account = $method->getAccount();
$account_id = $account->getID();
$account_uri = $this->getApplicationURI("/account/billing/{$account_id}/");
$account_uri = $account->getPaymentMethodsURI();
if ($request->isFormPost()) {
$xactions = array();
// TODO: ApplicationTransactions!!!!
$method
->setStatus(PhortunePaymentMethod::STATUS_DISABLED)
->save();
$xactions[] = $method->getApplicationTransactionTemplate()
->setTransactionType(
PhortunePaymentMethodStatusTransaction::TRANSACTIONTYPE)
->setNewValue(PhortunePaymentMethod::STATUS_DISABLED);
$editor = id(new PhortunePaymentMethodEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$editor->applyTransactions($method, $xactions);
return id(new AphrontRedirectResponse())->setURI($account_uri);
}

View file

@ -20,25 +20,36 @@ final class PhortunePaymentMethodEditController
return new Aphront404Response();
}
$next_uri = $method->getURI();
$account = $method->getAccount();
$account_uri = $this->getApplicationURI($account->getID().'/');
$v_name = $method->getName();
if ($request->isFormPost()) {
$v_name = $request->getStr('name');
$name = $request->getStr('name');
$xactions = array();
// TODO: Use ApplicationTransactions
$xactions[] = $method->getApplicationTransactionTemplate()
->setTransactionType(
PhortunePaymentMethodNameTransaction::TRANSACTIONTYPE)
->setNewValue($v_name);
$method->setName($name);
$method->save();
$editor = id(new PhortunePaymentMethodEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
return id(new AphrontRedirectResponse())->setURI($account_uri);
$editor->applyTransactions($method, $xactions);
return id(new AphrontRedirectResponse())->setURI($next_uri);
}
$provider = $method->buildPaymentProvider();
$form = id(new AphrontFormView())
->setUser($viewer)
->setViewer($viewer)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
@ -54,7 +65,7 @@ final class PhortunePaymentMethodEditController
->setValue($method->getDisplayExpires()))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($account_uri)
->addCancelButton($next_uri)
->setValue(pht('Save Changes')));
$box = id(new PHUIObjectBoxView())
@ -62,11 +73,12 @@ final class PhortunePaymentMethodEditController
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($account->getName(), $account_uri);
$crumbs->addTextCrumb($method->getDisplayName());
$crumbs->addTextCrumb(pht('Edit'));
$crumbs->setBorder(true);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($account->getName(), $account->getURI())
->addTextCrumb(pht('Payment Methods'), $account->getPaymentMethodsURI())
->addTextCrumb($method->getObjectName(), $method->getURI())
->addTextCrumb(pht('Edit'))
->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader(pht('Edit Payment Method'))
@ -74,7 +86,8 @@ final class PhortunePaymentMethodEditController
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
->setFooter(
array(
$box,
));
@ -82,7 +95,6 @@ final class PhortunePaymentMethodEditController
->setTitle(pht('Edit Payment Method'))
->setCrumbs($crumbs)
->appendChild($view);
}
}

View file

@ -0,0 +1,18 @@
<?php
final class PhortunePaymentMethodEditor
extends PhabricatorApplicationTransactionEditor {
public function getEditorApplicationClass() {
return 'PhabricatorPhortuneApplication';
}
public function getEditorObjectsDescription() {
return pht('Phortune Payment Methods');
}
public function getCreateObjectTitle($author, $object) {
return pht('%s created this payment method.', $author);
}
}

View file

@ -0,0 +1,10 @@
<?php
final class PhortunePaymentMethodTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new PhortunePaymentMethodTransaction();
}
}

View file

@ -8,6 +8,7 @@ final class PhortuneSubscriptionQuery
private $accountPHIDs;
private $merchantPHIDs;
private $statuses;
private $paymentMethodPHIDs;
private $needTriggers;
@ -36,24 +37,22 @@ final class PhortuneSubscriptionQuery
return $this;
}
public function withPaymentMethodPHIDs(array $method_phids) {
$this->paymentMethodPHIDs = $method_phids;
return $this;
}
public function needTriggers($need_triggers) {
$this->needTriggers = $need_triggers;
return $this;
}
public function newResultObject() {
return new PhortuneSubscription();
}
protected function loadPage() {
$table = new PhortuneSubscription();
$conn = $table->establishConnection('r');
$rows = queryfx_all(
$conn,
'SELECT subscription.* FROM %T subscription %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn),
$this->buildOrderClause($conn),
$this->buildLimitClause($conn));
return $table->loadAllFromArray($rows);
return $this->loadStandardPage($this->newResultObject());
}
protected function willFilterPage(array $subscriptions) {
@ -67,6 +66,7 @@ final class PhortuneSubscriptionQuery
$account = idx($accounts, $subscription->getAccountPHID());
if (!$account) {
unset($subscriptions[$key]);
$this->didRejectResult($subscription);
continue;
}
$subscription->attachAccount($account);
@ -86,6 +86,7 @@ final class PhortuneSubscriptionQuery
$merchant = idx($merchants, $subscription->getMerchantPHID());
if (!$merchant) {
unset($subscriptions[$key]);
$this->didRejectResult($subscription);
continue;
}
$subscription->attachMerchant($merchant);
@ -112,6 +113,7 @@ final class PhortuneSubscriptionQuery
$implementation = idx($implementations, $ref);
if (!$implementation) {
unset($subscriptions[$key]);
$this->didRejectResult($subscription);
continue;
}
$subscription->attachImplementation($implementation);
@ -133,6 +135,7 @@ final class PhortuneSubscriptionQuery
$trigger = idx($triggers, $subscription->getTriggerPHID());
if (!$trigger) {
unset($subscriptions[$key]);
$this->didRejectResult($subscription);
continue;
}
$subscription->attachTrigger($trigger);
@ -142,10 +145,8 @@ final class PhortuneSubscriptionQuery
return $subscriptions;
}
protected function buildWhereClause(AphrontDatabaseConnection $conn) {
$where = array();
$where[] = $this->buildPagingClause($conn);
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) {
$where[] = qsprintf(
@ -182,7 +183,18 @@ final class PhortuneSubscriptionQuery
$this->statuses);
}
return $this->formatWhereClause($conn, $where);
if ($this->paymentMethodPHIDs !== null) {
$where[] = qsprintf(
$conn,
'subscription.defaultPaymentMethodPHID IN (%Ls)',
$this->paymentMethodPHIDs);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'subscription';
}
public function getQueryApplicationClass() {

View file

@ -125,18 +125,6 @@ final class PhortuneSubscriptionSearchEngine
return parent::buildSavedQueryFromBuiltin($query_key);
}
protected function getRequiredHandlePHIDsForResultList(
array $subscriptions,
PhabricatorSavedQuery $query) {
$phids = array();
foreach ($subscriptions as $subscription) {
$phids[] = $subscription->getPHID();
$phids[] = $subscription->getMerchantPHID();
$phids[] = $subscription->getAuthorPHID();
}
return $phids;
}
protected function renderResultList(
array $subscriptions,
PhabricatorSavedQuery $query,
@ -147,7 +135,6 @@ final class PhortuneSubscriptionSearchEngine
$table = id(new PhortuneSubscriptionTableView())
->setUser($viewer)
->setHandles($handles)
->setSubscriptions($subscriptions);
$merchant = $this->getMerchant();

View file

@ -117,6 +117,12 @@ final class PhortuneAccount extends PhortuneDAO
$this->getID());
}
public function getPaymentMethodsURI() {
return urisprintf(
'/phortune/account/%d/methods/',
$this->getID());
}
public function attachMerchantPHIDs(array $merchant_phids) {
$this->merchantPHIDs = $merchant_phids;
return $this;

View file

@ -70,6 +70,10 @@ final class PhortuneMerchant extends PhortuneDAO
return $this->assertAttached($this->profileImageFile);
}
public function getObjectName() {
return pht('Merchant %d', $this->getID());
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */

View file

@ -9,7 +9,8 @@ final class PhortunePaymentMethod
implements
PhabricatorPolicyInterface,
PhabricatorExtendedPolicyInterface,
PhabricatorPolicyCodexInterface {
PhabricatorPolicyCodexInterface,
PhabricatorApplicationTransactionInterface {
const STATUS_ACTIVE = 'payment:active';
const STATUS_DISABLED = 'payment:disabled';
@ -140,6 +141,29 @@ final class PhortunePaymentMethod
return ($this->getStatus() === self::STATUS_ACTIVE);
}
public function getURI() {
return urisprintf(
'/phortune/account/%d/methods/%d/',
$this->getAccount()->getID(),
$this->getID());
}
public function getObjectName() {
return pht('Payment Method %d', $this->getID());
}
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
public function getApplicationTransactionEditor() {
return new PhortunePaymentMethodEditor();
}
public function getApplicationTransactionTemplate() {
return new PhortunePaymentMethodTransaction();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -0,0 +1,18 @@
<?php
final class PhortunePaymentMethodTransaction
extends PhabricatorModularTransaction {
public function getApplicationName() {
return 'phortune';
}
public function getApplicationTransactionType() {
return PhortunePaymentMethodPHIDType::TYPECONST;
}
public function getBaseTransactionClass() {
return 'PhortunePaymentMethodTransactionType';
}
}

View file

@ -3,19 +3,9 @@
final class PhortuneSubscriptionTableView extends AphrontView {
private $subscriptions;
private $handles;
private $isMerchantView;
private $notice;
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function getHandles() {
return $this->handles;
}
public function setSubscriptions(array $subscriptions) {
$this->subscriptions = $subscriptions;
return $this;
@ -40,9 +30,15 @@ final class PhortuneSubscriptionTableView extends AphrontView {
}
public function render() {
return $this->newTableView();
}
public function newTableView() {
$subscriptions = $this->getSubscriptions();
$handles = $this->getHandles();
$viewer = $this->getUser();
$viewer = $this->getViewer();
$phids = mpull($subscriptions, 'getPHID');
$handles = $viewer->loadHandles($phids);
$rows = array();
$rowc = array();

View file

@ -0,0 +1,39 @@
<?php
final class PhortunePaymentMethodNameTransaction
extends PhortunePaymentMethodTransactionType {
const TRANSACTIONTYPE = 'name';
public function generateOldValue($object) {
return $object->getName();
}
public function applyInternalEffects($object, $value) {
$object->setName($value);
}
public function getTitle() {
$old_value = $this->getOldValue();
$new_value = $this->getNewValue();
if (strlen($old_value) && strlen($new_value)) {
return pht(
'%s renamed this payment method from %s to %s.',
$this->renderAuthor(),
$this->renderOldValue(),
$this->renderNewValue());
} else if (strlen($new_value)) {
return pht(
'%s set the name of this payment method to %s.',
$this->renderAuthor(),
$this->renderNewValue());
} else {
return pht(
'%s removed the name of this payment method (was: %s).',
$this->renderAuthor(),
$this->renderOldValue());
}
}
}

View file

@ -0,0 +1,22 @@
<?php
final class PhortunePaymentMethodStatusTransaction
extends PhortunePaymentMethodTransactionType {
const TRANSACTIONTYPE = 'status';
public function generateOldValue($object) {
return $object->getStatus();
}
public function applyInternalEffects($object, $value) {
$object->setStatus($value);
}
public function getTitle() {
return pht(
'%s changed the status of this payment method.',
$this->renderAuthor());
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class PhortunePaymentMethodTransactionType
extends PhabricatorModularTransactionType {}

View file

@ -7,15 +7,6 @@
height: 34px;
}
.phortune-payment-method-list {
margin: 8px 24px 0;
}
.phortune-payment-method-list button {
margin: 4px 0;
width: 100%;
}
.phortune-payment-onetime-list {
width: 280px;
}