diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -9,7 +9,7 @@ 'names' => array( 'conpherence.pkg.css' => '3c8a0668', 'conpherence.pkg.js' => '020aebcf', - 'core.pkg.css' => 'eab5ccaf', + 'core.pkg.css' => '08baca0c', 'core.pkg.js' => '5c737607', 'differential.pkg.css' => 'b8df73d4', 'differential.pkg.js' => '67c9ea4c', @@ -30,7 +30,7 @@ 'rsrc/css/aphront/notification.css' => '30240bd2', 'rsrc/css/aphront/panel-view.css' => '46923d46', 'rsrc/css/aphront/phabricator-nav-view.css' => 'f8a0c1bf', - 'rsrc/css/aphront/table-view.css' => '76eda3f8', + 'rsrc/css/aphront/table-view.css' => 'daa1f9df', 'rsrc/css/aphront/tokenizer.css' => 'b52d0668', 'rsrc/css/aphront/tooltip.css' => 'e3f2412f', 'rsrc/css/aphront/typeahead-browse.css' => 'b7ed02d2', @@ -519,7 +519,7 @@ 'aphront-list-filter-view-css' => 'feb64255', 'aphront-multi-column-view-css' => 'fbc00ba3', 'aphront-panel-view-css' => '46923d46', - 'aphront-table-view-css' => '76eda3f8', + 'aphront-table-view-css' => 'daa1f9df', 'aphront-tokenizer-control-css' => 'b52d0668', 'aphront-tooltip-css' => 'e3f2412f', 'aphront-typeahead-control-css' => '8779483d', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -5015,6 +5015,7 @@ 'PhortuneCurrencySerializer' => 'applications/phortune/currency/PhortuneCurrencySerializer.php', 'PhortuneCurrencyTestCase' => 'applications/phortune/currency/__tests__/PhortuneCurrencyTestCase.php', 'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php', + 'PhortuneDisplayException' => 'applications/phortune/exception/PhortuneDisplayException.php', 'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.php', 'PhortuneInvoiceView' => 'applications/phortune/view/PhortuneInvoiceView.php', 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', @@ -11263,6 +11264,7 @@ 'PhortuneCurrencySerializer' => 'PhabricatorLiskSerializer', 'PhortuneCurrencyTestCase' => 'PhabricatorTestCase', 'PhortuneDAO' => 'PhabricatorLiskDAO', + 'PhortuneDisplayException' => 'Exception', 'PhortuneErrCode' => 'PhortuneConstants', 'PhortuneInvoiceView' => 'AphrontTagView', 'PhortuneLandingController' => 'PhortuneController', diff --git a/src/applications/fund/storage/FundBacker.php b/src/applications/fund/storage/FundBacker.php --- a/src/applications/fund/storage/FundBacker.php +++ b/src/applications/fund/storage/FundBacker.php @@ -76,6 +76,7 @@ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } @@ -91,6 +92,8 @@ return $initiative->getPolicy($capability); } return PhabricatorPolicies::POLICY_NOONE; + case PhabricatorPolicyCapability::CAN_EDIT: + return PhabricatorPolicies::POLICY_NOONE; } } diff --git a/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php b/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php --- a/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php +++ b/src/applications/phortune/controller/payment/PhortunePaymentMethodCreateController.php @@ -73,6 +73,7 @@ $provider = $providers[$provider_id]; $errors = array(); + $display_exception = null; if ($request->isFormPost() && $request->getBool('isProviderForm')) { $method = id(new PhortunePaymentMethod()) ->setAccountPHID($account->getPHID()) @@ -107,14 +108,23 @@ } if (!$errors) { - $errors = $provider->createPaymentMethodFromRequest( - $request, - $method, - $client_token); + try { + $provider->createPaymentMethodFromRequest( + $request, + $method, + $client_token); + } catch (PhortuneDisplayException $exception) { + $display_exception = $exception; + } catch (Exception $ex) { + $errors = array( + pht('There was an error adding this payment method:'), + $ex->getMessage(), + ); + } } } - if (!$errors) { + if (!$errors && !$display_exception) { $method->save(); // If we added this method on a cart flow, return to the cart to @@ -133,13 +143,17 @@ return id(new AphrontRedirectResponse())->setURI($next_uri); } else { - $dialog = id(new AphrontDialogView()) - ->setUser($viewer) + if ($display_exception) { + $dialog_body = $display_exception->getView(); + } else { + $dialog_body = id(new PHUIInfoView()) + ->setErrors($errors); + } + + return $this->newDialog() ->setTitle(pht('Error Adding Payment Method')) - ->appendChild(id(new PHUIInfoView())->setErrors($errors)) + ->appendChild($dialog_body) ->addCancelButton($request->getRequestURI()); - - return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/phortune/exception/PhortuneDisplayException.php b/src/applications/phortune/exception/PhortuneDisplayException.php new file mode 100644 --- /dev/null +++ b/src/applications/phortune/exception/PhortuneDisplayException.php @@ -0,0 +1,15 @@ +view = $view; + return $this; + } + + public function getView() { + return $this->view; + } + +} diff --git a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php --- a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php @@ -233,8 +233,6 @@ array $token) { $this->loadStripeAPILibraries(); - $errors = array(); - $secret_key = $this->getSecretKey(); $stripe_token = $token['stripeCardToken']; @@ -253,7 +251,15 @@ // 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); + try { + $customer = Stripe_Customer::create($params, $secret_key); + } catch (Stripe_CardError $ex) { + $display_exception = $this->newDisplayExceptionFromCardError($ex); + if ($display_exception) { + throw $display_exception; + } + throw $ex; + } $card = $info->card; @@ -267,8 +273,6 @@ 'stripe.customerID' => $customer->id, 'stripe.cardToken' => $stripe_token, )); - - return $errors; } public function renderCreatePaymentMethodForm( @@ -383,4 +387,84 @@ require_once $root.'/externals/stripe-php/lib/Stripe.php'; } + + private function newDisplayExceptionFromCardError(Stripe_CardError $ex) { + $body = $ex->getJSONBody(); + if (!$body) { + return null; + } + + $map = idx($body, 'error'); + if (!$map) { + return null; + } + + $view = array(); + + $message = idx($map, 'message'); + + $view[] = id(new PHUIInfoView()) + ->setErrors(array($message)); + + $view[] = phutil_tag( + 'div', + array( + 'class' => 'mlt mlb', + ), + pht('Additional details about this error:')); + + $rows = array(); + + $rows[] = array( + pht('Error Code'), + idx($map, 'code'), + ); + + $rows[] = array( + pht('Error Type'), + idx($map, 'type'), + ); + + $param = idx($map, 'param'); + if (strlen($param)) { + $rows[] = array( + pht('Error Param'), + $param, + ); + } + + $decline_code = idx($map, 'decline_code'); + if (strlen($decline_code)) { + $rows[] = array( + pht('Decline Code'), + $decline_code, + ); + } + + $doc_url = idx($map, 'doc_url'); + if ($doc_url) { + $rows[] = array( + pht('Learn More'), + phutil_tag( + 'a', + array( + 'href' => $doc_url, + 'target' => '_blank', + ), + $doc_url), + ); + } + + $view[] = id(new AphrontTableView($rows)) + ->setColumnClasses( + array( + 'header', + 'wide', + )); + + return id(new PhortuneDisplayException(get_class($ex))) + ->setView($view); + } + + } diff --git a/src/applications/policy/filter/PhabricatorPolicyFilter.php b/src/applications/policy/filter/PhabricatorPolicyFilter.php --- a/src/applications/policy/filter/PhabricatorPolicyFilter.php +++ b/src/applications/policy/filter/PhabricatorPolicyFilter.php @@ -175,9 +175,10 @@ if (!in_array($capability, $object_capabilities)) { throw new Exception( pht( - "Testing for capability '%s' on an object which does ". - "not have that capability!", - $capability)); + 'Testing for capability "%s" on an object ("%s") which does '. + 'not support that capability.', + $capability, + get_class($object))); } $policy = $this->getObjectPolicy($object, $capability); diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -45,16 +45,20 @@ background: inherit; } -.aphront-table-view th { +.aphront-table-view th, +.aphront-table-view td.header { font-weight: bold; white-space: nowrap; color: {$bluetext}; - text-shadow: 0 1px 0 white; font-weight: bold; - border-bottom: 1px solid {$thinblueborder}; + text-shadow: 0 1px 0 white; background-color: {$lightbluebackground}; } +.aphront-table-view th { + border-bottom: 1px solid {$thinblueborder}; +} + th.aphront-table-view-sortable-selected { background-color: {$greybackground}; } @@ -74,12 +78,8 @@ } .aphront-table-view td.header { - padding: 4px 8px; - white-space: nowrap; text-align: right; - color: {$bluetext}; - font-weight: bold; - vertical-align: top; + border-right: 1px solid {$thinblueborder}; } .aphront-table-view td {