[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/phortune/provider/ -> PhortuneBalancedPaymentProvider.php (source)

   1  <?php
   2  
   3  final class PhortuneBalancedPaymentProvider extends PhortunePaymentProvider {
   4  
   5    const BALANCED_MARKETPLACE_ID   = 'balanced.marketplace-id';
   6    const BALANCED_SECRET_KEY       = 'balanced.secret-key';
   7  
   8    public function isAcceptingLivePayments() {
   9      return !preg_match('/-test-/', $this->getSecretKey());
  10    }
  11  
  12    public function getName() {
  13      return pht('Balanced Payments');
  14    }
  15  
  16    public function getConfigureName() {
  17      return pht('Add Balanced Payments Account');
  18    }
  19  
  20    public function getConfigureDescription() {
  21      return pht(
  22        'Allows you to accept credit or debit card payments with a '.
  23        'balancedpayments.com account.');
  24    }
  25  
  26    public function getConfigureProvidesDescription() {
  27      return pht(
  28        'This merchant accepts credit and debit cards via Balanced Payments.');
  29    }
  30  
  31    public function getConfigureInstructions() {
  32      return pht(
  33        "To configure Balacned, register or log in to an existing account on ".
  34        "[[https://balancedpayments.com | balancedpayments.com]]. Once logged ".
  35        "in:\n\n".
  36        "  - Choose a marketplace.\n".
  37        "  - Find the **Marketplace ID** in {nav My Marketplace > Settings} and ".
  38        "    copy it into the field above.\n".
  39        "  - On the same screen, under **API keys**, choose **Add a key**, then ".
  40        "    **Show key secret**. Copy the value into the field above.\n\n".
  41        "You can either use a test marketplace to add this provider in test ".
  42        "mode, or use a live marketplace to accept live payments.");
  43    }
  44  
  45    public function getAllConfigurableProperties() {
  46      return array(
  47        self::BALANCED_MARKETPLACE_ID,
  48        self::BALANCED_SECRET_KEY,
  49      );
  50    }
  51  
  52    public function getAllConfigurableSecretProperties() {
  53      return array(
  54        self::BALANCED_SECRET_KEY,
  55      );
  56    }
  57  
  58    public function processEditForm(
  59      AphrontRequest $request,
  60      array $values) {
  61  
  62      $errors = array();
  63      $issues = array();
  64  
  65      if (!strlen($values[self::BALANCED_MARKETPLACE_ID])) {
  66        $errors[] = pht('Balanced Marketplace ID is required.');
  67        $issues[self::BALANCED_MARKETPLACE_ID] = pht('Required');
  68      }
  69  
  70      if (!strlen($values[self::BALANCED_SECRET_KEY])) {
  71        $errors[] = pht('Balanced Secret Key is required.');
  72        $issues[self::BALANCED_SECRET_KEY] = pht('Required');
  73      }
  74  
  75      return array($errors, $issues, $values);
  76    }
  77  
  78    public function extendEditForm(
  79      AphrontRequest $request,
  80      AphrontFormView $form,
  81      array $values,
  82      array $issues) {
  83  
  84      $form
  85        ->appendChild(
  86          id(new AphrontFormTextControl())
  87            ->setName(self::BALANCED_MARKETPLACE_ID)
  88            ->setValue($values[self::BALANCED_MARKETPLACE_ID])
  89            ->setError(idx($issues, self::BALANCED_MARKETPLACE_ID, true))
  90            ->setLabel(pht('Balanced Marketplace ID')))
  91        ->appendChild(
  92          id(new AphrontFormTextControl())
  93            ->setName(self::BALANCED_SECRET_KEY)
  94            ->setValue($values[self::BALANCED_SECRET_KEY])
  95            ->setError(idx($issues, self::BALANCED_SECRET_KEY, true))
  96            ->setLabel(pht('Balanced Secret Key')));
  97  
  98    }
  99  
 100    public function canRunConfigurationTest() {
 101      return true;
 102    }
 103  
 104    public function runConfigurationTest() {
 105      $this->loadBalancedAPILibraries();
 106  
 107      // TODO: This only tests that the secret key is correct. It's not clear
 108      // how to test that the marketplace is correct.
 109  
 110      try {
 111        Balanced\Settings::$api_key = $this->getSecretKey();
 112        Balanced\APIKey::query()->first();
 113      } catch (RESTful\Exceptions\HTTPError $error) {
 114        // NOTE: This exception doesn't print anything meaningful if it escapes
 115        // to top level. Replace it with something slightly readable.
 116        throw new Exception($error->response->body->description);
 117      }
 118    }
 119  
 120    public function getPaymentMethodDescription() {
 121      return pht('Add Credit or Debit Card');
 122    }
 123  
 124    public function getPaymentMethodIcon() {
 125      return 'Balanced';
 126    }
 127  
 128    public function getPaymentMethodProviderDescription() {
 129      return pht('Processed by Balanced');
 130    }
 131  
 132    public function getDefaultPaymentMethodDisplayName(
 133      PhortunePaymentMethod $method) {
 134      return pht('Credit/Debit Card');
 135    }
 136  
 137    protected function executeCharge(
 138      PhortunePaymentMethod $method,
 139      PhortuneCharge $charge) {
 140      $this->loadBalancedAPILibraries();
 141  
 142      $price = $charge->getAmountAsCurrency();
 143  
 144      // Build the string which will appear on the credit card statement.
 145      $charge_as = new PhutilURI(PhabricatorEnv::getProductionURI('/'));
 146      $charge_as = $charge_as->getDomain();
 147      $charge_as = id(new PhutilUTF8StringTruncator())
 148        ->setMaximumBytes(22)
 149        ->setTerminator('')
 150        ->truncateString($charge_as);
 151  
 152      try {
 153        Balanced\Settings::$api_key = $this->getSecretKey();
 154        $card = Balanced\Card::get($method->getMetadataValue('balanced.cardURI'));
 155        $debit = $card->debit($price->getValueInUSDCents(), $charge_as);
 156      } catch (RESTful\Exceptions\HTTPError $error) {
 157        // NOTE: This exception doesn't print anything meaningful if it escapes
 158        // to top level. Replace it with something slightly readable.
 159        throw new Exception($error->response->body->description);
 160      }
 161  
 162      $expect_status = 'succeeded';
 163      if ($debit->status !== $expect_status) {
 164        throw new Exception(
 165          pht(
 166            'Debit failed, expected "%s", got "%s".',
 167            $expect_status,
 168            $debit->status));
 169      }
 170  
 171      $charge->setMetadataValue('balanced.debitURI', $debit->uri);
 172      $charge->save();
 173    }
 174  
 175    protected function executeRefund(
 176      PhortuneCharge $charge,
 177      PhortuneCharge $refund) {
 178      $this->loadBalancedAPILibraries();
 179  
 180      $debit_uri = $charge->getMetadataValue('balanced.debitURI');
 181      if (!$debit_uri) {
 182        throw new Exception(pht('No Balanced debit URI!'));
 183      }
 184  
 185      $refund_cents = $refund
 186        ->getAmountAsCurrency()
 187        ->negate()
 188        ->getValueInUSDCents();
 189  
 190      $params = array(
 191        'amount' => $refund_cents,
 192      );
 193  
 194      try {
 195        Balanced\Settings::$api_key = $this->getSecretKey();
 196        $balanced_debit = Balanced\Debit::get($debit_uri);
 197        $balanced_refund = $balanced_debit->refunds->create($params);
 198      } catch (RESTful\Exceptions\HTTPError $error) {
 199        throw new Exception($error->response->body->description);
 200      }
 201  
 202      $refund->setMetadataValue('balanced.refundURI', $balanced_refund->uri);
 203      $refund->save();
 204    }
 205  
 206    public function updateCharge(PhortuneCharge $charge) {
 207      $this->loadBalancedAPILibraries();
 208  
 209      $debit_uri = $charge->getMetadataValue('balanced.debitURI');
 210      if (!$debit_uri) {
 211        throw new Exception(pht('No Balanced debit URI!'));
 212      }
 213  
 214      try {
 215        Balanced\Settings::$api_key = $this->getSecretKey();
 216        $balanced_debit = Balanced\Debit::get($debit_uri);
 217      } catch (RESTful\Exceptions\HTTPError $error) {
 218        throw new Exception($error->response->body->description);
 219      }
 220  
 221      // TODO: Deal with disputes / chargebacks / surprising refunds.
 222    }
 223  
 224    private function getMarketplaceID() {
 225      return $this
 226        ->getProviderConfig()
 227        ->getMetadataValue(self::BALANCED_MARKETPLACE_ID);
 228    }
 229  
 230    private function getSecretKey() {
 231      return $this
 232        ->getProviderConfig()
 233        ->getMetadataValue(self::BALANCED_SECRET_KEY);
 234    }
 235  
 236    private function getMarketplaceURI() {
 237      return '/v1/marketplaces/'.$this->getMarketplaceID();
 238    }
 239  
 240  
 241  /* -(  Adding Payment Methods  )--------------------------------------------- */
 242  
 243  
 244    public function canCreatePaymentMethods() {
 245      return true;
 246    }
 247  
 248    public function validateCreatePaymentMethodToken(array $token) {
 249      return isset($token['balancedMarketplaceURI']);
 250    }
 251  
 252  
 253    /**
 254     * @phutil-external-symbol class Balanced\Card
 255     * @phutil-external-symbol class Balanced\Debit
 256     * @phutil-external-symbol class Balanced\Settings
 257     * @phutil-external-symbol class Balanced\Marketplace
 258     * @phutil-external-symbol class Balanced\APIKey
 259     * @phutil-external-symbol class RESTful\Exceptions\HTTPError
 260     */
 261    public function createPaymentMethodFromRequest(
 262      AphrontRequest $request,
 263      PhortunePaymentMethod $method,
 264      array $token) {
 265      $this->loadBalancedAPILibraries();
 266  
 267      $errors = array();
 268  
 269      $account_phid = $method->getAccountPHID();
 270      $author_phid = $method->getAuthorPHID();
 271      $description = $account_phid.':'.$author_phid;
 272  
 273      try {
 274        Balanced\Settings::$api_key = $this->getSecretKey();
 275  
 276        $card = Balanced\Card::get($token['balancedMarketplaceURI']);
 277  
 278        $buyer = Balanced\Marketplace::mine()->createBuyer(
 279          null,
 280          $card->uri,
 281          array(
 282            'description' => $description,
 283          ));
 284  
 285      } catch (RESTful\Exceptions\HTTPError $error) {
 286        // NOTE: This exception doesn't print anything meaningful if it escapes
 287        // to top level. Replace it with something slightly readable.
 288        throw new Exception($error->response->body->description);
 289      }
 290  
 291      $method
 292        ->setBrand($card->brand)
 293        ->setLastFourDigits($card->last_four)
 294        ->setExpires($card->expiration_year, $card->expiration_month)
 295        ->setMetadata(
 296          array(
 297            'type' => 'balanced.account',
 298            'balanced.accountURI' => $buyer->uri,
 299            'balanced.cardURI' => $card->uri,
 300          ));
 301  
 302      return $errors;
 303    }
 304  
 305    public function renderCreatePaymentMethodForm(
 306      AphrontRequest $request,
 307      array $errors) {
 308  
 309      $ccform = id(new PhortuneCreditCardForm())
 310        ->setUser($request->getUser())
 311        ->setErrors($errors)
 312        ->addScript('https://js.balancedpayments.com/v1/balanced.js');
 313  
 314      Javelin::initBehavior(
 315        'balanced-payment-form',
 316        array(
 317          'balancedMarketplaceURI' => $this->getMarketplaceURI(),
 318          'formID'                 => $ccform->getFormID(),
 319        ));
 320  
 321      return $ccform->buildForm();
 322    }
 323  
 324    private function getBalancedShortErrorCode($error_code) {
 325      $prefix = 'cc:balanced:';
 326      if (strncmp($error_code, $prefix, strlen($prefix))) {
 327        return null;
 328      }
 329      return substr($error_code, strlen($prefix));
 330    }
 331  
 332    public function translateCreatePaymentMethodErrorCode($error_code) {
 333      $short_code = $this->getBalancedShortErrorCode($error_code);
 334  
 335      if ($short_code) {
 336        static $map = array(
 337        );
 338  
 339        if (isset($map[$short_code])) {
 340          return $map[$short_code];
 341        }
 342      }
 343  
 344      return $error_code;
 345    }
 346  
 347    public function getCreatePaymentMethodErrorMessage($error_code) {
 348      $short_code = $this->getBalancedShortErrorCode($error_code);
 349      if (!$short_code) {
 350        return null;
 351      }
 352  
 353      switch ($short_code) {
 354  
 355        default:
 356          break;
 357      }
 358  
 359  
 360      return null;
 361    }
 362  
 363    private function loadBalancedAPILibraries() {
 364      $root = dirname(phutil_get_library_root('phabricator'));
 365      require_once $root.'/externals/httpful/bootstrap.php';
 366      require_once $root.'/externals/restful/bootstrap.php';
 367      require_once $root.'/externals/balanced-php/bootstrap.php';
 368    }
 369  
 370  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1