@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

Make payment providers a configurable property of Merchants in Phortune

Summary:
Ref T2787. Instead of making providers global configuration, make them a thing on merchants with web configuration.

Payment methods and some of the pyament workflow needs to be retooled a bit after this, but this seemed like a reasonable cutoff point for this diff.

Test Plan: See screenshots.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T2787

Differential Revision: https://secure.phabricator.com/D10649

+1655 -537
+12
resources/sql/autopatches/20141007.phortuneprovider.sql
··· 1 + CREATE TABLE {$NAMESPACE}_phortune.phortune_paymentproviderconfig ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARCHAR(64) NOT NULL COLLATE utf8_bin, 4 + merchantPHID VARCHAR(64) NOT NULL COLLATE utf8_bin, 5 + providerClassKey BINARY(12) NOT NULL, 6 + providerClass VARCHAR(128) NOT NULL COLLATE utf8_bin, 7 + metadata LONGTEXT NOT NULL COLLATE utf8_bin, 8 + dateCreated INT UNSIGNED NOT NULL, 9 + dateModified INT UNSIGNED NOT NULL, 10 + UNIQUE KEY `key_phid` (phid), 11 + UNIQUE KEY `key_merchant` (merchantPHID, providerClassKey) 12 + ) ENGINE=InnoDB, COLLATE=utf8_bin;
+19
resources/sql/autopatches/20141007.phortuneproviderx.sql
··· 1 + CREATE TABLE {$NAMESPACE}_phortune.phortune_paymentproviderconfigtransaction ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARCHAR(64) COLLATE utf8_bin NOT NULL, 4 + authorPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, 5 + objectPHID VARCHAR(64) COLLATE utf8_bin NOT NULL, 6 + viewPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, 7 + editPolicy VARCHAR(64) COLLATE utf8_bin NOT NULL, 8 + commentPHID VARCHAR(64) COLLATE utf8_bin DEFAULT NULL, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) COLLATE utf8_bin NOT NULL, 11 + oldValue LONGTEXT COLLATE utf8_bin NOT NULL, 12 + newValue LONGTEXT COLLATE utf8_bin NOT NULL, 13 + contentSource LONGTEXT COLLATE utf8_bin NOT NULL, 14 + metadata LONGTEXT COLLATE utf8_bin NOT NULL, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + UNIQUE KEY `key_phid` (`phid`), 18 + KEY `key_object` (`objectPHID`) 19 + ) ENGINE=InnoDB, COLLATE utf8_general_ci;
+21 -10
src/__phutil_library_map__.php
··· 1966 1966 'PhabricatorPholioConfigOptions' => 'applications/pholio/config/PhabricatorPholioConfigOptions.php', 1967 1967 'PhabricatorPholioMockTestDataGenerator' => 'applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php', 1968 1968 'PhabricatorPhortuneApplication' => 'applications/phortune/application/PhabricatorPhortuneApplication.php', 1969 - 'PhabricatorPhortuneConfigOptions' => 'applications/phortune/option/PhabricatorPhortuneConfigOptions.php', 1970 1969 'PhabricatorPhragmentApplication' => 'applications/phragment/application/PhabricatorPhragmentApplication.php', 1971 1970 'PhabricatorPhrequentApplication' => 'applications/phrequent/application/PhabricatorPhrequentApplication.php', 1972 1971 'PhabricatorPhrequentConfigOptions' => 'applications/phrequent/config/PhabricatorPhrequentConfigOptions.php', ··· 2589 2588 'PhortuneMultiplePaymentProvidersException' => 'applications/phortune/exception/PhortuneMultiplePaymentProvidersException.php', 2590 2589 'PhortuneNoPaymentProviderException' => 'applications/phortune/exception/PhortuneNoPaymentProviderException.php', 2591 2590 'PhortuneNotImplementedException' => 'applications/phortune/exception/PhortuneNotImplementedException.php', 2591 + 'PhortunePayPalPaymentProvider' => 'applications/phortune/provider/PhortunePayPalPaymentProvider.php', 2592 2592 'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php', 2593 2593 'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/PhortunePaymentMethodCreateController.php', 2594 2594 'PhortunePaymentMethodDisableController' => 'applications/phortune/controller/PhortunePaymentMethodDisableController.php', ··· 2596 2596 'PhortunePaymentMethodPHIDType' => 'applications/phortune/phid/PhortunePaymentMethodPHIDType.php', 2597 2597 'PhortunePaymentMethodQuery' => 'applications/phortune/query/PhortunePaymentMethodQuery.php', 2598 2598 'PhortunePaymentProvider' => 'applications/phortune/provider/PhortunePaymentProvider.php', 2599 - 'PhortunePaymentProviderTestCase' => 'applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php', 2600 - 'PhortunePaypalPaymentProvider' => 'applications/phortune/provider/PhortunePaypalPaymentProvider.php', 2599 + 'PhortunePaymentProviderConfig' => 'applications/phortune/storage/PhortunePaymentProviderConfig.php', 2600 + 'PhortunePaymentProviderConfigEditor' => 'applications/phortune/editor/PhortunePaymentProviderConfigEditor.php', 2601 + 'PhortunePaymentProviderConfigQuery' => 'applications/phortune/query/PhortunePaymentProviderConfigQuery.php', 2602 + 'PhortunePaymentProviderConfigTransaction' => 'applications/phortune/storage/PhortunePaymentProviderConfigTransaction.php', 2603 + 'PhortunePaymentProviderConfigTransactionQuery' => 'applications/phortune/query/PhortunePaymentProviderConfigTransactionQuery.php', 2604 + 'PhortunePaymentProviderPHIDType' => 'applications/phortune/phid/PhortunePaymentProviderPHIDType.php', 2601 2605 'PhortuneProduct' => 'applications/phortune/storage/PhortuneProduct.php', 2602 2606 'PhortuneProductImplementation' => 'applications/phortune/product/PhortuneProductImplementation.php', 2603 2607 'PhortuneProductListController' => 'applications/phortune/controller/PhortuneProductListController.php', 2604 2608 'PhortuneProductPHIDType' => 'applications/phortune/phid/PhortuneProductPHIDType.php', 2605 2609 'PhortuneProductQuery' => 'applications/phortune/query/PhortuneProductQuery.php', 2606 2610 'PhortuneProductViewController' => 'applications/phortune/controller/PhortuneProductViewController.php', 2607 - 'PhortuneProviderController' => 'applications/phortune/controller/PhortuneProviderController.php', 2611 + 'PhortuneProviderActionController' => 'applications/phortune/controller/PhortuneProviderActionController.php', 2612 + 'PhortuneProviderEditController' => 'applications/phortune/controller/PhortuneProviderEditController.php', 2608 2613 'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php', 2609 2614 'PhortunePurchasePHIDType' => 'applications/phortune/phid/PhortunePurchasePHIDType.php', 2610 2615 'PhortunePurchaseQuery' => 'applications/phortune/query/PhortunePurchaseQuery.php', 2611 2616 'PhortunePurchaseViewController' => 'applications/phortune/controller/PhortunePurchaseViewController.php', 2612 2617 'PhortuneSchemaSpec' => 'applications/phortune/storage/PhortuneSchemaSpec.php', 2613 2618 'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php', 2614 - 'PhortuneTestExtraPaymentProvider' => 'applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php', 2615 2619 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', 2616 2620 'PhortuneWePayPaymentProvider' => 'applications/phortune/provider/PhortuneWePayPaymentProvider.php', 2617 2621 'PhragmentBrowseController' => 'applications/phragment/controller/PhragmentBrowseController.php', ··· 4935 4939 'PhabricatorPholioConfigOptions' => 'PhabricatorApplicationConfigOptions', 4936 4940 'PhabricatorPholioMockTestDataGenerator' => 'PhabricatorTestDataGenerator', 4937 4941 'PhabricatorPhortuneApplication' => 'PhabricatorApplication', 4938 - 'PhabricatorPhortuneConfigOptions' => 'PhabricatorApplicationConfigOptions', 4939 4942 'PhabricatorPhragmentApplication' => 'PhabricatorApplication', 4940 4943 'PhabricatorPhrequentApplication' => 'PhabricatorApplication', 4941 4944 'PhabricatorPhrequentConfigOptions' => 'PhabricatorApplicationConfigOptions', ··· 5639 5642 'PhortuneMultiplePaymentProvidersException' => 'Exception', 5640 5643 'PhortuneNoPaymentProviderException' => 'Exception', 5641 5644 'PhortuneNotImplementedException' => 'Exception', 5645 + 'PhortunePayPalPaymentProvider' => 'PhortunePaymentProvider', 5642 5646 'PhortunePaymentMethod' => array( 5643 5647 'PhortuneDAO', 5644 5648 'PhabricatorPolicyInterface', ··· 5648 5652 'PhortunePaymentMethodEditController' => 'PhortuneController', 5649 5653 'PhortunePaymentMethodPHIDType' => 'PhabricatorPHIDType', 5650 5654 'PhortunePaymentMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5651 - 'PhortunePaymentProviderTestCase' => 'PhabricatorTestCase', 5652 - 'PhortunePaypalPaymentProvider' => 'PhortunePaymentProvider', 5655 + 'PhortunePaymentProviderConfig' => array( 5656 + 'PhortuneDAO', 5657 + 'PhabricatorPolicyInterface', 5658 + ), 5659 + 'PhortunePaymentProviderConfigEditor' => 'PhabricatorApplicationTransactionEditor', 5660 + 'PhortunePaymentProviderConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5661 + 'PhortunePaymentProviderConfigTransaction' => 'PhabricatorApplicationTransaction', 5662 + 'PhortunePaymentProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 5663 + 'PhortunePaymentProviderPHIDType' => 'PhabricatorPHIDType', 5653 5664 'PhortuneProduct' => array( 5654 5665 'PhortuneDAO', 5655 5666 'PhabricatorPolicyInterface', ··· 5658 5669 'PhortuneProductPHIDType' => 'PhabricatorPHIDType', 5659 5670 'PhortuneProductQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5660 5671 'PhortuneProductViewController' => 'PhortuneController', 5661 - 'PhortuneProviderController' => 'PhortuneController', 5672 + 'PhortuneProviderActionController' => 'PhortuneController', 5673 + 'PhortuneProviderEditController' => 'PhortuneMerchantController', 5662 5674 'PhortunePurchase' => array( 5663 5675 'PhortuneDAO', 5664 5676 'PhabricatorPolicyInterface', ··· 5668 5680 'PhortunePurchaseViewController' => 'PhortuneController', 5669 5681 'PhortuneSchemaSpec' => 'PhabricatorConfigSchemaSpec', 5670 5682 'PhortuneStripePaymentProvider' => 'PhortunePaymentProvider', 5671 - 'PhortuneTestExtraPaymentProvider' => 'PhortunePaymentProvider', 5672 5683 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', 5673 5684 'PhortuneWePayPaymentProvider' => 'PhortunePaymentProvider', 5674 5685 'PhragmentBrowseController' => 'PhragmentController',
+5 -2
src/applications/phortune/application/PhabricatorPhortuneApplication.php
··· 58 58 'view/(?P<id>\d+)/' => 'PhortuneProductViewController', 59 59 'edit/(?:(?P<id>\d+)/)?' => 'PhortuneProductEditController', 60 60 ), 61 - 'provider/(?P<digest>[^/]+)/(?P<action>[^/]+)/' 62 - => 'PhortuneProviderController', 61 + 'provider/' => array( 62 + 'edit/(?:(?P<id>\d+)/)?' => 'PhortuneProviderEditController', 63 + '(?P<id>\d+)/(?P<action>[^/]+)/' 64 + => 'PhortuneProviderActionController', 65 + ), 63 66 'merchant/' => array( 64 67 '(?:query/(?P<queryKey>[^/]+)/)?' => 'PhortuneMerchantListController', 65 68 'edit/(?:(?P<id>\d+)/)?' => 'PhortuneMerchantEditController',
+57 -1
src/applications/phortune/controller/PhortuneMerchantViewController.php
··· 22 22 } 23 23 24 24 $crumbs = $this->buildApplicationCrumbs(); 25 - $crumbs->addTextCrumb(pht('Merchant %d', $merchant->getID())); 25 + $crumbs->addTextCrumb($merchant->getName()); 26 26 27 27 $title = pht( 28 28 'Merchant %d %s', ··· 38 38 $properties = $this->buildPropertyListView($merchant); 39 39 $actions = $this->buildActionListView($merchant); 40 40 $properties->setActionList($actions); 41 + 42 + $providers = $this->buildProviderList($merchant); 41 43 42 44 $box = id(new PHUIObjectBoxView()) 43 45 ->setHeader($header) ··· 57 59 array( 58 60 $crumbs, 59 61 $box, 62 + $providers, 60 63 $timeline, 61 64 ), 62 65 array( ··· 97 100 98 101 return $view; 99 102 } 103 + 104 + private function buildProviderList(PhortuneMerchant $merchant) { 105 + $viewer = $this->getRequest()->getUser(); 106 + $id = $merchant->getID(); 107 + 108 + $can_edit = PhabricatorPolicyFilter::hasCapability( 109 + $viewer, 110 + $merchant, 111 + PhabricatorPolicyCapability::CAN_EDIT); 112 + 113 + $provider_list = id(new PHUIObjectItemListView()) 114 + ->setNoDataString(pht('This merchant has no payment providers.')); 115 + 116 + $providers = id(new PhortunePaymentProviderConfigQuery()) 117 + ->setViewer($viewer) 118 + ->withMerchantPHIDs(array($merchant->getPHID())) 119 + ->execute(); 120 + foreach ($providers as $provider_config) { 121 + $provider = $provider_config->buildProvider(); 122 + $provider_id = $provider_config->getID(); 123 + 124 + $item = id(new PHUIObjectItemView()) 125 + ->setObjectName(pht('Provider %d', $provider_id)) 126 + ->setHeader($provider->getName()); 127 + 128 + $item->addAction( 129 + id(new PHUIListItemView()) 130 + ->setIcon('fa-pencil') 131 + ->setHref($this->getApplicationURI("/provider/edit/{$provider_id}")) 132 + ->setWorkflow(!$can_edit) 133 + ->setDisabled(!$can_edit)); 134 + 135 + $provider_list->addItem($item); 136 + } 137 + 138 + $add_action = id(new PHUIButtonView()) 139 + ->setTag('a') 140 + ->setHref($this->getApplicationURI('provider/edit/?merchantID='.$id)) 141 + ->setText(pht('Add Payment Provider')) 142 + ->setDisabled(!$can_edit) 143 + ->setWorkflow(!$can_edit) 144 + ->setIcon(id(new PHUIIconView())->setIconFont('fa-plus')); 145 + 146 + $header = id(new PHUIHeaderView()) 147 + ->setHeader(pht('Payment Providers')) 148 + ->addActionLink($add_action); 149 + 150 + return id(new PHUIObjectBoxView()) 151 + ->setHeader($header) 152 + ->appendChild($provider_list); 153 + } 154 + 155 + 100 156 101 157 }
+12 -14
src/applications/phortune/controller/PhortuneProviderController.php src/applications/phortune/controller/PhortuneProviderActionController.php
··· 1 1 <?php 2 2 3 - final class PhortuneProviderController extends PhortuneController { 3 + final class PhortuneProviderActionController extends PhortuneController { 4 4 5 - private $digest; 5 + private $id; 6 6 private $action; 7 7 8 8 public function willProcessRequest(array $data) { 9 - $this->digest = $data['digest']; 9 + $this->id = $data['id']; 10 10 $this->setAction($data['action']); 11 11 } 12 12 ··· 21 21 22 22 public function processRequest() { 23 23 $request = $this->getRequest(); 24 - $user = $request->getUser(); 25 - 26 - 27 - // NOTE: This use of digests to identify payment providers is because 28 - // payment provider keys don't necessarily have restrictions on what they 29 - // contain (so they might have stuff that's not safe to put in URIs), and 30 - // using digests prevents errors with URI encoding. 24 + $viewer = $request->getUser(); 31 25 32 - $provider = PhortunePaymentProvider::getProviderByDigest($this->digest); 33 - if (!$provider) { 34 - throw new Exception('Invalid payment provider digest!'); 26 + $provider_config = id(new PhortunePaymentProviderConfigQuery()) 27 + ->setViewer($viewer) 28 + ->withIDs(array($this->id)) 29 + ->executeOne(); 30 + if (!$provider_config) { 31 + return new Aphront404Response(); 35 32 } 36 33 34 + $provider = $provider_config->buildProvider(); 35 + 37 36 if (!$provider->canRespondToControllerAction($this->getAction())) { 38 37 return new Aphront404Response(); 39 38 } 40 - 41 39 42 40 $response = $provider->processControllerRequest($this, $request); 43 41
+292
src/applications/phortune/controller/PhortuneProviderEditController.php
··· 1 + <?php 2 + 3 + final class PhortuneProviderEditController 4 + extends PhortuneMerchantController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = idx($data, 'id'); 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + if ($this->id) { 17 + $provider_config = id(new PhortunePaymentProviderConfigQuery()) 18 + ->setViewer($viewer) 19 + ->withIDs(array($this->id)) 20 + ->requireCapabilities( 21 + array( 22 + PhabricatorPolicyCapability::CAN_VIEW, 23 + PhabricatorPolicyCapability::CAN_EDIT, 24 + )) 25 + ->executeOne(); 26 + if (!$provider_config) { 27 + return new Aphront404Response(); 28 + } 29 + $is_new = false; 30 + $is_choose_type = false; 31 + 32 + $merchant = $provider_config->getMerchant(); 33 + $merchant_id = $merchant->getID(); 34 + $cancel_uri = $this->getApplicationURI("merchant/{$merchant_id}/"); 35 + } else { 36 + $merchant = id(new PhortuneMerchantQuery()) 37 + ->setViewer($viewer) 38 + ->withIDs(array($request->getStr('merchantID'))) 39 + ->requireCapabilities( 40 + array( 41 + PhabricatorPolicyCapability::CAN_VIEW, 42 + PhabricatorPolicyCapability::CAN_EDIT, 43 + )) 44 + ->executeOne(); 45 + if (!$merchant) { 46 + return new Aphront404Response(); 47 + } 48 + $merchant_id = $merchant->getID(); 49 + 50 + $current_providers = id(new PhortunePaymentProviderConfigQuery()) 51 + ->setViewer($viewer) 52 + ->withMerchantPHIDs(array($merchant->getPHID())) 53 + ->execute(); 54 + $current_map = mgroup($current_providers, 'getProviderClass'); 55 + 56 + $provider_config = PhortunePaymentProviderConfig::initializeNewProvider( 57 + $merchant); 58 + 59 + $is_new = true; 60 + 61 + $classes = PhortunePaymentProvider::getAllProviders(); 62 + $class = $request->getStr('class'); 63 + if (empty($classes[$class]) || isset($current_map[$class])) { 64 + return $this->processChooseClassRequest( 65 + $request, 66 + $merchant, 67 + $current_map); 68 + } 69 + 70 + $provider_config->setProviderClass($class); 71 + 72 + $cancel_uri = $this->getApplicationURI( 73 + 'provider/edit/?merchantID='.$merchant_id); 74 + } 75 + 76 + $provider = $provider_config->buildProvider(); 77 + 78 + if ($is_new) { 79 + $title = pht('Create Payment Provider'); 80 + $button_text = pht('Create Provider'); 81 + } else { 82 + $title = pht( 83 + 'Edit Payment Provider %d %s', 84 + $provider_config->getID(), 85 + $provider->getName()); 86 + $button_text = pht('Save Changes'); 87 + } 88 + 89 + $errors = array(); 90 + if ($request->isFormPost() && $request->getStr('edit')) { 91 + $form_values = $provider->readEditFormValuesFromRequest($request); 92 + 93 + list($errors, $issues, $xaction_values) = $provider->processEditForm( 94 + $request, 95 + $form_values); 96 + 97 + if (!$errors) { 98 + // Find any secret fields which we're about to set to "*******" 99 + // (indicating that the user did not edit the value) and remove them 100 + // from the list of properties to update (so we don't write "******" 101 + // to permanent configuration. 102 + $secrets = $provider->getAllConfigurableSecretProperties(); 103 + $secrets = array_fuse($secrets); 104 + foreach ($xaction_values as $key => $value) { 105 + if ($provider->isConfigurationSecret($value)) { 106 + unset($xaction_values[$key]); 107 + } 108 + } 109 + 110 + if ($provider->canRunConfigurationTest()) { 111 + $proxy = clone $provider; 112 + $proxy_config = clone $provider_config; 113 + $proxy_config->setMetadata( 114 + $xaction_values + $provider_config->getMetadata()); 115 + $proxy->setProviderConfig($proxy_config); 116 + 117 + try { 118 + $proxy->runConfigurationTest(); 119 + } catch (Exception $ex) { 120 + $errors[] = pht('Unable to connect to payment provider:'); 121 + $errors[] = $ex->getMessage(); 122 + } 123 + } 124 + 125 + if (!$errors) { 126 + $template = id(new PhortunePaymentProviderConfigTransaction()) 127 + ->setTransactionType( 128 + PhortunePaymentProviderConfigTransaction::TYPE_PROPERTY); 129 + 130 + $xactions = array(); 131 + 132 + $xactions[] = id(new PhortunePaymentProviderConfigTransaction()) 133 + ->setTransactionType( 134 + PhortunePaymentProviderConfigTransaction::TYPE_CREATE) 135 + ->setNewValue(true); 136 + 137 + foreach ($xaction_values as $key => $value) { 138 + $xactions[] = id(clone $template) 139 + ->setMetadataValue( 140 + PhortunePaymentProviderConfigTransaction::PROPERTY_KEY, 141 + $key) 142 + ->setNewValue($value); 143 + } 144 + 145 + $editor = id(new PhortunePaymentProviderConfigEditor()) 146 + ->setActor($viewer) 147 + ->setContentSourceFromRequest($request) 148 + ->setContinueOnNoEffect(true); 149 + 150 + $editor->applyTransactions($provider_config, $xactions); 151 + 152 + $merchant_uri = $this->getApplicationURI( 153 + 'merchant/'.$merchant->getID().'/'); 154 + return id(new AphrontRedirectResponse())->setURI($merchant_uri); 155 + } 156 + } 157 + } else { 158 + $form_values = $provider->readEditFormValuesFromProviderConfig(); 159 + $issues = array(); 160 + } 161 + 162 + $form = id(new AphrontFormView()) 163 + ->setUser($viewer) 164 + ->addHiddenInput('merchantID', $merchant->getID()) 165 + ->addHiddenInput('class', $provider_config->getProviderClass()) 166 + ->addHiddenInput('edit', true) 167 + ->appendChild( 168 + id(new AphrontFormMarkupControl()) 169 + ->setLabel(pht('Provider Type')) 170 + ->setValue($provider->getName())); 171 + 172 + $provider->extendEditForm($request, $form, $form_values, $issues); 173 + 174 + $form 175 + ->appendChild( 176 + id(new AphrontFormSubmitControl()) 177 + ->setValue($button_text) 178 + ->addCancelButton($cancel_uri)) 179 + ->appendChild( 180 + id(new AphrontFormDividerControl())) 181 + ->appendRemarkupInstructions( 182 + $provider->getConfigureInstructions()); 183 + 184 + $crumbs = $this->buildApplicationCrumbs(); 185 + $crumbs->addTextCrumb($merchant->getName(), $cancel_uri); 186 + 187 + if ($is_new) { 188 + $crumbs->addTextCrumb(pht('Add Provider')); 189 + } else { 190 + $crumbs->addTextCrumb( 191 + pht('Edit Provider %d', $provider_config->getID())); 192 + } 193 + 194 + $box = id(new PHUIObjectBoxView()) 195 + ->setFormErrors($errors) 196 + ->setHeaderText($title) 197 + ->appendChild($form); 198 + 199 + return $this->buildApplicationPage( 200 + array( 201 + $crumbs, 202 + $box, 203 + ), 204 + array( 205 + 'title' => $title, 206 + )); 207 + } 208 + 209 + private function processChooseClassRequest( 210 + AphrontRequest $request, 211 + PhortuneMerchant $merchant, 212 + array $current_map) { 213 + 214 + $viewer = $request->getUser(); 215 + 216 + $providers = PhortunePaymentProvider::getAllProviders(); 217 + $v_class = null; 218 + $errors = array(); 219 + if ($request->isFormPost()) { 220 + $v_class = $request->getStr('class'); 221 + if (!isset($providers[$v_class])) { 222 + $errors[] = pht('You must select a valid provider type.'); 223 + } 224 + } 225 + 226 + $merchant_id = $merchant->getID(); 227 + $cancel_uri = $this->getApplicationURI("merchant/{$merchant_id}/"); 228 + 229 + if (!$v_class) { 230 + $v_class = key($providers); 231 + } 232 + 233 + $panel_classes = id(new AphrontFormRadioButtonControl()) 234 + ->setName('class') 235 + ->setValue($v_class); 236 + 237 + $providers = msort($providers, 'getConfigureName'); 238 + foreach ($providers as $class => $provider) { 239 + $disabled = isset($current_map[$class]); 240 + if ($disabled) { 241 + $description = phutil_tag( 242 + 'em', 243 + array(), 244 + pht( 245 + 'This merchant already has a payment account configured '. 246 + 'with this provider.')); 247 + } else { 248 + $description = $provider->getConfigureDescription(); 249 + } 250 + 251 + $panel_classes->addButton( 252 + $class, 253 + $provider->getConfigureName(), 254 + $description, 255 + null, 256 + $disabled); 257 + } 258 + 259 + $form = id(new AphrontFormView()) 260 + ->setUser($viewer) 261 + ->addHiddenInput('merchantID', $merchant->getID()) 262 + ->appendRemarkupInstructions( 263 + pht( 264 + 'Choose the type of payment provider to add:')) 265 + ->appendChild($panel_classes) 266 + ->appendChild( 267 + id(new AphrontFormSubmitControl()) 268 + ->setValue(pht('Continue')) 269 + ->addCancelButton($cancel_uri)); 270 + 271 + $title = pht('Add Payment Provider'); 272 + 273 + $crumbs = $this->buildApplicationCrumbs(); 274 + $crumbs->addTextCrumb($merchant->getName(), $cancel_uri); 275 + $crumbs->addTextCrumb($title); 276 + 277 + $box = id(new PHUIObjectBoxView()) 278 + ->setHeaderText($title) 279 + ->setFormErrors($errors) 280 + ->setForm($form); 281 + 282 + return $this->buildApplicationPage( 283 + array( 284 + $crumbs, 285 + $box, 286 + ), 287 + array( 288 + 'title' => $title, 289 + )); 290 + } 291 + 292 + }
+81
src/applications/phortune/editor/PhortunePaymentProviderConfigEditor.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentProviderConfigEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorPhortuneApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Phortune Payment Providers'); 12 + } 13 + 14 + public function getTransactionTypes() { 15 + $types = parent::getTransactionTypes(); 16 + 17 + $types[] = PhortunePaymentProviderConfigTransaction::TYPE_CREATE; 18 + $types[] = PhortunePaymentProviderConfigTransaction::TYPE_PROPERTY; 19 + 20 + return $types; 21 + } 22 + 23 + protected function getCustomTransactionOldValue( 24 + PhabricatorLiskDAO $object, 25 + PhabricatorApplicationTransaction $xaction) { 26 + switch ($xaction->getTransactionType()) { 27 + case PhortunePaymentProviderConfigTransaction::TYPE_CREATE: 28 + return null; 29 + case PhortunePaymentProviderConfigTransaction::TYPE_PROPERTY: 30 + $property_key = $xaction->getMetadataValue( 31 + PhortunePaymentProviderConfigTransaction::PROPERTY_KEY); 32 + return $object->getMetadataValue($property_key); 33 + } 34 + 35 + return parent::getCustomTransactionOldValue($object, $xaction); 36 + } 37 + 38 + protected function getCustomTransactionNewValue( 39 + PhabricatorLiskDAO $object, 40 + PhabricatorApplicationTransaction $xaction) { 41 + 42 + switch ($xaction->getTransactionType()) { 43 + case PhortunePaymentProviderConfigTransaction::TYPE_CREATE: 44 + case PhortunePaymentProviderConfigTransaction::TYPE_PROPERTY: 45 + return $xaction->getNewValue(); 46 + } 47 + 48 + return parent::getCustomTransactionNewValue($object, $xaction); 49 + } 50 + 51 + protected function applyCustomInternalTransaction( 52 + PhabricatorLiskDAO $object, 53 + PhabricatorApplicationTransaction $xaction) { 54 + 55 + switch ($xaction->getTransactionType()) { 56 + case PhortunePaymentProviderConfigTransaction::TYPE_CREATE: 57 + return; 58 + case PhortunePaymentProviderConfigTransaction::TYPE_PROPERTY: 59 + $property_key = $xaction->getMetadataValue( 60 + PhortunePaymentProviderConfigTransaction::PROPERTY_KEY); 61 + $object->setMetadataValue($property_key, $xaction->getNewValue()); 62 + return; 63 + } 64 + 65 + return parent::applyCustomInternalTransaction($object, $xaction); 66 + } 67 + 68 + protected function applyCustomExternalTransaction( 69 + PhabricatorLiskDAO $object, 70 + PhabricatorApplicationTransaction $xaction) { 71 + 72 + switch ($xaction->getTransactionType()) { 73 + case PhortunePaymentProviderConfigTransaction::TYPE_CREATE: 74 + case PhortunePaymentProviderConfigTransaction::TYPE_PROPERTY: 75 + return; 76 + } 77 + 78 + return parent::applyCustomExternalTransaction($object, $xaction); 79 + } 80 + 81 + }
-70
src/applications/phortune/option/PhabricatorPhortuneConfigOptions.php
··· 1 - <?php 2 - 3 - final class PhabricatorPhortuneConfigOptions 4 - extends PhabricatorApplicationConfigOptions { 5 - 6 - public function getName() { 7 - return pht('Phortune'); 8 - } 9 - 10 - public function getDescription() { 11 - return pht('Configure payments and billing.'); 12 - } 13 - 14 - public function getOptions() { 15 - return array( 16 - $this->newOption('phortune.stripe.publishable-key', 'string', null) 17 - ->setLocked(true) 18 - ->setDescription(pht('Stripe publishable key.')), 19 - $this->newOption('phortune.stripe.secret-key', 'string', null) 20 - ->setHidden(true) 21 - ->setDescription(pht('Stripe secret key.')), 22 - $this->newOption('phortune.balanced.marketplace-uri', 'string', null) 23 - ->setLocked(true) 24 - ->setDescription(pht('Balanced Marketplace URI.')), 25 - $this->newOption('phortune.balanced.secret-key', 'string', null) 26 - ->setHidden(true) 27 - ->setDescription(pht('Balanced secret key.')), 28 - $this->newOption('phortune.test.enabled', 'bool', false) 29 - ->setBoolOptions( 30 - array( 31 - pht('Enable Test Provider'), 32 - pht('Disable Test Provider'), 33 - )) 34 - ->setSummary(pht('Enable test payment provider.')) 35 - ->setDescription( 36 - pht( 37 - "Enable the test payment provider.\n\n". 38 - "NOTE: Enabling this provider gives all users infinite free ". 39 - "money! You should enable it **ONLY** for testing and ". 40 - "development.")) 41 - ->setLocked(true), 42 - $this->newOption('phortune.paypal.api-username', 'string', null) 43 - ->setLocked(true) 44 - ->setDescription( 45 - pht('PayPal API username.')), 46 - $this->newOption('phortune.paypal.api-password', 'string', null) 47 - ->setHidden(true) 48 - ->setDescription( 49 - pht('PayPal API password.')), 50 - $this->newOption('phortune.paypal.api-signature', 'string', null) 51 - ->setHidden(true) 52 - ->setDescription( 53 - pht('PayPal API signature.')), 54 - $this->newOption('phortune.wepay.client-id', 'string', null) 55 - ->setLocked(true) 56 - ->setDescription(pht('WePay application ID.')), 57 - $this->newOption('phortune.wepay.client-secret', 'string', null) 58 - ->setHidden(true) 59 - ->setDescription(pht('WePay application secret.')), 60 - $this->newOption('phortune.wepay.access-token', 'string', null) 61 - ->setHidden(true) 62 - ->setDescription(pht('WePay access token.')), 63 - $this->newOption('phortune.wepay.account-id', 'string', null) 64 - ->setLocked(true) 65 - ->setHidden(true) 66 - ->setDescription(pht('WePay account ID.')), 67 - ); 68 - } 69 - 70 - }
+38
src/applications/phortune/phid/PhortunePaymentProviderPHIDType.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentProviderPHIDType extends PhabricatorPHIDType { 4 + 5 + const TYPECONST = 'PHPR'; 6 + 7 + public function getTypeName() { 8 + return pht('Phortune Payment Provider'); 9 + } 10 + 11 + public function newObject() { 12 + return new PhortunePaymentProviderConfig(); 13 + } 14 + 15 + protected function buildQueryForObjects( 16 + PhabricatorObjectQuery $query, 17 + array $phids) { 18 + 19 + return id(new PhortunePaymentProviderConfigQuery()) 20 + ->withPHIDs($phids); 21 + } 22 + 23 + public function loadHandles( 24 + PhabricatorHandleQuery $query, 25 + array $handles, 26 + array $objects) { 27 + 28 + foreach ($handles as $phid => $handle) { 29 + $provider_config = $objects[$phid]; 30 + 31 + $id = $provider_config->getID(); 32 + 33 + $handle->setName(pht('Payment Provider %d', $id)); 34 + $handle->setURI("/phortune/provider/{$id}/"); 35 + } 36 + } 37 + 38 + }
+117 -12
src/applications/phortune/provider/PhortuneBalancedPaymentProvider.php
··· 2 2 3 3 final class PhortuneBalancedPaymentProvider extends PhortunePaymentProvider { 4 4 5 + const BALANCED_MARKETPLACE_ID = 'balanced.marketplace-id'; 6 + const BALANCED_SECRET_KEY = 'balanced.secret-key'; 7 + 5 8 public function isEnabled() { 6 9 return $this->getMarketplaceURI() && 7 10 $this->getSecretKey(); 8 11 } 9 12 10 - public function getProviderType() { 11 - return 'balanced'; 13 + public function getName() { 14 + return pht('Balanced Payments'); 15 + } 16 + 17 + public function getConfigureName() { 18 + return pht('Add Balanced Payments Account'); 19 + } 20 + 21 + public function getConfigureDescription() { 22 + return pht( 23 + 'Allows you to accept credit or debit card payments with a '. 24 + 'balancedpayments.com account.'); 25 + } 26 + 27 + public function getConfigureInstructions() { 28 + return pht( 29 + "To configure Balacned, register or log in to an existing account on ". 30 + "[[https://balancedpayments.com | balancedpayments.com]]. Once logged ". 31 + "in:\n\n". 32 + " - Choose a marketplace.\n". 33 + " - Find the **Marketplace ID** in {nav My Marketplace > Settings} and ". 34 + " copy it into the field above.\n". 35 + " - On the same screen, under **API keys**, choose **Add a key**, then ". 36 + " **Show key secret**. Copy the value into the field above.\n\n". 37 + "You can either use a test marketplace to add this provider in test ". 38 + "mode, or use a live marketplace to accept live payments."); 39 + } 40 + 41 + public function getAllConfigurableProperties() { 42 + return array( 43 + self::BALANCED_MARKETPLACE_ID, 44 + self::BALANCED_SECRET_KEY, 45 + ); 46 + } 47 + 48 + public function getAllConfigurableSecretProperties() { 49 + return array( 50 + self::BALANCED_SECRET_KEY, 51 + ); 52 + } 53 + 54 + public function processEditForm( 55 + AphrontRequest $request, 56 + array $values) { 57 + 58 + $errors = array(); 59 + $issues = array(); 60 + 61 + if (!strlen($values[self::BALANCED_MARKETPLACE_ID])) { 62 + $errors[] = pht('Balanced Marketplace ID is required.'); 63 + $issues[self::BALANCED_MARKETPLACE_ID] = pht('Required'); 64 + } 65 + 66 + if (!strlen($values[self::BALANCED_SECRET_KEY])) { 67 + $errors[] = pht('Balanced Secret Key is required.'); 68 + $issues[self::BALANCED_SECRET_KEY] = pht('Required'); 69 + } 70 + 71 + return array($errors, $issues, $values); 72 + } 73 + 74 + public function extendEditForm( 75 + AphrontRequest $request, 76 + AphrontFormView $form, 77 + array $values, 78 + array $issues) { 79 + 80 + $form 81 + ->appendChild( 82 + id(new AphrontFormTextControl()) 83 + ->setName(self::BALANCED_MARKETPLACE_ID) 84 + ->setValue($values[self::BALANCED_MARKETPLACE_ID]) 85 + ->setError(idx($issues, self::BALANCED_MARKETPLACE_ID, true)) 86 + ->setLabel(pht('Balanced Marketplace ID'))) 87 + ->appendChild( 88 + id(new AphrontFormTextControl()) 89 + ->setName(self::BALANCED_SECRET_KEY) 90 + ->setValue($values[self::BALANCED_SECRET_KEY]) 91 + ->setError(idx($issues, self::BALANCED_SECRET_KEY, true)) 92 + ->setLabel(pht('Balanced Secret Key'))); 93 + 12 94 } 13 95 14 - public function getProviderDomain() { 15 - return 'balancedpayments.com'; 96 + public function canRunConfigurationTest() { 97 + return true; 98 + } 99 + 100 + public function runConfigurationTest() { 101 + $root = dirname(phutil_get_library_root('phabricator')); 102 + require_once $root.'/externals/httpful/bootstrap.php'; 103 + require_once $root.'/externals/restful/bootstrap.php'; 104 + require_once $root.'/externals/balanced-php/bootstrap.php'; 105 + 106 + // TODO: This only tests that the secret key is correct. It's not clear 107 + // how to test that the marketplace is correct. 108 + 109 + try { 110 + Balanced\Settings::$api_key = $this->getSecretKey(); 111 + Balanced\APIKey::query()->first(); 112 + } catch (RESTful\Exceptions\HTTPError $error) { 113 + // NOTE: This exception doesn't print anything meaningful if it escapes 114 + // to top level. Replace it with something slightly readable. 115 + throw new Exception($error->response->body->description); 116 + } 16 117 } 17 118 18 119 public function getPaymentMethodDescription() { ··· 32 133 return pht('Credit/Debit Card'); 33 134 } 34 135 35 - public function canHandlePaymentMethod(PhortunePaymentMethod $method) { 36 - $type = $method->getMetadataValue('type'); 37 - return ($type === 'balanced.account'); 38 - } 39 - 40 136 protected function executeCharge( 41 137 PhortunePaymentMethod $method, 42 138 PhortuneCharge $charge) { ··· 79 175 $charge->save(); 80 176 } 81 177 82 - private function getMarketplaceURI() { 83 - return PhabricatorEnv::getEnvConfig('phortune.balanced.marketplace-uri'); 178 + private function getMarketplaceID() { 179 + return $this 180 + ->getProviderConfig() 181 + ->getMetadataValue(self::BALANCED_MARKETPLACE_ID); 84 182 } 85 183 86 184 private function getSecretKey() { 87 - return PhabricatorEnv::getEnvConfig('phortune.balanced.secret-key'); 185 + return $this 186 + ->getProviderConfig() 187 + ->getMetadataValue(self::BALANCED_SECRET_KEY); 188 + } 189 + 190 + private function getMarketplaceURI() { 191 + return '/v1/marketplace/'.$this->getMarketplaceID(); 88 192 } 89 193 90 194 ··· 104 208 * @phutil-external-symbol class Balanced\Card 105 209 * @phutil-external-symbol class Balanced\Settings 106 210 * @phutil-external-symbol class Balanced\Marketplace 211 + * @phutil-external-symbol class Balanced\APIKey 107 212 * @phutil-external-symbol class RESTful\Exceptions\HTTPError 108 213 */ 109 214 public function createPaymentMethodFromRequest(
+357
src/applications/phortune/provider/PhortunePayPalPaymentProvider.php
··· 1 + <?php 2 + 3 + final class PhortunePayPalPaymentProvider extends PhortunePaymentProvider { 4 + 5 + const PAYPAL_API_USERNAME = 'paypal.api-username'; 6 + const PAYPAL_API_PASSWORD = 'paypal.api-password'; 7 + const PAYPAL_API_SIGNATURE = 'paypal.api-signature'; 8 + const PAYPAL_MODE = 'paypal.mode'; 9 + 10 + public function isEnabled() { 11 + // TODO: See note in processControllerRequest(). 12 + return false; 13 + 14 + return $this->getPaypalAPIUsername() && 15 + $this->getPaypalAPIPassword() && 16 + $this->getPaypalAPISignature(); 17 + } 18 + 19 + public function getName() { 20 + return pht('PayPal'); 21 + } 22 + 23 + public function getConfigureName() { 24 + return pht('Add PayPal Payments Account'); 25 + } 26 + 27 + public function getConfigureDescription() { 28 + return pht( 29 + 'Allows you to accept various payment instruments with a paypal.com '. 30 + 'account.'); 31 + } 32 + 33 + public function getConfigureInstructions() { 34 + return pht( 35 + "To configure PayPal, register or log into an existing account on ". 36 + "[[https://paypal.com | paypal.com]] (for live payments) or ". 37 + "[[https://sandbox.paypal.com | sandbox.paypal.com]] (for test ". 38 + "payments). Once logged in:\n\n". 39 + " - Navigate to {nav Tools > API Access}.\n". 40 + " - Choose **View API Signature**.\n". 41 + " - Copy the **API Username**, **API Password** and **Signature** ". 42 + " into the fields above.\n\n". 43 + "You can select whether the provider operates in test mode or ". 44 + "accepts live payments using the **Mode** dropdown above.\n\n". 45 + "You can either use `sandbox.paypal.com` to retrieve live credentials, ". 46 + "or `paypal.com` to retrieve live credentials."); 47 + } 48 + 49 + public function getAllConfigurableProperties() { 50 + return array( 51 + self::PAYPAL_API_USERNAME, 52 + self::PAYPAL_API_PASSWORD, 53 + self::PAYPAL_API_SIGNATURE, 54 + self::PAYPAL_MODE, 55 + ); 56 + } 57 + 58 + public function getAllConfigurableSecretProperties() { 59 + return array( 60 + self::PAYPAL_API_PASSWORD, 61 + self::PAYPAL_API_SIGNATURE, 62 + ); 63 + } 64 + 65 + public function processEditForm( 66 + AphrontRequest $request, 67 + array $values) { 68 + 69 + $errors = array(); 70 + $issues = array(); 71 + 72 + if (!strlen($values[self::PAYPAL_API_USERNAME])) { 73 + $errors[] = pht('PayPal API Username is required.'); 74 + $issues[self::PAYPAL_API_USERNAME] = pht('Required'); 75 + } 76 + 77 + if (!strlen($values[self::PAYPAL_API_PASSWORD])) { 78 + $errors[] = pht('PayPal API Password is required.'); 79 + $issues[self::PAYPAL_API_PASSWORD] = pht('Required'); 80 + } 81 + 82 + if (!strlen($values[self::PAYPAL_API_SIGNATURE])) { 83 + $errors[] = pht('PayPal API Signature is required.'); 84 + $issues[self::PAYPAL_API_SIGNATURE] = pht('Required'); 85 + } 86 + 87 + if (!strlen($values[self::PAYPAL_MODE])) { 88 + $errors[] = pht('Mode is required.'); 89 + $issues[self::PAYPAL_MODE] = pht('Required'); 90 + } 91 + 92 + return array($errors, $issues, $values); 93 + } 94 + 95 + public function extendEditForm( 96 + AphrontRequest $request, 97 + AphrontFormView $form, 98 + array $values, 99 + array $issues) { 100 + 101 + $form 102 + ->appendChild( 103 + id(new AphrontFormTextControl()) 104 + ->setName(self::PAYPAL_API_USERNAME) 105 + ->setValue($values[self::PAYPAL_API_USERNAME]) 106 + ->setError(idx($issues, self::PAYPAL_API_USERNAME, true)) 107 + ->setLabel(pht('Paypal API Username'))) 108 + ->appendChild( 109 + id(new AphrontFormTextControl()) 110 + ->setName(self::PAYPAL_API_PASSWORD) 111 + ->setValue($values[self::PAYPAL_API_PASSWORD]) 112 + ->setError(idx($issues, self::PAYPAL_API_PASSWORD, true)) 113 + ->setLabel(pht('Paypal API Password'))) 114 + ->appendChild( 115 + id(new AphrontFormTextControl()) 116 + ->setName(self::PAYPAL_API_SIGNATURE) 117 + ->setValue($values[self::PAYPAL_API_SIGNATURE]) 118 + ->setError(idx($issues, self::PAYPAL_API_SIGNATURE, true)) 119 + ->setLabel(pht('Paypal API Signature'))) 120 + ->appendChild( 121 + id(new AphrontFormSelectControl()) 122 + ->setName(self::PAYPAL_MODE) 123 + ->setValue($values[self::PAYPAL_MODE]) 124 + ->setError(idx($issues, self::PAYPAL_MODE)) 125 + ->setLabel(pht('Mode')) 126 + ->setOptions( 127 + array( 128 + 'test' => pht('Test Mode'), 129 + 'live' => pht('Live Mode'), 130 + ))); 131 + 132 + return; 133 + } 134 + 135 + public function canRunConfigurationTest() { 136 + return true; 137 + } 138 + 139 + public function runConfigurationTest() { 140 + $result = $this 141 + ->newPaypalAPICall() 142 + ->setRawPayPalQuery('GetBalance', array()) 143 + ->resolve(); 144 + } 145 + 146 + public function getPaymentMethodDescription() { 147 + return pht('Credit Card or PayPal Account'); 148 + } 149 + 150 + public function getPaymentMethodIcon() { 151 + return celerity_get_resource_uri('rsrc/image/phortune/paypal.png'); 152 + } 153 + 154 + public function getPaymentMethodProviderDescription() { 155 + return 'PayPal'; 156 + } 157 + 158 + protected function executeCharge( 159 + PhortunePaymentMethod $payment_method, 160 + PhortuneCharge $charge) { 161 + throw new Exception('!'); 162 + } 163 + 164 + private function getPaypalAPIUsername() { 165 + return $this 166 + ->getProviderConfig() 167 + ->getMetadataValue(self::PAYPAL_API_USERNAME); 168 + } 169 + 170 + private function getPaypalAPIPassword() { 171 + return $this 172 + ->getProviderConfig() 173 + ->getMetadataValue(self::PAYPAL_API_PASSWORD); 174 + } 175 + 176 + private function getPaypalAPISignature() { 177 + return $this 178 + ->getProviderConfig() 179 + ->getMetadataValue(self::PAYPAL_API_SIGNATURE); 180 + } 181 + 182 + /* -( One-Time Payments )-------------------------------------------------- */ 183 + 184 + public function canProcessOneTimePayments() { 185 + return true; 186 + } 187 + 188 + /* -( Controllers )-------------------------------------------------------- */ 189 + 190 + 191 + public function canRespondToControllerAction($action) { 192 + switch ($action) { 193 + case 'checkout': 194 + case 'charge': 195 + case 'cancel': 196 + return true; 197 + } 198 + return parent::canRespondToControllerAction(); 199 + } 200 + 201 + public function processControllerRequest( 202 + PhortuneProviderActionController $controller, 203 + AphrontRequest $request) { 204 + 205 + $viewer = $request->getUser(); 206 + 207 + $cart = $controller->loadCart($request->getInt('cartID')); 208 + if (!$cart) { 209 + return new Aphront404Response(); 210 + } 211 + 212 + $charge = $controller->loadActiveCharge($cart); 213 + switch ($controller->getAction()) { 214 + case 'checkout': 215 + if ($charge) { 216 + throw new Exception(pht('Cart is already charging!')); 217 + } 218 + break; 219 + case 'charge': 220 + case 'cancel': 221 + if (!$charge) { 222 + throw new Exception(pht('Cart is not charging yet!')); 223 + } 224 + break; 225 + } 226 + 227 + switch ($controller->getAction()) { 228 + case 'checkout': 229 + $return_uri = $this->getControllerURI( 230 + 'charge', 231 + array( 232 + 'cartID' => $cart->getID(), 233 + )); 234 + 235 + $cancel_uri = $this->getControllerURI( 236 + 'cancel', 237 + array( 238 + 'cartID' => $cart->getID(), 239 + )); 240 + 241 + $price = $cart->getTotalPriceAsCurrency(); 242 + 243 + $charge = $cart->willApplyCharge($viewer, $this); 244 + 245 + $params = array( 246 + 'PAYMENTREQUEST_0_AMT' => $price->formatBareValue(), 247 + 'PAYMENTREQUEST_0_CURRENCYCODE' => $price->getCurrency(), 248 + 'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale', 249 + 'PAYMENTREQUEST_0_CUSTOM' => $charge->getPHID(), 250 + 251 + 'RETURNURL' => $return_uri, 252 + 'CANCELURL' => $cancel_uri, 253 + 254 + // TODO: This should be cart-dependent if we eventually support 255 + // physical goods. 256 + 'NOSHIPPING' => '1', 257 + ); 258 + 259 + $result = $this 260 + ->newPaypalAPICall() 261 + ->setRawPayPalQuery('SetExpressCheckout', $params) 262 + ->resolve(); 263 + 264 + $uri = new PhutilURI('https://www.sandbox.paypal.com/cgi-bin/webscr'); 265 + $uri->setQueryParams( 266 + array( 267 + 'cmd' => '_express-checkout', 268 + 'token' => $result['TOKEN'], 269 + )); 270 + 271 + $cart->setMetadataValue('provider.checkoutURI', $uri); 272 + $cart->save(); 273 + 274 + $charge->setMetadataValue('paypal.token', $result['TOKEN']); 275 + $charge->save(); 276 + 277 + return id(new AphrontRedirectResponse()) 278 + ->setIsExternal(true) 279 + ->setURI($uri); 280 + case 'charge': 281 + $token = $request->getStr('token'); 282 + 283 + $params = array( 284 + 'TOKEN' => $token, 285 + ); 286 + 287 + $result = $this 288 + ->newPaypalAPICall() 289 + ->setRawPayPalQuery('GetExpressCheckoutDetails', $params) 290 + ->resolve(); 291 + 292 + var_dump($result); 293 + 294 + if ($result['CUSTOM'] !== $charge->getPHID()) { 295 + throw new Exception( 296 + pht('Paypal checkout does not match Phortune charge!')); 297 + } 298 + 299 + if ($result['CHECKOUTSTATUS'] !== 'PaymentActionNotInitiated') { 300 + throw new Exception( 301 + pht( 302 + 'Expected status "%s", got "%s".', 303 + 'PaymentActionNotInitiated', 304 + $result['CHECKOUTSTATUS'])); 305 + } 306 + 307 + $price = $cart->getTotalPriceAsCurrency(); 308 + 309 + $params = array( 310 + 'TOKEN' => $token, 311 + 'PAYERID' => $result['PAYERID'], 312 + 313 + 'PAYMENTREQUEST_0_AMT' => $price->formatBareValue(), 314 + 'PAYMENTREQUEST_0_CURRENCYCODE' => $price->getCurrency(), 315 + 'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale', 316 + ); 317 + 318 + $result = $this 319 + ->newPaypalAPICall() 320 + ->setRawPayPalQuery('DoExpressCheckoutPayment', $params) 321 + ->resolve(); 322 + 323 + // TODO: Paypal can send requests back in "PaymentReview" status, 324 + // and does this for test transactions. We're supposed to hold 325 + // the transaction and poll the API every 6 hours. This is unreasonably 326 + // difficult for now and we can't reasonably just fail these charges. 327 + 328 + var_dump($result); 329 + 330 + die(); 331 + break; 332 + case 'cancel': 333 + var_dump($_REQUEST); 334 + break; 335 + } 336 + 337 + throw new Exception( 338 + pht('Unsupported action "%s".', $controller->getAction())); 339 + } 340 + 341 + private function newPaypalAPICall() { 342 + $mode = $this->getProviderConfig()->getMetadataValue(self::PAYPAL_MODE); 343 + if ($mode == 'live') { 344 + $host = 'https://api-3t.paypal.com/nvp'; 345 + } else { 346 + $host = 'https://api-3t.sandbox.paypal.com/nvp'; 347 + } 348 + 349 + return id(new PhutilPayPalAPIFuture()) 350 + ->setHost($host) 351 + ->setAPIUsername($this->getPaypalAPIUsername()) 352 + ->setAPIPassword($this->getPaypalAPIPassword()) 353 + ->setAPISignature($this->getPaypalAPISignature()); 354 + } 355 + 356 + 357 + }
+109 -57
src/applications/phortune/provider/PhortunePaymentProvider.php
··· 5 5 */ 6 6 abstract class PhortunePaymentProvider { 7 7 8 + private $providerConfig; 9 + 10 + public function setProviderConfig( 11 + PhortunePaymentProviderConfig $provider_config) { 12 + $this->providerConfig = $provider_config; 13 + return $this; 14 + } 15 + 16 + public function getProviderConfig() { 17 + return $this->providerConfig; 18 + } 19 + 20 + /** 21 + * Return a short name which identifies this provider. 22 + */ 23 + abstract public function getName(); 24 + 25 + 26 + /* -( Configuring Providers )---------------------------------------------- */ 27 + 28 + 29 + /** 30 + * Return a human-readable provider name for use on the merchant workflow 31 + * where a merchant owner adds providers. 32 + */ 33 + abstract public function getConfigureName(); 34 + 35 + 36 + /** 37 + * Return a human-readable provider description for use on the merchant 38 + * workflow where a merchant owner adds providers. 39 + */ 40 + abstract public function getConfigureDescription(); 41 + 42 + abstract public function getConfigureInstructions(); 43 + 44 + abstract public function getAllConfigurableProperties(); 45 + 46 + abstract public function getAllConfigurableSecretProperties(); 47 + /** 48 + * Read a dictionary of properties from the provider's configuration for 49 + * use when editing the provider. 50 + */ 51 + public function readEditFormValuesFromProviderConfig() { 52 + $properties = $this->getAllConfigurableProperties(); 53 + $config = $this->getProviderConfig(); 54 + 55 + $secrets = $this->getAllConfigurableSecretProperties(); 56 + $secrets = array_fuse($secrets); 57 + 58 + $map = array(); 59 + foreach ($properties as $property) { 60 + $map[$property] = $config->getMetadataValue($property); 61 + if (isset($secrets[$property])) { 62 + $map[$property] = $this->renderConfigurationSecret($map[$property]); 63 + } 64 + } 65 + 66 + return $map; 67 + } 68 + 69 + 70 + /** 71 + * Read a dictionary of properties from a request for use when editing the 72 + * provider. 73 + */ 74 + public function readEditFormValuesFromRequest(AphrontRequest $request) { 75 + $properties = $this->getAllConfigurableProperties(); 76 + 77 + $map = array(); 78 + foreach ($properties as $property) { 79 + $map[$property] = $request->getStr($property); 80 + } 81 + 82 + return $map; 83 + } 84 + 85 + 86 + abstract public function processEditForm( 87 + AphrontRequest $request, 88 + array $values); 89 + 90 + abstract public function extendEditForm( 91 + AphrontRequest $request, 92 + AphrontFormView $form, 93 + array $values, 94 + array $issues); 95 + 96 + protected function renderConfigurationSecret($value) { 97 + if (strlen($value)) { 98 + return str_repeat('*', strlen($value)); 99 + } 100 + return ''; 101 + } 102 + 103 + public function isConfigurationSecret($value) { 104 + return preg_match('/^\*+\z/', trim($value)); 105 + } 106 + 107 + abstract public function canRunConfigurationTest(); 108 + 109 + public function runConfigurationTest() { 110 + throw new PhortuneNotImplementedException($this); 111 + } 112 + 8 113 9 114 /* -( Selecting Providers )------------------------------------------------ */ 10 115 11 116 12 117 public static function getAllProviders() { 13 - $objects = id(new PhutilSymbolLoader()) 118 + return id(new PhutilSymbolLoader()) 14 119 ->setAncestorClass('PhortunePaymentProvider') 15 120 ->loadObjects(); 16 - 17 - return mpull($objects, null, 'getProviderKey'); 18 121 } 19 122 20 123 public static function getEnabledProviders() { ··· 47 150 return $providers; 48 151 } 49 152 50 - public static function getProviderByDigest($digest) { 51 - $providers = self::getEnabledProviders(); 52 - foreach ($providers as $key => $provider) { 53 - $provider_digest = PhabricatorHash::digestForIndex($key); 54 - if ($provider_digest == $digest) { 55 - return $provider; 56 - } 57 - } 58 - return null; 59 - } 60 - 61 153 abstract public function isEnabled(); 62 154 63 - final public function getProviderKey() { 64 - return $this->getProviderType().'@'.$this->getProviderDomain(); 65 - } 66 - 67 - 68 - /** 69 - * Return a short string which uniquely identifies this provider's protocol 70 - * type, like "stripe", "paypal", or "balanced". 71 - */ 72 - abstract public function getProviderType(); 73 - 74 - 75 - /** 76 - * Return a short string which uniquely identifies the domain for this 77 - * provider, like "stripe.com" or "google.com". 78 - * 79 - * This is distinct from the provider type so that protocols are not bound 80 - * to a single domain. This is probably not relevant for payments, but this 81 - * assumption burned us pretty hard with authentication and it's easy enough 82 - * to avoid. 83 - */ 84 - abstract public function getProviderDomain(); 85 - 86 155 abstract public function getPaymentMethodDescription(); 87 156 abstract public function getPaymentMethodIcon(); 88 157 abstract public function getPaymentMethodProviderDescription(); 89 158 90 - 91 - /** 92 - * Determine of a provider can handle a payment method. 93 - * 94 - * @return bool True if this provider can apply charges to the payment method. 95 - */ 96 - abstract public function canHandlePaymentMethod( 97 - PhortunePaymentMethod $method); 98 - 99 159 final public function applyCharge( 100 160 PhortunePaymentMethod $payment_method, 101 161 PhortuneCharge $charge) { 102 - 103 - $charge->setStatus(PhortuneCharge::STATUS_CHARGING); 104 - $charge->save(); 105 - 106 162 $this->executeCharge($payment_method, $charge); 107 - 108 - $charge->setStatus(PhortuneCharge::STATUS_CHARGED); 109 - $charge->save(); 110 163 } 111 164 112 165 abstract protected function executeCharge( ··· 230 283 array $params = array(), 231 284 $local = false) { 232 285 233 - $digest = PhabricatorHash::digestForIndex($this->getProviderKey()); 234 - 286 + $id = $this->getProviderConfig()->getID(); 235 287 $app = PhabricatorApplication::getByClass('PhabricatorPhortuneApplication'); 236 - $path = $app->getBaseURI().'provider/'.$digest.'/'.$action.'/'; 288 + $path = $app->getBaseURI().'provider/'.$id.'/'.$action.'/'; 237 289 238 290 $uri = new PhutilURI($path); 239 291 $uri->setQueryParams($params); ··· 250 302 } 251 303 252 304 public function processControllerRequest( 253 - PhortuneProviderController $controller, 305 + PhortuneProviderActionController $controller, 254 306 AphrontRequest $request) { 255 307 throw new PhortuneNotImplementedException($this); 256 308 }
-225
src/applications/phortune/provider/PhortunePaypalPaymentProvider.php
··· 1 - <?php 2 - 3 - final class PhortunePaypalPaymentProvider extends PhortunePaymentProvider { 4 - 5 - public function isEnabled() { 6 - // TODO: See note in processControllerRequest(). 7 - return false; 8 - 9 - return $this->getPaypalAPIUsername() && 10 - $this->getPaypalAPIPassword() && 11 - $this->getPaypalAPISignature(); 12 - } 13 - 14 - public function getProviderType() { 15 - return 'paypal'; 16 - } 17 - 18 - public function getProviderDomain() { 19 - return 'paypal.com'; 20 - } 21 - 22 - public function getPaymentMethodDescription() { 23 - return pht('Credit Card or Paypal Account'); 24 - } 25 - 26 - public function getPaymentMethodIcon() { 27 - return celerity_get_resource_uri('rsrc/image/phortune/paypal.png'); 28 - } 29 - 30 - public function getPaymentMethodProviderDescription() { 31 - return 'Paypal'; 32 - } 33 - 34 - public function canHandlePaymentMethod(PhortunePaymentMethod $method) { 35 - $type = $method->getMetadataValue('type'); 36 - return ($type == 'paypal'); 37 - } 38 - 39 - protected function executeCharge( 40 - PhortunePaymentMethod $payment_method, 41 - PhortuneCharge $charge) { 42 - throw new Exception('!'); 43 - } 44 - 45 - private function getPaypalAPIUsername() { 46 - return PhabricatorEnv::getEnvConfig('phortune.paypal.api-username'); 47 - } 48 - 49 - private function getPaypalAPIPassword() { 50 - return PhabricatorEnv::getEnvConfig('phortune.paypal.api-password'); 51 - } 52 - 53 - private function getPaypalAPISignature() { 54 - return PhabricatorEnv::getEnvConfig('phortune.paypal.api-signature'); 55 - } 56 - 57 - /* -( One-Time Payments )-------------------------------------------------- */ 58 - 59 - public function canProcessOneTimePayments() { 60 - return true; 61 - } 62 - 63 - /* -( Controllers )-------------------------------------------------------- */ 64 - 65 - 66 - public function canRespondToControllerAction($action) { 67 - switch ($action) { 68 - case 'checkout': 69 - case 'charge': 70 - case 'cancel': 71 - return true; 72 - } 73 - return parent::canRespondToControllerAction(); 74 - } 75 - 76 - public function processControllerRequest( 77 - PhortuneProviderController $controller, 78 - AphrontRequest $request) { 79 - 80 - $viewer = $request->getUser(); 81 - 82 - $cart = $controller->loadCart($request->getInt('cartID')); 83 - if (!$cart) { 84 - return new Aphront404Response(); 85 - } 86 - 87 - $charge = $controller->loadActiveCharge($cart); 88 - switch ($controller->getAction()) { 89 - case 'checkout': 90 - if ($charge) { 91 - throw new Exception(pht('Cart is already charging!')); 92 - } 93 - break; 94 - case 'charge': 95 - case 'cancel': 96 - if (!$charge) { 97 - throw new Exception(pht('Cart is not charging yet!')); 98 - } 99 - break; 100 - } 101 - 102 - switch ($controller->getAction()) { 103 - case 'checkout': 104 - $return_uri = $this->getControllerURI( 105 - 'charge', 106 - array( 107 - 'cartID' => $cart->getID(), 108 - )); 109 - 110 - $cancel_uri = $this->getControllerURI( 111 - 'cancel', 112 - array( 113 - 'cartID' => $cart->getID(), 114 - )); 115 - 116 - $price = $cart->getTotalPriceAsCurrency(); 117 - 118 - $charge = $cart->willApplyCharge($viewer, $this); 119 - 120 - $params = array( 121 - 'PAYMENTREQUEST_0_AMT' => $price->formatBareValue(), 122 - 'PAYMENTREQUEST_0_CURRENCYCODE' => $price->getCurrency(), 123 - 'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale', 124 - 'PAYMENTREQUEST_0_CUSTOM' => $charge->getPHID(), 125 - 126 - 'RETURNURL' => $return_uri, 127 - 'CANCELURL' => $cancel_uri, 128 - 129 - // TODO: This should be cart-dependent if we eventually support 130 - // physical goods. 131 - 'NOSHIPPING' => '1', 132 - ); 133 - 134 - $result = $this 135 - ->newPaypalAPICall() 136 - ->setRawPayPalQuery('SetExpressCheckout', $params) 137 - ->resolve(); 138 - 139 - $uri = new PhutilURI('https://www.sandbox.paypal.com/cgi-bin/webscr'); 140 - $uri->setQueryParams( 141 - array( 142 - 'cmd' => '_express-checkout', 143 - 'token' => $result['TOKEN'], 144 - )); 145 - 146 - $cart->setMetadataValue('provider.checkoutURI', $uri); 147 - $cart->save(); 148 - 149 - $charge->setMetadataValue('paypal.token', $result['TOKEN']); 150 - $charge->save(); 151 - 152 - return id(new AphrontRedirectResponse()) 153 - ->setIsExternal(true) 154 - ->setURI($uri); 155 - case 'charge': 156 - $token = $request->getStr('token'); 157 - 158 - $params = array( 159 - 'TOKEN' => $token, 160 - ); 161 - 162 - $result = $this 163 - ->newPaypalAPICall() 164 - ->setRawPayPalQuery('GetExpressCheckoutDetails', $params) 165 - ->resolve(); 166 - 167 - var_dump($result); 168 - 169 - if ($result['CUSTOM'] !== $charge->getPHID()) { 170 - throw new Exception( 171 - pht('Paypal checkout does not match Phortune charge!')); 172 - } 173 - 174 - if ($result['CHECKOUTSTATUS'] !== 'PaymentActionNotInitiated') { 175 - throw new Exception( 176 - pht( 177 - 'Expected status "%s", got "%s".', 178 - 'PaymentActionNotInitiated', 179 - $result['CHECKOUTSTATUS'])); 180 - } 181 - 182 - $price = $cart->getTotalPriceAsCurrency(); 183 - 184 - $params = array( 185 - 'TOKEN' => $token, 186 - 'PAYERID' => $result['PAYERID'], 187 - 188 - 'PAYMENTREQUEST_0_AMT' => $price->formatBareValue(), 189 - 'PAYMENTREQUEST_0_CURRENCYCODE' => $price->getCurrency(), 190 - 'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale', 191 - ); 192 - 193 - $result = $this 194 - ->newPaypalAPICall() 195 - ->setRawPayPalQuery('DoExpressCheckoutPayment', $params) 196 - ->resolve(); 197 - 198 - // TODO: Paypal can send requests back in "PaymentReview" status, 199 - // and does this for test transactions. We're supposed to hold 200 - // the transaction and poll the API every 6 hours. This is unreasonably 201 - // difficult for now and we can't reasonably just fail these charges. 202 - 203 - var_dump($result); 204 - 205 - die(); 206 - break; 207 - case 'cancel': 208 - var_dump($_REQUEST); 209 - break; 210 - } 211 - 212 - throw new Exception( 213 - pht('Unsupported action "%s".', $controller->getAction())); 214 - } 215 - 216 - private function newPaypalAPICall() { 217 - return id(new PhutilPayPalAPIFuture()) 218 - ->setHost('https://api-3t.sandbox.paypal.com/nvp') 219 - ->setAPIUsername($this->getPaypalAPIUsername()) 220 - ->setAPIPassword($this->getPaypalAPIPassword()) 221 - ->setAPISignature($this->getPaypalAPISignature()); 222 - } 223 - 224 - 225 - }
+96 -9
src/applications/phortune/provider/PhortuneStripePaymentProvider.php
··· 2 2 3 3 final class PhortuneStripePaymentProvider extends PhortunePaymentProvider { 4 4 5 + const STRIPE_PUBLISHABLE_KEY = 'stripe.publishable-key'; 6 + const STRIPE_SECRET_KEY = 'stripe.secret-key'; 7 + 5 8 public function isEnabled() { 6 9 return $this->getPublishableKey() && 7 10 $this->getSecretKey(); 8 11 } 9 12 10 - public function getProviderType() { 11 - return 'stripe'; 13 + public function getName() { 14 + return pht('Stripe'); 12 15 } 13 16 14 - public function getProviderDomain() { 15 - return 'stripe.com'; 17 + public function getConfigureName() { 18 + return pht('Add Stripe Payments Account'); 19 + } 20 + 21 + public function getConfigureDescription() { 22 + return pht( 23 + 'Allows you to accept credit or debit card payments with a '. 24 + 'stripe.com account.'); 16 25 } 17 26 18 27 public function getPaymentMethodDescription() { ··· 32 41 return pht('Credit/Debit Card'); 33 42 } 34 43 35 - public function canHandlePaymentMethod(PhortunePaymentMethod $method) { 36 - $type = $method->getMetadataValue('type'); 37 - return ($type === 'stripe.customer'); 44 + public function getAllConfigurableProperties() { 45 + return array( 46 + self::STRIPE_PUBLISHABLE_KEY, 47 + self::STRIPE_SECRET_KEY, 48 + ); 49 + } 50 + 51 + public function getAllConfigurableSecretProperties() { 52 + return array( 53 + self::STRIPE_SECRET_KEY, 54 + ); 55 + } 56 + 57 + public function processEditForm( 58 + AphrontRequest $request, 59 + array $values) { 60 + 61 + $errors = array(); 62 + $issues = array(); 63 + 64 + if (!strlen($values[self::STRIPE_SECRET_KEY])) { 65 + $errors[] = pht('Stripe Secret Key is required.'); 66 + $issues[self::STRIPE_SECRET_KEY] = pht('Required'); 67 + } 68 + 69 + if (!strlen($values[self::STRIPE_PUBLISHABLE_KEY])) { 70 + $errors[] = pht('Stripe Publishable Key is required.'); 71 + $issues[self::STRIPE_PUBLISHABLE_KEY] = pht('Required'); 72 + } 73 + 74 + return array($errors, $issues, $values); 75 + } 76 + 77 + public function extendEditForm( 78 + AphrontRequest $request, 79 + AphrontFormView $form, 80 + array $values, 81 + array $issues) { 82 + 83 + $form 84 + ->appendChild( 85 + id(new AphrontFormTextControl()) 86 + ->setName(self::STRIPE_SECRET_KEY) 87 + ->setValue($values[self::STRIPE_SECRET_KEY]) 88 + ->setError(idx($issues, self::STRIPE_SECRET_KEY, true)) 89 + ->setLabel(pht('Stripe Secret Key'))) 90 + ->appendChild( 91 + id(new AphrontFormTextControl()) 92 + ->setName(self::STRIPE_PUBLISHABLE_KEY) 93 + ->setValue($values[self::STRIPE_PUBLISHABLE_KEY]) 94 + ->setError(idx($issues, self::STRIPE_PUBLISHABLE_KEY, true)) 95 + ->setLabel(pht('Stripe Publishable Key'))); 96 + } 97 + 98 + public function getConfigureInstructions() { 99 + return pht( 100 + "To configure Stripe, register or log in to an existing account on ". 101 + "[[https://stripe.com | stripe.com]]. Once logged in:\n\n". 102 + " - Go to {nav icon=user, name=Your Account > Account Settings ". 103 + "> API Keys}\n". 104 + " - Copy the **Secret Key** and **Publishable Key** into the fields ". 105 + "above.\n\n". 106 + "You can either use the test keys to add this provider in test mode, ". 107 + "or the live keys to accept live payments."); 108 + } 109 + 110 + public function canRunConfigurationTest() { 111 + return true; 112 + } 113 + 114 + public function runConfigurationTest() { 115 + $root = dirname(phutil_get_library_root('phabricator')); 116 + require_once $root.'/externals/stripe-php/lib/Stripe.php'; 117 + 118 + $secret_key = $this->getSecretKey(); 119 + $account = Stripe_Account::retrieve($secret_key); 38 120 } 39 121 40 122 /** 41 123 * @phutil-external-symbol class Stripe_Charge 42 124 * @phutil-external-symbol class Stripe_CardError 125 + * @phutil-external-symbol class Stripe_Account 43 126 */ 44 127 protected function executeCharge( 45 128 PhortunePaymentMethod $method, ··· 76 159 } 77 160 78 161 private function getPublishableKey() { 79 - return PhabricatorEnv::getEnvConfig('phortune.stripe.publishable-key'); 162 + return $this 163 + ->getProviderConfig() 164 + ->getMetadataValue(self::STRIPE_PUBLISHABLE_KEY); 80 165 } 81 166 82 167 private function getSecretKey() { 83 - return PhabricatorEnv::getEnvConfig('phortune.stripe.secret-key'); 168 + return $this 169 + ->getProviderConfig() 170 + ->getMetadataValue(self::STRIPE_SECRET_KEY); 84 171 } 85 172 86 173
+47 -9
src/applications/phortune/provider/PhortuneTestPaymentProvider.php
··· 6 6 return PhabricatorEnv::getEnvConfig('phortune.test.enabled'); 7 7 } 8 8 9 - public function getProviderType() { 10 - return 'test'; 9 + public function getName() { 10 + return pht('Test Payments'); 11 11 } 12 12 13 - public function getProviderDomain() { 14 - return 'example.com'; 13 + public function getConfigureName() { 14 + return pht('Test Payments'); 15 + } 16 + 17 + public function getConfigureDescription() { 18 + return pht( 19 + 'Adds a test provider to allow you to test payments. This allows '. 20 + 'users to make purchases by clicking a button without actually paying '. 21 + 'any money.'); 22 + } 23 + 24 + public function getConfigureInstructions() { 25 + return pht('This providers does not require any special configuration.'); 26 + } 27 + 28 + public function canRunConfigurationTest() { 29 + return false; 15 30 } 16 31 17 32 public function getPaymentMethodDescription() { ··· 29 44 public function getDefaultPaymentMethodDisplayName( 30 45 PhortunePaymentMethod $method) { 31 46 return pht('Vast Wealth'); 32 - } 33 - 34 - public function canHandlePaymentMethod(PhortunePaymentMethod $method) { 35 - $type = $method->getMetadataValue('type'); 36 - return ($type === 'test.wealth' || $type == 'test.multiple'); 37 47 } 38 48 39 49 protected function executeCharge( ··· 41 51 PhortuneCharge $charge) { 42 52 return; 43 53 } 54 + 55 + public function getAllConfigurableProperties() { 56 + return array(); 57 + } 58 + 59 + public function getAllConfigurableSecretProperties() { 60 + return array(); 61 + } 62 + 63 + public function processEditForm( 64 + AphrontRequest $request, 65 + array $values) { 66 + 67 + $errors = array(); 68 + $issues = array(); 69 + $values = array(); 70 + 71 + return array($errors, $issues, $values); 72 + } 73 + 74 + public function extendEditForm( 75 + AphrontRequest $request, 76 + AphrontFormView $form, 77 + array $values, 78 + array $issues) { 79 + return; 80 + } 81 + 44 82 45 83 46 84 /* -( Adding Payment Methods )--------------------------------------------- */
+143 -14
src/applications/phortune/provider/PhortuneWePayPaymentProvider.php
··· 2 2 3 3 final class PhortuneWePayPaymentProvider extends PhortunePaymentProvider { 4 4 5 + const WEPAY_CLIENT_ID = 'wepay.client-id'; 6 + const WEPAY_CLIENT_SECRET = 'wepay.client-secret'; 7 + const WEPAY_ACCESS_TOKEN = 'wepay.access-token'; 8 + const WEPAY_ACCOUNT_ID = 'wepay.account-id'; 9 + 5 10 public function isEnabled() { 6 11 return $this->getWePayClientID() && 7 12 $this->getWePayClientSecret() && ··· 9 14 $this->getWePayAccountID(); 10 15 } 11 16 12 - public function getProviderType() { 13 - return 'wepay'; 17 + public function getName() { 18 + return pht('WePay'); 19 + } 20 + 21 + public function getConfigureName() { 22 + return pht('Add WePay Payments Account'); 23 + } 24 + 25 + public function getConfigureDescription() { 26 + return pht( 27 + 'Allows you to accept credit or debit card payments with a '. 28 + 'wepay.com account.'); 29 + } 30 + 31 + public function getConfigureInstructions() { 32 + return pht( 33 + "To configure WePay, register or log in to an existing account on ". 34 + "[[https://wepay.com | wepay.com]] (for live payments) or ". 35 + "[[https://stage.wepay.com | stage.wepay.com]] (for testing). ". 36 + "Once logged in:\n\n". 37 + " - Create an API application if you don't already have one.\n". 38 + " - Click the API application name to go to the detail page.\n". 39 + " - Copy **Client ID**, **Client Secret**, **Access Token** and ". 40 + " **AccountID** from that page to the fields above.\n\n". 41 + "You can either use `stage.wepay.com` to retrieve test credentials, ". 42 + "or `wepay.com` to retrieve live credentials for accepting live ". 43 + "payments."); 44 + } 45 + 46 + public function canRunConfigurationTest() { 47 + return true; 48 + } 49 + 50 + public function runConfigurationTest() { 51 + $root = dirname(phutil_get_library_root('phabricator')); 52 + require_once $root.'/externals/wepay/wepay.php'; 53 + 54 + WePay::useStaging( 55 + $this->getWePayClientID(), 56 + $this->getWePayClientSecret()); 57 + 58 + $wepay = new WePay($this->getWePayAccessToken()); 59 + $params = array( 60 + 'client_id' => $this->getWePayClientID(), 61 + 'client_secret' => $this->getWePayClientSecret(), 62 + ); 63 + 64 + $wepay->request('app', $params); 65 + } 66 + 67 + public function getAllConfigurableProperties() { 68 + return array( 69 + self::WEPAY_CLIENT_ID, 70 + self::WEPAY_CLIENT_SECRET, 71 + self::WEPAY_ACCESS_TOKEN, 72 + self::WEPAY_ACCOUNT_ID, 73 + ); 74 + } 75 + 76 + public function getAllConfigurableSecretProperties() { 77 + return array( 78 + self::WEPAY_CLIENT_SECRET, 79 + ); 80 + } 81 + 82 + public function processEditForm( 83 + AphrontRequest $request, 84 + array $values) { 85 + 86 + $errors = array(); 87 + $issues = array(); 88 + 89 + if (!strlen($values[self::WEPAY_CLIENT_ID])) { 90 + $errors[] = pht('WePay Client ID is required.'); 91 + $issues[self::WEPAY_CLIENT_ID] = pht('Required'); 92 + } 93 + 94 + if (!strlen($values[self::WEPAY_CLIENT_SECRET])) { 95 + $errors[] = pht('WePay Client Secret is required.'); 96 + $issues[self::WEPAY_CLIENT_SECRET] = pht('Required'); 97 + } 98 + 99 + if (!strlen($values[self::WEPAY_ACCESS_TOKEN])) { 100 + $errors[] = pht('WePay Access Token is required.'); 101 + $issues[self::WEPAY_ACCESS_TOKEN] = pht('Required'); 102 + } 103 + 104 + if (!strlen($values[self::WEPAY_ACCOUNT_ID])) { 105 + $errors[] = pht('WePay Account ID is required.'); 106 + $issues[self::WEPAY_ACCOUNT_ID] = pht('Required'); 107 + } 108 + 109 + return array($errors, $issues, $values); 14 110 } 15 111 16 - public function getProviderDomain() { 17 - return 'wepay.com'; 112 + public function extendEditForm( 113 + AphrontRequest $request, 114 + AphrontFormView $form, 115 + array $values, 116 + array $issues) { 117 + 118 + $form 119 + ->appendChild( 120 + id(new AphrontFormTextControl()) 121 + ->setName(self::WEPAY_CLIENT_ID) 122 + ->setValue($values[self::WEPAY_CLIENT_ID]) 123 + ->setError(idx($issues, self::WEPAY_CLIENT_ID, true)) 124 + ->setLabel(pht('WePay Client ID'))) 125 + ->appendChild( 126 + id(new AphrontFormTextControl()) 127 + ->setName(self::WEPAY_CLIENT_SECRET) 128 + ->setValue($values[self::WEPAY_CLIENT_SECRET]) 129 + ->setError(idx($issues, self::WEPAY_CLIENT_SECRET, true)) 130 + ->setLabel(pht('WePay Client Secret'))) 131 + ->appendChild( 132 + id(new AphrontFormTextControl()) 133 + ->setName(self::WEPAY_ACCESS_TOKEN) 134 + ->setValue($values[self::WEPAY_ACCESS_TOKEN]) 135 + ->setError(idx($issues, self::WEPAY_ACCESS_TOKEN, true)) 136 + ->setLabel(pht('WePay Access Token'))) 137 + ->appendChild( 138 + id(new AphrontFormTextControl()) 139 + ->setName(self::WEPAY_ACCOUNT_ID) 140 + ->setValue($values[self::WEPAY_ACCOUNT_ID]) 141 + ->setError(idx($issues, self::WEPAY_ACCOUNT_ID, true)) 142 + ->setLabel(pht('WePay Account ID'))); 143 + 18 144 } 19 145 20 146 public function getPaymentMethodDescription() { ··· 29 155 return 'WePay'; 30 156 } 31 157 32 - public function canHandlePaymentMethod(PhortunePaymentMethod $method) { 33 - $type = $method->getMetadataValue('type'); 34 - return ($type == 'wepay'); 35 - } 36 - 37 158 protected function executeCharge( 38 159 PhortunePaymentMethod $payment_method, 39 160 PhortuneCharge $charge) { ··· 41 162 } 42 163 43 164 private function getWePayClientID() { 44 - return PhabricatorEnv::getEnvConfig('phortune.wepay.client-id'); 165 + return $this 166 + ->getProviderConfig() 167 + ->getMetadataValue(self::WEPAY_CLIENT_ID); 45 168 } 46 169 47 170 private function getWePayClientSecret() { 48 - return PhabricatorEnv::getEnvConfig('phortune.wepay.client-secret'); 171 + return $this 172 + ->getProviderConfig() 173 + ->getMetadataValue(self::WEPAY_CLIENT_SECRET); 49 174 } 50 175 51 176 private function getWePayAccessToken() { 52 - return PhabricatorEnv::getEnvConfig('phortune.wepay.access-token'); 177 + return $this 178 + ->getProviderConfig() 179 + ->getMetadataValue(self::WEPAY_ACCESS_TOKEN); 53 180 } 54 181 55 182 private function getWePayAccountID() { 56 - return PhabricatorEnv::getEnvConfig('phortune.wepay.account-id'); 183 + return $this 184 + ->getProviderConfig() 185 + ->getMetadataValue(self::WEPAY_ACCOUNT_ID); 57 186 } 58 187 59 188 ··· 81 210 * @phutil-external-symbol class WePay 82 211 */ 83 212 public function processControllerRequest( 84 - PhortuneProviderController $controller, 213 + PhortuneProviderActionController $controller, 85 214 AphrontRequest $request) { 86 215 87 216 $viewer = $request->getUser();
-51
src/applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php
··· 1 - <?php 2 - 3 - final class PhortunePaymentProviderTestCase extends PhabricatorTestCase { 4 - 5 - public function getPhabricatorTestCaseConfiguration() { 6 - return array( 7 - self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true, 8 - ); 9 - } 10 - 11 - public function testNoPaymentProvider() { 12 - $env = PhabricatorEnv::beginScopedEnv(); 13 - $env->overrideEnvConfig('phortune.test.enabled', true); 14 - 15 - $method = id(new PhortunePaymentMethod()) 16 - ->setMetadataValue('type', 'hugs'); 17 - 18 - $caught = null; 19 - try { 20 - $provider = $method->buildPaymentProvider(); 21 - } catch (Exception $ex) { 22 - $caught = $ex; 23 - } 24 - 25 - $this->assertTrue( 26 - ($caught instanceof PhortuneNoPaymentProviderException), 27 - 'No provider should accept hugs; they are not a currency.'); 28 - } 29 - 30 - public function testMultiplePaymentProviders() { 31 - $env = PhabricatorEnv::beginScopedEnv(); 32 - $env->overrideEnvConfig('phortune.test.enabled', true); 33 - 34 - $method = id(new PhortunePaymentMethod()) 35 - ->setMetadataValue('type', 'test.multiple'); 36 - 37 - $caught = null; 38 - try { 39 - $provider = $method->buildPaymentProvider(); 40 - } catch (Exception $ex) { 41 - $caught = $ex; 42 - } 43 - 44 - $this->assertTrue( 45 - ($caught instanceof PhortuneMultiplePaymentProvidersException), 46 - 'Expect exception when more than one provider handles a payment method.'); 47 - } 48 - 49 - 50 - 51 - }
-40
src/applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php
··· 1 - <?php 2 - 3 - final class PhortuneTestExtraPaymentProvider extends PhortunePaymentProvider { 4 - 5 - public function isEnabled() { 6 - return false; 7 - } 8 - 9 - public function getProviderType() { 10 - return 'test2'; 11 - } 12 - 13 - public function getProviderDomain() { 14 - return 'example.com'; 15 - } 16 - 17 - public function getPaymentMethodDescription() { 18 - return pht('You Should Not Be Able to See This'); 19 - } 20 - 21 - public function getPaymentMethodIcon() { 22 - return celerity_get_resource_uri('/rsrc/image/phortune/test.png'); 23 - } 24 - 25 - public function getPaymentMethodProviderDescription() { 26 - return pht('Just for Unit Tests'); 27 - } 28 - 29 - public function canHandlePaymentMethod(PhortunePaymentMethod $method) { 30 - $type = $method->getMetadataValue('type'); 31 - return ($type === 'test.multiple'); 32 - } 33 - 34 - protected function executeCharge( 35 - PhortunePaymentMethod $payment_method, 36 - PhortuneCharge $charge) { 37 - return; 38 - } 39 - 40 - }
+95
src/applications/phortune/query/PhortunePaymentProviderConfigQuery.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentProviderConfigQuery 4 + extends PhabricatorCursorPagedPolicyAwareQuery { 5 + 6 + private $ids; 7 + private $phids; 8 + private $merchantPHIDs; 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 withMerchantPHIDs(array $phids) { 21 + $this->merchantPHIDs = $phids; 22 + return $this; 23 + } 24 + 25 + protected function loadPage() { 26 + $table = new PhortunePaymentProviderConfig(); 27 + $conn = $table->establishConnection('r'); 28 + 29 + $rows = queryfx_all( 30 + $conn, 31 + 'SELECT * FROM %T %Q %Q %Q', 32 + $table->getTableName(), 33 + $this->buildWhereClause($conn), 34 + $this->buildOrderClause($conn), 35 + $this->buildLimitClause($conn)); 36 + 37 + return $table->loadAllFromArray($rows); 38 + } 39 + 40 + protected function willFilterPage(array $provider_configs) { 41 + $merchant_phids = mpull($provider_configs, 'getMerchantPHID'); 42 + $merchants = id(new PhortuneMerchantQuery()) 43 + ->setViewer($this->getViewer()) 44 + ->setParentQuery($this) 45 + ->withPHIDs($merchant_phids) 46 + ->execute(); 47 + $merchants = mpull($merchants, null, 'getPHID'); 48 + 49 + foreach ($provider_configs as $key => $config) { 50 + $merchant = idx($merchants, $config->getMerchantPHID()); 51 + if (!$merchant) { 52 + $this->didRejectResult($config); 53 + unset($provider_configs[$key]); 54 + continue; 55 + } 56 + $config->attachMerchant($merchant); 57 + } 58 + 59 + return $provider_configs; 60 + } 61 + 62 + private function buildWhereClause(AphrontDatabaseConnection $conn) { 63 + $where = array(); 64 + 65 + if ($this->ids !== null) { 66 + $where[] = qsprintf( 67 + $conn, 68 + 'id IN (%Ld)', 69 + $this->ids); 70 + } 71 + 72 + if ($this->phids !== null) { 73 + $where[] = qsprintf( 74 + $conn, 75 + 'phid IN (%Ls)', 76 + $this->phids); 77 + } 78 + 79 + if ($this->merchantPHIDs !== null) { 80 + $where[] = qsprintf( 81 + $conn, 82 + 'merchantPHID IN (%Ls)', 83 + $this->merchantPHIDs); 84 + } 85 + 86 + $where[] = $this->buildPagingClause($conn); 87 + 88 + return $this->formatWhereClause($where); 89 + } 90 + 91 + public function getQueryApplicationClass() { 92 + return 'PhabricatorPhortuneApplication'; 93 + } 94 + 95 + }
+10
src/applications/phortune/query/PhortunePaymentProviderConfigTransactionQuery.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentProviderConfigTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new PhortunePaymentProviderConfigTransaction(); 8 + } 9 + 10 + }
+2 -23
src/applications/phortune/storage/PhortunePaymentMethod.php
··· 14 14 protected $status; 15 15 protected $accountPHID; 16 16 protected $authorPHID; 17 + protected $providerPHID; 17 18 protected $expires; 18 19 protected $metadata = array(); 19 20 protected $brand; 20 21 protected $lastFourDigits; 21 - protected $providerType; 22 - protected $providerDomain; 23 22 24 23 private $account = self::ATTACHABLE; 25 24 ··· 34 33 'status' => 'text64', 35 34 'brand' => 'text64', 36 35 'expires' => 'text16', 37 - 'providerType' => 'text16', 38 - 'providerDomain' => 'text64', 39 36 'lastFourDigits' => 'text16', 40 37 ), 41 38 self::CONFIG_KEY_SCHEMA => array( ··· 75 72 } 76 73 77 74 public function buildPaymentProvider() { 78 - $providers = PhortunePaymentProvider::getAllProviders(); 79 - 80 - $accept = array(); 81 - foreach ($providers as $provider) { 82 - if ($provider->canHandlePaymentMethod($this)) { 83 - $accept[] = $provider; 84 - } 85 - } 86 - 87 - if (!$accept) { 88 - throw new PhortuneNoPaymentProviderException($this); 89 - } 90 - 91 - if (count($accept) > 1) { 92 - throw new PhortuneMultiplePaymentProvidersException($this, $accept); 93 - } 94 - 95 - return head($accept); 75 + throw new Exception(pht('TODO: Reimplement this junk.')); 96 76 } 97 - 98 77 99 78 public function getDisplayName() { 100 79 if (strlen($this->name)) {
+96
src/applications/phortune/storage/PhortunePaymentProviderConfig.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentProviderConfig extends PhortuneDAO 4 + implements PhabricatorPolicyInterface { 5 + 6 + protected $merchantPHID; 7 + protected $providerClassKey; 8 + protected $providerClass; 9 + protected $metadata = array(); 10 + 11 + private $merchant = self::ATTACHABLE; 12 + 13 + public static function initializeNewProvider( 14 + PhortuneMerchant $merchant) { 15 + return id(new PhortunePaymentProviderConfig()) 16 + ->setMerchantPHID($merchant->getPHID()); 17 + } 18 + 19 + public function getConfiguration() { 20 + return array( 21 + self::CONFIG_AUX_PHID => true, 22 + self::CONFIG_SERIALIZATION => array( 23 + 'metadata' => self::SERIALIZATION_JSON, 24 + ), 25 + self::CONFIG_COLUMN_SCHEMA => array( 26 + 'providerClassKey' => 'bytes12', 27 + 'providerClass' => 'text128', 28 + ), 29 + self::CONFIG_KEY_SCHEMA => array( 30 + 'key_merchant' => array( 31 + 'columns' => array('merchantPHID', 'providerClassKey'), 32 + 'unique' => true, 33 + ), 34 + ), 35 + ) + parent::getConfiguration(); 36 + } 37 + 38 + public function save() { 39 + $this->providerClassKey = PhabricatorHash::digestForIndex( 40 + $this->providerClass); 41 + 42 + return parent::save(); 43 + } 44 + 45 + public function generatePHID() { 46 + return PhabricatorPHID::generateNewPHID( 47 + PhortunePaymentProviderPHIDType::TYPECONST); 48 + } 49 + 50 + public function attachMerchant(PhortuneMerchant $merchant) { 51 + $this->merchant = $merchant; 52 + return $this; 53 + } 54 + 55 + public function getMerchant() { 56 + return $this->assertAttached($this->merchant); 57 + } 58 + 59 + public function getMetadataValue($key, $default = null) { 60 + return idx($this->metadata, $key, $default); 61 + } 62 + 63 + public function setMetadataValue($key, $value) { 64 + $this->metadata[$key] = $value; 65 + return $this; 66 + } 67 + 68 + public function buildProvider() { 69 + return newv($this->getProviderClass(), array()) 70 + ->setProviderConfig($this); 71 + } 72 + 73 + 74 + /* -( PhabricatorPolicyInterface )----------------------------------------- */ 75 + 76 + 77 + public function getCapabilities() { 78 + return array( 79 + PhabricatorPolicyCapability::CAN_VIEW, 80 + PhabricatorPolicyCapability::CAN_EDIT, 81 + ); 82 + } 83 + 84 + public function getPolicy($capability) { 85 + return $this->getMerchant()->getPolicy($capability); 86 + } 87 + 88 + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 89 + return $this->getMerchant()->hasAutomaticCapability($capability, $viewer); 90 + } 91 + 92 + public function describeAutomaticCapability($capability) { 93 + return pht('Providers have the policies of their merchant.'); 94 + } 95 + 96 + }
+46
src/applications/phortune/storage/PhortunePaymentProviderConfigTransaction.php
··· 1 + <?php 2 + 3 + final class PhortunePaymentProviderConfigTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + const TYPE_CREATE = 'paymentprovider:create'; 7 + const TYPE_PROPERTY = 'paymentprovider:property'; 8 + 9 + const PROPERTY_KEY = 'provider-property'; 10 + 11 + public function getApplicationName() { 12 + return 'phortune'; 13 + } 14 + 15 + public function getApplicationTransactionType() { 16 + return PhortunePaymentProviderPHIDType::TYPECONST; 17 + } 18 + 19 + public function getApplicationTransactionCommentObject() { 20 + return null; 21 + } 22 + 23 + public function getTitle() { 24 + $author_phid = $this->getAuthorPHID(); 25 + 26 + $old = $this->getOldValue(); 27 + $new = $this->getNewValue(); 28 + 29 + switch ($this->getTransactionType()) { 30 + case self::TYPE_CREATE: 31 + return pht( 32 + '%s created this payment provider.', 33 + $this->renderHandleLink($author_phid)); 34 + case self::TYPE_PROPERTY: 35 + // TODO: Allow providers to improve this. 36 + 37 + return pht( 38 + '%s edited a property of this payment provider.', 39 + $this->renderHandleLink($author_phid)); 40 + break; 41 + } 42 + 43 + return parent::getTitle(); 44 + } 45 + 46 + }