From b170d1c15f7db11bb76bfe6ce9d5a7c514b7e24d Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 2 Mar 2015 13:01:08 -0800 Subject: [PATCH] Make it easier to add payment methods for subscription autopay Summary: Fixes T7424. Ref T6308. Currently, there's no option to just add a card directly from the autopay UI. Add a button so this works. Also, chip away at T6308 a bit. This isn't perfect but looks a little less out of place. Test Plan: {F327637} - Added a payment method, then set it as autopay. Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T6308, T7424 Differential Revision: https://secure.phabricator.com/D11935 --- resources/celerity/map.php | 11 ++- .../PhortunePaymentMethodCreateController.php | 51 +++++++----- .../PhortuneSubscriptionEditController.php | 17 ++++ .../PhortuneStripePaymentProvider.php | 2 + .../provider/PhortuneTestPaymentProvider.php | 2 + .../phortune/view/PhortuneCreditCardForm.php | 73 +++++++++++------- src/view/form/control/AphrontFormControl.php | 11 +++ .../phortune/phortune-credit-card-form.css | 5 +- webroot/rsrc/css/phui/phui-form-view.css | 4 + webroot/rsrc/image/credit_cards.png | Bin 5963 -> 0 bytes 10 files changed, 119 insertions(+), 57 deletions(-) delete mode 100644 webroot/rsrc/image/credit_cards.png diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 0f41171f70..d8841e11fd 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => 'cd54f10c', + 'core.pkg.css' => 'd9fa6161', 'core.pkg.js' => '23d653bb', 'darkconsole.pkg.js' => '8ab24e01', 'differential.pkg.css' => '4c3242f8', @@ -83,7 +83,7 @@ return array( 'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => '95174bdd', - 'rsrc/css/application/phortune/phortune-credit-card-form.css' => 'b25b4beb', + 'rsrc/css/application/phortune/phortune-credit-card-form.css' => '8391eb02', 'rsrc/css/application/phortune/phortune.css' => '9149f103', 'rsrc/css/application/phrequent/phrequent.css' => 'ffc185ad', 'rsrc/css/application/phriction/phriction-document-css.css' => '0d16bc9a', @@ -129,7 +129,7 @@ return array( 'rsrc/css/phui/phui-document.css' => '620b1eec', 'rsrc/css/phui/phui-feed-story.css' => 'c9f3a0b5', 'rsrc/css/phui/phui-fontkit.css' => '4394f216', - 'rsrc/css/phui/phui-form-view.css' => '8b78a986', + 'rsrc/css/phui/phui-form-view.css' => '76f0b086', 'rsrc/css/phui/phui-form.css' => 'f535f938', 'rsrc/css/phui/phui-header-view.css' => '083669db', 'rsrc/css/phui/phui-icon.css' => 'd35aa857', @@ -242,7 +242,6 @@ return array( 'rsrc/image/checker_dark.png' => 'd8e65881', 'rsrc/image/checker_light.png' => 'a0155918', 'rsrc/image/checker_lighter.png' => 'd5da91b6', - 'rsrc/image/credit_cards.png' => '72b8ede8', 'rsrc/image/darkload.gif' => '1ffd3ec6', 'rsrc/image/divot.png' => '94dded62', 'rsrc/image/examples/hero.png' => '979a86ae', @@ -766,7 +765,7 @@ return array( 'pholio-edit-css' => '3ad9d1ee', 'pholio-inline-comments-css' => '8e545e49', 'phortune-credit-card-form' => '2290aeef', - 'phortune-credit-card-form-css' => 'b25b4beb', + 'phortune-credit-card-form-css' => '8391eb02', 'phortune-css' => '9149f103', 'phrequent-css' => 'ffc185ad', 'phriction-document-css' => '0d16bc9a', @@ -784,7 +783,7 @@ return array( 'phui-font-icon-base-css' => '3dad2ae3', 'phui-fontkit-css' => '4394f216', 'phui-form-css' => 'f535f938', - 'phui-form-view-css' => '8b78a986', + 'phui-form-view-css' => '76f0b086', 'phui-header-view-css' => '083669db', 'phui-icon-view-css' => 'd35aa857', 'phui-image-mask-css' => '5a8b09c8', diff --git a/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php b/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php index df28a1c2a7..50210fa76c 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php @@ -20,6 +20,7 @@ final class PhortunePaymentMethodCreateController if (!$account) { return new Aphront404Response(); } + $account_id = $account->getID(); $merchant = id(new PhortuneMerchantQuery()) ->setViewer($viewer) @@ -30,8 +31,12 @@ final class PhortunePaymentMethodCreateController } $cart_id = $request->getInt('cartID'); + $subscription_id = $request->getInt('subscriptionID'); if ($cart_id) { $cancel_uri = $this->getApplicationURI("cart/{$cart_id}/checkout/"); + } else if ($subscription_id) { + $cancel_uri = $this->getApplicationURI( + "{$account_id}/subscription/edit/{$subscription_id}/"); } else { $cancel_uri = $this->getApplicationURI($account->getID().'/'); } @@ -43,26 +48,31 @@ final class PhortunePaymentMethodCreateController 'methods.'); } - $provider_id = $request->getInt('providerID'); - if (empty($providers[$provider_id])) { - $choices = array(); - foreach ($providers as $provider) { - $choices[] = $this->renderSelectProvider($provider); + if (count($providers) == 1) { + // If there's only one provider, always choose it. + $provider_id = head_key($providers); + } else { + $provider_id = $request->getInt('providerID'); + if (empty($providers[$provider_id])) { + $choices = array(); + foreach ($providers as $provider) { + $choices[] = $this->renderSelectProvider($provider); + } + + $content = phutil_tag( + 'div', + array( + 'class' => 'phortune-payment-method-list', + ), + $choices); + + return $this->newDialog() + ->setRenderDialogAsDiv(true) + ->setTitle(pht('Add Payment Method')) + ->appendParagraph(pht('Choose a payment method to add:')) + ->appendChild($content) + ->addCancelButton($cancel_uri); } - - $content = phutil_tag( - 'div', - array( - 'class' => 'phortune-payment-method-list', - ), - $choices); - - return $this->newDialog() - ->setRenderDialogAsDiv(true) - ->setTitle(pht('Add Payment Method')) - ->appendParagraph(pht('Choose a payment method to add:')) - ->appendChild($content) - ->addCancelButton($cancel_uri); } $provider = $providers[$provider_id]; @@ -114,6 +124,8 @@ final class PhortunePaymentMethodCreateController if ($cart_id) { $next_uri = $this->getApplicationURI( "cart/{$cart_id}/checkout/?paymentMethodID=".$method->getID()); + } else if ($subscription_id) { + $next_uri = $cancel_uri; } else { $account_uri = $this->getApplicationURI($account->getID().'/'); $next_uri = new PhutilURI($account_uri); @@ -140,6 +152,7 @@ final class PhortunePaymentMethodCreateController ->setWorkflow(true) ->addHiddenInput('providerID', $provider_id) ->addHiddenInput('cartID', $request->getInt('cartID')) + ->addHiddenInput('subscriptionID', $request->getInt('subscriptionID')) ->addHiddenInput('isProviderForm', true) ->appendChild( id(new AphrontFormSubmitControl()) diff --git a/src/applications/phortune/controller/PhortuneSubscriptionEditController.php b/src/applications/phortune/controller/PhortuneSubscriptionEditController.php index e2615ebb1c..8a3b3ddb93 100644 --- a/src/applications/phortune/controller/PhortuneSubscriptionEditController.php +++ b/src/applications/phortune/controller/PhortuneSubscriptionEditController.php @@ -103,6 +103,20 @@ final class PhortuneSubscriptionEditController extends PhortuneController { $view_uri); $crumbs->addTextCrumb(pht('Edit')); + + $uri = $this->getApplicationURI($account->getID().'/card/new/'); + $uri = new PhutilURI($uri); + $uri->setQueryParam('merchantID', $merchant->getID()); + $uri->setQueryParam('subscriptionID', $subscription->getID()); + + $add_method_button = phutil_tag( + 'a', + array( + 'href' => $uri, + 'class' => 'button grey', + ), + pht('Add Payment Method...')); + $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( @@ -111,6 +125,9 @@ final class PhortuneSubscriptionEditController extends PhortuneController { ->setLabel(pht('Autopay With')) ->setValue($current_phid) ->setOptions($options)) + ->appendChild( + id(new AphrontFormMarkupControl()) + ->setValue($add_method_button)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save Changes')) diff --git a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php index d0ed2b0b00..f751e3cbc5 100644 --- a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php @@ -277,6 +277,8 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider { array $errors) { $ccform = id(new PhortuneCreditCardForm()) + ->setSecurityAssurance( + pht('Payments are processed securely by Stripe.')) ->setUser($request->getUser()) ->setErrors($errors) ->addScript('https://js.stripe.com/v2/'); diff --git a/src/applications/phortune/provider/PhortuneTestPaymentProvider.php b/src/applications/phortune/provider/PhortuneTestPaymentProvider.php index 3139d2692e..02d57e3b6a 100644 --- a/src/applications/phortune/provider/PhortuneTestPaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneTestPaymentProvider.php @@ -144,6 +144,8 @@ final class PhortuneTestPaymentProvider extends PhortunePaymentProvider { array $errors) { $ccform = id(new PhortuneCreditCardForm()) + ->setSecurityAssurance( + pht('This is a test payment provider.')) ->setUser($request->getUser()) ->setErrors($errors); diff --git a/src/applications/phortune/view/PhortuneCreditCardForm.php b/src/applications/phortune/view/PhortuneCreditCardForm.php index 335123e04e..58bf4cda0c 100644 --- a/src/applications/phortune/view/PhortuneCreditCardForm.php +++ b/src/applications/phortune/view/PhortuneCreditCardForm.php @@ -10,6 +10,16 @@ final class PhortuneCreditCardForm { private $cardNumberError; private $cardCVCError; private $cardExpirationError; + private $securityAssurance; + + public function setSecurityAssurance($security_assurance) { + $this->securityAssurance = $security_assurance; + return $this; + } + + public function getSecurityAssurance() { + return $this->securityAssurance; + } public function setUser(PhabricatorUser $user) { $this->user = $user; @@ -57,11 +67,11 @@ final class PhortuneCreditCardForm { $errors = $this->errors; $e_number = isset($errors[PhortuneErrCode::ERR_CC_INVALID_NUMBER]) ? pht('Invalid') - : true; + : null; $e_cvc = isset($errors[PhortuneErrCode::ERR_CC_INVALID_CVC]) ? pht('Invalid') - : true; + : null; $e_expiry = isset($errors[PhortuneErrCode::ERR_CC_INVALID_EXPIRY]) ? pht('Invalid') @@ -70,37 +80,42 @@ final class PhortuneCreditCardForm { $form ->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, - ), - )))) + id(new AphrontFormTextControl()) + ->setLabel('Card Number') + ->setDisableAutocomplete(true) + ->setSigil('number-input') + ->setError($e_number)) ->appendChild( id(new AphrontFormTextControl()) - ->setLabel('Card Number') - ->setDisableAutocomplete(true) - ->setSigil('number-input') - ->setError($e_number)) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel('CVC') - ->setDisableAutocomplete(true) - ->setSigil('cvc-input') - ->setError($e_cvc)) + ->setLabel('CVC') + ->setDisableAutocomplete(true) + ->addClass('aphront-form-cvc-input') + ->setSigil('cvc-input') + ->setError($e_cvc)) ->appendChild( id(new PhortuneMonthYearExpiryControl()) - ->setLabel('Expiration') - ->setUser($this->user) - ->setError($e_expiry)); + ->setLabel('Expiration') + ->setUser($this->user) + ->setError($e_expiry)); + + $assurance = $this->getSecurityAssurance(); + if ($assurance) { + $assurance = phutil_tag( + 'div', + array( + 'class' => 'phortune-security-assurance', + ), + array( + id(new PHUIIconView()) + ->setIconFont('fa-lock grey'), + ' ', + $assurance, + )); + + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setValue($assurance)); + } return $form; } diff --git a/src/view/form/control/AphrontFormControl.php b/src/view/form/control/AphrontFormControl.php index 3c63d527d4..f2be0764b6 100644 --- a/src/view/form/control/AphrontFormControl.php +++ b/src/view/form/control/AphrontFormControl.php @@ -14,6 +14,7 @@ abstract class AphrontFormControl extends AphrontView { private $formPage; private $required; private $hidden; + private $classes; public function setHidden($hidden) { $this->hidden = $hidden; @@ -162,6 +163,11 @@ abstract class AphrontFormControl extends AphrontView { return true; } + public function addClass($class) { + $this->classes[] = $class; + return $this; + } + final public function render() { if (!$this->shouldRender()) { return null; @@ -225,6 +231,11 @@ abstract class AphrontFormControl extends AphrontView { $classes[] = 'aphront-form-control'; $classes[] = 'grouped'; $classes[] = $custom_class; + if ($this->classes) { + foreach ($this->classes as $class) { + $classes[] = $class; + } + } $style = $this->controlStyle; if ($this->hidden) { diff --git a/webroot/rsrc/css/application/phortune/phortune-credit-card-form.css b/webroot/rsrc/css/application/phortune/phortune-credit-card-form.css index b7904195ae..62a1d7a80b 100644 --- a/webroot/rsrc/css/application/phortune/phortune-credit-card-form.css +++ b/webroot/rsrc/css/application/phortune/phortune-credit-card-form.css @@ -2,7 +2,6 @@ * @provides phortune-credit-card-form-css */ -.credit-card-logos { - background: url(/rsrc/image/credit_cards.png) no-repeat 0px 2px; - height: 32px; +.phortune-security-assurance { + color: {$lightgreytext}; } diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index 28f4883cc6..ddd950c122 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -111,6 +111,10 @@ width: 100%; } +.aphront-form-cvc-input input { + width: 64px; +} + .aphront-form-input textarea { display: block; width: 100%; diff --git a/webroot/rsrc/image/credit_cards.png b/webroot/rsrc/image/credit_cards.png deleted file mode 100644 index dc54a4fca4f4fed984c52481e798c759da9d2d75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5963 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