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:
parent
9c43029277
commit
a8bc87578e
10 changed files with 204 additions and 270 deletions
|
@ -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',
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
103
src/applications/phortune/view/PhortuneCreditCardForm.php
Normal file
103
src/applications/phortune/view/PhortuneCreditCardForm.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* @provides stripe-payment-form-css
|
* @provides phortune-credit-card-form-css
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.credit-card-logos {
|
.credit-card-logos {
|
17
webroot/rsrc/externals/stripe-js/stripe_core.js
vendored
17
webroot/rsrc/externals/stripe-js/stripe_core.js
vendored
|
@ -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={});
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in a new issue