[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
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 }
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 |