diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 6e03e6cbda..7dcf758313 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -2258,7 +2258,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-stripe-payment-form' => array( - 'uri' => '/res/30bcbbb1/rsrc/js/application/phortune/behavior-stripe-payment-form.js', + 'uri' => '/res/e4149d37/rsrc/js/application/phortune/behavior-stripe-payment-form.js', 'type' => 'js', 'requires' => array( @@ -2266,7 +2266,6 @@ celerity_register_resource_map(array( 1 => 'javelin-dom', 2 => 'javelin-json', 3 => 'javelin-workflow', - 4 => 'stripe-core', ), 'disk' => '/rsrc/js/application/phortune/behavior-stripe-payment-form.js', ), diff --git a/src/aphront/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php b/src/aphront/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php index fff59aab6d..3855dd4a89 100644 --- a/src/aphront/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php +++ b/src/aphront/console/plugin/errorlog/DarkConsoleErrorLogPluginAPI.php @@ -9,6 +9,15 @@ final class DarkConsoleErrorLogPluginAPI { private static $discardMode = false; + public static function registerErrorHandler() { + // NOTE: This forces PhutilReadableSerializer to load, so that we are + // able to handle errors which fire from inside autoloaders (PHP will not + // reenter autoloaders). + PhutilReadableSerializer::printableValue(null); + PhutilErrorHandler::setErrorListener( + array('DarkConsoleErrorLogPluginAPI', 'handleErrors')); + } + public static function enableDiscardMode() { self::$discardMode = true; } diff --git a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php b/src/applications/phortune/controller/PhortunePaymentMethodEditController.php index fdc8f13151..4623ea67d1 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodEditController.php @@ -9,28 +9,10 @@ final class PhortunePaymentMethodEditController $this->accountID = $data['accountID']; } - /** - * @phutil-external-symbol class Stripe_Token - * @phutil-external-symbol class Stripe_Customer - */ public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); - $stripe_publishable_key = PhabricatorEnv::getEnvConfig( - 'stripe.publishable-key'); - if (!$stripe_publishable_key) { - throw new Exception( - "Stripe publishable API key (`stripe.publishable-key`) is ". - "not configured."); - } - - $stripe_secret_key = PhabricatorEnv::getEnvConfig('stripe.secret-key'); - if (!$stripe_secret_key) { - throw new Exception( - "Stripe secret API kye (`stripe.secret-key`) is not configured."); - } - $account = id(new PhortuneAccountQuery()) ->setViewer($user) ->withIDs(array($this->accountID)) @@ -39,171 +21,113 @@ final class PhortunePaymentMethodEditController return new Aphront404Response(); } + $cancel_uri = $this->getApplicationURI($account->getID().'/'); $account_uri = $this->getApplicationURI($account->getID().'/'); - $e_card_number = true; - $e_card_cvc = true; - $e_card_exp = true; + $providers = PhortunePaymentProvider::getProvidersForAddPaymentMethod(); + if (!$providers) { + throw new Exception( + "There are no payment providers enabled that can add payment ". + "methods."); + } - $errors = array(); - if ($request->isFormPost()) { - $card_errors = $request->getStr('cardErrors'); - $stripe_token = $request->getStr('stripeToken'); - if ($card_errors) { - $raw_errors = json_decode($card_errors); - list($e_card_number, - $e_card_cvc, - $e_card_exp, - $messages) = $this->parseRawErrors($raw_errors); - $errors = array_merge($errors, $messages); - } else if (!$stripe_token) { - $errors[] = pht('There was an unknown error processing your card.'); + $provider_key = $request->getStr('providerKey'); + if (empty($providers[$provider_key])) { + $choices = array(); + foreach ($providers as $provider) { + $choices[] = $this->renderSelectProvider($provider); } + return $this->buildResponse($choices, $account_uri); + } - if (!$errors) { - $root = dirname(phutil_get_library_root('phabricator')); - require_once $root.'/externals/stripe-php/lib/Stripe.php'; - - try { - // First, make sure the token is valid. - $info = id(new Stripe_Token()) - ->retrieve($stripe_token, $stripe_secret_key); - - // Then, we need to create a Customer in order to be able to charge - // the card more than once. We create one Customer for each card; - // they do not map to PhortuneAccounts because we allow an account to - // have more than one active card. - $customer = Stripe_Customer::create( - array( - 'card' => $stripe_token, - 'description' => $account->getPHID().':'.$user->getUserName(), - ), $stripe_secret_key); - - $card = $info->card; - } catch (Exception $ex) { - phlog($ex); - $errors[] = pht( - 'There was an error communicating with the payments backend.'); - } + $provider = $providers[$provider_key]; - if (!$errors) { - $payment_method = id(new PhortunePaymentMethod()) - ->setAccountPHID($account->getPHID()) - ->setAuthorPHID($user->getPHID()) - ->setName($card->type.' / '.$card->last4) - ->setStatus(PhortunePaymentMethod::STATUS_ACTIVE) - ->setExpiresEpoch(strtotime($card->exp_year.'-'.$card->exp_month)) - ->setMetadata( - array( - 'type' => 'stripe.customer', - 'stripe.customerID' => $customer->id, - 'stripe.tokenID' => $stripe_token, - )) - ->save(); + $errors = array(); + if ($request->isFormPost() && $request->getBool('isProviderForm')) { + $method = id(new PhortunePaymentMethod()) + ->setAccountPHID($account->getPHID()) + ->setAuthorPHID($user->getPHID()) + ->setStatus(PhortunePaymentMethod::STATUS_ACTIVE) + ->setMetadataValue('providerKey', $provider->getProviderKey()); - $save_uri = new PhutilURI($account_uri); - $save_uri->setFragment('payment'); + $errors = $provider->createPaymentMethodFromRequest($request, $method); - return id(new AphrontRedirectResponse())->setURI($save_uri); - } + if (!$errors) { + $method->save(); + + $save_uri = new PhutilURI($account_uri); + $save_uri->setFragment('payment'); + return id(new AphrontRedirectResponse())->setURI($save_uri); + } else { + $dialog = id(new AphrontDialogView()) + ->setUser($user) + ->setTitle(pht('Error Adding Payment Method')) + ->appendChild(id(new AphrontErrorView())->setErrors($errors)) + ->addCancelButton($request->getRequestURI()); + + return id(new AphrontDialogResponse())->setDialog($dialog); } + } - $dialog = id(new AphrontDialogView()) - ->setUser($user) - ->setTitle(pht('Error Adding Card')) - ->appendChild(id(new AphrontErrorView())->setErrors($errors)) - ->addCancelButton($request->getRequestURI()); + $form = $provider->renderCreatePaymentMethodForm($request, $errors); - return id(new AphrontDialogResponse())->setDialog($dialog); - } + $form + ->setUser($user) + ->setAction($request->getRequestURI()) + ->setWorkflow(true) + ->addHiddenInput('providerKey', $provider_key) + ->addHiddenInput('isProviderForm', true) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Add Payment Method')) + ->addCancelButton($account_uri)); if ($errors) { $errors = id(new AphrontErrorView()) ->setErrors($errors); } - $header = id(new PhabricatorHeaderView()) - ->setHeader(pht('Add New Payment Method')); + return $this->buildResponse( + array($errors, $form), + $account_uri); + } - $form_id = celerity_generate_unique_node_id(); - require_celerity_resource('stripe-payment-form-css'); - require_celerity_resource('aphront-tooltip-css'); - Javelin::initBehavior('phabricator-tooltips'); + private function renderSelectProvider( + PhortunePaymentProvider $provider) { - $form = id(new AphrontFormView()) - ->setID($form_id) - ->setUser($user) - ->setWorkflow(true) - ->setAction($request->getRequestURI()) - ->appendChild( - id(new AphrontFormMarkupControl()) - ->setLabel('') - ->setValue( - javelin_tag( - 'div', - array( - 'class' => 'credit-card-logos', - 'sigil' => 'has-tooltip', - 'meta' => array( - 'tip' => 'We support Visa, Mastercard, American Express, '. - 'Discover, JCB, and Diners Club.', - 'size' => 440, - ) - )))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel('Card Number') - ->setDisableAutocomplete(true) - ->setSigil('number-input') - ->setError($e_card_number)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel('CVC') - ->setDisableAutocomplete(true) - ->setSigil('cvc-input') - ->setError($e_card_cvc)) - ->appendChild( - id(new PhortuneMonthYearExpiryControl()) - ->setLabel('Expiration') - ->setUser($user) - ->setError($e_card_exp)) - ->appendChild( - javelin_tag( - 'input', - array( - 'hidden' => true, - 'name' => 'stripeToken', - 'sigil' => 'stripe-token-input', - ))) - ->appendChild( - javelin_tag( - 'input', - array( - 'hidden' => true, - 'name' => 'cardErrors', - 'sigil' => 'card-errors-input' - ))) - ->appendChild( - phutil_tag( - 'input', - array( - 'hidden' => true, - 'name' => 'stripeKey', - 'value' => $stripe_publishable_key, - ))) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue('Add Payment Method') - ->addCancelButton($account_uri)); + $request = $this->getRequest(); + $user = $request->getUser(); - Javelin::initBehavior( - 'stripe-payment-form', + $description = $provider->getPaymentMethodDescription(); + $icon = $provider->getPaymentMethodIcon(); + $details = $provider->getPaymentMethodProviderDescription(); + + $button = phutil_tag( + 'button', + array( + 'class' => 'grey', + ), array( - 'stripePublishKey' => $stripe_publishable_key, - 'root' => $form_id, + $description, + phutil_tag('br'), + $icon, + $details, )); + $form = id(new AphrontFormView()) + ->setUser($user) + ->addHiddenInput('providerKey', $provider->getProviderKey()) + ->appendChild($button); + + return $form; + } + + private function buildResponse($content, $account_uri) { + $request = $this->getRequest(); + $title = pht('Add Payment Method'); + $header = id(new PhabricatorHeaderView()) + ->setHeader($title); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addCrumb( @@ -215,84 +139,17 @@ final class PhortunePaymentMethodEditController ->setName(pht('Payment Methods')) ->setHref($request->getRequestURI())); - return - $this->buildStandardPageResponse( - array( - $crumbs, - $header, - $errors, - $form, - ), - array( - 'title' => $title, - 'device' => true, - 'dust' => true, - )); - } - - /** - * Stripe JS and calls to Stripe handle all errors with processing this - * form. This function takes the raw errors - in the form of an array - * where each elementt is $type => $message - and figures out what if - * any fields were invalid and pulls the messages into a flat object. - * - * See https://stripe.com/docs/api#errors for more information on possible - * errors. - */ - private function parseRawErrors($errors) { - $card_number_error = null; - $card_cvc_error = null; - $card_expiration_error = null; - $messages = array(); - foreach ($errors as $index => $error) { - $type = key($error); - $msg = reset($error); - $messages[] = $msg; - switch ($type) { - case 'number': - case 'invalid_number': - case 'incorrect_number': - $card_number_error = pht('Invalid'); - break; - case 'cvc': - case 'invalid_cvc': - case 'incorrect_cvc': - $card_cvc_error = pht('Invalid'); - break; - case 'expiry': - case 'invalid_expiry_month': - case 'invalid_expiry_year': - $card_expiration_error = pht('Invalid'); - break; - case 'card_declined': - case 'expired_card': - case 'duplicate_transaction': - case 'processing_error': - // these errors don't map well to field(s) being bad - break; - case 'invalid_amount': - case 'missing': - default: - // these errors only happen if we (not the user) messed up so log it - $error = sprintf( - 'error_type: %s error_message: %s', - $type, - $msg); - $this->logStripeError($error); - break; - } - } - - return array( - $card_number_error, - $card_cvc_error, - $card_expiration_error, - $messages - ); - } - - private function logStripeError($message) { - phlog('STRIPE-ERROR '.$message); + return $this->buildApplicationPage( + array( + $crumbs, + $header, + $content, + ), + array( + 'title' => $title, + 'device' => true, + 'dust' => true, + )); } } diff --git a/src/applications/phortune/exception/PhortuneNotImplementedException.php b/src/applications/phortune/exception/PhortuneNotImplementedException.php new file mode 100644 index 0000000000..89a2258b18 --- /dev/null +++ b/src/applications/phortune/exception/PhortuneNotImplementedException.php @@ -0,0 +1,11 @@ +setAncestorClass('PhortunePaymentProvider') + ->loadObjects(); + + return mpull($objects, null, 'getProviderKey'); + } + + public static function getEnabledProviders() { + $providers = self::getAllProviders(); + foreach ($providers as $key => $provider) { + if (!$provider->isEnabled()) { + unset($providers[$key]); + } + } + return $providers; + } + + public static function getProvidersForAddPaymentMethod() { + $providers = self::getEnabledProviders(); + foreach ($providers as $key => $provider) { + if (!$provider->canCreatePaymentMethods()) { + unset($providers[$key]); + } + } + return $providers; + } + + abstract public function isEnabled(); + + final public function getProviderKey() { + return $this->getProviderType().'@'.$this->getProviderDomain(); + } + + + /** + * Return a short string which uniquely identifies this provider's protocol + * type, like "stripe", "paypal", or "balanced". + */ + abstract public function getProviderType(); + + + /** + * Return a short string which uniquely identifies the domain for this + * provider, like "stripe.com" or "google.com". + * + * This is distinct from the provider type so that protocols are not bound + * to a single domain. This is probably not relevant for payments, but this + * assumption burned us pretty hard with authentication and it's easy enough + * to avoid. + */ + abstract public function getProviderDomain(); + + abstract public function getPaymentMethodDescription(); + abstract public function getPaymentMethodIcon(); + abstract public function getPaymentMethodProviderDescription(); + + /** * Determine of a provider can handle a payment method. * @@ -15,4 +80,36 @@ abstract class PhortunePaymentProvider { PhortunePaymentMethod $payment_method, PhortuneCharge $charge); + + +/* -( Adding Payment Methods )--------------------------------------------- */ + + + /** + * @task addmethod + */ + public function canCreatePaymentMethods() { + return false; + } + + + /** + * @task addmethod + */ + public function createPaymentMethodFromRequest( + AphrontRequest $request, + PhortunePaymentMethod $method) { + throw new PhortuneNotImplementedException($this); + } + + + /** + * @task addmethod + */ + public function renderCreatePaymentMethodForm( + AphrontRequest $request, + array $errors) { + throw new PhortuneNotImplementedException($this); + } + } diff --git a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php index 5d26b15393..8066eba1f4 100644 --- a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php @@ -2,6 +2,32 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider { + public function isEnabled() { + return $this->getPublishableKey() && + $this->getSecretKey(); + } + + public function getProviderType() { + return 'stripe'; + } + + public function getProviderDomain() { + return 'stripe.com'; + } + + public function getPaymentMethodDescription() { + return pht('Add Credit or Debit Card (US and Canada)'); + } + + public function getPaymentMethodIcon() { + return celerity_get_resource_uri('/rsrc/image/phortune/stripe.png'); + } + + public function getPaymentMethodProviderDescription() { + return pht('Processed by Stripe'); + } + + public function canHandlePaymentMethod(PhortunePaymentMethod $method) { $type = $method->getMetadataValue('type'); return ($type === 'stripe.customer'); @@ -32,8 +58,219 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider { $charge->setMetadataValue('stripe.chargeID', $id); } + private function getPublishableKey() { + return PhabricatorEnv::getEnvConfig('stripe.publishable-key'); + } + private function getSecretKey() { return PhabricatorEnv::getEnvConfig('stripe.secret-key'); } + +/* -( Adding Payment Methods )--------------------------------------------- */ + + + public function canCreatePaymentMethods() { + return true; + } + + + /** + * @phutil-external-symbol class Stripe_Token + * @phutil-external-symbol class Stripe_Customer + */ + public function createPaymentMethodFromRequest( + AphrontRequest $request, + PhortunePaymentMethod $method) { + + $card_errors = $request->getStr('cardErrors'); + $stripe_token = $request->getStr('stripeToken'); + if ($card_errors) { + $raw_errors = json_decode($card_errors); + $errors = $this->parseRawCreatePaymentMethodErrors($raw_errors); + } else if (!$stripe_token) { + $errors[] = pht('There was an unknown error processing your card.'); + } + + $secret_key = $this->getSecretKey(); + + if (!$errors) { + $root = dirname(phutil_get_library_root('phabricator')); + require_once $root.'/externals/stripe-php/lib/Stripe.php'; + + try { + // First, make sure the token is valid. + $info = id(new Stripe_Token())->retrieve($stripe_token, $secret_key); + + $account_phid = $method->getAccountPHID(); + $author_phid = $method->getAuthorPHID(); + + $params = array( + 'card' => $stripe_token, + 'description' => $account_phid.':'.$author_phid, + ); + + // Then, we need to create a Customer in order to be able to charge + // the card more than once. We create one Customer for each card; + // they do not map to PhortuneAccounts because we allow an account to + // have more than one active card. + $customer = Stripe_Customer::create($params, $secret_key); + + $card = $info->card; + $method + ->setName($card->type.' / '.$card->last4) + ->setExpiresEpoch(strtotime($card->exp_year.'-'.$card->exp_month)) + ->setMetadata( + array( + 'type' => 'stripe.customer', + 'stripe.customerID' => $customer->id, + 'stripe.tokenID' => $stripe_token, + )); + } catch (Exception $ex) { + phlog($ex); + $errors[] = pht( + 'There was an error communicating with the payments backend.'); + } + } + + return $errors; + } + + public function renderCreatePaymentMethodForm( + AphrontRequest $request, + array $errors) { + + $e_card_number = isset($errors['number']) ? pht('Invalid') : true; + $e_card_cvc = isset($errors['cvc']) ? pht('Invalid') : true; + $e_card_exp = isset($errors['exp']) ? pht('Invalid') : null; + + $user = $request->getUser(); + + $form_id = celerity_generate_unique_node_id(); + require_celerity_resource('stripe-payment-form-css'); + require_celerity_resource('aphront-tooltip-css'); + Javelin::initBehavior('phabricator-tooltips'); + + $form = id(new AphrontFormView()) + ->setID($form_id) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel('') + ->setValue( + javelin_tag( + 'div', + array( + 'class' => 'credit-card-logos', + 'sigil' => 'has-tooltip', + 'meta' => array( + 'tip' => 'We support Visa, Mastercard, American Express, '. + 'Discover, JCB, and Diners Club.', + 'size' => 440, + ) + )))) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Card Number') + ->setDisableAutocomplete(true) + ->setSigil('number-input') + ->setError($e_card_number)) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('CVC') + ->setDisableAutocomplete(true) + ->setSigil('cvc-input') + ->setError($e_card_cvc)) + ->appendChild( + id(new PhortuneMonthYearExpiryControl()) + ->setLabel('Expiration') + ->setUser($user) + ->setError($e_card_exp)) + ->appendChild( + javelin_tag( + 'input', + array( + 'hidden' => true, + 'name' => 'stripeToken', + 'sigil' => 'stripe-token-input', + ))) + ->appendChild( + javelin_tag( + 'input', + array( + 'hidden' => true, + 'name' => 'cardErrors', + 'sigil' => 'card-errors-input' + ))); + + require_celerity_resource('stripe-core'); + Javelin::initBehavior( + 'stripe-payment-form', + array( + 'stripePublishKey' => $this->getPublishableKey(), + 'root' => $form_id, + )); + + return $form; + } + + + /** + * Stripe JS and calls to Stripe handle all errors with processing this + * form. This function takes the raw errors - in the form of an array + * where each elementt is $type => $message - and figures out what if + * any fields were invalid and pulls the messages into a flat object. + * + * See https://stripe.com/docs/api#errors for more information on possible + * errors. + */ + private function parseRawCreatePaymentMethodErrors(array $raw_errors) { + $errors = array(); + + foreach ($raw_errors as $type) { + $error_key = null; + $message = pht('A card processing error has occurred.'); + switch ($type) { + case 'number': + case 'invalid_number': + case 'incorrect_number': + $error_key = 'number'; + $message = pht('Invalid or incorrect credit card number.'); + break; + case 'cvc': + case 'invalid_cvc': + case 'incorrect_cvc': + $error_key = 'cvc'; + $message = pht('Card CVC is invalid or incorrect.'); + break; + case 'expiry': + case 'invalid_expiry_month': + case 'invalid_expiry_year': + $error_key = 'exp'; + $message = pht('Card expiration date is invalid or incorrect.'); + break; + case 'card_declined': + case 'expired_card': + case 'duplicate_transaction': + case 'processing_error': + // these errors don't map well to field(s) being bad + break; + case 'invalid_amount': + case 'missing': + default: + // these errors only happen if we (not the user) messed up so log it + $error = sprintf('[Stripe Error] %s', $type); + phlog($error); + break; + } + + if ($error_key === null || isset($errors[$error_key])) { + $errors[] = $message; + } else { + $errors[$error_key] = $message; + } + } + + return $errors; + } + } diff --git a/src/applications/phortune/provider/PhortuneTestPaymentProvider.php b/src/applications/phortune/provider/PhortuneTestPaymentProvider.php index 059c8c16c3..3ab4cb7a8b 100644 --- a/src/applications/phortune/provider/PhortuneTestPaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneTestPaymentProvider.php @@ -2,6 +2,30 @@ final class PhortuneTestPaymentProvider extends PhortunePaymentProvider { + public function isEnabled() { + return true; + } + + public function getProviderType() { + return 'test'; + } + + public function getProviderDomain() { + return 'example.com'; + } + + public function getPaymentMethodDescription() { + return pht('Add Mountain of Virtual Wealth'); + } + + public function getPaymentMethodIcon() { + return celerity_get_resource_uri('/rsrc/image/phortune/test.png'); + } + + public function getPaymentMethodProviderDescription() { + return pht('Infinite Free Money'); + } + public function canHandlePaymentMethod(PhortunePaymentMethod $method) { $type = $method->getMetadataValue('type'); return ($type === 'test.cash' || $type === 'test.multiple'); diff --git a/src/applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php b/src/applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php index 3453ea7552..5ea6376750 100644 --- a/src/applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php +++ b/src/applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php @@ -2,6 +2,30 @@ final class PhortuneTestExtraPaymentProvider extends PhortunePaymentProvider { + public function isEnabled() { + return false; + } + + public function getProviderType() { + return 'test2'; + } + + public function getProviderDomain() { + return 'example.com'; + } + + public function getPaymentMethodDescription() { + return pht('You Should Not Be Able to See This'); + } + + public function getPaymentMethodIcon() { + return celerity_get_resource_uri('/rsrc/image/phortune/test.png'); + } + + public function getPaymentMethodProviderDescription() { + return pht('Just for Unit Tests'); + } + public function canHandlePaymentMethod(PhortunePaymentMethod $method) { $type = $method->getMetadataValue('type'); return ($type === 'test.multiple'); diff --git a/src/applications/phortune/storage/PhortunePaymentMethod.php b/src/applications/phortune/storage/PhortunePaymentMethod.php index 5293be4ff7..2df720188c 100644 --- a/src/applications/phortune/storage/PhortunePaymentMethod.php +++ b/src/applications/phortune/storage/PhortunePaymentMethod.php @@ -60,16 +60,12 @@ final class PhortunePaymentMethod extends PhortuneDAO } public function buildPaymentProvider() { - $providers = id(new PhutilSymbolLoader()) - ->setAncestorClass('PhortunePaymentProvider') - ->setConcreteOnly(true) - ->selectAndLoadSymbols(); + $providers = PhortunePaymentProvider::getAllProviders(); $accept = array(); foreach ($providers as $provider) { - $obj = newv($provider['name'], array()); - if ($obj->canHandlePaymentMethod($this)) { - $accept[] = $obj; + if ($provider->canHandlePaymentMethod($this)) { + $accept[] = $provider; } } diff --git a/webroot/index.php b/webroot/index.php index 5a9479eab1..7ee22338cc 100644 --- a/webroot/index.php +++ b/webroot/index.php @@ -20,9 +20,7 @@ try { )); DarkConsoleXHProfPluginAPI::hookProfiler(); - - PhutilErrorHandler::setErrorListener( - array('DarkConsoleErrorLogPluginAPI', 'handleErrors')); + DarkConsoleErrorLogPluginAPI::registerErrorHandler(); $sink = new AphrontPHPHTTPSink(); diff --git a/webroot/rsrc/js/application/phortune/behavior-stripe-payment-form.js b/webroot/rsrc/js/application/phortune/behavior-stripe-payment-form.js index 23645be1c5..4c06828dd2 100644 --- a/webroot/rsrc/js/application/phortune/behavior-stripe-payment-form.js +++ b/webroot/rsrc/js/application/phortune/behavior-stripe-payment-form.js @@ -4,7 +4,6 @@ * javelin-dom * javelin-json * javelin-workflow - * stripe-core */ JX.behavior('stripe-payment-form', function(config) { @@ -23,38 +22,6 @@ JX.behavior('stripe-payment-form', function(config) { }; } - var stripeErrorObject = function(type) { - var errorPre = 'Stripe (our payments provider) has detected your card '; - var errorPost = ' is invalid.'; - var msg = ''; - var result = {}; - - switch (type) { - case 'number': - msg = errorPre + 'number' + errorPost; - break; - case 'cvc': - msg = errorPre + 'CVC' + errorPost; - break; - case 'expiry': - msg = errorPre + 'expiration date' + errorPost; - break; - case 'stripe': - msg = 'Stripe (our payments provider) is experiencing issues. ' + - 'Please try again.'; - break; - case 'invalid_request': - default: - msg = 'Unknown error.'; - // TODO - how best report bugs? would be good to get - // user feedback since this shouldn't happen! - break; - } - - result[type] = msg; - return result; - } - var onsubmit = function(e) { e.kill(); @@ -63,21 +30,19 @@ JX.behavior('stripe-payment-form', function(config) { var cardData = getCardData(); var errors = []; if (!Stripe.validateCardNumber(cardData.number)) { - errors.push(stripeErrorObject('number')); + errors.push('number'); } if (!Stripe.validateCVC(cardData.cvc)) { - errors.push(stripeErrorObject('cvc')); + errors.push('cvc'); } - if (!Stripe.validateExpiry(cardData.month, - cardData.year)) { - errors.push(stripeErrorObject('expiry')); + if (!Stripe.validateExpiry(cardData.month, cardData.year)) { + errors.push('expiry'); } - if (errors.length != 0) { - cardErrors.value = JX.JSON.stringify(errors); + if (errors.length) { + cardErrors.value = JX.JSON.stringify(errors); JX.Workflow.newFromForm(root) .start(); - return; } @@ -97,16 +62,14 @@ JX.behavior('stripe-payment-form', function(config) { var errors = []; switch (response.error.type) { case 'card_error': - var error = {}; - error[response.error.code] = response.error.message; - errors.push(error); + errors.push(response.error.code); break; case 'invalid_request_error': - errors.push(stripeErrorObject('invalid_request')); + errors.push('invalid_request'); break; case 'api_error': default: - errors.push(stripeErrorObject('stripe')); + errors.push('stripe'); break; } cardErrors.value = JX.JSON.stringify(errors);