[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |