mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-12 18:02:40 +01:00
General cleanup for adding payment methods in Phortune
Summary: This has no real behavioral changes (except better error handling), it just factors things out to be a bit cleaner. In particular: - Move more shared form behaviors into the common JS form component. - Move more error handling into shared pathways. - Make the specialized Stripe / Balanced methods do less work. This needs some more polish for nontrival errors (especially on the Balanced side) but none of the error behavior is worse than it was and a lot of it is much better. Ref T2787. Test Plan: Hit all invalid form errors, added valid payment methods with Stripe and Balacned. Reviewers: btrahan, chad Reviewed By: btrahan CC: aran Maniphest Tasks: T2787 Differential Revision: https://secure.phabricator.com/D5771
This commit is contained in:
parent
6efba56448
commit
7a5f622820
16 changed files with 443 additions and 287 deletions
19
resources/sql/patches/20130423.phortunepaymentrevised.sql
Normal file
19
resources/sql/patches/20130423.phortunepaymentrevised.sql
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
TRUNCATE TABLE {$NAMESPACE}_phortune.phortune_paymentmethod;
|
||||||
|
|
||||||
|
ALTER TABLE {$NAMESPACE}_phortune.phortune_paymentmethod
|
||||||
|
ADD brand VARCHAR(64) NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE {$NAMESPACE}_phortune.phortune_paymentmethod
|
||||||
|
ADD expires VARCHAR(16) NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE {$NAMESPACE}_phortune.phortune_paymentmethod
|
||||||
|
ADD providerType VARCHAR(16) NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE {$NAMESPACE}_phortune.phortune_paymentmethod
|
||||||
|
ADD providerDomain VARCHAR(64) NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE {$NAMESPACE}_phortune.phortune_paymentmethod
|
||||||
|
ADD lastFourDigits VARCHAR(16) NOT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE {$NAMESPACE}_phortune.phortune_paymentmethod
|
||||||
|
DROP expiresEpoch;
|
|
@ -1276,15 +1276,13 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'javelin-behavior-balanced-payment-form' =>
|
'javelin-behavior-balanced-payment-form' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/2a850a31/rsrc/js/application/phortune/behavior-balanced-payment-form.js',
|
'uri' => '/res/6876492d/rsrc/js/application/phortune/behavior-balanced-payment-form.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
0 => 'javelin-behavior',
|
0 => 'javelin-behavior',
|
||||||
1 => 'javelin-dom',
|
1 => 'javelin-dom',
|
||||||
2 => 'javelin-json',
|
2 => 'phortune-credit-card-form',
|
||||||
3 => 'javelin-workflow',
|
|
||||||
4 => 'phortune-credit-card-form',
|
|
||||||
),
|
),
|
||||||
'disk' => '/rsrc/js/application/phortune/behavior-balanced-payment-form.js',
|
'disk' => '/rsrc/js/application/phortune/behavior-balanced-payment-form.js',
|
||||||
),
|
),
|
||||||
|
@ -2272,15 +2270,13 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'javelin-behavior-stripe-payment-form' =>
|
'javelin-behavior-stripe-payment-form' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/2ae12d96/rsrc/js/application/phortune/behavior-stripe-payment-form.js',
|
'uri' => '/res/c1a12d77/rsrc/js/application/phortune/behavior-stripe-payment-form.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
0 => 'javelin-behavior',
|
0 => 'javelin-behavior',
|
||||||
1 => 'javelin-dom',
|
1 => 'javelin-dom',
|
||||||
2 => 'javelin-json',
|
2 => 'phortune-credit-card-form',
|
||||||
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',
|
||||||
),
|
),
|
||||||
|
@ -3605,12 +3601,15 @@ celerity_register_resource_map(array(
|
||||||
),
|
),
|
||||||
'phortune-credit-card-form' =>
|
'phortune-credit-card-form' =>
|
||||||
array(
|
array(
|
||||||
'uri' => '/res/7be5799a/rsrc/js/application/phortune/phortune-credit-card-form.js',
|
'uri' => '/res/bc948778/rsrc/js/application/phortune/phortune-credit-card-form.js',
|
||||||
'type' => 'js',
|
'type' => 'js',
|
||||||
'requires' =>
|
'requires' =>
|
||||||
array(
|
array(
|
||||||
0 => 'javelin-install',
|
0 => 'javelin-install',
|
||||||
1 => 'javelin-dom',
|
1 => 'javelin-dom',
|
||||||
|
2 => 'javelin-json',
|
||||||
|
3 => 'javelin-workflow',
|
||||||
|
4 => 'javelin-util',
|
||||||
),
|
),
|
||||||
'disk' => '/rsrc/js/application/phortune/phortune-credit-card-form.js',
|
'disk' => '/rsrc/js/application/phortune/phortune-credit-card-form.js',
|
||||||
),
|
),
|
||||||
|
|
|
@ -1583,9 +1583,11 @@ phutil_register_library_map(array(
|
||||||
'PhortuneBalancedPaymentProvider' => 'applications/phortune/provider/PhortuneBalancedPaymentProvider.php',
|
'PhortuneBalancedPaymentProvider' => 'applications/phortune/provider/PhortuneBalancedPaymentProvider.php',
|
||||||
'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php',
|
'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php',
|
||||||
'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php',
|
'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php',
|
||||||
|
'PhortuneConstants' => 'applications/phortune/constants/PhortuneConstants.php',
|
||||||
'PhortuneController' => 'applications/phortune/controller/PhortuneController.php',
|
'PhortuneController' => 'applications/phortune/controller/PhortuneController.php',
|
||||||
'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php',
|
'PhortuneCreditCardForm' => 'applications/phortune/view/PhortuneCreditCardForm.php',
|
||||||
'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php',
|
'PhortuneDAO' => 'applications/phortune/storage/PhortuneDAO.php',
|
||||||
|
'PhortuneErrCode' => 'applications/phortune/constants/PhortuneErrCode.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',
|
||||||
|
@ -3308,6 +3310,7 @@ phutil_register_library_map(array(
|
||||||
'PhortuneCharge' => 'PhortuneDAO',
|
'PhortuneCharge' => 'PhortuneDAO',
|
||||||
'PhortuneController' => 'PhabricatorController',
|
'PhortuneController' => 'PhabricatorController',
|
||||||
'PhortuneDAO' => 'PhabricatorLiskDAO',
|
'PhortuneDAO' => 'PhabricatorLiskDAO',
|
||||||
|
'PhortuneErrCode' => 'PhortuneConstants',
|
||||||
'PhortuneLandingController' => 'PhortuneController',
|
'PhortuneLandingController' => 'PhortuneController',
|
||||||
'PhortuneMonthYearExpiryControl' => 'AphrontFormControl',
|
'PhortuneMonthYearExpiryControl' => 'AphrontFormControl',
|
||||||
'PhortuneMultiplePaymentProvidersException' => 'Exception',
|
'PhortuneMultiplePaymentProvidersException' => 'Exception',
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
abstract class PhortuneConstants {
|
||||||
|
|
||||||
|
}
|
11
src/applications/phortune/constants/PhortuneErrCode.php
Normal file
11
src/applications/phortune/constants/PhortuneErrCode.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhortuneErrCode extends PhortuneConstants {
|
||||||
|
|
||||||
|
// NOTE: These constants also appear in Javascript.
|
||||||
|
|
||||||
|
const ERR_CC_INVALID_NUMBER = 'cc:invalid:number';
|
||||||
|
const ERR_CC_INVALID_CVC = 'cc:invalid:cvc';
|
||||||
|
const ERR_CC_INVALID_EXPIRY = 'cc:invalid:expiry';
|
||||||
|
|
||||||
|
}
|
|
@ -111,7 +111,7 @@ final class PhortuneAccountViewController extends PhortuneController {
|
||||||
|
|
||||||
foreach ($methods as $method) {
|
foreach ($methods as $method) {
|
||||||
$item = new PhabricatorObjectItemView();
|
$item = new PhabricatorObjectItemView();
|
||||||
$item->setHeader($method->getName());
|
$item->setHeader($method->getBrand().' / '.$method->getLastFourDigits());
|
||||||
|
|
||||||
switch ($method->getStatus()) {
|
switch ($method->getStatus()) {
|
||||||
case PhortunePaymentMethod::STATUS_ACTIVE:
|
case PhortunePaymentMethod::STATUS_ACTIVE:
|
||||||
|
@ -126,10 +126,6 @@ final class PhortuneAccountViewController extends PhortuneController {
|
||||||
phabricator_datetime($method->getDateCreated(), $user),
|
phabricator_datetime($method->getDateCreated(), $user),
|
||||||
$this->getHandle($method->getAuthorPHID())->renderLink()));
|
$this->getHandle($method->getAuthorPHID())->renderLink()));
|
||||||
|
|
||||||
if ($method->getExpiresEpoch() < time() + (60 * 60 * 24 * 30)) {
|
|
||||||
$item->addAttribute(pht('Expires Soon!'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$list->addItem($item);
|
$list->addItem($item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,9 +48,38 @@ final class PhortunePaymentMethodEditController
|
||||||
->setAccountPHID($account->getPHID())
|
->setAccountPHID($account->getPHID())
|
||||||
->setAuthorPHID($user->getPHID())
|
->setAuthorPHID($user->getPHID())
|
||||||
->setStatus(PhortunePaymentMethod::STATUS_ACTIVE)
|
->setStatus(PhortunePaymentMethod::STATUS_ACTIVE)
|
||||||
->setMetadataValue('providerKey', $provider->getProviderKey());
|
->setProviderType($provider->getProviderType())
|
||||||
|
->setProviderDomain($provider->getProviderDomain());
|
||||||
|
|
||||||
$errors = $provider->createPaymentMethodFromRequest($request, $method);
|
if (!$errors) {
|
||||||
|
$errors = $this->processClientErrors(
|
||||||
|
$provider,
|
||||||
|
$request->getStr('errors'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$errors) {
|
||||||
|
$client_token_raw = $request->getStr('token');
|
||||||
|
$client_token = json_decode($client_token_raw, true);
|
||||||
|
if (!is_array($client_token)) {
|
||||||
|
$errors[] = pht(
|
||||||
|
'There was an error decoding token information submitted by the '.
|
||||||
|
'client. Expected a JSON-encoded token dictionary, received: %s.',
|
||||||
|
nonempty($client_token_raw, pht('nothing')));
|
||||||
|
} else {
|
||||||
|
if (!$provider->validateCreatePaymentMethodToken($client_token)) {
|
||||||
|
$errors[] = pht(
|
||||||
|
'There was an error with the payment token submitted by the '.
|
||||||
|
'client. Expected a valid dictionary, received: %s.',
|
||||||
|
$client_token_raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$errors) {
|
||||||
|
$errors = $provider->createPaymentMethodFromRequest(
|
||||||
|
$request,
|
||||||
|
$method,
|
||||||
|
$client_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!$errors) {
|
if (!$errors) {
|
||||||
$method->save();
|
$method->save();
|
||||||
|
@ -152,4 +181,61 @@ final class PhortunePaymentMethodEditController
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function processClientErrors(
|
||||||
|
PhortunePaymentProvider $provider,
|
||||||
|
$client_errors_raw) {
|
||||||
|
|
||||||
|
$errors = array();
|
||||||
|
|
||||||
|
$client_errors = json_decode($client_errors_raw, true);
|
||||||
|
if (!is_array($client_errors)) {
|
||||||
|
$errors[] = pht(
|
||||||
|
'There was an error decoding error information submitted by the '.
|
||||||
|
'client. Expected a JSON-encoded list of error codes, received: %s.',
|
||||||
|
nonempty($client_errors_raw, pht('nothing')));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (array_unique($client_errors) as $key => $client_error) {
|
||||||
|
$client_errors[$key] = $provider->translateCreatePaymentMethodErrorCode(
|
||||||
|
$client_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (array_unique($client_errors) as $client_error) {
|
||||||
|
switch ($client_error) {
|
||||||
|
case PhortuneErrCode::ERR_CC_INVALID_NUMBER:
|
||||||
|
$message = pht(
|
||||||
|
'The card number you entered is not a valid card number. Check '.
|
||||||
|
'that you entered it correctly.');
|
||||||
|
break;
|
||||||
|
case PhortuneErrCode::ERR_CC_INVALID_CVC:
|
||||||
|
$message = pht(
|
||||||
|
'The CVC code you entered is not a valid CVC code. Check that '.
|
||||||
|
'you entered it correctly. The CVC code is a 3-digit or 4-digit '.
|
||||||
|
'numeric code which usually appears on the back of the card.');
|
||||||
|
break;
|
||||||
|
case PhortuneErrCode::ERR_CC_INVALID_EXPIRY:
|
||||||
|
$message = pht(
|
||||||
|
'The card expiration date is not a valid expiration date. Check '.
|
||||||
|
'that you entered it correctly. You can not add an expired card '.
|
||||||
|
'as a payment method.');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$message = $provider->getCreatePaymentErrorMessage($client_error);
|
||||||
|
if (!$message) {
|
||||||
|
$message = pht(
|
||||||
|
"There was an unexpected error ('%s') processing payment ".
|
||||||
|
"information.",
|
||||||
|
$client_error);
|
||||||
|
|
||||||
|
phlog($message);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$errors[$client_error] = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,71 +55,61 @@ final class PhortuneBalancedPaymentProvider extends PhortunePaymentProvider {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function validateCreatePaymentMethodToken(array $token) {
|
||||||
|
return isset($token['balancedMarketplaceURI']);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @phutil-external-symbol class Balanced\Card
|
||||||
* @phutil-external-symbol class Balanced\Settings
|
* @phutil-external-symbol class Balanced\Settings
|
||||||
* @phutil-external-symbol class Balanced\Marketplace
|
* @phutil-external-symbol class Balanced\Marketplace
|
||||||
* @phutil-external-symbol class RESTful\Exceptions\HTTPError
|
* @phutil-external-symbol class RESTful\Exceptions\HTTPError
|
||||||
*/
|
*/
|
||||||
public function createPaymentMethodFromRequest(
|
public function createPaymentMethodFromRequest(
|
||||||
AphrontRequest $request,
|
AphrontRequest $request,
|
||||||
PhortunePaymentMethod $method) {
|
PhortunePaymentMethod $method,
|
||||||
|
array $token) {
|
||||||
$card_errors = $request->getStr('cardErrors');
|
|
||||||
$balanced_data = $request->getStr('balancedCardData');
|
|
||||||
|
|
||||||
$errors = array();
|
$errors = array();
|
||||||
if ($card_errors) {
|
|
||||||
$raw_errors = json_decode($card_errors);
|
$root = dirname(phutil_get_library_root('phabricator'));
|
||||||
$errors = $this->parseRawCreatePaymentMethodErrors($raw_errors);
|
require_once $root.'/externals/httpful/bootstrap.php';
|
||||||
|
require_once $root.'/externals/restful/bootstrap.php';
|
||||||
|
require_once $root.'/externals/balanced-php/bootstrap.php';
|
||||||
|
|
||||||
|
$account_phid = $method->getAccountPHID();
|
||||||
|
$author_phid = $method->getAuthorPHID();
|
||||||
|
$description = $account_phid.':'.$author_phid;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Balanced\Settings::$api_key = $this->getSecretKey();
|
||||||
|
|
||||||
|
$card = Balanced\Card::get($token['balancedMarketplaceURI']);
|
||||||
|
|
||||||
|
$buyer = Balanced\Marketplace::mine()->createBuyer(
|
||||||
|
null,
|
||||||
|
$card->uri,
|
||||||
|
array(
|
||||||
|
'description' => $description,
|
||||||
|
));
|
||||||
|
|
||||||
|
} catch (RESTful\Exceptions\HTTPError $error) {
|
||||||
|
// NOTE: This exception doesn't print anything meaningful if it escapes
|
||||||
|
// to top level. Replace it with something slightly readable.
|
||||||
|
throw new Exception($error->response->body->description);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$errors) {
|
$method
|
||||||
$data = json_decode($balanced_data, true);
|
->setBrand($card->brand)
|
||||||
if (!is_array($data)) {
|
->setLastFourDigits($card->last_four)
|
||||||
$errors[] = pht('An error occurred decoding card data.');
|
->setExpires($card->expiration_year, $card->expiration_month)
|
||||||
}
|
->setMetadata(
|
||||||
}
|
array(
|
||||||
|
'type' => 'balanced.account',
|
||||||
if (!$errors) {
|
'balanced.accountURI' => $buyer->uri,
|
||||||
$root = dirname(phutil_get_library_root('phabricator'));
|
'balanced.cardURI' => $card->uri,
|
||||||
require_once $root.'/externals/httpful/bootstrap.php';
|
));
|
||||||
require_once $root.'/externals/restful/bootstrap.php';
|
|
||||||
require_once $root.'/externals/balanced-php/bootstrap.php';
|
|
||||||
|
|
||||||
$account_phid = $method->getAccountPHID();
|
|
||||||
$author_phid = $method->getAuthorPHID();
|
|
||||||
$description = $account_phid.':'.$author_phid;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
Balanced\Settings::$api_key = $this->getSecretKey();
|
|
||||||
$buyer = Balanced\Marketplace::mine()->createBuyer(
|
|
||||||
null,
|
|
||||||
$data['uri'],
|
|
||||||
array(
|
|
||||||
'description' => $description,
|
|
||||||
));
|
|
||||||
|
|
||||||
} catch (RESTful\Exceptions\HTTPError $error) {
|
|
||||||
// NOTE: This exception doesn't print anything meaningful if it escapes
|
|
||||||
// to top level. Replace it with something slightly readable.
|
|
||||||
throw new Exception($error->response->body->description);
|
|
||||||
}
|
|
||||||
|
|
||||||
$exp_string = $data['expiration_year'].'-'.$data['expiration_month'];
|
|
||||||
$epoch = strtotime($exp_string);
|
|
||||||
|
|
||||||
$method
|
|
||||||
->setName($data['brand'].' / '.$data['last_four'])
|
|
||||||
->setExpiresEpoch($epoch)
|
|
||||||
->setMetadata(
|
|
||||||
array(
|
|
||||||
'type' => 'balanced.account',
|
|
||||||
'balanced.accountURI' => $buyer->uri,
|
|
||||||
'balanced.cardURI' => $data['uri'],
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $errors;
|
return $errors;
|
||||||
}
|
}
|
||||||
|
@ -130,9 +120,7 @@ final class PhortuneBalancedPaymentProvider extends PhortunePaymentProvider {
|
||||||
|
|
||||||
$ccform = id(new PhortuneCreditCardForm())
|
$ccform = id(new PhortuneCreditCardForm())
|
||||||
->setUser($request->getUser())
|
->setUser($request->getUser())
|
||||||
->setCardNumberError(isset($errors['number']) ? pht('Invalid') : true)
|
->setErrors($errors)
|
||||||
->setCardCVCError(isset($errors['cvc']) ? pht('Invalid') : true)
|
|
||||||
->setCardExpirationError(isset($errors['exp']) ? pht('Invalid') : null)
|
|
||||||
->addScript('https://js.balancedpayments.com/v1/balanced.js');
|
->addScript('https://js.balancedpayments.com/v1/balanced.js');
|
||||||
|
|
||||||
Javelin::initBehavior(
|
Javelin::initBehavior(
|
||||||
|
@ -145,27 +133,43 @@ final class PhortuneBalancedPaymentProvider extends PhortunePaymentProvider {
|
||||||
return $ccform->buildForm();
|
return $ccform->buildForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function parseRawCreatePaymentMethodErrors(array $raw_errors) {
|
private function getBalancedShortErrorCode($error_code) {
|
||||||
$errors = array();
|
$prefix = 'cc:balanced:';
|
||||||
|
if (strncmp($error_code, $prefix, strlen($prefix))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return substr($error_code, strlen($prefix));
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($raw_errors as $error) {
|
public function translateCreatePaymentMethodErrorCode($error_code) {
|
||||||
switch ($error) {
|
$short_code = $this->getBalancedShortErrorCode($error_code);
|
||||||
case 'number':
|
|
||||||
$errors[$error] = pht('Card number is incorrect or invalid.');
|
if ($short_code) {
|
||||||
break;
|
static $map = array(
|
||||||
case 'cvc':
|
);
|
||||||
$errors[$error] = pht('CVC code is incorrect or invalid.');
|
|
||||||
break;
|
if (isset($map[$short_code])) {
|
||||||
case 'exp':
|
return $map[$short_code];
|
||||||
$errors[$error] = pht('Card expiration date is incorrect.');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$errors[] = $error;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $errors;
|
return $error_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatePaymentMethodErrorMessage($error_code) {
|
||||||
|
$short_code = $this->getBalancedShortErrorCode($error_code);
|
||||||
|
if (!$short_code) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($short_code) {
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,12 +92,37 @@ abstract class PhortunePaymentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task addmethod
|
||||||
|
*/
|
||||||
|
public function translateCreatePaymentMethodErrorCode($error_code) {
|
||||||
|
throw new PhortuneNotImplementedException($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task addmethod
|
||||||
|
*/
|
||||||
|
public function getCreatePaymentMethodErrorMessage($error_code) {
|
||||||
|
throw new PhortuneNotImplementedException($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task addmethod
|
||||||
|
*/
|
||||||
|
public function validateCreatePaymentMethodToken(array $token) {
|
||||||
|
throw new PhortuneNotImplementedException($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @task addmethod
|
* @task addmethod
|
||||||
*/
|
*/
|
||||||
public function createPaymentMethodFromRequest(
|
public function createPaymentMethodFromRequest(
|
||||||
AphrontRequest $request,
|
AphrontRequest $request,
|
||||||
PhortunePaymentMethod $method) {
|
PhortunePaymentMethod $method,
|
||||||
|
array $token) {
|
||||||
throw new PhortuneNotImplementedException($this);
|
throw new PhortuneNotImplementedException($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,63 +81,45 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider {
|
||||||
*/
|
*/
|
||||||
public function createPaymentMethodFromRequest(
|
public function createPaymentMethodFromRequest(
|
||||||
AphrontRequest $request,
|
AphrontRequest $request,
|
||||||
PhortunePaymentMethod $method) {
|
PhortunePaymentMethod $method,
|
||||||
|
array $token) {
|
||||||
$card_errors = $request->getStr('cardErrors');
|
|
||||||
$stripe_token = $request->getStr('stripeToken');
|
|
||||||
|
|
||||||
$errors = array();
|
$errors = array();
|
||||||
if ($card_errors) {
|
|
||||||
$raw_errors = json_decode($card_errors);
|
|
||||||
$errors = $this->parseRawCreatePaymentMethodErrors($raw_errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$errors) {
|
$root = dirname(phutil_get_library_root('phabricator'));
|
||||||
if (!$stripe_token) {
|
require_once $root.'/externals/stripe-php/lib/Stripe.php';
|
||||||
$errors[] = pht('There was an unknown error processing your card.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$errors) {
|
$secret_key = $this->getSecretKey();
|
||||||
$root = dirname(phutil_get_library_root('phabricator'));
|
$stripe_token = $token['stripeCardToken'];
|
||||||
require_once $root.'/externals/stripe-php/lib/Stripe.php';
|
|
||||||
|
|
||||||
try {
|
// First, make sure the token is valid.
|
||||||
// First, make sure the token is valid.
|
$info = id(new Stripe_Token())->retrieve($stripe_token, $secret_key);
|
||||||
$secret_key = $this->getSecretKey();
|
|
||||||
|
|
||||||
$info = id(new Stripe_Token())->retrieve($stripe_token, $secret_key);
|
$account_phid = $method->getAccountPHID();
|
||||||
|
$author_phid = $method->getAuthorPHID();
|
||||||
|
|
||||||
$account_phid = $method->getAccountPHID();
|
$params = array(
|
||||||
$author_phid = $method->getAuthorPHID();
|
'card' => $stripe_token,
|
||||||
|
'description' => $account_phid.':'.$author_phid,
|
||||||
|
);
|
||||||
|
|
||||||
$params = array(
|
// Then, we need to create a Customer in order to be able to charge
|
||||||
'card' => $stripe_token,
|
// the card more than once. We create one Customer for each card;
|
||||||
'description' => $account_phid.':'.$author_phid,
|
// they do not map to PhortuneAccounts because we allow an account to
|
||||||
);
|
// have more than one active card.
|
||||||
|
$customer = Stripe_Customer::create($params, $secret_key);
|
||||||
|
|
||||||
// Then, we need to create a Customer in order to be able to charge
|
$card = $info->card;
|
||||||
// the card more than once. We create one Customer for each card;
|
$method
|
||||||
// they do not map to PhortuneAccounts because we allow an account to
|
->setBrand($card->type)
|
||||||
// have more than one active card.
|
->setLastFourDigits($card->last4)
|
||||||
$customer = Stripe_Customer::create($params, $secret_key);
|
->setExpires($card->exp_year, $card->exp_month)
|
||||||
|
->setMetadata(
|
||||||
$card = $info->card;
|
array(
|
||||||
$method
|
'type' => 'stripe.customer',
|
||||||
->setName($card->type.' / '.$card->last4)
|
'stripe.customerID' => $customer->id,
|
||||||
->setExpiresEpoch(strtotime($card->exp_year.'-'.$card->exp_month))
|
'stripe.cardToken' => $stripe_token,
|
||||||
->setMetadata(
|
));
|
||||||
array(
|
|
||||||
'type' => 'stripe.customer',
|
|
||||||
'stripe.customerID' => $customer->id,
|
|
||||||
'stripe.tokenID' => $stripe_token,
|
|
||||||
));
|
|
||||||
} catch (Exception $ex) {
|
|
||||||
phlog($ex);
|
|
||||||
$errors[] = pht(
|
|
||||||
'There was an error communicating with the payments backend.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $errors;
|
return $errors;
|
||||||
}
|
}
|
||||||
|
@ -148,9 +130,7 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider {
|
||||||
|
|
||||||
$ccform = id(new PhortuneCreditCardForm())
|
$ccform = id(new PhortuneCreditCardForm())
|
||||||
->setUser($request->getUser())
|
->setUser($request->getUser())
|
||||||
->setCardNumberError(isset($errors['number']) ? pht('Invalid') : true)
|
->setErrors($errors)
|
||||||
->setCardCVCError(isset($errors['cvc']) ? pht('Invalid') : true)
|
|
||||||
->setCardExpirationError(isset($errors['exp']) ? pht('Invalid') : null)
|
|
||||||
->addScript('https://js.stripe.com/v2/');
|
->addScript('https://js.stripe.com/v2/');
|
||||||
|
|
||||||
Javelin::initBehavior(
|
Javelin::initBehavior(
|
||||||
|
@ -163,64 +143,84 @@ final class PhortuneStripePaymentProvider extends PhortunePaymentProvider {
|
||||||
return $ccform->buildForm();
|
return $ccform->buildForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getStripeShortErrorCode($error_code) {
|
||||||
|
$prefix = 'cc:stripe:';
|
||||||
|
if (strncmp($error_code, $prefix, strlen($prefix))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return substr($error_code, strlen($prefix));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
public function validateCreatePaymentMethodToken(array $token) {
|
||||||
* Stripe JS and calls to Stripe handle all errors with processing this
|
return isset($token['stripeCardToken']);
|
||||||
* form. This function takes the raw errors - in the form of an array
|
}
|
||||||
* where each elementt is $type => $message - and figures out what if
|
|
||||||
* any fields were invalid and pulls the messages into a flat object.
|
|
||||||
*
|
|
||||||
* See https://stripe.com/docs/api#errors for more information on possible
|
|
||||||
* errors.
|
|
||||||
*/
|
|
||||||
private function parseRawCreatePaymentMethodErrors(array $raw_errors) {
|
|
||||||
$errors = array();
|
|
||||||
|
|
||||||
foreach ($raw_errors as $type) {
|
public function translateCreatePaymentMethodErrorCode($error_code) {
|
||||||
$error_key = null;
|
$short_code = $this->getStripeShortErrorCode($error_code);
|
||||||
$message = pht('A card processing error has occurred.');
|
|
||||||
switch ($type) {
|
|
||||||
case 'number':
|
|
||||||
case 'invalid_number':
|
|
||||||
case 'incorrect_number':
|
|
||||||
$error_key = 'number';
|
|
||||||
$message = pht('Invalid or incorrect credit card number.');
|
|
||||||
break;
|
|
||||||
case 'cvc':
|
|
||||||
case 'invalid_cvc':
|
|
||||||
case 'incorrect_cvc':
|
|
||||||
$error_key = 'cvc';
|
|
||||||
$message = pht('Card CVC is invalid or incorrect.');
|
|
||||||
break;
|
|
||||||
case 'expiry':
|
|
||||||
case 'invalid_expiry_month':
|
|
||||||
case 'invalid_expiry_year':
|
|
||||||
$error_key = 'exp';
|
|
||||||
$message = pht('Card expiration date is invalid or incorrect.');
|
|
||||||
break;
|
|
||||||
case 'card_declined':
|
|
||||||
case 'expired_card':
|
|
||||||
case 'duplicate_transaction':
|
|
||||||
case 'processing_error':
|
|
||||||
// these errors don't map well to field(s) being bad
|
|
||||||
break;
|
|
||||||
case 'invalid_amount':
|
|
||||||
case 'missing':
|
|
||||||
default:
|
|
||||||
// these errors only happen if we (not the user) messed up so log it
|
|
||||||
$error = sprintf('[Stripe Error] %s', $type);
|
|
||||||
phlog($error);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($error_key === null || isset($errors[$error_key])) {
|
if ($short_code) {
|
||||||
$errors[] = $message;
|
static $map = array(
|
||||||
} else {
|
'error:invalid_number' => PhortuneErrCode::ERR_CC_INVALID_NUMBER,
|
||||||
$errors[$error_key] = $message;
|
'error:invalid_cvc' => PhortuneErrCode::ERR_CC_INVALID_CVC,
|
||||||
|
'error:invalid_expiry_month' => PhortuneErrCode::ERR_CC_INVALID_EXPIRY,
|
||||||
|
'error:invalid_expiry_year' => PhortuneErrCode::ERR_CC_INVALID_EXPIRY,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isset($map[$short_code])) {
|
||||||
|
return $map[$short_code];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $errors;
|
return $error_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://stripe.com/docs/api#errors for more information on possible
|
||||||
|
* errors.
|
||||||
|
*/
|
||||||
|
public function getCreatePaymentMethodErrorMessage($error_code) {
|
||||||
|
$short_code = $this->getStripeShortErrorCode($error_code);
|
||||||
|
if (!$short_code) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($short_code) {
|
||||||
|
case 'error:incorrect_number':
|
||||||
|
$error_key = 'number';
|
||||||
|
$message = pht('Invalid or incorrect credit card number.');
|
||||||
|
break;
|
||||||
|
case 'error:incorrect_cvc':
|
||||||
|
$error_key = 'cvc';
|
||||||
|
$message = pht('Card CVC is invalid or incorrect.');
|
||||||
|
break;
|
||||||
|
$error_key = 'exp';
|
||||||
|
$message = pht('Card expiration date is invalid or incorrect.');
|
||||||
|
break;
|
||||||
|
case 'error:invalid_expiry_month':
|
||||||
|
case 'error:invalid_expiry_year':
|
||||||
|
case 'error:invalid_cvc':
|
||||||
|
case 'error:invalid_number':
|
||||||
|
// NOTE: These should be translated into Phortune error codes earlier,
|
||||||
|
// so we don't expect to receive them here. They are listed for clarity
|
||||||
|
// and completeness. If we encounter one, we treat it as an unknown
|
||||||
|
// error.
|
||||||
|
break;
|
||||||
|
case 'error:invalid_amount':
|
||||||
|
case 'error:missing':
|
||||||
|
case 'error:card_declined':
|
||||||
|
case 'error:expired_card':
|
||||||
|
case 'error:duplicate_transaction':
|
||||||
|
case 'error:processing_error':
|
||||||
|
default:
|
||||||
|
// NOTE: These errors currently don't recevive a detailed message.
|
||||||
|
// NOTE: We can also end up here with "http:nnn" messages.
|
||||||
|
|
||||||
|
// TODO: At least some of these should have a better message, or be
|
||||||
|
// translated into common errors above.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,16 @@ final class PhortunePaymentMethod extends PhortuneDAO
|
||||||
const STATUS_FAILED = 'payment:failed';
|
const STATUS_FAILED = 'payment:failed';
|
||||||
const STATUS_REMOVED = 'payment:removed';
|
const STATUS_REMOVED = 'payment:removed';
|
||||||
|
|
||||||
protected $name;
|
protected $name = '';
|
||||||
protected $status;
|
protected $status;
|
||||||
protected $accountPHID;
|
protected $accountPHID;
|
||||||
protected $authorPHID;
|
protected $authorPHID;
|
||||||
protected $expiresEpoch;
|
protected $expires;
|
||||||
protected $metadata = array();
|
protected $metadata = array();
|
||||||
|
protected $brand;
|
||||||
|
protected $lastFourDigits;
|
||||||
|
protected $providerType;
|
||||||
|
protected $providerDomain;
|
||||||
|
|
||||||
private $account;
|
private $account;
|
||||||
|
|
||||||
|
@ -47,7 +51,7 @@ final class PhortunePaymentMethod extends PhortuneDAO
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDescription() {
|
public function getDescription() {
|
||||||
return pht('Expires %s', date('m/y'), $this->getExpiresEpoch());
|
return '...';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMetadataValue($key, $default = null) {
|
public function getMetadataValue($key, $default = null) {
|
||||||
|
@ -80,6 +84,11 @@ final class PhortunePaymentMethod extends PhortuneDAO
|
||||||
return head($accept);
|
return head($accept);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setExpires($year, $month) {
|
||||||
|
$this->expires = $year.'-'.$month;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ final class PhortuneCreditCardForm {
|
||||||
private $formID;
|
private $formID;
|
||||||
private $scripts = array();
|
private $scripts = array();
|
||||||
private $user;
|
private $user;
|
||||||
|
private $errors = array();
|
||||||
|
|
||||||
private $cardNumberError;
|
private $cardNumberError;
|
||||||
private $cardCVCError;
|
private $cardCVCError;
|
||||||
|
@ -15,18 +16,8 @@ final class PhortuneCreditCardForm {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCardExpirationError($card_expiration_error) {
|
public function setErrors(array $errors) {
|
||||||
$this->cardExpirationError = $card_expiration_error;
|
$this->errors = $errors;
|
||||||
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;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +54,19 @@ final class PhortuneCreditCardForm {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$errors = $this->errors;
|
||||||
|
$e_number = isset($errors[PhortuneErrCode::ERR_CC_INVALID_NUMBER])
|
||||||
|
? pht('Invalid')
|
||||||
|
: true;
|
||||||
|
|
||||||
|
$e_cvc = isset($errors[PhortuneErrCode::ERR_CC_INVALID_CVC])
|
||||||
|
? pht('Invalid')
|
||||||
|
: true;
|
||||||
|
|
||||||
|
$e_expiry = isset($errors[PhortuneErrCode::ERR_CC_INVALID_EXPIRY])
|
||||||
|
? pht('Invalid')
|
||||||
|
: null;
|
||||||
|
|
||||||
$form
|
$form
|
||||||
->setID($form_id)
|
->setID($form_id)
|
||||||
->appendChild(
|
->appendChild(
|
||||||
|
@ -85,18 +89,18 @@ final class PhortuneCreditCardForm {
|
||||||
->setLabel('Card Number')
|
->setLabel('Card Number')
|
||||||
->setDisableAutocomplete(true)
|
->setDisableAutocomplete(true)
|
||||||
->setSigil('number-input')
|
->setSigil('number-input')
|
||||||
->setError($this->cardNumberError))
|
->setError($e_number))
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
->setLabel('CVC')
|
->setLabel('CVC')
|
||||||
->setDisableAutocomplete(true)
|
->setDisableAutocomplete(true)
|
||||||
->setSigil('cvc-input')
|
->setSigil('cvc-input')
|
||||||
->setError($this->cardCVCError))
|
->setError($e_cvc))
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new PhortuneMonthYearExpiryControl())
|
id(new PhortuneMonthYearExpiryControl())
|
||||||
->setLabel('Expiration')
|
->setLabel('Expiration')
|
||||||
->setUser($this->user)
|
->setUser($this->user)
|
||||||
->setError($this->cardExpirationError));
|
->setError($e_expiry));
|
||||||
|
|
||||||
return $form;
|
return $form;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1246,6 +1246,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
||||||
'type' => 'sql',
|
'type' => 'sql',
|
||||||
'name' => $this->getPatchPath('20130423.updateexternalaccount.sql'),
|
'name' => $this->getPatchPath('20130423.updateexternalaccount.sql'),
|
||||||
),
|
),
|
||||||
|
'20130423.phortunepaymentrevised.sql' => array(
|
||||||
|
'type' => 'sql',
|
||||||
|
'name' => $this->getPatchPath('20130423.phortunepaymentrevised.sql'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,70 +2,56 @@
|
||||||
* @provides javelin-behavior-balanced-payment-form
|
* @provides javelin-behavior-balanced-payment-form
|
||||||
* @requires javelin-behavior
|
* @requires javelin-behavior
|
||||||
* javelin-dom
|
* javelin-dom
|
||||||
* javelin-json
|
|
||||||
* javelin-workflow
|
|
||||||
* phortune-credit-card-form
|
* phortune-credit-card-form
|
||||||
*/
|
*/
|
||||||
|
|
||||||
JX.behavior('balanced-payment-form', function(config) {
|
JX.behavior('balanced-payment-form', function(config) {
|
||||||
balanced.init(config.balancedMarketplaceURI);
|
balanced.init(config.balancedMarketplaceURI);
|
||||||
|
|
||||||
var root = JX.$(config.formID);
|
var ccform = new JX.PhortuneCreditCardForm(JX.$(config.formID), onsubmit);
|
||||||
var ccform = new JX.PhortuneCreditCardForm(root);
|
|
||||||
|
|
||||||
var onsubmit = function(e) {
|
function onsubmit(card_data) {
|
||||||
e.kill();
|
|
||||||
|
|
||||||
var cardData = ccform.getCardData();
|
|
||||||
var errors = [];
|
var errors = [];
|
||||||
|
|
||||||
if (!balanced.card.isCardNumberValid(cardData.number)) {
|
if (!balanced.card.isCardNumberValid(card_data.number)) {
|
||||||
errors.push('number');
|
errors.push('cc:invalid:number');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!balanced.card.isSecurityCodeValid(cardData.number, cardData.cvc)) {
|
if (!balanced.card.isSecurityCodeValid(card_data.number, card_data.cvc)) {
|
||||||
errors.push('cvc');
|
errors.push('cc:invalid:cvc');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!balanced.card.isExpiryValid(cardData.month, cardData.year)) {
|
if (!balanced.card.isExpiryValid(card_data.month, card_data.year)) {
|
||||||
errors.push('expiry');
|
errors.push('cc:invalid:expiry');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
JX.Workflow
|
ccform.submitForm(errors);
|
||||||
.newFromForm(root, {cardErrors: JX.JSON.stringify(errors)})
|
|
||||||
.start();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
card_number: cardData.number,
|
card_number: card_data.number,
|
||||||
security_code: cardData.cvc,
|
security_code: card_data.cvc,
|
||||||
expiration_month: cardData.month,
|
expiration_month: card_data.month,
|
||||||
expiration_year: cardData.year
|
expiration_year: card_data.year
|
||||||
};
|
};
|
||||||
|
|
||||||
balanced.card.create(data, onresponse);
|
balanced.card.create(data, onresponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
var onresponse = function(response) {
|
function onresponse(response) {
|
||||||
|
var token = null;
|
||||||
var errors = [];
|
var errors = [];
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
errors = [response.error.type];
|
errors = ['cc:balanced:error:' + response.error.type];
|
||||||
} else if (response.status != 201) {
|
} else if (response.status != 201) {
|
||||||
errors = ['balanced:' + response.status];
|
errors = ['cc:balanced:http:' + response.status];
|
||||||
|
} else {
|
||||||
|
token = response.data.uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
var params = {
|
ccform.submitForm(errors, {balancedMarketplaceURI: token});
|
||||||
cardErrors: JX.JSON.stringify(errors),
|
|
||||||
balancedCardData: JX.JSON.stringify(response.data)
|
|
||||||
};
|
|
||||||
|
|
||||||
JX.Workflow
|
|
||||||
.newFromForm(root, params)
|
|
||||||
.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JX.DOM.listen(root, 'submit', null, onsubmit);
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,72 +2,56 @@
|
||||||
* @provides javelin-behavior-stripe-payment-form
|
* @provides javelin-behavior-stripe-payment-form
|
||||||
* @requires javelin-behavior
|
* @requires javelin-behavior
|
||||||
* javelin-dom
|
* javelin-dom
|
||||||
* javelin-json
|
|
||||||
* javelin-workflow
|
|
||||||
* phortune-credit-card-form
|
* phortune-credit-card-form
|
||||||
*/
|
*/
|
||||||
|
|
||||||
JX.behavior('stripe-payment-form', function(config) {
|
JX.behavior('stripe-payment-form', function(config) {
|
||||||
Stripe.setPublishableKey(config.stripePublishableKey);
|
Stripe.setPublishableKey(config.stripePublishableKey);
|
||||||
|
|
||||||
var root = JX.$(config.formID);
|
var ccform = new JX.PhortuneCreditCardForm(JX.$(config.formID), onsubmit);
|
||||||
var ccform = new JX.PhortuneCreditCardForm(root);
|
|
||||||
|
|
||||||
var onsubmit = function(e) {
|
function onsubmit(card_data) {
|
||||||
e.kill();
|
|
||||||
|
|
||||||
// validate the card data with Stripe client API and submit the form
|
|
||||||
// with any detected errors
|
|
||||||
var cardData = ccform.getCardData();
|
|
||||||
var errors = [];
|
var errors = [];
|
||||||
|
|
||||||
if (!Stripe.validateCardNumber(cardData.number)) {
|
if (!Stripe.validateCardNumber(card_data.number)) {
|
||||||
errors.push('number');
|
errors.push('cc:invalid:number');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Stripe.validateCVC(cardData.cvc)) {
|
if (!Stripe.validateCVC(card_data.cvc)) {
|
||||||
errors.push('cvc');
|
errors.push('cc:invalid:cvc');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Stripe.validateExpiry(cardData.month, cardData.year)) {
|
if (!Stripe.validateExpiry(card_data.month, card_data.year)) {
|
||||||
errors.push('expiry');
|
errors.push('cc:invalid:expiry');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.length) {
|
if (errors.length) {
|
||||||
JX.Workflow
|
ccform.submitForm(errors);
|
||||||
.newFromForm(root, {cardErrors: JX.JSON.stringify(errors)})
|
|
||||||
.start();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
number: cardData.number,
|
number: card_data.number,
|
||||||
cvc: cardData.cvc,
|
cvc: card_data.cvc,
|
||||||
exp_month: cardData.month,
|
exp_month: card_data.month,
|
||||||
exp_year: cardData.year
|
exp_year: card_data.year
|
||||||
};
|
};
|
||||||
|
|
||||||
Stripe.createToken(data, onresponse);
|
Stripe.createToken(data, onresponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
var onresponse = function(status, response) {
|
function onresponse(status, response) {
|
||||||
var errors = [];
|
var errors = [];
|
||||||
var token = null;
|
var token = null;
|
||||||
if (response.error) {
|
if (status != 200) {
|
||||||
errors = [response.error.type];
|
errors.push('cc:stripe:http:' + status);
|
||||||
|
} else if (response.error) {
|
||||||
|
errors.push('cc:stripe:error:' + response.error.type);
|
||||||
} else {
|
} else {
|
||||||
token = response.id;
|
token = response.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
var params = {
|
ccform.submitForm(errors, {stripeCardToken: token});
|
||||||
cardErrors: JX.JSON.stringify(errors),
|
|
||||||
stripeToken: token
|
|
||||||
};
|
|
||||||
|
|
||||||
JX.Workflow
|
|
||||||
.newFromForm(root, params)
|
|
||||||
.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JX.DOM.listen(root, 'submit', null, onsubmit);
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
* @provides phortune-credit-card-form
|
* @provides phortune-credit-card-form
|
||||||
* @requires javelin-install
|
* @requires javelin-install
|
||||||
* javelin-dom
|
* javelin-dom
|
||||||
|
* javelin-json
|
||||||
|
* javelin-workflow
|
||||||
|
* javelin-util
|
||||||
|
* @javelin
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,21 +13,21 @@
|
||||||
*
|
*
|
||||||
* To construct an object for a form:
|
* To construct an object for a form:
|
||||||
*
|
*
|
||||||
* new JX.PhortuneCreditCardForm(form_root_node);
|
* new JX.PhortuneCreditCardForm(form_root_node, submit_callback);
|
||||||
*
|
*
|
||||||
* To read card data from a form:
|
|
||||||
*
|
|
||||||
* var data = ccform.getCardData();
|
|
||||||
*/
|
*/
|
||||||
JX.install('PhortuneCreditCardForm', {
|
JX.install('PhortuneCreditCardForm', {
|
||||||
construct : function(root) {
|
construct : function(root, onsubmit) {
|
||||||
this._root = root;
|
this._root = root;
|
||||||
|
this._submitCallback = onsubmit;
|
||||||
|
JX.DOM.listen(root, 'submit', null, JX.bind(this, this._onsubmit));
|
||||||
},
|
},
|
||||||
|
|
||||||
members : {
|
members : {
|
||||||
_root : null,
|
_root : null,
|
||||||
|
_submitCallback : null,
|
||||||
|
|
||||||
getCardData : function() {
|
_getCardData : function() {
|
||||||
var root = this._root;
|
var root = this._root;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -32,7 +36,24 @@ JX.install('PhortuneCreditCardForm', {
|
||||||
month : JX.DOM.find(root, 'select', 'month-input' ).value,
|
month : JX.DOM.find(root, 'select', 'month-input' ).value,
|
||||||
year : JX.DOM.find(root, 'select', 'year-input' ).value
|
year : JX.DOM.find(root, 'select', 'year-input' ).value
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
submitForm : function(errors, token) {
|
||||||
|
var params = {
|
||||||
|
errors: JX.JSON.stringify(errors),
|
||||||
|
token: JX.JSON.stringify(token || {})
|
||||||
|
};
|
||||||
|
|
||||||
|
JX.Workflow
|
||||||
|
.newFromForm(this._root, params)
|
||||||
|
.start();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onsubmit : function(e) {
|
||||||
|
e.kill();
|
||||||
|
this._submitCallback(this._getCardData());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue