Skip to content
Browse files

Phortune v0.1: add payment methods

Summary:
Hook @btrahan's Stripe form to the rest of Phortune.

  - Users can add payment methods.
  - They are saved to Stripe and associated with PhortunePaymentMethods on our side.
  - Payment methods appear on account overview.

Test Plan:
{F37548}
{F37549}
{F37550}

Reviewers: chad, btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2787

Differential Revision: https://secure.phabricator.com/D5438
  • Loading branch information...
1 parent 960ac3b commit 4f3b5f0ea97491e610b7cb4c84cc56cb5f9c19a2 @epriestley epriestley committed
View
14 resources/sql/patches/20130323.phortunepayment.sql
@@ -0,0 +1,14 @@
+CREATE TABLE {$NAMESPACE}_phortune.phortune_paymentmethod (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ name VARCHAR(255) NOT NULL,
+ status VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ accountPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ expiresEpoch INT UNSIGNED,
+ metadata LONGTEXT NOT NULL COLLATE utf8_bin,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_phid` (phid),
+ KEY `key_account` (accountPHID, status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
View
12 src/__phutil_library_map__.php
@@ -1359,6 +1359,7 @@
'PhabricatorStorageManagementUpgradeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php',
'PhabricatorStorageManagementWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php',
'PhabricatorStoragePatch' => 'infrastructure/storage/management/PhabricatorStoragePatch.php',
+ 'PhabricatorStripeConfigOptions' => 'applications/phortune/option/PhabricatorStripeConfigOptions.php',
'PhabricatorSubscribableInterface' => 'applications/subscriptions/interface/PhabricatorSubscribableInterface.php',
'PhabricatorSubscribersQuery' => 'applications/subscriptions/query/PhabricatorSubscribersQuery.php',
'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php',
@@ -1540,13 +1541,13 @@
'PhortuneLandingController' => 'applications/phortune/controller/PhortuneLandingController.php',
'PhortuneMonthYearExpiryControl' => 'applications/phortune/control/PhortuneMonthYearExpiryControl.php',
'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php',
+ 'PhortunePaymentMethodEditController' => 'applications/phortune/controller/PhortunePaymentMethodEditController.php',
'PhortunePaymentMethodListController' => 'applications/phortune/controller/PhortunePaymentMethodListController.php',
+ 'PhortunePaymentMethodQuery' => 'applications/phortune/query/PhortunePaymentMethodQuery.php',
'PhortunePaymentMethodViewController' => 'applications/phortune/controller/PhortunePaymentMethodViewController.php',
'PhortuneProduct' => 'applications/phortune/storage/PhortuneProduct.php',
'PhortunePurchase' => 'applications/phortune/storage/PhortunePurchase.php',
- 'PhortuneStripeBaseController' => 'applications/phortune/stripe/controller/PhortuneStripeBaseController.php',
- 'PhortuneStripePaymentFormView' => 'applications/phortune/stripe/view/PhortuneStripePaymentFormView.php',
- 'PhortuneStripeTestPaymentFormController' => 'applications/phortune/stripe/controller/PhortuneStripeTestPaymentFormController.php',
+ 'PhortuneStripePaymentFormView' => 'applications/phortune/view/PhortuneStripePaymentFormView.php',
'PhrictionActionConstants' => 'applications/phriction/constants/PhrictionActionConstants.php',
'PhrictionChangeType' => 'applications/phriction/constants/PhrictionChangeType.php',
'PhrictionConstants' => 'applications/phriction/constants/PhrictionConstants.php',
@@ -2977,6 +2978,7 @@
'PhabricatorStorageManagementStatusWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementUpgradeWorkflow' => 'PhabricatorStorageManagementWorkflow',
'PhabricatorStorageManagementWorkflow' => 'PhutilArgumentWorkflow',
+ 'PhabricatorStripeConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorSubscribersQuery' => 'PhabricatorQuery',
'PhabricatorSubscriptionsEditController' => 'PhabricatorController',
'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor',
@@ -3194,13 +3196,13 @@
0 => 'PhortuneDAO',
1 => 'PhabricatorPolicyInterface',
),
+ 'PhortunePaymentMethodEditController' => 'PhortuneController',
'PhortunePaymentMethodListController' => 'PhabricatorController',
+ 'PhortunePaymentMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhortunePaymentMethodViewController' => 'PhabricatorController',
'PhortuneProduct' => 'PhortuneDAO',
'PhortunePurchase' => 'PhortuneDAO',
- 'PhortuneStripeBaseController' => 'PhabricatorController',
'PhortuneStripePaymentFormView' => 'AphrontView',
- 'PhortuneStripeTestPaymentFormController' => 'PhortuneStripeBaseController',
'PhrictionActionConstants' => 'PhrictionConstants',
'PhrictionChangeType' => 'PhrictionConstants',
'PhrictionContent' =>
View
9 src/applications/phortune/application/PhabricatorApplicationPhortune.php
@@ -32,17 +32,14 @@ public function getRoutes() {
'' => 'PhortuneLandingController',
'(?P<accountID>\d+)/' => array(
'' => 'PhortuneAccountViewController',
+ 'paymentmethod/' => array(
+ 'edit/' => 'PhortunePaymentMethodEditController',
+ ),
),
-
'account/' => array(
'' => 'PhortuneAccountListController',
'edit/(?:(?P<id>\d+)/)?' => 'PhortuneAccountEditController',
),
- 'paymentmethod/' => array(
- '' => 'PhortunePaymentMethodListController',
- 'view/(?P<id>\d+)/' => 'PhortunePaymentMethodViewController',
- 'edit/(?:(?P<id>\d+)/)?' => 'PhortunePaymentMethodEditController',
- ),
'stripe/' => array(
'testpaymentform/' => 'PhortuneStripeTestPaymentFormController',
),
View
34 src/applications/phortune/controller/PhortuneAccountViewController.php
@@ -95,6 +95,40 @@ private function buildPaymentMethodsSection(PhortuneAccount $account) {
->setNoDataString(
pht('No payment methods associated with this account.'));
+ $methods = id(new PhortunePaymentMethodQuery())
+ ->setViewer($user)
+ ->withAccountPHIDs(array($account->getPHID()))
+ ->withStatus(PhortunePaymentMethodQuery::STATUS_OPEN)
+ ->execute();
+
+ if ($methods) {
+ $this->loadHandles(mpull($methods, 'getAuthorPHID'));
+ }
+
+ foreach ($methods as $method) {
+ $item = new PhabricatorObjectItemView();
+ $item->setHeader($method->getName());
+
+ switch ($method->getStatus()) {
+ case PhortunePaymentMethod::STATUS_ACTIVE:
+ $item->addAttribute(pht('Active'));
+ $item->setBarColor('green');
+ break;
+ }
+
+ $item->addAttribute(
+ pht(
+ 'Added %s by %s',
+ phabricator_datetime($method->getDateCreated(), $user),
+ $this->getHandle($method->getAuthorPHID())->renderLink()));
+
+ if ($method->getExpiresEpoch() < time() + (60 * 60 * 24 * 30)) {
+ $item->addAttribute(pht('Expires Soon!'));
+ }
+
+ $list->addItem($item);
+ }
+
return array(
$header,
$actions,
View
298 src/applications/phortune/controller/PhortunePaymentMethodEditController.php
@@ -0,0 +1,298 @@
+<?php
+
+final class PhortunePaymentMethodEditController
+ extends PhortuneController {
+
+ private $accountID;
+
+ public function willProcessRequest(array $data) {
+ $this->accountID = $data['accountID'];
+ }
+
+ /**
+ * @phutil-external-symbol class Stripe_Token
+ * @phutil-external-symbol class Stripe_Customer
+ */
+ public function processRequest() {
+ $request = $this->getRequest();
+ $user = $request->getUser();
+
+ $stripe_publishable_key = PhabricatorEnv::getEnvConfig(
+ 'stripe.publishable-key');
+ if (!$stripe_publishable_key) {
+ throw new Exception(
+ "Stripe publishable API key (`stripe.publishable-key`) is ".
+ "not configured.");
+ }
+
+ $stripe_secret_key = PhabricatorEnv::getEnvConfig('stripe.secret-key');
+ if (!$stripe_secret_key) {
+ throw new Exception(
+ "Stripe secret API kye (`stripe.secret-key`) is not configured.");
+ }
+
+ $account = id(new PhortuneAccountQuery())
+ ->setViewer($user)
+ ->withIDs(array($this->accountID))
+ ->executeOne();
+ if (!$account) {
+ return new Aphront404Response();
+ }
+
+ $account_uri = $this->getApplicationURI($account->getID().'/');
+
+ $e_card_number = true;
+ $e_card_cvc = true;
+ $e_card_exp = true;
+
+ $errors = array();
+ if ($request->isFormPost()) {
+ $card_errors = $request->getStr('cardErrors');
+ $stripe_token = $request->getStr('stripeToken');
+ if ($card_errors) {
+ $raw_errors = json_decode($card_errors);
+ list($e_card_number,
+ $e_card_cvc,
+ $e_card_exp,
+ $messages) = $this->parseRawErrors($raw_errors);
+ $errors = array_merge($errors, $messages);
+ } else if (!$stripe_token) {
+ $errors[] = pht('There was an unknown error processing your card.');
+ }
+
+ if (!$errors) {
+ $root = dirname(phutil_get_library_root('phabricator'));
+ require_once $root.'/externals/stripe-php/lib/Stripe.php';
+
+ try {
+ // First, make sure the token is valid.
+ $info = id(new Stripe_Token())
+ ->retrieve($stripe_token, $stripe_secret_key);
+
+ // Then, we need to create a Customer in order to be able to charge
+ // the card more than once. We create one Customer for each card;
+ // they do not map to PhortuneAccounts because we allow an account to
+ // have more than one active card.
+ $customer = Stripe_Customer::create(
+ array(
+ 'card' => $stripe_token,
+ 'description' => $account->getPHID().':'.$user->getUserName(),
+ ), $stripe_secret_key);
+
+ $card = $info->card;
+ } catch (Exception $ex) {
+ phlog($ex);
+ $errors[] = pht(
+ 'There was an error communicating with the payments backend.');
+ }
+
+ if (!$errors) {
+ $payment_method = id(new PhortunePaymentMethod())
+ ->setAccountPHID($account->getPHID())
+ ->setAuthorPHID($user->getPHID())
+ ->setName($card->type.' / '.$card->last4)
+ ->setStatus(PhortunePaymentMethod::STATUS_ACTIVE)
+ ->setExpiresEpoch(strtotime($card->exp_year.'-'.$card->exp_month))
+ ->setMetadata(
+ array(
+ 'type' => 'stripe.customer',
+ 'stripeCustomerID' => $customer->id,
+ 'stripeTokenID' => $stripe_token,
+ ))
+ ->save();
+
+ $save_uri = new PhutilURI($account_uri);
+ $save_uri->setFragment('payment');
+
+ return id(new AphrontRedirectResponse())->setURI($save_uri);
+ }
+ }
+
+ $dialog = id(new AphrontDialogView())
+ ->setUser($user)
+ ->setTitle(pht('Error Adding Card'))
+ ->appendChild(id(new AphrontErrorView())->setErrors($errors))
+ ->addCancelButton($request->getRequestURI());
+
+ return id(new AphrontDialogResponse())->setDialog($dialog);
+ }
+
+ if ($errors) {
+ $errors = id(new AphrontErrorView())
+ ->setErrors($errors);
+ }
+
+ $header = id(new PhabricatorHeaderView())
+ ->setHeader(pht('Add New Payment Method'));
+
+ $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($user)
+ ->setWorkflow(true)
+ ->setAction($request->getRequestURI())
+ ->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'
+ )))
+ ->appendChild(
+ phutil_tag(
+ 'input',
+ array(
+ 'hidden' => true,
+ 'name' => 'stripeKey',
+ 'value' => $stripe_publishable_key,
+ )))
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue('Add Payment Method')
+ ->addCancelButton($account_uri));
+
+ Javelin::initBehavior(
+ 'stripe-payment-form',
+ array(
+ 'stripePublishKey' => $stripe_publishable_key,
+ 'root' => $form_id,
+ ));
+
+ $title = pht('Add Payment Method');
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName(pht('Account'))
+ ->setHref($account_uri));
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName(pht('Payment Methods'))
+ ->setHref($request->getRequestURI()));
+
+ return
+ $this->buildStandardPageResponse(
+ array(
+ $crumbs,
+ $header,
+ $errors,
+ $form,
+ ),
+ array(
+ 'title' => $title,
+ 'device' => true,
+ 'dust' => true,
+ ));
+ }
+
+ /**
+ * Stripe JS and calls to Stripe handle all errors with processing this
+ * 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 parseRawErrors($errors) {
+ $card_number_error = null;
+ $card_cvc_error = null;
+ $card_expiration_error = null;
+ $messages = array();
+ foreach ($errors as $index => $error) {
+ $type = key($error);
+ $msg = reset($error);
+ $messages[] = $msg;
+ switch ($type) {
+ case 'number':
+ case 'invalid_number':
+ case 'incorrect_number':
+ $card_number_error = pht('Invalid');
+ break;
+ case 'cvc':
+ case 'invalid_cvc':
+ case 'incorrect_cvc':
+ $card_cvc_error = pht('Invalid');
+ break;
+ case 'expiry':
+ case 'invalid_expiry_month':
+ case 'invalid_expiry_year':
+ $card_expiration_error = pht('Invalid');
+ 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(
+ 'error_type: %s error_message: %s',
+ $type,
+ $msg);
+ $this->logStripeError($error);
+ break;
+ }
+ }
+
+ return array(
+ $card_number_error,
+ $card_cvc_error,
+ $card_expiration_error,
+ $messages
+ );
+ }
+
+ private function logStripeError($message) {
+ phlog('STRIPE-ERROR '.$message);
+ }
+
+}
View
25 src/applications/phortune/option/PhabricatorStripeConfigOptions.php
@@ -0,0 +1,25 @@
+<?php
+
+final class PhabricatorStripeConfigOptions
+ extends PhabricatorApplicationConfigOptions {
+
+ public function getName() {
+ return pht("Integration with Stripe");
+ }
+
+ public function getDescription() {
+ return pht("Configure Stripe payments.");
+ }
+
+ public function getOptions() {
+ return array(
+ $this->newOption('stripe.publishable-key', 'string', null)
+ ->setDescription(
+ pht('Stripe publishable key.')),
+ $this->newOption('stripe.secret-key', 'string', null)
+ ->setDescription(
+ pht('Stripe secret key.')),
+ );
+ }
+
+}
View
117 src/applications/phortune/query/PhortunePaymentMethodQuery.php
@@ -0,0 +1,117 @@
+<?php
+
+final class PhortunePaymentMethodQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+ private $accountPHIDs;
+
+ const STATUS_ANY = 'status-any';
+ const STATUS_OPEN = 'status-open';
+ private $status = self::STATUS_ANY;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ public function withAccountPHIDs(array $phids) {
+ $this->accountPHIDs = $phids;
+ return $this;
+ }
+
+ public function withStatus($status) {
+ $this->status = $status;
+ return $this;
+ }
+
+ protected function loadPage() {
+ $table = new PhortunePaymentMethod();
+ $conn = $table->establishConnection('r');
+
+ $rows = queryfx_all(
+ $conn,
+ 'SELECT * FROM %T %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildWhereClause($conn),
+ $this->buildOrderClause($conn),
+ $this->buildLimitClause($conn));
+
+ return $table->loadAllFromArray($rows);
+ }
+
+ protected function willFilterPage(array $methods) {
+ if (!$methods) {
+ return array();
+ }
+
+ $accounts = id(new PhortuneAccountQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs(mpull($methods, 'getAccountPHID'))
+ ->execute();
+ $accounts = mpull($accounts, null, 'getPHID');
+
+ foreach ($methods as $key => $method) {
+ $account = idx($accounts, $method->getAccountPHID());
+ if (!$account) {
+ unset($methods[$key]);
+ continue;
+ }
+ $method->attachAccount($account);
+ }
+
+ return $methods;
+ }
+
+ private function buildWhereClause(AphrontDatabaseConnection $conn) {
+ $where = array();
+
+ if ($this->ids) {
+ $where[] = qsprintf(
+ $conn,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids) {
+ $where[] = qsprintf(
+ $conn,
+ 'phid IN (%Ls)',
+ $this->phids);
+ }
+
+ if ($this->accountPHIDs) {
+ $where[] = qsprintf(
+ $conn,
+ 'accountPHID IN (%Ls)',
+ $this->accountPHIDs);
+ }
+
+ switch ($this->status) {
+ case self::STATUS_ANY;
+ break;
+ case self::STATUS_OPEN:
+ $where[] = qsprintf(
+ $conn,
+ 'status in (%Ls)',
+ array(
+ PhortunePaymentMethod::STATUS_ACTIVE,
+ PhortunePaymentMethod::STATUS_FAILED,
+ ));
+ break;
+ default:
+ throw new Exception("Unknown status '{$this->status}'!");
+ }
+
+ $where[] = $this->buildPagingClause($conn);
+
+ return $this->formatWhereClause($where);
+ }
+
+}
View
6 src/applications/phortune/storage/PhortunePaymentMethod.php
@@ -7,9 +7,15 @@
final class PhortunePaymentMethod extends PhortuneDAO
implements PhabricatorPolicyInterface {
+ const STATUS_ACTIVE = 'payment:active';
+ const STATUS_FAILED = 'payment:failed';
+ const STATUS_REMOVED = 'payment:removed';
+
protected $name;
+ protected $status;
protected $accountPHID;
protected $authorPHID;
+ protected $expiresEpoch;
protected $metadata;
private $account;
View
17 src/applications/phortune/stripe/controller/PhortuneStripeBaseController.php
@@ -1,17 +0,0 @@
-<?php
-
-abstract class PhortuneStripeBaseController extends PhabricatorController {
-
- public function buildStandardPageResponse($view, array $data) {
- $page = $this->buildStandardPageView();
-
- $page->setApplicationName('Phortune - Stripe');
- $page->setBaseURI('/phortune/stripe/');
- $page->setTitle(idx($data, 'title'));
- $page->appendChild($view);
-
- $response = new AphrontWebpageResponse();
- return $response->setContent($page->render());
- }
-
-}
View
146 src/applications/phortune/stripe/controller/PhortuneStripeTestPaymentFormController.php
@@ -1,146 +0,0 @@
-<?php
-
-final class PhortuneStripeTestPaymentFormController
-extends PhortuneStripeBaseController {
- public function processRequest() {
- $request = $this->getRequest();
- $user = $request->getUser();
- $title = 'Test Payment Form';
- $error_view = null;
- $card_number_error = null;
- $card_cvc_error = null;
- $card_expiration_error = null;
- $stripe_key = $request->getStr('stripeKey');
- if (!$stripe_key) {
- $error_view = id(new AphrontErrorView())
- ->setTitle('Missing stripeKey parameter in URI');
- }
-
- if (!$error_view && $request->isFormPost()) {
- $card_errors = $request->getStr('cardErrors');
- $stripe_token = $request->getStr('stripeToken');
- if ($card_errors) {
- $raw_errors = json_decode($card_errors);
- list($card_number_error,
- $card_cvc_error,
- $card_expiration_error,
- $messages) = $this->parseRawErrors($raw_errors);
- $error_view = id(new AphrontErrorView())
- ->setTitle('There were errors processing your card.')
- ->setErrors($messages);
- } else if (!$stripe_token) {
- // this shouldn't happen, so show the user a very generic error
- // message and log that this error occurred...!
- $error_view = id(new AphrontErrorView())
- ->setTitle('There was an unknown error processing your card.')
- ->setErrors(array('Please try again.'));
- $error = 'payment form submitted but no stripe token and no errors';
- $this->logStripeError($error);
- } else {
- // success -- do something with $stripe_token!!
- }
- } else if (!$error_view) {
- $error_view = id(new AphrontErrorView())
- ->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
- ->setTitle(
- 'If you are using a test stripe key, use 4242424242424242, '.
- 'any three digits for CVC, and any valid expiration date to '.
- 'test!');
- }
-
- $view = id(new AphrontPanelView())
- ->setWidth(AphrontPanelView::WIDTH_FORM)
- ->setHeader($title);
-
- $form = id(new PhortuneStripePaymentFormView())
- ->setUser($user)
- ->setStripeKey($stripe_key)
- ->setCardNumberError($card_number_error)
- ->setCardCVCError($card_cvc_error)
- ->setCardExpirationError($card_expiration_error);
-
- $view->appendChild($form);
-
- return
- $this->buildStandardPageResponse(
- array(
- $error_view,
- $view,
- ),
- array(
- 'title' => $title,
- ));
- }
-
- /**
- * Stripe JS and calls to Stripe handle all errors with processing this
- * 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 parseRawErrors($errors) {
- $card_number_error = null;
- $card_cvc_error = null;
- $card_expiration_error = null;
- $messages = array();
- foreach ($errors as $index => $error) {
- $type = key($error);
- $msg = reset($error);
- $messages[] = $msg;
- switch ($type) {
- case 'number':
- case 'invalid_number':
- case 'incorrect_number':
- $card_number_error = true;
- break;
- case 'cvc':
- case 'invalid_cvc':
- case 'incorrect_cvc':
- $card_cvc_error = true;
- break;
- case 'expiry':
- case 'invalid_expiry_month':
- case 'invalid_expiry_year':
- $card_expiration_error = true;
- 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(
- 'error_type: %s error_message: %s',
- $type,
- $msg);
- $this->logStripeError($error);
- break;
- }
- }
-
- // append a helpful "fix this" to the messages to be displayed to the user
- $messages[] = pht(
- 'Please fix these errors and try again.',
- count($messages));
-
- return array(
- $card_number_error,
- $card_cvc_error,
- $card_expiration_error,
- $messages
- );
- }
-
- private function logStripeError($message) {
- phlog('STRIPE-ERROR '.$message);
- }
-
-
-}
View
4 ...pe/view/PhortuneStripePaymentFormView.php → ...ne/view/PhortuneStripePaymentFormView.php
@@ -39,7 +39,7 @@ private function getCardExpirationError() {
}
public function render() {
- $form_id = celerity_generate_unique_node_id();
+ $form_id = celerity_generate_unique_node_id();
require_celerity_resource('stripe-payment-form-css');
require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('phabricator-tooltips');
@@ -105,7 +105,7 @@ public function render() {
)))
->appendChild(
id(new AphrontFormSubmitControl())
- ->setValue('Submit Payment'));
+ ->setValue('Add Payment Method'));
Javelin::initBehavior(
'stripe-payment-form',
View
4 src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -1206,6 +1206,10 @@ public function getPatches() {
'type' => 'sql',
'name' => $this->getPatchPath('20130322.phortune.sql'),
),
+ '20130323.phortunepayment.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20130323.phortunepayment.sql'),
+ ),
);
}
View
19 webroot/rsrc/js/application/phortune/behavior-stripe-payment-form.js
@@ -3,6 +3,7 @@
* @requires javelin-behavior
* javelin-dom
* javelin-json
+ * javelin-workflow
* stripe-core
*/
@@ -73,8 +74,11 @@ JX.behavior('stripe-payment-form', function(config) {
}
if (errors.length != 0) {
cardErrors.value = JX.JSON.stringify(errors);
- root.submit();
- return true;
+
+ JX.Workflow.newFromForm(root)
+ .start();
+
+ return;
}
// no errors detected so contact Stripe asynchronously
@@ -110,14 +114,13 @@ JX.behavior('stripe-payment-form', function(config) {
// success - we can use the token to create a customer object with
// Stripe and let the billing commence!
var token = response['id'];
+ cardErrors.value = '[]';
stripeToken.value = token;
}
- root.submit();
+
+ JX.Workflow.newFromForm(root)
+ .start();
}
- JX.DOM.listen(
- root,
- 'submit',
- null,
- onsubmit);
+ JX.DOM.listen(root, 'submit', null, onsubmit);
});

0 comments on commit 4f3b5f0

Please sign in to comment.
Something went wrong with that request. Please try again.