diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2479,10 +2479,10 @@ 'PhortuneNoPaymentProviderException' => 'applications/phortune/exception/PhortuneNoPaymentProviderException.php', 'PhortuneNotImplementedException' => 'applications/phortune/exception/PhortuneNotImplementedException.php', 'PhortunePaymentMethod' => 'applications/phortune/storage/PhortunePaymentMethod.php', + 'PhortunePaymentMethodCreateController' => 'applications/phortune/controller/PhortunePaymentMethodCreateController.php', + 'PhortunePaymentMethodDisableController' => 'applications/phortune/controller/PhortunePaymentMethodDisableController.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', 'PhortunePaymentProvider' => 'applications/phortune/provider/PhortunePaymentProvider.php', 'PhortunePaymentProviderTestCase' => 'applications/phortune/provider/__tests__/PhortunePaymentProviderTestCase.php', 'PhortunePaypalPaymentProvider' => 'applications/phortune/provider/PhortunePaypalPaymentProvider.php', @@ -5386,10 +5386,10 @@ 'PhortuneDAO', 'PhabricatorPolicyInterface', ), + 'PhortunePaymentMethodCreateController' => 'PhortuneController', + 'PhortunePaymentMethodDisableController' => 'PhortuneController', 'PhortunePaymentMethodEditController' => 'PhortuneController', - 'PhortunePaymentMethodListController' => 'PhabricatorController', 'PhortunePaymentMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', - 'PhortunePaymentMethodViewController' => 'PhabricatorController', 'PhortunePaymentProviderTestCase' => 'PhabricatorTestCase', 'PhortunePaypalPaymentProvider' => 'PhortunePaymentProvider', 'PhortuneProduct' => array( diff --git a/src/applications/phortune/application/PhabricatorPhortuneApplication.php b/src/applications/phortune/application/PhabricatorPhortuneApplication.php --- a/src/applications/phortune/application/PhabricatorPhortuneApplication.php +++ b/src/applications/phortune/application/PhabricatorPhortuneApplication.php @@ -36,11 +36,15 @@ '' => 'PhortuneLandingController', '(?P\d+)/' => array( '' => 'PhortuneAccountViewController', - 'paymentmethod/' => array( - 'edit/' => 'PhortunePaymentMethodEditController', + 'card/' => array( + 'new/' => 'PhortunePaymentMethodCreateController', ), 'buy/(?P\d+)/' => 'PhortuneProductPurchaseController', ), + 'card/(?P\d+)/' => array( + 'edit/' => 'PhortunePaymentMethodEditController', + 'disable/' => 'PhortunePaymentMethodDisableController', + ), 'cart/(?P\d+)/' => array( '' => 'PhortuneCartViewController', 'checkout/' => 'PhortuneCartCheckoutController', diff --git a/src/applications/phortune/controller/PhortuneAccountViewController.php b/src/applications/phortune/controller/PhortuneAccountViewController.php --- a/src/applications/phortune/controller/PhortuneAccountViewController.php +++ b/src/applications/phortune/controller/PhortuneAccountViewController.php @@ -79,7 +79,12 @@ private function buildPaymentMethodsSection(PhortuneAccount $account) { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $account, + PhabricatorPolicyCapability::CAN_EDIT); $id = $account->getID(); @@ -88,19 +93,18 @@ ->addActionLink( id(new PHUIButtonView()) ->setTag('a') - ->setHref($this->getApplicationURI($id.'/paymentmethod/edit/')) + ->setHref($this->getApplicationURI($id.'/card/new/')) ->setText(pht('Add Payment Method')) ->setIcon(id(new PHUIIconView())->setIconFont('fa-plus'))); $list = id(new PHUIObjectItemListView()) - ->setUser($user) + ->setUser($viewer) ->setNoDataString( pht('No payment methods associated with this account.')); $methods = id(new PhortunePaymentMethodQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) - ->withStatus(PhortunePaymentMethodQuery::STATUS_OPEN) ->execute(); if ($methods) { @@ -108,21 +112,40 @@ } foreach ($methods as $method) { + $id = $method->getID(); + $item = new PHUIObjectItemView(); - $item->setHeader($method->getBrand().' / '.$method->getLastFourDigits()); + $item->setHeader($method->getFullDisplayName()); switch ($method->getStatus()) { case PhortunePaymentMethod::STATUS_ACTIVE: - $item->addAttribute(pht('Active')); $item->setBarColor('green'); + + $disable_uri = $this->getApplicationURI('card/'.$id.'/disable/'); + $item->addAction( + id(new PHUIListItemView()) + ->setIcon('fa-times') + ->setHref($disable_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + break; + case PhortunePaymentMethod::STATUS_DISABLED: + $item->setDisabled(true); break; } - $item->addAttribute( - pht( - 'Added %s by %s', - phabricator_datetime($method->getDateCreated(), $user), - $this->getHandle($method->getAuthorPHID())->renderLink())); + $provider = $method->buildPaymentProvider(); + $item->addAttribute($provider->getPaymentMethodProviderDescription()); + $item->setImageURI($provider->getPaymentMethodIcon()); + + $edit_uri = $this->getApplicationURI('card/'.$id.'/edit/'); + + $item->addAction( + id(new PHUIListItemView()) + ->setIcon('fa-pencil') + ->setHref($edit_uri) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); $list->addItem($item); } diff --git a/src/applications/phortune/controller/PhortuneCartCheckoutController.php b/src/applications/phortune/controller/PhortuneCartCheckoutController.php --- a/src/applications/phortune/controller/PhortuneCartCheckoutController.php +++ b/src/applications/phortune/controller/PhortuneCartCheckoutController.php @@ -28,7 +28,7 @@ $methods = id(new PhortunePaymentMethodQuery()) ->setViewer($viewer) ->withAccountPHIDs(array($account->getPHID())) - ->withStatus(PhortunePaymentMethodQuery::STATUS_OPEN) + ->withStatuses(array(PhortunePaymentMethod::STATUS_ACTIVE)) ->execute(); $e_method = null; @@ -57,6 +57,7 @@ ->setAccountPHID($account->getPHID()) ->setCartPHID($cart->getPHID()) ->setAuthorPHID($viewer->getPHID()) + ->setPaymentProviderKey($provider->getProviderKey()) ->setPaymentMethodPHID($method->getPHID()) ->setAmountInCents($cart->getTotalPriceInCents()) ->setStatus(PhortuneCharge::STATUS_PENDING); @@ -105,7 +106,7 @@ $method_control->setError($e_method); $payment_method_uri = $this->getApplicationURI( - $account->getID().'/paymentmethod/edit/'); + $account->getID().'/card/new/'); $form = id(new AphrontFormView()) ->setUser($viewer) diff --git a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php b/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php copy from src/applications/phortune/controller/PhortunePaymentMethodEditController.php copy to src/applications/phortune/controller/PhortunePaymentMethodCreateController.php --- a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodCreateController.php @@ -1,6 +1,6 @@ methodID = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $method = id(new PhortunePaymentMethodQuery()) + ->setViewer($viewer) + ->withIDs(array($this->methodID)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$method) { + return new Aphront404Response(); + } + + if ($method->getStatus() == PhortunePaymentMethod::STATUS_DISABLED) { + return new Aphront400Response(); + } + + $account = $method->getAccount(); + $account_uri = $this->getApplicationURI($account->getID().'/'); + + if ($request->isFormPost()) { + + // TODO: ApplicationTransactions! + $method + ->setStatus(PhortunePaymentMethod::STATUS_DISABLED) + ->save(); + + return id(new AphrontRedirectResponse())->setURI($account_uri); + } + + return $this->newDialog() + ->setTitle(pht('Disable Payment Method?')) + ->setShortTitle(pht('Disable Payment Method')) + ->appendParagraph( + pht( + 'Disable the payment method "%s"?', + phutil_tag( + 'strong', + array(), + $method->getFullDisplayName()))) + ->appendParagraph( + pht( + 'You will no longer be able to make payments using this payment '. + 'method. Disabled payment methods can not be reactivated.')) + ->addCancelButton($account_uri) + ->addSubmitButton(pht('Disable Payment Method')); + } + +} diff --git a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php b/src/applications/phortune/controller/PhortunePaymentMethodEditController.php --- a/src/applications/phortune/controller/PhortunePaymentMethodEditController.php +++ b/src/applications/phortune/controller/PhortunePaymentMethodEditController.php @@ -3,133 +3,74 @@ final class PhortunePaymentMethodEditController extends PhortuneController { - private $accountID; + private $methodID; public function willProcessRequest(array $data) { - $this->accountID = $data['accountID']; + $this->methodID = $data['id']; } public function processRequest() { $request = $this->getRequest(); - $user = $request->getUser(); + $viewer = $request->getUser(); - $account = id(new PhortuneAccountQuery()) - ->setViewer($user) - ->withIDs(array($this->accountID)) + $method = id(new PhortunePaymentMethodQuery()) + ->setViewer($viewer) + ->withIDs(array($this->methodID)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) ->executeOne(); - if (!$account) { + if (!$method) { return new Aphront404Response(); } - $cancel_uri = $this->getApplicationURI($account->getID().'/'); + $account = $method->getAccount(); $account_uri = $this->getApplicationURI($account->getID().'/'); - $providers = PhortunePaymentProvider::getProvidersForAddPaymentMethod(); - if (!$providers) { - throw new Exception( - 'There are no payment providers enabled that can add payment '. - 'methods.'); - } + if ($request->isFormPost()) { - $provider_key = $request->getStr('providerKey'); - if (empty($providers[$provider_key])) { - $choices = array(); - foreach ($providers as $provider) { - $choices[] = $this->renderSelectProvider($provider); - } + $name = $request->getStr('name'); - $content = phutil_tag( - 'div', - array( - 'class' => 'phortune-payment-method-list', - ), - $choices); - - return $this->newDialog() - ->setRenderDialogAsDiv(true) - ->setTitle(pht('Add Payment Method')) - ->appendParagraph(pht('Choose a payment method to add:')) - ->appendChild($content) - ->addCancelButton($account_uri); - } + // TODO: Use ApplicationTransactions - $provider = $providers[$provider_key]; - - $errors = array(); - if ($request->isFormPost() && $request->getBool('isProviderForm')) { - $method = id(new PhortunePaymentMethod()) - ->setAccountPHID($account->getPHID()) - ->setAuthorPHID($user->getPHID()) - ->setStatus(PhortunePaymentMethod::STATUS_ACTIVE) - ->setProviderType($provider->getProviderType()) - ->setProviderDomain($provider->getProviderDomain()); - - 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) { - $method->save(); - - $save_uri = new PhutilURI($account_uri); - $save_uri->setFragment('payment'); - return id(new AphrontRedirectResponse())->setURI($save_uri); - } else { - $dialog = id(new AphrontDialogView()) - ->setUser($user) - ->setTitle(pht('Error Adding Payment Method')) - ->appendChild(id(new AphrontErrorView())->setErrors($errors)) - ->addCancelButton($request->getRequestURI()); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } + $method->setName($name); + $method->save(); + + return id(new AphrontRedirectResponse())->setURI($account_uri); } - $form = $provider->renderCreatePaymentMethodForm($request, $errors); + $provider = $method->buildPaymentProvider(); - $form - ->setUser($user) - ->setAction($request->getRequestURI()) - ->setWorkflow(true) - ->addHiddenInput('providerKey', $provider_key) - ->addHiddenInput('isProviderForm', true) + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Name')) + ->setName('name') + ->setValue($method->getName())) + ->appendChild( + id(new AphrontFormStaticControl()) + ->setLabel(pht('Details')) + ->setValue($method->getSummary())) + ->appendChild( + id(new AphrontFormStaticControl()) + ->setLabel(pht('Expires')) + ->setValue($method->getDisplayExpires())) ->appendChild( id(new AphrontFormSubmitControl()) - ->setValue(pht('Add Payment Method')) - ->addCancelButton($account_uri)); + ->addCancelButton($account_uri) + ->setValue(pht('Save Changes'))); $box = id(new PHUIObjectBoxView()) - ->setHeaderText($provider->getPaymentMethodDescription()) - ->setForm($form); + ->setHeaderText(pht('Edit Payment Method')) + ->appendChild($form); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Add Payment Method')); + $crumbs->addTextCrumb($account->getName(), $account_uri); + $crumbs->addTextCrumb($method->getDisplayName()); + $crumbs->addTextCrumb(pht('Edit')); return $this->buildApplicationPage( array( @@ -137,97 +78,8 @@ $box, ), array( - 'title' => $provider->getPaymentMethodDescription(), + 'title' => pht('Edit Payment Method'), )); } - private function renderSelectProvider( - PhortunePaymentProvider $provider) { - - $request = $this->getRequest(); - $user = $request->getUser(); - - $description = $provider->getPaymentMethodDescription(); - $icon_uri = $provider->getPaymentMethodIcon(); - $details = $provider->getPaymentMethodProviderDescription(); - - $this->requireResource('phortune-css'); - - $icon = id(new PHUIIconView()) - ->setImage($icon_uri) - ->addClass('phortune-payment-icon'); - - $button = id(new PHUIButtonView()) - ->setSize(PHUIButtonView::BIG) - ->setColor(PHUIButtonView::GREY) - ->setIcon($icon) - ->setText($description) - ->setSubtext($details); - - $form = id(new AphrontFormView()) - ->setUser($user) - ->addHiddenInput('providerKey', $provider->getProviderKey()) - ->appendChild($button); - - return $form; - } - - 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->getCreatePaymentMethodErrorMessage( - $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; - } - } diff --git a/src/applications/phortune/controller/PhortunePaymentMethodListController.php b/src/applications/phortune/controller/PhortunePaymentMethodListController.php deleted file mode 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodListController.php +++ /dev/null @@ -1,21 +0,0 @@ -getRequest(); - $user = $request->getUser(); - - $title = pht('Payment Methods'); - $crumbs = $this->buildApplicationCrumbs(); - - return $this->buildApplicationPage( - array( - $crumbs, - ), - array( - 'title' => $title, - )); - } - -} diff --git a/src/applications/phortune/controller/PhortunePaymentMethodViewController.php b/src/applications/phortune/controller/PhortunePaymentMethodViewController.php deleted file mode 100644 --- a/src/applications/phortune/controller/PhortunePaymentMethodViewController.php +++ /dev/null @@ -1,19 +0,0 @@ -buildApplicationCrumbs(); - - return $this->buildApplicationPage( - array( - $crumbs, - ), - array( - 'title' => $title, - )); - } - -} diff --git a/src/applications/phortune/provider/PhortuneBalancedPaymentProvider.php b/src/applications/phortune/provider/PhortuneBalancedPaymentProvider.php --- a/src/applications/phortune/provider/PhortuneBalancedPaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneBalancedPaymentProvider.php @@ -27,6 +27,10 @@ return pht('Processed by Balanced'); } + public function getDefaultPaymentMethodDisplayName( + PhortunePaymentMethod $method) { + return pht('Credit/Debit Card'); + } public function canHandlePaymentMethod(PhortunePaymentMethod $method) { $type = $method->getMetadataValue('type'); diff --git a/src/applications/phortune/provider/PhortunePaymentProvider.php b/src/applications/phortune/provider/PhortunePaymentProvider.php --- a/src/applications/phortune/provider/PhortunePaymentProvider.php +++ b/src/applications/phortune/provider/PhortunePaymentProvider.php @@ -169,6 +169,11 @@ throw new PhortuneNotImplementedException($this); } + public function getDefaultPaymentMethodDisplayName( + PhortunePaymentMethod $method) { + throw new PhortuneNotImplementedException($this); + } + /* -( One-Time Payments )-------------------------------------------------- */ diff --git a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php --- a/src/applications/phortune/provider/PhortuneStripePaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneStripePaymentProvider.php @@ -27,6 +27,10 @@ return pht('Processed by Stripe'); } + public function getDefaultPaymentMethodDisplayName( + PhortunePaymentMethod $method) { + return pht('Credit/Debit Card'); + } public function canHandlePaymentMethod(PhortunePaymentMethod $method) { $type = $method->getMetadataValue('type'); diff --git a/src/applications/phortune/provider/PhortuneTestPaymentProvider.php b/src/applications/phortune/provider/PhortuneTestPaymentProvider.php --- a/src/applications/phortune/provider/PhortuneTestPaymentProvider.php +++ b/src/applications/phortune/provider/PhortuneTestPaymentProvider.php @@ -26,9 +26,14 @@ return pht('Infinite Free Money'); } + public function getDefaultPaymentMethodDisplayName( + PhortunePaymentMethod $method) { + return pht('Vast Wealth'); + } + public function canHandlePaymentMethod(PhortunePaymentMethod $method) { $type = $method->getMetadataValue('type'); - return ($type === 'test.cash' || $type === 'test.multiple'); + return ($type === 'test.wealth' || $type == 'test.multiple'); } protected function executeCharge( @@ -69,8 +74,13 @@ $method ->setExpires('2050', '01') ->setBrand('FreeMoney') - ->setLastFourDigits('9999'); + ->setLastFourDigits('9999') + ->setMetadata( + array( + 'type' => 'test.wealth', + )); + return array(); } diff --git a/src/applications/phortune/query/PhortunePaymentMethodQuery.php b/src/applications/phortune/query/PhortunePaymentMethodQuery.php --- a/src/applications/phortune/query/PhortunePaymentMethodQuery.php +++ b/src/applications/phortune/query/PhortunePaymentMethodQuery.php @@ -6,10 +6,7 @@ private $ids; private $phids; private $accountPHIDs; - - const STATUS_ANY = 'status-any'; - const STATUS_OPEN = 'status-open'; - private $status = self::STATUS_ANY; + private $statuses; public function withIDs(array $ids) { $this->ids = $ids; @@ -26,8 +23,8 @@ return $this; } - public function withStatus($status) { - $this->status = $status; + public function withStatuses(array $statuses) { + $this->statuses = $statuses; return $this; } @@ -77,41 +74,32 @@ private function buildWhereClause(AphrontDatabaseConnection $conn) { $where = array(); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( $conn, 'id IN (%Ld)', $this->ids); } - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( $conn, 'phid IN (%Ls)', $this->phids); } - if ($this->accountPHIDs) { + if ($this->accountPHIDs !== null) { $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}'!"); + if ($this->statuses !== null) { + $where[] = qsprintf( + $conn, + 'status IN (%Ls)', + $this->statuses); } $where[] = $this->buildPagingClause($conn); diff --git a/src/applications/phortune/storage/PhortunePaymentMethod.php b/src/applications/phortune/storage/PhortunePaymentMethod.php --- a/src/applications/phortune/storage/PhortunePaymentMethod.php +++ b/src/applications/phortune/storage/PhortunePaymentMethod.php @@ -8,8 +8,7 @@ implements PhabricatorPolicyInterface { const STATUS_ACTIVE = 'payment:active'; - const STATUS_FAILED = 'payment:failed'; - const STATUS_REMOVED = 'payment:removed'; + const STATUS_DISABLED = 'payment:disabled'; protected $name = ''; protected $status; @@ -82,11 +81,36 @@ return head($accept); } + + public function getDisplayName() { + if (strlen($this->name)) { + return $this->name; + } + + $provider = $this->buildPaymentProvider(); + return $provider->getDefaultPaymentMethodDisplayName($this); + } + + public function getFullDisplayName() { + return pht('%s (%s)', $this->getDisplayName(), $this->getSummary()); + } + + public function getSummary() { + return pht('%s %s', $this->getBrand(), $this->getLastFourDigits()); + } + public function setExpires($year, $month) { $this->expires = $year.'-'.$month; return $this; } + public function getDisplayExpires() { + list($year, $month) = explode('-', $this->getExpires()); + $month = sprintf('%02d', $month); + $year = substr($year, -2); + return $month.'/'.$year; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */