public function processControllerRequest(PhortuneProviderActionController $controller, AphrontRequest $request)
 {
     $viewer = $request->getUser();
     $cart = $controller->loadCart($request->getInt('cartID'));
     if (!$cart) {
         return new Aphront404Response();
     }
     $charge = $controller->loadActiveCharge($cart);
     switch ($controller->getAction()) {
         case 'checkout':
             if ($charge) {
                 throw new Exception(pht('Cart is already charging!'));
             }
             break;
         case 'charge':
         case 'cancel':
             if (!$charge) {
                 throw new Exception(pht('Cart is not charging yet!'));
             }
             break;
     }
     switch ($controller->getAction()) {
         case 'checkout':
             $return_uri = $this->getControllerURI('charge', array('cartID' => $cart->getID()));
             $cancel_uri = $this->getControllerURI('cancel', array('cartID' => $cart->getID()));
             $price = $cart->getTotalPriceAsCurrency();
             $charge = $cart->willApplyCharge($viewer, $this);
             $params = array('PAYMENTREQUEST_0_AMT' => $price->formatBareValue(), 'PAYMENTREQUEST_0_CURRENCYCODE' => $price->getCurrency(), 'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale', 'PAYMENTREQUEST_0_CUSTOM' => $charge->getPHID(), 'PAYMENTREQUEST_0_DESC' => $cart->getName(), 'RETURNURL' => $return_uri, 'CANCELURL' => $cancel_uri, 'NOSHIPPING' => '1');
             $result = $this->newPaypalAPICall()->setRawPayPalQuery('SetExpressCheckout', $params)->resolve();
             $uri = new PhutilURI('https://www.sandbox.paypal.com/cgi-bin/webscr');
             $uri->setQueryParams(array('cmd' => '_express-checkout', 'token' => $result['TOKEN']));
             $cart->setMetadataValue('provider.checkoutURI', (string) $uri);
             $cart->save();
             $charge->setMetadataValue('paypal.token', $result['TOKEN']);
             $charge->save();
             return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI($uri);
         case 'charge':
             if ($cart->getStatus() !== PhortuneCart::STATUS_PURCHASING) {
                 return id(new AphrontRedirectResponse())->setURI($cart->getCheckoutURI());
             }
             $token = $request->getStr('token');
             $params = array('TOKEN' => $token);
             $result = $this->newPaypalAPICall()->setRawPayPalQuery('GetExpressCheckoutDetails', $params)->resolve();
             if ($result['CUSTOM'] !== $charge->getPHID()) {
                 throw new Exception(pht('Paypal checkout does not match Phortune charge!'));
             }
             if ($result['CHECKOUTSTATUS'] !== 'PaymentActionNotInitiated') {
                 return $controller->newDialog()->setTitle(pht('Payment Already Processed'))->appendParagraph(pht('The payment response for this charge attempt has already ' . 'been processed.'))->addCancelButton($cart->getCheckoutURI(), pht('Continue'));
             }
             $price = $cart->getTotalPriceAsCurrency();
             $params = array('TOKEN' => $token, 'PAYERID' => $result['PAYERID'], 'PAYMENTREQUEST_0_AMT' => $price->formatBareValue(), 'PAYMENTREQUEST_0_CURRENCYCODE' => $price->getCurrency(), 'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale');
             $result = $this->newPaypalAPICall()->setRawPayPalQuery('DoExpressCheckoutPayment', $params)->resolve();
             $transaction_id = $result['PAYMENTINFO_0_TRANSACTIONID'];
             $success = false;
             $hold = false;
             switch ($result['PAYMENTINFO_0_PAYMENTSTATUS']) {
                 case 'Processed':
                 case 'Completed':
                 case 'Completed-Funds-Held':
                     $success = true;
                     break;
                 case 'In-Progress':
                 case 'Pending':
                     // TODO: We can capture more information about this stuff.
                     $hold = true;
                     break;
                 case 'Denied':
                 case 'Expired':
                 case 'Failed':
                 case 'Partially-Refunded':
                 case 'Canceled-Reversal':
                 case 'None':
                 case 'Refunded':
                 case 'Reversed':
                 case 'Voided':
                 default:
                     // These are all failure states.
                     break;
             }
             $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
             $charge->setMetadataValue('paypal.transactionID', $transaction_id);
             $charge->save();
             if ($success) {
                 $cart->didApplyCharge($charge);
                 $response = id(new AphrontRedirectResponse())->setURI($cart->getCheckoutURI());
             } else {
                 if ($hold) {
                     $cart->didHoldCharge($charge);
                     $response = $controller->newDialog()->setTitle(pht('Charge On Hold'))->appendParagraph(pht('Your charge is on hold, for reasons?'))->addCancelButton($cart->getCheckoutURI(), pht('Continue'));
                 } else {
                     $cart->didFailCharge($charge);
                     $response = $controller->newDialog()->setTitle(pht('Charge Failed'))->addCancelButton($cart->getCheckoutURI(), pht('Continue'));
                 }
             }
             unset($unguarded);
             return $response;
         case 'cancel':
             if ($cart->getStatus() === PhortuneCart::STATUS_PURCHASING) {
                 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
                 // TODO: Since the user cancelled this, we could conceivably just
                 // throw it away or make it more clear that it's a user cancel.
                 $cart->didFailCharge($charge);
                 unset($unguarded);
             }
             return id(new AphrontRedirectResponse())->setURI($cart->getCheckoutURI());
     }
     throw new Exception(pht('Unsupported action "%s".', $controller->getAction()));
 }
 /**
  * @phutil-external-symbol class WePay
  */
 public function processControllerRequest(PhortuneProviderActionController $controller, AphrontRequest $request)
 {
     $wepay = $this->loadWePayAPILibraries();
     $viewer = $request->getUser();
     $cart = $controller->loadCart($request->getInt('cartID'));
     if (!$cart) {
         return new Aphront404Response();
     }
     $charge = $controller->loadActiveCharge($cart);
     switch ($controller->getAction()) {
         case 'checkout':
             if ($charge) {
                 throw new Exception(pht('Cart is already charging!'));
             }
             break;
         case 'charge':
         case 'cancel':
             if (!$charge) {
                 throw new Exception(pht('Cart is not charging yet!'));
             }
             break;
     }
     switch ($controller->getAction()) {
         case 'checkout':
             $return_uri = $this->getControllerURI('charge', array('cartID' => $cart->getID()));
             $cancel_uri = $this->getControllerURI('cancel', array('cartID' => $cart->getID()));
             $price = $cart->getTotalPriceAsCurrency();
             $params = array('account_id' => $this->getWePayAccountID(), 'short_description' => $cart->getName(), 'type' => 'SERVICE', 'amount' => $price->formatBareValue(), 'long_description' => $cart->getName(), 'reference_id' => $cart->getPHID(), 'app_fee' => 0, 'fee_payer' => 'Payee', 'redirect_uri' => $return_uri, 'fallback_uri' => $cancel_uri, 'auto_capture' => true, 'require_shipping' => 0, 'shipping_fee' => 0, 'charge_tax' => 0, 'mode' => 'regular', 'funding_sources' => 'cc');
             $charge = $cart->willApplyCharge($viewer, $this);
             $result = $wepay->request('checkout/create', $params);
             $cart->setMetadataValue('provider.checkoutURI', $result->checkout_uri);
             $cart->save();
             $charge->setMetadataValue('wepay.checkoutID', $result->checkout_id);
             $charge->save();
             $uri = new PhutilURI($result->checkout_uri);
             return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI($uri);
         case 'charge':
             if ($cart->getStatus() !== PhortuneCart::STATUS_PURCHASING) {
                 return id(new AphrontRedirectResponse())->setURI($cart->getCheckoutURI());
             }
             $checkout_id = $request->getInt('checkout_id');
             $params = array('checkout_id' => $checkout_id);
             $checkout = $wepay->request('checkout', $params);
             if ($checkout->reference_id != $cart->getPHID()) {
                 throw new Exception(pht('Checkout reference ID does not match cart PHID!'));
             }
             $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
             switch ($checkout->state) {
                 case 'authorized':
                 case 'reserved':
                 case 'captured':
                     // TODO: Are these all really "done" states, and not "hold"
                     // states? Cards and bank accounts both come back as "authorized"
                     // on the staging environment. Figure out what happens in
                     // production?
                     $cart->didApplyCharge($charge);
                     $response = id(new AphrontRedirectResponse())->setURI($cart->getCheckoutURI());
                     break;
                 default:
                     // It's not clear if we can ever get here on the web workflow,
                     // WePay doesn't seem to return back to us after a failure (the
                     // workflow dead-ends instead).
                     $cart->didFailCharge($charge);
                     $response = $controller->newDialog()->setTitle(pht('Charge Failed'))->appendParagraph(pht('Unable to make payment (checkout state is "%s").', $checkout->state))->addCancelButton($cart->getCheckoutURI(), pht('Continue'));
                     break;
             }
             unset($unguarded);
             return $response;
         case 'cancel':
             // TODO: I don't know how it's possible to cancel out of a WePay
             // charge workflow.
             throw new Exception(pht('How did you get here? WePay has no cancel flow in its UI...?'));
             break;
     }
     throw new Exception(pht('Unsupported action "%s".', $controller->getAction()));
 }