diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 7dcf758313..ccaa18d5ac 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/e4149d37/rsrc/js/application/phortune/behavior-stripe-payment-form.js', + 'uri' => '/res/62dc91b4/rsrc/js/application/phortune/behavior-stripe-payment-form.js', 'type' => 'js', 'requires' => array( @@ -2266,6 +2266,7 @@ celerity_register_resource_map(array( 1 => 'javelin-dom', 2 => 'javelin-json', 3 => 'javelin-workflow', + 4 => 'phortune-credit-card-form', ), 'disk' => '/rsrc/js/application/phortune/behavior-stripe-payment-form.js', ), @@ -3588,6 +3589,26 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/css/application/pholio/pholio-inline-comments.css', ), + 'phortune-credit-card-form' => + array( + 'uri' => '/res/7be5799a/rsrc/js/application/phortune/phortune-credit-card-form.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + 1 => 'javelin-dom', + ), + 'disk' => '/rsrc/js/application/phortune/phortune-credit-card-form.js', + ), + 'phortune-credit-card-form-css' => + array( + 'uri' => '/res/563c8c6d/rsrc/css/application/phortune/phortune-credit-card-form.css', + 'type' => 'css', + 'requires' => + array( + ), + 'disk' => '/rsrc/css/application/phortune/phortune-credit-card-form.css', + ), 'phrequent-css' => array( 'uri' => '/res/9d6f3eb7/rsrc/css/application/phrequent/phrequent.css', @@ -3921,24 +3942,6 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/css/sprite-tokens.css', ), - 'stripe-core' => - array( - 'uri' => '/res/fc74303d/rsrc/externals/stripe-js/stripe_core.js', - 'type' => 'js', - 'requires' => - array( - ), - 'disk' => '/rsrc/externals/stripe-js/stripe_core.js', - ), - 'stripe-payment-form-css' => - array( - 'uri' => '/res/634a6371/rsrc/css/application/phortune/stripe-payment-form.css', - 'type' => 'css', - 'requires' => - array( - ), - 'disk' => '/rsrc/css/application/phortune/stripe-payment-form.css', - ), 'syntax-highlighting-css' => array( 'uri' => '/res/cb3b9dc0/rsrc/css/core/syntax.css', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index f33b88e0f5..31034c0d48 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1583,11 +1583,13 @@ phutil_register_library_map(array( 'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php', 'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php', 'PhortuneController' => 'applications/phortune/controller/PhortuneController.php', + 'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php', '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', + 'PhortuneNotImplementedException' => 'applications/phortune/exception/PhortuneNotImplementedException.php', 'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php', 'PhortunePaymentMethodEditController' => 'applications/phortune/controller/PhortunePaymentMethodEditController.php', 'PhortunePaymentMethodListController' => 'applications/phortune/controller/PhortunePaymentMethodListController.php', @@ -1604,7 +1606,6 @@ phutil_register_library_map(array( 'PhortuneProductTransactionQuery' => 'applications/phortune/query/PhortuneProductTransactionQuery.php', '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', @@ -3309,6 +3310,7 @@ phutil_register_library_map(array( 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', 'PhortuneMultiplePaymentProvidersException' => 'Exception', 'PhortuneNoPaymentProviderException' => 'Exception', + 'PhortuneNotImplementedException' => 'Exception', 'PhortunePaymentMethod' => array( 0 => 'PhortuneDAO', @@ -3332,7 +3334,6 @@ phutil_register_library_map(array( 'PhortuneProductTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneProductViewController' => 'PhortuneController', 'PhortunePurchase' => 'PhortuneDAO', - 'PhortuneStripePaymentFormView' => 'AphrontView', 'PhortuneStripePaymentProvider' => 'PhortunePaymentProvider', 'PhortuneTestExtraPaymentProvider' => 'PhortunePaymentProvider', 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', diff --git a/src/applications/phortune/provider/PhortunePaymentProvider.php b/src/applications/phortune/provider/PhortunePaymentProvider.php index 42703e242f..036c6f2b26 100644 --- a/src/applications/phortune/provider/PhortunePaymentProvider.php +++ b/src/applications/phortune/provider/PhortunePaymentProvider.php @@ -81,7 +81,6 @@ abstract class PhortunePaymentProvider { PhortuneCharge $charge); - /* -( Adding Payment Methods )--------------------------------------------- */ @@ -112,4 +111,5 @@ abstract class PhortunePaymentProvider { throw new PhortuneNotImplementedException($this); } + } diff --git a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php index 8066eba1f4..5abc9a58a3 100644 --- a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php @@ -85,6 +85,8 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider { $card_errors = $request->getStr('cardErrors'); $stripe_token = $request->getStr('stripeToken'); + + $errors = array(); if ($card_errors) { $raw_errors = json_decode($card_errors); $errors = $this->parseRawCreatePaymentMethodErrors($raw_errors); @@ -140,77 +142,21 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider { 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; + $ccform = id(new PhortuneCreditCardForm()) + ->setUser($request->getUser()) + ->setCardNumberError(isset($errors['number']) ? pht('Invalid') : true) + ->setCardCVCError(isset($errors['cvc']) ? pht('Invalid') : true) + ->setCardExpirationError(isset($errors['exp']) ? pht('Invalid') : null) + ->addScript('https://js.stripe.com/v2/'); - $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, + 'stripePublishableKey' => $this->getPublishableKey(), + 'formID' => $ccform->getFormID(), )); - return $form; + return $ccform->buildForm(); } diff --git a/src/applications/phortune/view/PhortuneCreditCardForm.php b/src/applications/phortune/view/PhortuneCreditCardForm.php new file mode 100644 index 0000000000..da042ea858 --- /dev/null +++ b/src/applications/phortune/view/PhortuneCreditCardForm.php @@ -0,0 +1,103 @@ +user = $user; + return $this; + } + + public function setCardExpirationError($card_expiration_error) { + $this->cardExpirationError = $card_expiration_error; + return $this; + } + + public function setCardCVCError($card_cvc_error) { + $this->cardCVCError = $card_cvc_error; + return $this; + } + + public function setCardNumberError($card_number_error) { + $this->cardNumberError = $card_number_error; + return $this; + } + + public function addScript($script_uri) { + $this->scripts[] = $script_uri; + return $this; + } + + public function getFormID() { + if (!$this->formID) { + $this->formID = celerity_generate_unique_node_id(); + } + return $this->formID; + } + + public function buildForm() { + $form_id = $this->getFormID(); + + require_celerity_resource('phortune-credit-card-form-css'); + require_celerity_resource('phortune-credit-card-form'); + + require_celerity_resource('aphront-tooltip-css'); + Javelin::initBehavior('phabricator-tooltips'); + + $form = new AphrontFormView(); + + foreach ($this->scripts as $script) { + $form->appendChild( + phutil_tag( + 'script', + array( + 'type' => 'text/javascript', + 'src' => $script, + ))); + } + + $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, + ) + )))) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Card Number') + ->setDisableAutocomplete(true) + ->setSigil('number-input') + ->setError($this->cardNumberError)) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('CVC') + ->setDisableAutocomplete(true) + ->setSigil('cvc-input') + ->setError($this->cardCVCError)) + ->appendChild( + id(new PhortuneMonthYearExpiryControl()) + ->setLabel('Expiration') + ->setUser($this->user) + ->setError($this->cardExpirationError)); + + return $form; + } +} diff --git a/src/applications/phortune/view/PhortuneStripePaymentFormView.php b/src/applications/phortune/view/PhortuneStripePaymentFormView.php deleted file mode 100644 index d037894d6c..0000000000 --- a/src/applications/phortune/view/PhortuneStripePaymentFormView.php +++ /dev/null @@ -1,119 +0,0 @@ -stripeKey = $key; - return $this; - } - private function getStripeKey() { - return $this->stripeKey; - } - - public function setCardNumberError($error) { - $this->cardNumberError = $error; - return $this; - } - private function getCardNumberError() { - return $this->cardNumberError; - } - - public function setCardCVCError($error) { - $this->cardCVCError = $error; - return $this; - } - private function getCardCVCError() { - return $this->cardCVCError; - } - - public function setCardExpirationError($error) { - $this->cardExpirationError = $error; - return $this; - } - private function getCardExpirationError() { - return $this->cardExpirationError; - } - - public function render() { - $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) - ->setUser($this->getUser()) - ->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($this->getCardNumberError())) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel('CVC') - ->setDisableAutocomplete(true) - ->setSigil('cvc-input') - ->setError($this->getCardCVCError())) - ->appendChild( - id(new PhortuneMonthYearExpiryControl()) - ->setLabel('Expiration') - ->setUser($this->getUser()) - ->setError($this->getCardExpirationError())) - ->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' => $this->getStripeKey(), - ))) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue('Add Payment Method')); - - Javelin::initBehavior( - 'stripe-payment-form', - array( - 'stripePublishKey' => $this->getStripeKey(), - 'root' => $form_id, - )); - - return $form->render(); - } -} diff --git a/webroot/rsrc/css/application/phortune/stripe-payment-form.css b/webroot/rsrc/css/application/phortune/phortune-credit-card-form.css similarity index 72% rename from webroot/rsrc/css/application/phortune/stripe-payment-form.css rename to webroot/rsrc/css/application/phortune/phortune-credit-card-form.css index 2aac0ce627..b7904195ae 100644 --- a/webroot/rsrc/css/application/phortune/stripe-payment-form.css +++ b/webroot/rsrc/css/application/phortune/phortune-credit-card-form.css @@ -1,5 +1,5 @@ /** - * @provides stripe-payment-form-css + * @provides phortune-credit-card-form-css */ .credit-card-logos { diff --git a/webroot/rsrc/externals/stripe-js/stripe_core.js b/webroot/rsrc/externals/stripe-js/stripe_core.js deleted file mode 100644 index 83ce17876d..0000000000 --- a/webroot/rsrc/externals/stripe-js/stripe_core.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @provides stripe-core - * @do-not-minify - */ -(function(c){function k(a){return a.replace(/^\s+|\s+$/g,"")}function n(){if(!c.publishableKey)throw"No Publishable API Key: Call Stripe.setPublishableKey to provide your key.";}var d=null,l={};typeof window!=="undefined"&&!window.JSON&&(window.JSON={});(function(){if(typeof JSON.parse!=="function")JSON.parse=function(a,b){function d(a,e){var c,h,f=a[e];if(f&&typeof f==="object")for(c in f)Object.hasOwnProperty.call(f,c)&&(h=d(f,c),h!==void 0?f[c]=h:delete f[c]);return b.call(a,e,f)}var e=RegExp("[\\u0000\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]", -"g"),a=String(a);e.lastIndex=0;e.test(a)&&(a=a.replace(e,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return e=eval("("+a+")"),typeof b==="function"?d({"":e},""):e;throw new SyntaxError("JSON.parse");}})();var v=function(a){function b(){d=null;var a=document.getElementsByTagName("body")[0], -b=document.createElement("iframe");m="stripeFrame"+(new Date).getTime();q=j.apiURL+"/js/v1/apitunnel.html";var i=q+"#"+encodeURIComponent(window.location.href);b.setAttribute("src",i);b.setAttribute("name",m);b.setAttribute("id",m);b.setAttribute("frameborder","0");b.setAttribute("scrolling","no");b.setAttribute("allowtransparency","true");b.setAttribute("width",0);b.setAttribute("height",0);b.setAttribute("style","position:absolute;top:0;left:0;width:0;height:0");i=function(){d=window.frames[m]; -c()};b.attachEvent?b.attachEvent("onload",i):b.onload=i;a.appendChild(b)}function c(){if(d){var b=o.length;if(b>0){for(var e=0;e=10)if(b=a.length<=16)if(a.match(/^[0-9]+$/)===null)b=!1;else{var a=a.split("").reverse().join(""),c=0,d;for(b=0;b=3&&a.length<=4};c.validateExpiry=function(a,b){var a=k(a),b=k(b),c=new Date;return a.match(/^[0-9]+$/)!==null&&b.match(/^[0-9]+$/)!==null&&b>c.getFullYear()||b==c.getFullYear()&&a>=c.getMonth()+1};c.createToken=function(a,b,d){typeof b==="function"&&(d=b,b=null);n();var e={expMonth:"exp_month",expYear:"exp_year",addressLine1:"address_line_1",addressLine2:"address_line_2",addressZip:"address_zip",addressState:"address_state", -addressCountry:"address_country"};for(convertibleParam in e)e.hasOwnProperty(convertibleParam)&&a.hasOwnProperty(convertibleParam)&&(a[e[convertibleParam]]=a[convertibleParam],delete a[convertibleParam]);params={card:a};b!==null&&(params.amount=b);c.transport.callAPI("POST","tokens",params,c.publishableKey,d)};c.getToken=function(a,b){n();c.transport.callAPI("GET","tokens/"+a,{},c.publishableKey,b)};c.setPublishableKey=function(a){c.publishableKey=a};l.init()})(typeof exports!=="undefined"&&exports!== -null?exports:window.Stripe={}); 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 4c06828dd2..9715e5f0c7 100644 --- a/webroot/rsrc/js/application/phortune/behavior-stripe-payment-form.js +++ b/webroot/rsrc/js/application/phortune/behavior-stripe-payment-form.js @@ -4,84 +4,63 @@ * javelin-dom * javelin-json * javelin-workflow + * phortune-credit-card-form */ JX.behavior('stripe-payment-form', function(config) { - Stripe.setPublishableKey(config.stripePublishKey); + Stripe.setPublishableKey(config.stripePublishableKey); - var root = JX.$(config.root); - var cardErrors = JX.DOM.find(root, 'input', 'card-errors-input'); - var stripeToken = JX.DOM.find(root, 'input', 'stripe-token-input'); - - var getCardData = function() { - return { - number : JX.DOM.find(root, 'input', 'number-input').value, - cvc : JX.DOM.find(root, 'input', 'cvc-input' ).value, - month : JX.DOM.find(root, 'select', 'month-input' ).value, - year : JX.DOM.find(root, 'select', 'year-input' ).value - }; - } + var root = JX.$(config.formID); + var ccform = new JX.PhortuneCreditCardForm(root); var onsubmit = function(e) { e.kill(); // validate the card data with Stripe client API and submit the form // with any detected errors - var cardData = getCardData(); - var errors = []; + var cardData = ccform.getCardData(); + var errors = []; + if (!Stripe.validateCardNumber(cardData.number)) { errors.push('number'); } + if (!Stripe.validateCVC(cardData.cvc)) { errors.push('cvc'); } + if (!Stripe.validateExpiry(cardData.month, cardData.year)) { errors.push('expiry'); } if (errors.length) { - cardErrors.value = JX.JSON.stringify(errors); - JX.Workflow.newFromForm(root) + JX.Workflow + .newFromForm(root, {cardErrors: JX.JSON.stringify(errors)}) .start(); return; } - // no errors detected so contact Stripe asynchronously - var submitData = { - number : cardData.number, - cvc : cardData.cvc, - exp_month : cardData.month, - exp_year : cardData.year + var data = { + number: cardData.number, + cvc: cardData.cvc, + exp_month: cardData.month, + exp_year: cardData.year }; - Stripe.createToken(submitData, stripeResponseHandler); - return false; + + Stripe.createToken(data, onresponse); } - var stripeResponseHandler = function(status, response) { + var onresponse = function(status, response) { + var errors = []; + var token = null; if (response.error) { - var errors = []; - switch (response.error.type) { - case 'card_error': - errors.push(response.error.code); - break; - case 'invalid_request_error': - errors.push('invalid_request'); - break; - case 'api_error': - default: - errors.push('stripe'); - break; - } - cardErrors.value = JX.JSON.stringify(errors); + errors = [response.error.type]; } else { - // success - we can use the token to create a customer object with - // Stripe and let the billing commence! - var token = response['id']; - cardErrors.value = '[]'; - stripeToken.value = token; + token = response.id; } - JX.Workflow.newFromForm(root) + JX.Workflow + .newFromForm(root, {cardErrors: errors, stripeToken: token}) .start(); } diff --git a/webroot/rsrc/js/application/phortune/phortune-credit-card-form.js b/webroot/rsrc/js/application/phortune/phortune-credit-card-form.js new file mode 100644 index 0000000000..fc6d063303 --- /dev/null +++ b/webroot/rsrc/js/application/phortune/phortune-credit-card-form.js @@ -0,0 +1,38 @@ +/** + * @provides phortune-credit-card-form + * @requires javelin-install + * javelin-dom + */ + +/** + * Simple wrapper for credit card forms generated by `PhortuneCreditCardForm`. + * + * To construct an object for a form: + * + * new JX.PhortuneCreditCardForm(form_root_node); + * + * To read card data from a form: + * + * var data = ccform.getCardData(); + */ +JX.install('PhortuneCreditCardForm', { + construct : function(root) { + this._root = root; + }, + + members : { + _root : null, + + getCardData : function() { + var root = this._root; + + return { + number : JX.DOM.find(root, 'input', 'number-input').value, + cvc : JX.DOM.find(root, 'input', 'cvc-input' ).value, + month : JX.DOM.find(root, 'select', 'month-input' ).value, + year : JX.DOM.find(root, 'select', 'year-input' ).value + }; + } + } + +});