1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-01 19:22:42 +01:00

Clean up rendering of credit card form

Summary:
General cleanup and separation into generic vs Stripe blocks of code.

  - There was an old CC form view for Stripe stuff that I never cleaned up; clean that up.
  - Move non-Stripe CC form rendering into a base class (Balanced can reuse it).
  - Move non-Stripe CC form JS into a shareable class.
  - Simplify JS a bit (JX.Workflow can add extra parameters to a request, so we don't need hidden inputs).
  - Genericize CSS.
  - Depend on Stripe JS directly, if they're down we're not going to be able to add cards anyway.

Ref T2787.

Test Plan: Hit all Stripe errors and added new cards.

Reviewers: btrahan, chad

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2787

Differential Revision: https://secure.phabricator.com/D5758
This commit is contained in:
epriestley 2013-04-25 09:46:59 -07:00
parent 9c43029277
commit a8bc87578e
10 changed files with 204 additions and 270 deletions

View file

@ -2258,7 +2258,7 @@ celerity_register_resource_map(array(
), ),
'javelin-behavior-stripe-payment-form' => 'javelin-behavior-stripe-payment-form' =>
array( 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', 'type' => 'js',
'requires' => 'requires' =>
array( array(
@ -2266,6 +2266,7 @@ celerity_register_resource_map(array(
1 => 'javelin-dom', 1 => 'javelin-dom',
2 => 'javelin-json', 2 => 'javelin-json',
3 => 'javelin-workflow', 3 => 'javelin-workflow',
4 => 'phortune-credit-card-form',
), ),
'disk' => '/rsrc/js/application/phortune/behavior-stripe-payment-form.js', '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', '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' => 'phrequent-css' =>
array( array(
'uri' => '/res/9d6f3eb7/rsrc/css/application/phrequent/phrequent.css', 'uri' => '/res/9d6f3eb7/rsrc/css/application/phrequent/phrequent.css',
@ -3921,24 +3942,6 @@ celerity_register_resource_map(array(
), ),
'disk' => '/rsrc/css/sprite-tokens.css', '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' => 'syntax-highlighting-css' =>
array( array(
'uri' => '/res/cb3b9dc0/rsrc/css/core/syntax.css', 'uri' => '/res/cb3b9dc0/rsrc/css/core/syntax.css',

View file

@ -1583,11 +1583,13 @@ phutil_register_library_map(array(
'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php', 'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php',
'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php', 'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php',
'PhortuneController' => 'applications/phortune/controller/PhortuneController.php', 'PhortuneController' => 'applications/phortune/controller/PhortuneController.php',
'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php',
'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php', 'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php',
'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php', 'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php',
'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php', 'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php',
'PhortuneMultiplePaymentProvidersException' => 'applications/phortune/exception/PhortuneMultiplePaymentProvidersException.php', 'PhortuneMultiplePaymentProvidersException' => 'applications/phortune/exception/PhortuneMultiplePaymentProvidersException.php',
'PhortuneNoPaymentProviderException' => 'applications/phortune/exception/PhortuneNoPaymentProviderException.php', 'PhortuneNoPaymentProviderException' => 'applications/phortune/exception/PhortuneNoPaymentProviderException.php',
'PhortuneNotImplementedException' => 'applications/phortune/exception/PhortuneNotImplementedException.php',
'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php', 'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php',
'PhortunePaymentMethodEditController' => 'applications/phortune/controller/PhortunePaymentMethodEditController.php', 'PhortunePaymentMethodEditController' => 'applications/phortune/controller/PhortunePaymentMethodEditController.php',
'PhortunePaymentMethodListController' => 'applications/phortune/controller/PhortunePaymentMethodListController.php', 'PhortunePaymentMethodListController' => 'applications/phortune/controller/PhortunePaymentMethodListController.php',
@ -1604,7 +1606,6 @@ phutil_register_library_map(array(
'PhortuneProductTransactionQuery' => 'applications/phortune/query/PhortuneProductTransactionQuery.php', 'PhortuneProductTransactionQuery' => 'applications/phortune/query/PhortuneProductTransactionQuery.php',
'PhortuneProductViewController' => 'applications/phortune/controller/PhortuneProductViewController.php', 'PhortuneProductViewController' => 'applications/phortune/controller/PhortuneProductViewController.php',
'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php', 'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php',
'PhortuneStripePaymentFormView' => 'applications/phortune/view/PhortuneStripePaymentFormView.php',
'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php', 'PhortuneStripePaymentProvider' => 'applications/phortune/provider/PhortuneStripePaymentProvider.php',
'PhortuneTestExtraPaymentProvider' => 'applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php', 'PhortuneTestExtraPaymentProvider' => 'applications/phortune/provider/__tests__/PhortuneTestExtraPaymentProvider.php',
'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php', 'PhortuneTestPaymentProvider' => 'applications/phortune/provider/PhortuneTestPaymentProvider.php',
@ -3309,6 +3310,7 @@ phutil_register_library_map(array(
'PhortuneMonthYearExpiryControl' => 'AphrontFormControl', 'PhortuneMonthYearExpiryControl' => 'AphrontFormControl',
'PhortuneMultiplePaymentProvidersException' => 'Exception', 'PhortuneMultiplePaymentProvidersException' => 'Exception',
'PhortuneNoPaymentProviderException' => 'Exception', 'PhortuneNoPaymentProviderException' => 'Exception',
'PhortuneNotImplementedException' => 'Exception',
'PhortunePaymentMethod' => 'PhortunePaymentMethod' =>
array( array(
0 => 'PhortuneDAO', 0 => 'PhortuneDAO',
@ -3332,7 +3334,6 @@ phutil_register_library_map(array(
'PhortuneProductTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhortuneProductTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhortuneProductViewController' => 'PhortuneController', 'PhortuneProductViewController' => 'PhortuneController',
'PhortunePurchase' => 'PhortuneDAO', 'PhortunePurchase' => 'PhortuneDAO',
'PhortuneStripePaymentFormView' => 'AphrontView',
'PhortuneStripePaymentProvider' => 'PhortunePaymentProvider', 'PhortuneStripePaymentProvider' => 'PhortunePaymentProvider',
'PhortuneTestExtraPaymentProvider' => 'PhortunePaymentProvider', 'PhortuneTestExtraPaymentProvider' => 'PhortunePaymentProvider',
'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider', 'PhortuneTestPaymentProvider' => 'PhortunePaymentProvider',

View file

@ -81,7 +81,6 @@ abstract class PhortunePaymentProvider {
PhortuneCharge $charge); PhortuneCharge $charge);
/* -( Adding Payment Methods )--------------------------------------------- */ /* -( Adding Payment Methods )--------------------------------------------- */
@ -112,4 +111,5 @@ abstract class PhortunePaymentProvider {
throw new PhortuneNotImplementedException($this); throw new PhortuneNotImplementedException($this);
} }
} }

View file

@ -85,6 +85,8 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider {
$card_errors = $request->getStr('cardErrors'); $card_errors = $request->getStr('cardErrors');
$stripe_token = $request->getStr('stripeToken'); $stripe_token = $request->getStr('stripeToken');
$errors = array();
if ($card_errors) { if ($card_errors) {
$raw_errors = json_decode($card_errors); $raw_errors = json_decode($card_errors);
$errors = $this->parseRawCreatePaymentMethodErrors($raw_errors); $errors = $this->parseRawCreatePaymentMethodErrors($raw_errors);
@ -140,77 +142,21 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider {
AphrontRequest $request, AphrontRequest $request,
array $errors) { array $errors) {
$e_card_number = isset($errors['number']) ? pht('Invalid') : true; $ccform = id(new PhortuneCreditCardForm())
$e_card_cvc = isset($errors['cvc']) ? pht('Invalid') : true; ->setUser($request->getUser())
$e_card_exp = isset($errors['exp']) ? pht('Invalid') : null; ->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( Javelin::initBehavior(
'stripe-payment-form', 'stripe-payment-form',
array( array(
'stripePublishKey' => $this->getPublishableKey(), 'stripePublishableKey' => $this->getPublishableKey(),
'root' => $form_id, 'formID' => $ccform->getFormID(),
)); ));
return $form; return $ccform->buildForm();
} }

View file

@ -0,0 +1,103 @@
<?php
final class PhortuneCreditCardForm {
private $formID;
private $scripts = array();
private $user;
private $cardNumberError;
private $cardCVCError;
private $cardExpirationError;
public function setUser(PhabricatorUser $user) {
$this->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;
}
}

View file

@ -1,119 +0,0 @@
<?php
final class PhortuneStripePaymentFormView extends AphrontView {
private $stripeKey;
private $cardNumberError;
private $cardCVCError;
private $cardExpirationError;
public function setStripeKey($key) {
$this->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();
}
}

View file

@ -1,5 +1,5 @@
/** /**
* @provides stripe-payment-form-css * @provides phortune-credit-card-form-css
*/ */
.credit-card-logos { .credit-card-logos {

View file

@ -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<b;++e){var i=o[e].message,r=i.id;h[r]=o[e].callback;a.postMessage(i,j.apiURL,q,d);f[r]=window.setTimeout(function(a){h[a](504,{error:{message:"There was an error processing your card"}});delete h[a];delete f[a]},6E4,r)}o=[]}}}if(typeof a==="undefined"){var a={},e=function(a){if(typeof a==="undefined"){var a={},b=function(){var a={};a.serialize=function(b,e){var d=[],c;for(c in b)if(b.hasOwnProperty(c)){var u=
e?e+"["+c+"]":c,f=b[c];d.push(typeof f=="object"?a.serialize(f,u):encodeURIComponent(u)+"="+encodeURIComponent(f))}return d.join("&")};a.deserialize=function(a){for(var b={},a=a.split("&"),e=a.length,c=null,d=null,i=0;i<e;++i){d=a[i].split("=");d[0]=decodeURIComponent(d[0]);d[1]=decodeURIComponent(d[1]);for(var f=d[0],c=[],g=-1;(g=f.indexOf("["))!==-1;)c.push(f.substr(g,f.indexOf("]")-g+1)),f=f.substr(f.indexOf("]")+1);if(c.length===0)b[d[0]]=d[1];else{g=d[0].substr(0,d[0].indexOf("["));typeof b[g]===
"undefined"&&(b[g]={});for(var f=b[g],t=c.length,p=0;p<t-1;++p)g=c[p].substr(1,c[p].length-2),typeof f[g]==="undefined"&&(f[g]={}),f=f[g];c=c[t-1];g=c.substr(1,c.length-2);f[g]=d[1]}}return b};return a};typeof a!=="undefined"?a=b():exports.createSerializer=b}return{postMessage:function(b,c,d,e){if(typeof window!=="undefined")b=a.serialize(b),typeof window.postMessage==="undefined"?e.location.href=d+"#"+ +new Date+Math.floor(Math.random()*1E3)+"&"+b:e.postMessage(b,c)},receiveMessage:function(b,c){if(typeof window!==
"undefined")if(window.postMessage)attachedCallback=function(d){if(d.origin.toLowerCase()!==c.toLowerCase())return!1;b(a.deserialize(d.data))},window.addEventListener?window.addEventListener("message",attachedCallback,!1):window.attachEvent("onmessage",attachedCallback);else{var d=window.location.hash;setInterval(function(){var c=window.location.hash,e=/^#?\d+&/;if(c!==d&&e.test(c))d=c,window.location.hash="",b(a.deserialize(c.replace(e,"")))},100)}}}};typeof a!=="undefined"?a=e():exports.createXD=
e}var d=null,o=[],l=0,h={},f={},k=!1,m,q,j={apiURL:"https://api.stripe.com",onMessage:function(a){var b=a.id,c=null,c=a.response===null||a.response===""?{error:{message:"There was an error processing your card"}}:JSON.parse(a.response);h[b](parseInt(a.status),c);window.clearTimeout(f[b]);delete h[b];delete f[b]}},n=!1,s=function(){b();n||(a.receiveMessage(j.onMessage,j.apiURL),n=!0)};j.init=function(){if(!m||!document.getElementById(m))typeof document!=="undefined"&&document&&document.body?s():typeof window!==
"undefined"&&window&&!k&&(window.addEventListener?window.addEventListener("load",s,!1):window.attachEvent&&window.attachEvent("onload",s)),k=!0};j.callAPI=function(a,b,d,e,f){if(a!=="POST"&&a!=="GET"&&a!=="DELETE")throw"You can only call the API with POST, GET or DELETE";j.init();var h=(l++).toString();o.push({message:{id:h,method:a,url:"/v1/"+b,params:d,key:e},callback:f});c()};return j};typeof l!=="undefined"?l=v():exports.createTransport=v;c.transport=l;c.validateCardNumber=function(a){var a=a.replace(/\s+|-/g,
""),b;if(b=a.length>=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<a.length;++b)d=parseInt(a.charAt(b),10),b%2!=0&&(d*=2),c+=d<10?d:d-9;b=c!=0&&c%10==0}return b};c.cardType=function(a){if(!d){d={};for(var b=40;b<=49;++b)d[b]="Visa";for(b=50;b<=59;++b)d[b]="MasterCard";d[34]=d[37]="American Express";d[60]=d[62]=d[64]=d[65]="Discover";d[35]="JCB";d[30]=d[36]=d[38]=d[39]="Diners Club"}a=d[a.substr(0,2)];return typeof a==="undefined"?
"Unknown":a};c.validateCVC=function(a){a=k(a);return a.match(/^[0-9]+$/)!==null&&a.length>=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={});

View file

@ -4,84 +4,63 @@
* javelin-dom * javelin-dom
* javelin-json * javelin-json
* javelin-workflow * javelin-workflow
* phortune-credit-card-form
*/ */
JX.behavior('stripe-payment-form', function(config) { JX.behavior('stripe-payment-form', function(config) {
Stripe.setPublishableKey(config.stripePublishKey); Stripe.setPublishableKey(config.stripePublishableKey);
var root = JX.$(config.root); var root = JX.$(config.formID);
var cardErrors = JX.DOM.find(root, 'input', 'card-errors-input'); var ccform = new JX.PhortuneCreditCardForm(root);
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 onsubmit = function(e) { var onsubmit = function(e) {
e.kill(); e.kill();
// validate the card data with Stripe client API and submit the form // validate the card data with Stripe client API and submit the form
// with any detected errors // with any detected errors
var cardData = getCardData(); var cardData = ccform.getCardData();
var errors = []; var errors = [];
if (!Stripe.validateCardNumber(cardData.number)) { if (!Stripe.validateCardNumber(cardData.number)) {
errors.push('number'); errors.push('number');
} }
if (!Stripe.validateCVC(cardData.cvc)) { if (!Stripe.validateCVC(cardData.cvc)) {
errors.push('cvc'); errors.push('cvc');
} }
if (!Stripe.validateExpiry(cardData.month, cardData.year)) { if (!Stripe.validateExpiry(cardData.month, cardData.year)) {
errors.push('expiry'); errors.push('expiry');
} }
if (errors.length) { if (errors.length) {
cardErrors.value = JX.JSON.stringify(errors); JX.Workflow
JX.Workflow.newFromForm(root) .newFromForm(root, {cardErrors: JX.JSON.stringify(errors)})
.start(); .start();
return; return;
} }
// no errors detected so contact Stripe asynchronously var data = {
var submitData = { number: cardData.number,
number : cardData.number, cvc: cardData.cvc,
cvc : cardData.cvc, exp_month: cardData.month,
exp_month : cardData.month, exp_year: cardData.year
exp_year : cardData.year
}; };
Stripe.createToken(submitData, stripeResponseHandler);
return false; Stripe.createToken(data, onresponse);
} }
var stripeResponseHandler = function(status, response) { var onresponse = function(status, response) {
if (response.error) {
var errors = []; var errors = [];
switch (response.error.type) { var token = null;
case 'card_error': if (response.error) {
errors.push(response.error.code); errors = [response.error.type];
break;
case 'invalid_request_error':
errors.push('invalid_request');
break;
case 'api_error':
default:
errors.push('stripe');
break;
}
cardErrors.value = JX.JSON.stringify(errors);
} else { } else {
// success - we can use the token to create a customer object with token = response.id;
// Stripe and let the billing commence!
var token = response['id'];
cardErrors.value = '[]';
stripeToken.value = token;
} }
JX.Workflow.newFromForm(root) JX.Workflow
.newFromForm(root, {cardErrors: errors, stripeToken: token})
.start(); .start();
} }

View file

@ -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
};
}
}
});