From f790a2aeec5bbd835753162f9dd0bc9a5be62f3f Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 25 Apr 2013 09:45:43 -0700 Subject: [PATCH] Add basic payment providers to Phortune Summary: Abstract out the Stripiness of payment providers so we can add a Test provider (and maybe MtGox / Balanced / Paypal / etc). Ref T2787. Test Plan: Ran unit tests. Reviewers: btrahan, chad Reviewed By: btrahan CC: aran Maniphest Tasks: T2787 Differential Revision: https://secure.phabricator.com/D5752 --- src/__phutil_library_map__.php | 13 +++++ .../PhortunePaymentMethodEditController.php | 4 +- ...rtuneMultiplePaymentProvidersException.php | 23 +++++++++ .../PhortuneNoPaymentProviderException.php | 14 ++++++ .../provider/PhortunePaymentProvider.php | 18 +++++++ .../PhortuneStripePaymentProvider.php | 39 +++++++++++++++ .../provider/PhortuneTestPaymentProvider.php | 16 +++++++ .../PhortunePaymentProviderTestCase.php | 47 +++++++++++++++++++ .../PhortuneTestExtraPaymentProvider.php | 16 +++++++ .../phortune/storage/PhortuneCharge.php | 3 +- .../storage/PhortunePaymentMethod.php | 36 +++++++++++++- 11 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 src/applications/phortune/exception/PhortuneMultiplePaymentProvidersException.php create mode 100644 src/applications/phortune/exception/PhortuneNoPaymentProviderException.php create mode 100644 src/applications/phortune/provider/PhortunePaymentProvider.php create mode 100644 src/applications/phortune/provider/PhortuneStripePaymentProvider.php create mode 100644 src/applications/phortune/provider/PhortuneTestPaymentProvider.php create mode 100644 src/applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php create mode 100644 src/applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 0f2538fe67..f33b88e0f5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1586,11 +1586,15 @@ phutil_register_library_map(array( 'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php', 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', 'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php', + 'PhortuneMultiplePaymentProvidersException' => 'applications/phortune/exception/PhortuneMultiplePaymentProvidersException.php', + 'PhortuneNoPaymentProviderException' => 'applications/phortune/exception/PhortuneNoPaymentProviderException.php', 'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php', 'PhortunePaymentMethodEditController' => 'applications/phortune/controller/PhortunePaymentMethodEditController.php', 'PhortunePaymentMethodListController' => 'applications/phortune/controller/PhortunePaymentMethodListController.php', 'PhortunePaymentMethodQuery' => 'applications/phortune/query/PhortunePaymentMethodQuery.php', 'PhortunePaymentMethodViewController' => 'applications/phortune/controller/PhortunePaymentMethodViewController.php', + 'PhortunePaymentProvider' => 'applications/phortune/provider/PhortunePaymentProvider.php', + 'PhortunePaymentProviderTestCase' => 'applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php', 'PhortuneProduct' => 'applications/phortune/storage/PhortuneProduct.php', 'PhortuneProductEditController' => 'applications/phortune/controller/PhortuneProductEditController.php', 'PhortuneProductEditor' => 'applications/phortune/editor/PhortuneProductEditor.php', @@ -1601,6 +1605,9 @@ phutil_register_library_map(array( 'PhortuneProductViewController' => 'applications/phortune/controller/PhortuneProductViewController.php', 'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php', 'PhortuneStripePaymentFormView' => 'applications/phortune/view/PhortuneStripePaymentFormView.php', + 'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php', + 'PhortuneTestExtraPaymentProvider' => 'applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php', + 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', 'PhortuneUtil' => 'applications/phortune/util/PhortuneUtil.php', 'PhrequentController' => 'applications/phrequent/controller/PhrequentController.php', 'PhrequentDAO' => 'applications/phrequent/storage/PhrequentDAO.php', @@ -3300,6 +3307,8 @@ phutil_register_library_map(array( 'PhortuneDAO' => 'PhabricatorLiskDAO', 'PhortuneLandingController' => 'PhortuneController', 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', + 'PhortuneMultiplePaymentProvidersException' => 'Exception', + 'PhortuneNoPaymentProviderException' => 'Exception', 'PhortunePaymentMethod' => array( 0 => 'PhortuneDAO', @@ -3309,6 +3318,7 @@ phutil_register_library_map(array( 'PhortunePaymentMethodListController' => 'PhabricatorController', 'PhortunePaymentMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhortunePaymentMethodViewController' => 'PhabricatorController', + 'PhortunePaymentProviderTestCase' => 'PhabricatorTestCase', 'PhortuneProduct' => array( 0 => 'PhortuneDAO', @@ -3323,6 +3333,9 @@ phutil_register_library_map(array( 'PhortuneProductViewController' => 'PhortuneController', 'PhortunePurchase' => 'PhortuneDAO', 'PhortuneStripePaymentFormView' => 'AphrontView', + 'PhortuneStripePaymentProvider' => 'PhortunePaymentProvider', + 'PhortuneTestExtraPaymentProvider' => 'PhortunePaymentProvider', + 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', 'PhrequentController' => 'PhabricatorController', 'PhrequentDAO' => 'PhabricatorLiskDAO', 'PhrequentListController' => 'PhrequentController', diff --git a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php b/src/applications/phortune/controller/PhortunePaymentMethodEditController.php index 7d618d73b1..fdc8f13151 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodEditController.php @@ -96,8 +96,8 @@ final class PhortunePaymentMethodEditController ->setMetadata( array( 'type' => 'stripe.customer', - 'stripeCustomerID' => $customer->id, - 'stripeTokenID' => $stripe_token, + 'stripe.customerID' => $customer->id, + 'stripe.tokenID' => $stripe_token, )) ->save(); diff --git a/src/applications/phortune/exception/PhortuneMultiplePaymentProvidersException.php b/src/applications/phortune/exception/PhortuneMultiplePaymentProvidersException.php new file mode 100644 index 0000000000..82d74042c5 --- /dev/null +++ b/src/applications/phortune/exception/PhortuneMultiplePaymentProvidersException.php @@ -0,0 +1,23 @@ +getMetadataValue('type'); + + $provider_names = array(); + foreach ($providers as $provider) { + $provider_names[] = get_class($provider); + } + + return parent::__construct( + "More than one payment provider can handle charging payments for this ". + "payment method. This is ambiguous and likely indicates that a payment ". + "provider is not properly implemented. You may be able to use a ". + "different payment method to complete this transaction. The payment ". + "method type is '{$type}'. The providers claiming to handle it are: ". + implode(', ', $provider_names).'.'); + } + +} diff --git a/src/applications/phortune/exception/PhortuneNoPaymentProviderException.php b/src/applications/phortune/exception/PhortuneNoPaymentProviderException.php new file mode 100644 index 0000000000..698c799729 --- /dev/null +++ b/src/applications/phortune/exception/PhortuneNoPaymentProviderException.php @@ -0,0 +1,14 @@ +getMetadataValue('type'); + + return parent::__construct( + "No available payment provider can handle charging payments for this ". + "payment method. You may be able to use a different payment method to ". + "complete this transaction. The payment method type is '{$type}'."); + } + +} diff --git a/src/applications/phortune/provider/PhortunePaymentProvider.php b/src/applications/phortune/provider/PhortunePaymentProvider.php new file mode 100644 index 0000000000..4e26d089e4 --- /dev/null +++ b/src/applications/phortune/provider/PhortunePaymentProvider.php @@ -0,0 +1,18 @@ +getMetadataValue('type'); + return ($type === 'stripe.customer'); + } + + /** + * @phutil-external-symbol class Stripe_Charge + */ + protected function executeCharge( + PhortunePaymentMethod $method, + PhortuneCharge $charge) { + + $secret_key = $this->getSecretKey(); + $params = array( + 'amount' => $charge->getAmountInCents(), + 'currency' => 'usd', + 'customer' => $method->getMetadataValue('stripe.customerID'), + 'description' => $charge->getPHID(), + 'capture' => true, + ); + + $stripe_charge = Stripe_Charge::create($params, $secret_key); + $id = $stripe_charge->id; + if (!$id) { + throw new Exception("Stripe charge call did not return an ID!"); + } + + $charge->setMetadataValue('stripe.chargeID', $id); + } + + private function getSecretKey() { + return PhabricatorEnv::getEnvConfig('stripe.secret-key'); + } + +} diff --git a/src/applications/phortune/provider/PhortuneTestPaymentProvider.php b/src/applications/phortune/provider/PhortuneTestPaymentProvider.php new file mode 100644 index 0000000000..059c8c16c3 --- /dev/null +++ b/src/applications/phortune/provider/PhortuneTestPaymentProvider.php @@ -0,0 +1,16 @@ +getMetadataValue('type'); + return ($type === 'test.cash' || $type === 'test.multiple'); + } + + protected function executeCharge( + PhortunePaymentMethod $payment_method, + PhortuneCharge $charge) { + return; + } + +} diff --git a/src/applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php b/src/applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php new file mode 100644 index 0000000000..0616fcb66a --- /dev/null +++ b/src/applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php @@ -0,0 +1,47 @@ + true, + ); + } + + public function testNoPaymentProvider() { + $method = id(new PhortunePaymentMethod()) + ->setMetadataValue('type', 'hugs'); + + $caught = null; + try { + $provider = $method->buildPaymentProvider(); + } catch (Exception $ex) { + $caught = $ex; + } + + $this->assertEqual( + true, + ($caught instanceof PhortuneNoPaymentProviderException), + 'No provider should accept hugs; they are not a currency.'); + } + + public function testMultiplePaymentProviders() { + $method = id(new PhortunePaymentMethod()) + ->setMetadataValue('type', 'test.multiple'); + + $caught = null; + try { + $provider = $method->buildPaymentProvider(); + } catch (Exception $ex) { + $caught = $ex; + } + + $this->assertEqual( + true, + ($caught instanceof PhortuneMultiplePaymentProvidersException), + 'Expect exception when more than one provider handles a payment method.'); + } + + + +} diff --git a/src/applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php b/src/applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php new file mode 100644 index 0000000000..3453ea7552 --- /dev/null +++ b/src/applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php @@ -0,0 +1,16 @@ +getMetadataValue('type'); + return ($type === 'test.multiple'); + } + + protected function executeCharge( + PhortunePaymentMethod $payment_method, + PhortuneCharge $charge) { + return; + } + +} diff --git a/src/applications/phortune/storage/PhortuneCharge.php b/src/applications/phortune/storage/PhortuneCharge.php index a53dbec546..2c7487f53e 100644 --- a/src/applications/phortune/storage/PhortuneCharge.php +++ b/src/applications/phortune/storage/PhortuneCharge.php @@ -11,6 +11,7 @@ final class PhortuneCharge extends PhortuneDAO { const STATUS_PENDING = 'charge:pending'; const STATUS_AUTHORIZED = 'charge:authorized'; + const STATUS_CHARGING = 'charge:charging'; const STATUS_CHARGED = 'charge:charged'; const STATUS_FAILED = 'charge:failed'; @@ -19,7 +20,7 @@ final class PhortuneCharge extends PhortuneDAO { protected $paymentMethodPHID; protected $amountInCents; protected $status; - protected $metadata; + protected $metadata = array(); public function getConfiguration() { return array( diff --git a/src/applications/phortune/storage/PhortunePaymentMethod.php b/src/applications/phortune/storage/PhortunePaymentMethod.php index 5a38c79e9e..5293be4ff7 100644 --- a/src/applications/phortune/storage/PhortunePaymentMethod.php +++ b/src/applications/phortune/storage/PhortunePaymentMethod.php @@ -16,7 +16,7 @@ final class PhortunePaymentMethod extends PhortuneDAO protected $accountPHID; protected $authorPHID; protected $expiresEpoch; - protected $metadata; + protected $metadata = array(); private $account; @@ -50,6 +50,40 @@ final class PhortunePaymentMethod extends PhortuneDAO return pht('Expires %s', date('m/y'), $this->getExpiresEpoch()); } + public function getMetadataValue($key, $default = null) { + return idx($this->getMetadata(), $key, $default); + } + + public function setMetadataValue($key, $value) { + $this->metadata[$key] = $value; + return $this; + } + + public function buildPaymentProvider() { + $providers = id(new PhutilSymbolLoader()) + ->setAncestorClass('PhortunePaymentProvider') + ->setConcreteOnly(true) + ->selectAndLoadSymbols(); + + $accept = array(); + foreach ($providers as $provider) { + $obj = newv($provider['name'], array()); + if ($obj->canHandlePaymentMethod($this)) { + $accept[] = $obj; + } + } + + if (!$accept) { + throw new PhortuneNoPaymentProviderException($this); + } + + if (count($accept) > 1) { + throw new PhortuneMultiplePaymentProvidersException($this, $accept); + } + + return head($accept); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */