[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

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

   1  <?php
   2  
   3  final class PhortuneWePayPaymentProvider extends PhortunePaymentProvider {
   4  
   5    const WEPAY_CLIENT_ID       = 'wepay.client-id';
   6    const WEPAY_CLIENT_SECRET   = 'wepay.client-secret';
   7    const WEPAY_ACCESS_TOKEN    = 'wepay.access-token';
   8    const WEPAY_ACCOUNT_ID      = 'wepay.account-id';
   9  
  10    public function isAcceptingLivePayments() {
  11      return preg_match('/^PRODUCTION_/', $this->getWePayAccessToken());
  12    }
  13  
  14    public function getName() {
  15      return pht('WePay');
  16    }
  17  
  18    public function getConfigureName() {
  19      return pht('Add WePay Payments Account');
  20    }
  21  
  22    public function getConfigureDescription() {
  23      return pht(
  24        'Allows you to accept credit or debit card payments with a '.
  25        'wepay.com account.');
  26    }
  27  
  28    public function getConfigureProvidesDescription() {
  29      return pht('This merchant accepts credit and debit cards via WePay.');
  30    }
  31  
  32    public function getConfigureInstructions() {
  33      return pht(
  34        "To configure WePay, register or log in to an existing account on ".
  35        "[[https://wepay.com | wepay.com]] (for live payments) or ".
  36        "[[https://stage.wepay.com | stage.wepay.com]] (for testing). ".
  37        "Once logged in:\n\n".
  38        "  - Create an API application if you don't already have one.\n".
  39        "  - Click the API application name to go to the detail page.\n".
  40        "  - Copy **Client ID**, **Client Secret**, **Access Token** and ".
  41        "    **AccountID** from that page to the fields above.\n\n".
  42        "You can either use `stage.wepay.com` to retrieve test credentials, ".
  43        "or `wepay.com` to retrieve live credentials for accepting live ".
  44        "payments.");
  45    }
  46  
  47    public function canRunConfigurationTest() {
  48      return true;
  49    }
  50  
  51    public function runConfigurationTest() {
  52      $this->loadWePayAPILibraries();
  53  
  54      WePay::useStaging(
  55        $this->getWePayClientID(),
  56        $this->getWePayClientSecret());
  57  
  58      $wepay = new WePay($this->getWePayAccessToken());
  59      $params = array(
  60        'client_id' => $this->getWePayClientID(),
  61        'client_secret' => $this->getWePayClientSecret(),
  62      );
  63  
  64      $wepay->request('app', $params);
  65    }
  66  
  67    public function getAllConfigurableProperties() {
  68      return array(
  69        self::WEPAY_CLIENT_ID,
  70        self::WEPAY_CLIENT_SECRET,
  71        self::WEPAY_ACCESS_TOKEN,
  72        self::WEPAY_ACCOUNT_ID,
  73      );
  74    }
  75  
  76    public function getAllConfigurableSecretProperties() {
  77      return array(
  78        self::WEPAY_CLIENT_SECRET,
  79      );
  80    }
  81  
  82    public function processEditForm(
  83      AphrontRequest $request,
  84      array $values) {
  85  
  86      $errors = array();
  87      $issues = array();
  88  
  89      if (!strlen($values[self::WEPAY_CLIENT_ID])) {
  90        $errors[] = pht('WePay Client ID is required.');
  91        $issues[self::WEPAY_CLIENT_ID] = pht('Required');
  92      }
  93  
  94      if (!strlen($values[self::WEPAY_CLIENT_SECRET])) {
  95        $errors[] = pht('WePay Client Secret is required.');
  96        $issues[self::WEPAY_CLIENT_SECRET] = pht('Required');
  97      }
  98  
  99      if (!strlen($values[self::WEPAY_ACCESS_TOKEN])) {
 100        $errors[] = pht('WePay Access Token is required.');
 101        $issues[self::WEPAY_ACCESS_TOKEN] = pht('Required');
 102      }
 103  
 104      if (!strlen($values[self::WEPAY_ACCOUNT_ID])) {
 105        $errors[] = pht('WePay Account ID is required.');
 106        $issues[self::WEPAY_ACCOUNT_ID] = pht('Required');
 107      }
 108  
 109      return array($errors, $issues, $values);
 110    }
 111  
 112    public function extendEditForm(
 113      AphrontRequest $request,
 114      AphrontFormView $form,
 115      array $values,
 116      array $issues) {
 117  
 118      $form
 119        ->appendChild(
 120          id(new AphrontFormTextControl())
 121            ->setName(self::WEPAY_CLIENT_ID)
 122            ->setValue($values[self::WEPAY_CLIENT_ID])
 123            ->setError(idx($issues, self::WEPAY_CLIENT_ID, true))
 124            ->setLabel(pht('WePay Client ID')))
 125        ->appendChild(
 126          id(new AphrontFormTextControl())
 127            ->setName(self::WEPAY_CLIENT_SECRET)
 128            ->setValue($values[self::WEPAY_CLIENT_SECRET])
 129            ->setError(idx($issues, self::WEPAY_CLIENT_SECRET, true))
 130            ->setLabel(pht('WePay Client Secret')))
 131        ->appendChild(
 132          id(new AphrontFormTextControl())
 133            ->setName(self::WEPAY_ACCESS_TOKEN)
 134            ->setValue($values[self::WEPAY_ACCESS_TOKEN])
 135            ->setError(idx($issues, self::WEPAY_ACCESS_TOKEN, true))
 136            ->setLabel(pht('WePay Access Token')))
 137        ->appendChild(
 138          id(new AphrontFormTextControl())
 139            ->setName(self::WEPAY_ACCOUNT_ID)
 140            ->setValue($values[self::WEPAY_ACCOUNT_ID])
 141            ->setError(idx($issues, self::WEPAY_ACCOUNT_ID, true))
 142            ->setLabel(pht('WePay Account ID')));
 143  
 144    }
 145  
 146    public function getPaymentMethodDescription() {
 147      return pht('Credit or Debit Card');
 148    }
 149  
 150    public function getPaymentMethodIcon() {
 151      return 'WePay';
 152    }
 153  
 154    public function getPaymentMethodProviderDescription() {
 155      return 'WePay';
 156    }
 157  
 158    protected function executeCharge(
 159      PhortunePaymentMethod $payment_method,
 160      PhortuneCharge $charge) {
 161      throw new Exception('!');
 162    }
 163  
 164    private function getWePayClientID() {
 165      return $this
 166        ->getProviderConfig()
 167        ->getMetadataValue(self::WEPAY_CLIENT_ID);
 168    }
 169  
 170    private function getWePayClientSecret() {
 171      return $this
 172        ->getProviderConfig()
 173        ->getMetadataValue(self::WEPAY_CLIENT_SECRET);
 174    }
 175  
 176    private function getWePayAccessToken() {
 177      return $this
 178        ->getProviderConfig()
 179        ->getMetadataValue(self::WEPAY_ACCESS_TOKEN);
 180    }
 181  
 182    private function getWePayAccountID() {
 183      return $this
 184        ->getProviderConfig()
 185        ->getMetadataValue(self::WEPAY_ACCOUNT_ID);
 186    }
 187  
 188    protected function executeRefund(
 189      PhortuneCharge $charge,
 190      PhortuneCharge $refund) {
 191      $wepay = $this->loadWePayAPILibraries();
 192  
 193      $checkout_id = $this->getWePayCheckoutID($charge);
 194  
 195      $params = array(
 196        'checkout_id' => $checkout_id,
 197        'refund_reason' => pht('Refund'),
 198        'amount' => $refund->getAmountAsCurrency()->negate()->formatBareValue(),
 199      );
 200  
 201      $wepay->request('checkout/refund', $params);
 202    }
 203  
 204    public function updateCharge(PhortuneCharge $charge) {
 205      $wepay = $this->loadWePayAPILibraries();
 206  
 207      $params = array(
 208        'checkout_id' => $this->getWePayCheckoutID($charge),
 209      );
 210      $wepay_checkout = $wepay->request('checkout', $params);
 211  
 212      // TODO: Deal with disputes / chargebacks / surprising refunds.
 213    }
 214  
 215  
 216  /* -(  One-Time Payments  )-------------------------------------------------- */
 217  
 218    public function canProcessOneTimePayments() {
 219      return true;
 220    }
 221  
 222  
 223  /* -(  Controllers  )-------------------------------------------------------- */
 224  
 225  
 226    public function canRespondToControllerAction($action) {
 227      switch ($action) {
 228        case 'checkout':
 229        case 'charge':
 230        case 'cancel':
 231          return true;
 232      }
 233      return parent::canRespondToControllerAction();
 234    }
 235  
 236    /**
 237     * @phutil-external-symbol class WePay
 238     */
 239    public function processControllerRequest(
 240      PhortuneProviderActionController $controller,
 241      AphrontRequest $request) {
 242      $wepay = $this->loadWePayAPILibraries();
 243  
 244      $viewer = $request->getUser();
 245  
 246      $cart = $controller->loadCart($request->getInt('cartID'));
 247      if (!$cart) {
 248        return new Aphront404Response();
 249      }
 250  
 251      $charge = $controller->loadActiveCharge($cart);
 252      switch ($controller->getAction()) {
 253        case 'checkout':
 254          if ($charge) {
 255            throw new Exception(pht('Cart is already charging!'));
 256          }
 257          break;
 258        case 'charge':
 259        case 'cancel':
 260          if (!$charge) {
 261            throw new Exception(pht('Cart is not charging yet!'));
 262          }
 263          break;
 264      }
 265  
 266      switch ($controller->getAction()) {
 267        case 'checkout':
 268          $return_uri = $this->getControllerURI(
 269            'charge',
 270            array(
 271              'cartID' => $cart->getID(),
 272            ));
 273  
 274          $cancel_uri = $this->getControllerURI(
 275            'cancel',
 276            array(
 277              'cartID' => $cart->getID(),
 278            ));
 279  
 280          $price = $cart->getTotalPriceAsCurrency();
 281  
 282          $params = array(
 283            'account_id'        => $this->getWePayAccountID(),
 284            'short_description' => $cart->getName(),
 285            'type'              => 'SERVICE',
 286            'amount'            => $price->formatBareValue(),
 287            'long_description'  => $cart->getName(),
 288            'reference_id'      => $cart->getPHID(),
 289            'app_fee'           => 0,
 290            'fee_payer'         => 'Payee',
 291            'redirect_uri'      => $return_uri,
 292            'fallback_uri'      => $cancel_uri,
 293  
 294            // NOTE: If we don't `auto_capture`, we might get a result back in
 295            // either an "authorized" or a "reserved" state. We can't capture
 296            // an "authorized" result, so just autocapture.
 297  
 298            'auto_capture'      => true,
 299            'require_shipping'  => 0,
 300            'shipping_fee'      => 0,
 301            'charge_tax'        => 0,
 302            'mode'              => 'regular',
 303  
 304            // TODO: We could accept bank accounts but the hold/capture rules
 305            // are not quite clear. Just accept credit cards for now.
 306            'funding_sources'   => 'cc',
 307          );
 308  
 309          $charge = $cart->willApplyCharge($viewer, $this);
 310          $result = $wepay->request('checkout/create', $params);
 311  
 312          $cart->setMetadataValue('provider.checkoutURI', $result->checkout_uri);
 313          $cart->save();
 314  
 315          $charge->setMetadataValue('wepay.checkoutID', $result->checkout_id);
 316          $charge->save();
 317  
 318          $uri = new PhutilURI($result->checkout_uri);
 319          return id(new AphrontRedirectResponse())
 320            ->setIsExternal(true)
 321            ->setURI($uri);
 322        case 'charge':
 323          if ($cart->getStatus() !== PhortuneCart::STATUS_PURCHASING) {
 324            return id(new AphrontRedirectResponse())
 325              ->setURI($cart->getCheckoutURI());
 326          }
 327  
 328          $checkout_id = $request->getInt('checkout_id');
 329          $params = array(
 330            'checkout_id' => $checkout_id,
 331          );
 332  
 333          $checkout = $wepay->request('checkout', $params);
 334          if ($checkout->reference_id != $cart->getPHID()) {
 335            throw new Exception(
 336              pht('Checkout reference ID does not match cart PHID!'));
 337          }
 338  
 339          $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
 340            switch ($checkout->state) {
 341              case 'authorized':
 342              case 'reserved':
 343              case 'captured':
 344                // TODO: Are these all really "done" states, and not "hold"
 345                // states? Cards and bank accounts both come back as "authorized"
 346                // on the staging environment. Figure out what happens in
 347                // production?
 348  
 349                $cart->didApplyCharge($charge);
 350  
 351                $response = id(new AphrontRedirectResponse())->setURI(
 352                   $cart->getCheckoutURI());
 353                break;
 354              default:
 355                // It's not clear if we can ever get here on the web workflow,
 356                // WePay doesn't seem to return back to us after a failure (the
 357                // workflow dead-ends instead).
 358  
 359                $cart->didFailCharge($charge);
 360  
 361                $response = $controller
 362                  ->newDialog()
 363                  ->setTitle(pht('Charge Failed'))
 364                  ->appendParagraph(
 365                    pht(
 366                      'Unable to make payment (checkout state is "%s").',
 367                      $checkout->state))
 368                  ->addCancelButton($cart->getCheckoutURI(), pht('Continue'));
 369                break;
 370            }
 371          unset($unguarded);
 372  
 373          return $response;
 374        case 'cancel':
 375          // TODO: I don't know how it's possible to cancel out of a WePay
 376          // charge workflow.
 377          throw new Exception(
 378            pht('How did you get here? WePay has no cancel flow in its UI...?'));
 379          break;
 380      }
 381  
 382      throw new Exception(
 383        pht('Unsupported action "%s".', $controller->getAction()));
 384    }
 385  
 386    private function loadWePayAPILibraries() {
 387      $root = dirname(phutil_get_library_root('phabricator'));
 388      require_once $root.'/externals/wepay/wepay.php';
 389  
 390      WePay::useStaging(
 391        $this->getWePayClientID(),
 392        $this->getWePayClientSecret());
 393  
 394      return new WePay($this->getWePayAccessToken());
 395    }
 396  
 397    private function getWePayCheckoutID(PhortuneCharge $charge) {
 398      $checkout_id = $charge->getMetadataValue('wepay.checkoutID');
 399      if ($checkout_id === null) {
 400        throw new Exception(pht('No WePay Checkout ID present on charge!'));
 401      }
 402      return $checkout_id;
 403    }
 404  
 405  }


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