1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-28 16:30:59 +01:00

Allow merchants to disable payment providers and show more UI info

Summary:
Ref T2787.

  - Allow merchants to disable payment providers.
  - Show more useful information about providers on the payments page.
  - Make test vs live more clear.
  - Show merchant status.
  - Add a description to merchants to flesh them out a bit -- the merchant areas of responsibilities seem to be fitting well with accounts, etc.

Test Plan: {F215109}

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T2787

Differential Revision: https://secure.phabricator.com/D10662
This commit is contained in:
epriestley 2014-10-08 08:31:24 -07:00
parent 795eb3669e
commit 19db3fbb60
19 changed files with 338 additions and 36 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phortune.phortune_merchant
ADD description LONGTEXT NOT NULL COLLATE utf8_bin;

View file

@ -0,0 +1,5 @@
ALTER TABLE {$NAMESPACE}_phortune.phortune_paymentproviderconfig
ADD isEnabled BOOL NOT NULL;
UPDATE {$NAMESPACE}_phortune.phortune_paymentproviderconfig
SET isEnabled = 1;

View file

@ -2609,6 +2609,7 @@ phutil_register_library_map(array(
'PhortuneProductQuery' => 'applications/phortune/query/PhortuneProductQuery.php',
'PhortuneProductViewController' => 'applications/phortune/controller/PhortuneProductViewController.php',
'PhortuneProviderActionController' => 'applications/phortune/controller/PhortuneProviderActionController.php',
'PhortuneProviderDisableController' => 'applications/phortune/controller/PhortuneProviderDisableController.php',
'PhortuneProviderEditController' => 'applications/phortune/controller/PhortuneProviderEditController.php',
'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php',
'PhortunePurchasePHIDType' => 'applications/phortune/phid/PhortunePurchasePHIDType.php',
@ -5670,6 +5671,7 @@ phutil_register_library_map(array(
'PhortuneProductQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortuneProductViewController' => 'PhortuneController',
'PhortuneProviderActionController' => 'PhortuneController',
'PhortuneProviderDisableController' => 'PhortuneMerchantController',
'PhortuneProviderEditController' => 'PhortuneMerchantController',
'PhortunePurchase' => array(
'PhortuneDAO',

View file

@ -60,6 +60,7 @@ final class PhabricatorPhortuneApplication extends PhabricatorApplication {
),
'provider/' => array(
'edit/(?:(?P<id>\d+)/)?' => 'PhortuneProviderEditController',
'disable/(?P<id>\d+)/' => 'PhortuneProviderDisableController',
'(?P<id>\d+)/(?P<action>[^/]+)/'
=> 'PhortuneProviderActionController',
),

View file

@ -51,14 +51,17 @@ final class PhortuneMerchantEditController
$e_name = true;
$v_name = $merchant->getName();
$v_desc = $merchant->getDescription();
$validation_exception = null;
if ($request->isFormPost()) {
$v_name = $request->getStr('name');
$v_desc = $request->getStr('desc');
$v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy');
$type_name = PhortuneMerchantTransaction::TYPE_NAME;
$type_desc = PhortuneMerchantTransaction::TYPE_DESCRIPTION;
$type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
$type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
@ -68,6 +71,10 @@ final class PhortuneMerchantEditController
->setTransactionType($type_name)
->setNewValue($v_name);
$xactions[] = id(new PhortuneMerchantTransaction())
->setTransactionType($type_desc)
->setNewValue($v_desc);
$xactions[] = id(new PhortuneMerchantTransaction())
->setTransactionType($type_view)
->setNewValue($v_view);
@ -110,6 +117,11 @@ final class PhortuneMerchantEditController
->setLabel(pht('Name'))
->setValue($v_name)
->setError($e_name))
->appendChild(
id(new PhabricatorRemarkupControl())
->setName('desc')
->setLabel(pht('Description'))
->setValue($v_desc))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('viewPolicy')

View file

@ -35,11 +35,18 @@ final class PhortuneMerchantViewController
->setUser($viewer)
->setPolicyObject($merchant);
$properties = $this->buildPropertyListView($merchant);
$providers = id(new PhortunePaymentProviderConfigQuery())
->setViewer($viewer)
->withMerchantPHIDs(array($merchant->getPHID()))
->execute();
$properties = $this->buildPropertyListView($merchant, $providers);
$actions = $this->buildActionListView($merchant);
$properties->setActionList($actions);
$providers = $this->buildProviderList($merchant);
$provider_list = $this->buildProviderList(
$merchant,
$providers);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
@ -59,7 +66,7 @@ final class PhortuneMerchantViewController
array(
$crumbs,
$box,
$providers,
$provider_list,
$timeline,
),
array(
@ -67,13 +74,85 @@ final class PhortuneMerchantViewController
));
}
private function buildPropertyListView(PhortuneMerchant $merchant) {
private function buildPropertyListView(
PhortuneMerchant $merchant,
array $providers) {
$viewer = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($merchant);
$status_view = new PHUIStatusListView();
$have_any = false;
$any_test = false;
foreach ($providers as $provider_config) {
$provider = $provider_config->buildProvider();
if ($provider->isEnabled()) {
$have_any = true;
}
if (!$provider->isAcceptingLivePayments()) {
$any_test = true;
}
}
if ($have_any) {
$status_view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
->setTarget(pht('Accepts Payments'))
->setNote(pht('This merchant can accept payments.')));
if ($any_test) {
$status_view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'yellow')
->setTarget(pht('Test Mode'))
->setNote(pht('This merchant is accepting test payments.')));
} else {
$status_view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green')
->setTarget(pht('Live Mode'))
->setNote(pht('This merchant is accepting live payments.')));
}
} else if ($providers) {
$status_view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_REJECT, 'red')
->setTarget(pht('No Enabled Providers'))
->setNote(
pht(
'All of the payment providers for this merchant are '.
'disabled.')));
} else {
$status_view->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_WARNING, 'yellow')
->setTarget(pht('No Providers'))
->setNote(
pht(
'This merchant does not have any payment providers configured '.
'yet, so it can not accept payments. Add a provider.')));
}
$view->addProperty(pht('Status'), $status_view);
$view->invokeWillRenderEvent();
$description = $merchant->getDescription();
if (strlen($description)) {
$description = PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())->setContent($description),
'default',
$viewer);
$view->addSectionHeader(pht('Description'));
$view->addTextContent($description);
}
return $view;
}
@ -101,7 +180,10 @@ final class PhortuneMerchantViewController
return $view;
}
private function buildProviderList(PhortuneMerchant $merchant) {
private function buildProviderList(
PhortuneMerchant $merchant,
array $providers) {
$viewer = $this->getRequest()->getUser();
$id = $merchant->getID();
@ -113,24 +195,61 @@ final class PhortuneMerchantViewController
$provider_list = id(new PHUIObjectItemListView())
->setNoDataString(pht('This merchant has no payment providers.'));
$providers = id(new PhortunePaymentProviderConfigQuery())
->setViewer($viewer)
->withMerchantPHIDs(array($merchant->getPHID()))
->execute();
foreach ($providers as $provider_config) {
$provider = $provider_config->buildProvider();
$provider_id = $provider_config->getID();
$item = id(new PHUIObjectItemView())
->setObjectName(pht('Provider %d', $provider_id))
->setHeader($provider->getName());
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-pencil')
->setHref($this->getApplicationURI("/provider/edit/{$provider_id}"))
->setWorkflow(!$can_edit)
->setDisabled(!$can_edit));
if ($provider->isEnabled()) {
if ($provider->isAcceptingLivePayments()) {
$item->setBarColor('green');
} else {
$item->setBarColor('yellow');
$item->addIcon('fa-exclamation-triangle', pht('Test Mode'));
}
$item->addAttribute($provider->getConfigureProvidesDescription());
} else {
// Don't show disabled providers to users who can't manage the merchant
// account.
if (!$can_edit) {
continue;
}
$item->setDisabled(true);
$item->addAttribute(
phutil_tag('em', array(), pht('This payment provider is disabled.')));
}
if ($can_edit) {
$edit_uri = $this->getApplicationURI(
"/provider/edit/{$provider_id}/");
$disable_uri = $this->getApplicationURI(
"/provider/disable/{$provider_id}/");
if ($provider->isEnabled()) {
$disable_icon = 'fa-times';
$disable_name = pht('Disable');
} else {
$disable_icon = 'fa-check';
$disable_name = pht('Enable');
}
$item->addAction(
id(new PHUIListItemView())
->setIcon($disable_icon)
->setHref($disable_uri)
->setName($disable_name)
->setWorkflow(true));
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-pencil')
->setHref($edit_uri)
->setName(pht('Edit')));
}
$provider_list->addItem($item);
}

View file

@ -0,0 +1,76 @@
<?php
final class PhortuneProviderDisableController
extends PhortuneMerchantController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$viewer = $request->getUser();
$provider_config = id(new PhortunePaymentProviderConfigQuery())
->setViewer($viewer)
->withIDs(array($this->id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$provider_config) {
return new Aphront404Response();
}
$merchant = $provider_config->getMerchant();
$merchant_id = $merchant->getID();
$cancel_uri = $this->getApplicationURI("merchant/{$merchant_id}/");
$provider = $provider_config->buildProvider();
if ($request->isFormPost()) {
$new_status = !$provider_config->getIsEnabled();
$xactions = array();
$xactions[] = id(new PhortunePaymentProviderConfigTransaction())
->setTransactionType(
PhortunePaymentProviderConfigTransaction::TYPE_ENABLE)
->setNewValue($new_status);
$editor = id(new PhortunePaymentProviderConfigEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$editor->applyTransactions($provider_config, $xactions);
return id(new AphrontRedirectResponse())->setURI($cancel_uri);
}
if ($provider_config->getIsEnabled()) {
$title = pht('Disable Provider?');
$body = pht(
'If you disable this payment provider, users will no longer be able '.
'to use it to make new payments.');
$button = pht('Disable Provider');
} else {
$title = pht('Enable Provider?');
$body = pht(
'If you enable this payment provider, users will be able to use it to '.
'make new payments.');
$button = pht('Enable Provider');
}
return $this->newDialog()
->setTitle($title)
->appendParagraph($body)
->addSubmitButton($button)
->addCancelButton($cancel_uri);
}
}

View file

@ -15,6 +15,7 @@ final class PhortuneMerchantEditor
$types = parent::getTransactionTypes();
$types[] = PhortuneMerchantTransaction::TYPE_NAME;
$types[] = PhortuneMerchantTransaction::TYPE_DESCRIPTION;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
@ -27,6 +28,8 @@ final class PhortuneMerchantEditor
switch ($xaction->getTransactionType()) {
case PhortuneMerchantTransaction::TYPE_NAME:
return $object->getName();
case PhortuneMerchantTransaction::TYPE_DESCRIPTION:
return $object->getDescription();
}
return parent::getCustomTransactionOldValue($object, $xaction);
@ -38,6 +41,7 @@ final class PhortuneMerchantEditor
switch ($xaction->getTransactionType()) {
case PhortuneMerchantTransaction::TYPE_NAME:
case PhortuneMerchantTransaction::TYPE_DESCRIPTION:
return $xaction->getNewValue();
}
@ -52,6 +56,9 @@ final class PhortuneMerchantEditor
case PhortuneMerchantTransaction::TYPE_NAME:
$object->setName($xaction->getNewValue());
return;
case PhortuneMerchantTransaction::TYPE_DESCRIPTION:
$object->setDescription($xaction->getNewValue());
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
@ -63,6 +70,7 @@ final class PhortuneMerchantEditor
switch ($xaction->getTransactionType()) {
case PhortuneMerchantTransaction::TYPE_NAME:
case PhortuneMerchantTransaction::TYPE_DESCRIPTION:
return;
}

View file

@ -16,6 +16,7 @@ final class PhortunePaymentProviderConfigEditor
$types[] = PhortunePaymentProviderConfigTransaction::TYPE_CREATE;
$types[] = PhortunePaymentProviderConfigTransaction::TYPE_PROPERTY;
$types[] = PhortunePaymentProviderConfigTransaction::TYPE_ENABLE;
return $types;
}
@ -26,6 +27,8 @@ final class PhortunePaymentProviderConfigEditor
switch ($xaction->getTransactionType()) {
case PhortunePaymentProviderConfigTransaction::TYPE_CREATE:
return null;
case PhortunePaymentProviderConfigTransaction::TYPE_ENABLE:
return (int)$object->getIsEnabled();
case PhortunePaymentProviderConfigTransaction::TYPE_PROPERTY:
$property_key = $xaction->getMetadataValue(
PhortunePaymentProviderConfigTransaction::PROPERTY_KEY);
@ -43,6 +46,8 @@ final class PhortunePaymentProviderConfigEditor
case PhortunePaymentProviderConfigTransaction::TYPE_CREATE:
case PhortunePaymentProviderConfigTransaction::TYPE_PROPERTY:
return $xaction->getNewValue();
case PhortunePaymentProviderConfigTransaction::TYPE_ENABLE:
return (int)$xaction->getNewValue();
}
return parent::getCustomTransactionNewValue($object, $xaction);
@ -60,6 +65,8 @@ final class PhortunePaymentProviderConfigEditor
PhortunePaymentProviderConfigTransaction::PROPERTY_KEY);
$object->setMetadataValue($property_key, $xaction->getNewValue());
return;
case PhortunePaymentProviderConfigTransaction::TYPE_ENABLE:
return $object->setIsEnabled((int)$xaction->getNewValue());
}
return parent::applyCustomInternalTransaction($object, $xaction);
@ -72,6 +79,7 @@ final class PhortunePaymentProviderConfigEditor
switch ($xaction->getTransactionType()) {
case PhortunePaymentProviderConfigTransaction::TYPE_CREATE:
case PhortunePaymentProviderConfigTransaction::TYPE_PROPERTY:
case PhortunePaymentProviderConfigTransaction::TYPE_ENABLE:
return;
}

View file

@ -5,9 +5,8 @@ final class PhortuneBalancedPaymentProvider extends PhortunePaymentProvider {
const BALANCED_MARKETPLACE_ID = 'balanced.marketplace-id';
const BALANCED_SECRET_KEY = 'balanced.secret-key';
public function isEnabled() {
return $this->getMarketplaceURI() &&
$this->getSecretKey();
public function isAcceptingLivePayments() {
return !preg_match('/-test-/', $this->getSecretKey());
}
public function getName() {
@ -24,6 +23,11 @@ final class PhortuneBalancedPaymentProvider extends PhortunePaymentProvider {
'balancedpayments.com account.');
}
public function getConfigureProvidesDescription() {
return pht(
'This merchant accepts credit and debit cards via Balanced Payments.');
}
public function getConfigureInstructions() {
return pht(
"To configure Balacned, register or log in to an existing account on ".

View file

@ -10,10 +10,11 @@ final class PhortunePayPalPaymentProvider extends PhortunePaymentProvider {
public function isEnabled() {
// TODO: See note in processControllerRequest().
return false;
}
return $this->getPaypalAPIUsername() &&
$this->getPaypalAPIPassword() &&
$this->getPaypalAPISignature();
public function isAcceptingLivePayments() {
$mode = $this->getProviderConfig()->getMetadataValue(self::PAYPAL_MODE);
return ($mode === 'live');
}
public function getName() {
@ -30,6 +31,11 @@ final class PhortunePayPalPaymentProvider extends PhortunePaymentProvider {
'account.');
}
public function getConfigureProvidesDescription() {
return pht(
'This merchant accepts payments via PayPal.');
}
public function getConfigureInstructions() {
return pht(
"To configure PayPal, register or log into an existing account on ".
@ -339,8 +345,7 @@ final class PhortunePayPalPaymentProvider extends PhortunePaymentProvider {
}
private function newPaypalAPICall() {
$mode = $this->getProviderConfig()->getMetadataValue(self::PAYPAL_MODE);
if ($mode == 'live') {
if ($this->isAcceptingLivePayments()) {
$host = 'https://api-3t.paypal.com/nvp';
} else {
$host = 'https://api-3t.sandbox.paypal.com/nvp';

View file

@ -41,6 +41,8 @@ abstract class PhortunePaymentProvider {
abstract public function getConfigureInstructions();
abstract public function getConfigureProvidesDescription();
abstract public function getAllConfigurableProperties();
abstract public function getAllConfigurableSecretProperties();
@ -120,8 +122,11 @@ abstract class PhortunePaymentProvider {
->loadObjects();
}
abstract public function isEnabled();
public function isEnabled() {
return $this->getProviderConfig()->getIsEnabled();
}
abstract public function isAcceptingLivePayments();
abstract public function getPaymentMethodDescription();
abstract public function getPaymentMethodIcon();
abstract public function getPaymentMethodProviderDescription();

View file

@ -5,9 +5,8 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider {
const STRIPE_PUBLISHABLE_KEY = 'stripe.publishable-key';
const STRIPE_SECRET_KEY = 'stripe.secret-key';
public function isEnabled() {
return $this->getPublishableKey() &&
$this->getSecretKey();
public function isAcceptingLivePayments() {
return preg_match('/_live_/', $this->getPublishableKey());
}
public function getName() {
@ -24,6 +23,11 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider {
'stripe.com account.');
}
public function getConfigureProvidesDescription() {
return pht(
'This merchant accepts credit and debit cards via Stripe.');
}
public function getPaymentMethodDescription() {
return pht('Add Credit or Debit Card (US and Canada)');
}

View file

@ -2,8 +2,8 @@
final class PhortuneTestPaymentProvider extends PhortunePaymentProvider {
public function isEnabled() {
return PhabricatorEnv::getEnvConfig('phortune.test.enabled');
public function isAcceptingLivePayments() {
return false;
}
public function getName() {
@ -21,6 +21,10 @@ final class PhortuneTestPaymentProvider extends PhortunePaymentProvider {
'any money.');
}
public function getConfigureProvidesDescription() {
return pht('This merchant accepts test payments.');
}
public function getConfigureInstructions() {
return pht('This providers does not require any special configuration.');
}

View file

@ -7,11 +7,8 @@ final class PhortuneWePayPaymentProvider extends PhortunePaymentProvider {
const WEPAY_ACCESS_TOKEN = 'wepay.access-token';
const WEPAY_ACCOUNT_ID = 'wepay.account-id';
public function isEnabled() {
return $this->getWePayClientID() &&
$this->getWePayClientSecret() &&
$this->getWePayAccessToken() &&
$this->getWePayAccountID();
public function isAcceptingLivePayments() {
return preg_match('/^PRODUCTION_/', $this->getWePayAccessToken());
}
public function getName() {
@ -28,6 +25,10 @@ final class PhortuneWePayPaymentProvider extends PhortunePaymentProvider {
'wepay.com account.');
}
public function getConfigureProvidesDescription() {
return pht('This merchant accepts credit and debit cards via WePay.');
}
public function getConfigureInstructions() {
return pht(
"To configure WePay, register or log in to an existing account on ".

View file

@ -6,6 +6,7 @@ final class PhortuneMerchant extends PhortuneDAO
protected $name;
protected $viewPolicy;
protected $editPolicy;
protected $description;
public static function initializeNewMerchant(PhabricatorUser $actor) {
return id(new PhortuneMerchant())
@ -18,6 +19,7 @@ final class PhortuneMerchant extends PhortuneDAO
self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text255',
'description' => 'text',
),
) + parent::getConfiguration();
}

View file

@ -4,6 +4,7 @@ final class PhortuneMerchantTransaction
extends PhabricatorApplicationTransaction {
const TYPE_NAME = 'merchant:name';
const TYPE_DESCRIPTION = 'merchant:description';
public function getApplicationName() {
return 'phortune';
@ -37,9 +38,38 @@ final class PhortuneMerchantTransaction
$new);
}
break;
case self::TYPE_DESCRIPTION:
return pht(
'%s updated the description for this merchant.',
$this->renderHandleLink($author_phid));
}
return parent::getTitle();
}
public function shouldHide() {
$old = $this->getOldValue();
switch ($this->getTransactionType()) {
case self::TYPE_DESCRIPTION:
return ($old === null);
}
return parent::shouldHide();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case self::TYPE_DESCRIPTION:
return ($this->getOldValue() !== null);
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
return $this->renderTextCorpusChangeDetails(
$viewer,
$this->getOldValue(),
$this->getNewValue());
}
}

View file

@ -6,6 +6,7 @@ final class PhortunePaymentProviderConfig extends PhortuneDAO
protected $merchantPHID;
protected $providerClassKey;
protected $providerClass;
protected $isEnabled;
protected $metadata = array();
private $merchant = self::ATTACHABLE;
@ -13,7 +14,8 @@ final class PhortunePaymentProviderConfig extends PhortuneDAO
public static function initializeNewProvider(
PhortuneMerchant $merchant) {
return id(new PhortunePaymentProviderConfig())
->setMerchantPHID($merchant->getPHID());
->setMerchantPHID($merchant->getPHID())
->setIsEnabled(1);
}
public function getConfiguration() {
@ -25,6 +27,7 @@ final class PhortunePaymentProviderConfig extends PhortuneDAO
self::CONFIG_COLUMN_SCHEMA => array(
'providerClassKey' => 'bytes12',
'providerClass' => 'text128',
'isEnabled' => 'bool',
),
self::CONFIG_KEY_SCHEMA => array(
'key_merchant' => array(

View file

@ -5,6 +5,7 @@ final class PhortunePaymentProviderConfigTransaction
const TYPE_CREATE = 'paymentprovider:create';
const TYPE_PROPERTY = 'paymentprovider:property';
const TYPE_ENABLE = 'paymentprovider:enable';
const PROPERTY_KEY = 'provider-property';
@ -31,6 +32,16 @@ final class PhortunePaymentProviderConfigTransaction
return pht(
'%s created this payment provider.',
$this->renderHandleLink($author_phid));
case self::TYPE_ENABLE:
if ($new) {
return pht(
'%s enabled this payment provider.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s disabled this payment provider.',
$this->renderHandleLink($author_phid));
}
case self::TYPE_PROPERTY:
// TODO: Allow providers to improve this.