Skip to content

Commit 6efba56

Browse files
author
epriestley
committed
Implement Balanced Payments as a PhortunePaymentProvider
Summary: Allows Balanced payment methods to be added. This works essentially the same way as Stripe, except everything is a little bit different. Slightly more stuff could be shared, but I feel //mostly// good about this. I'll probably do a bit more cleanup next. Some of the error handling is messy, in particular. Ref T2787. Test Plan: Added Balanced and Stripe payment methods. Reviewers: btrahan, chad Reviewed By: btrahan CC: aran Maniphest Tasks: T2787 Differential Revision: https://secure.phabricator.com/D5765
1 parent 2378678 commit 6efba56

File tree

9 files changed

+308
-35
lines changed

9 files changed

+308
-35
lines changed

src/__celerity_resource_map__.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,20 @@
12741274
),
12751275
'disk' => '/rsrc/js/application/diffusion/behavior-audit-preview.js',
12761276
),
1277+
'javelin-behavior-balanced-payment-form' =>
1278+
array(
1279+
'uri' => '/res/2a850a31/rsrc/js/application/phortune/behavior-balanced-payment-form.js',
1280+
'type' => 'js',
1281+
'requires' =>
1282+
array(
1283+
0 => 'javelin-behavior',
1284+
1 => 'javelin-dom',
1285+
2 => 'javelin-json',
1286+
3 => 'javelin-workflow',
1287+
4 => 'phortune-credit-card-form',
1288+
),
1289+
'disk' => '/rsrc/js/application/phortune/behavior-balanced-payment-form.js',
1290+
),
12771291
'javelin-behavior-conpherence-drag-and-drop-photo' =>
12781292
array(
12791293
'uri' => '/res/9e3eb1cd/rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js',
@@ -2258,7 +2272,7 @@
22582272
),
22592273
'javelin-behavior-stripe-payment-form' =>
22602274
array(
2261-
'uri' => '/res/62dc91b4/rsrc/js/application/phortune/behavior-stripe-payment-form.js',
2275+
'uri' => '/res/2ae12d96/rsrc/js/application/phortune/behavior-stripe-payment-form.js',
22622276
'type' => 'js',
22632277
'requires' =>
22642278
array(

src/__phutil_library_map__.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,7 @@
12191219
'PhabricatorPhabricatorOAuthConfigOptions' => 'applications/config/option/PhabricatorPhabricatorOAuthConfigOptions.php',
12201220
'PhabricatorPhameConfigOptions' => 'applications/phame/config/PhabricatorPhameConfigOptions.php',
12211221
'PhabricatorPholioConfigOptions' => 'applications/pholio/config/PhabricatorPholioConfigOptions.php',
1222+
'PhabricatorPhortuneConfigOptions' => 'applications/phortune/option/PhabricatorPhortuneConfigOptions.php',
12221223
'PhabricatorPhrequentConfigOptions' => 'applications/phrequent/config/PhabricatorPhrequentConfigOptions.php',
12231224
'PhabricatorPhrictionConfigOptions' => 'applications/phriction/config/PhabricatorPhrictionConfigOptions.php',
12241225
'PhabricatorPinboardItemView' => 'view/layout/PhabricatorPinboardItemView.php',
@@ -1400,7 +1401,6 @@
14001401
'PhabricatorStorageManagementUpgradeWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php',
14011402
'PhabricatorStorageManagementWorkflow' => 'infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php',
14021403
'PhabricatorStoragePatch' => 'infrastructure/storage/management/PhabricatorStoragePatch.php',
1403-
'PhabricatorStripeConfigOptions' => 'applications/phortune/option/PhabricatorStripeConfigOptions.php',
14041404
'PhabricatorSubscribableInterface' => 'applications/subscriptions/interface/PhabricatorSubscribableInterface.php',
14051405
'PhabricatorSubscribersQuery' => 'applications/subscriptions/query/PhabricatorSubscribersQuery.php',
14061406
'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php',
@@ -1580,6 +1580,7 @@
15801580
'PhortuneAccountTransaction' => 'applications/phortune/storage/PhortuneAccountTransaction.php',
15811581
'PhortuneAccountTransactionQuery' => 'applications/phortune/query/PhortuneAccountTransactionQuery.php',
15821582
'PhortuneAccountViewController' => 'applications/phortune/controller/PhortuneAccountViewController.php',
1583+
'PhortuneBalancedPaymentProvider' => 'applications/phortune/provider/PhortuneBalancedPaymentProvider.php',
15831584
'PhortuneCart' => 'applications/phortune/storage/PhortuneCart.php',
15841585
'PhortuneCharge' => 'applications/phortune/storage/PhortuneCharge.php',
15851586
'PhortuneController' => 'applications/phortune/controller/PhortuneController.php',
@@ -2923,6 +2924,7 @@
29232924
'PhabricatorPhabricatorOAuthConfigOptions' => 'PhabricatorApplicationConfigOptions',
29242925
'PhabricatorPhameConfigOptions' => 'PhabricatorApplicationConfigOptions',
29252926
'PhabricatorPholioConfigOptions' => 'PhabricatorApplicationConfigOptions',
2927+
'PhabricatorPhortuneConfigOptions' => 'PhabricatorApplicationConfigOptions',
29262928
'PhabricatorPhrequentConfigOptions' => 'PhabricatorApplicationConfigOptions',
29272929
'PhabricatorPhrictionConfigOptions' => 'PhabricatorApplicationConfigOptions',
29282930
'PhabricatorPinboardItemView' => 'AphrontView',
@@ -3091,7 +3093,6 @@
30913093
'PhabricatorStorageManagementStatusWorkflow' => 'PhabricatorStorageManagementWorkflow',
30923094
'PhabricatorStorageManagementUpgradeWorkflow' => 'PhabricatorStorageManagementWorkflow',
30933095
'PhabricatorStorageManagementWorkflow' => 'PhutilArgumentWorkflow',
3094-
'PhabricatorStripeConfigOptions' => 'PhabricatorApplicationConfigOptions',
30953096
'PhabricatorSubscribersQuery' => 'PhabricatorQuery',
30963097
'PhabricatorSubscriptionsEditController' => 'PhabricatorController',
30973098
'PhabricatorSubscriptionsEditor' => 'PhabricatorEditor',
@@ -3302,6 +3303,7 @@
33023303
'PhortuneAccountTransaction' => 'PhabricatorApplicationTransaction',
33033304
'PhortuneAccountTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
33043305
'PhortuneAccountViewController' => 'PhortuneController',
3306+
'PhortuneBalancedPaymentProvider' => 'PhortunePaymentProvider',
33053307
'PhortuneCart' => 'PhortuneDAO',
33063308
'PhortuneCharge' => 'PhortuneDAO',
33073309
'PhortuneController' => 'PhabricatorController',

src/applications/phortune/exception/PhortuneNotImplementedException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ final class PhortuneNotImplementedException extends Exception {
55
public function __construct(PhortunePaymentProvider $provider) {
66
$class = get_class($provider);
77
return parent::__construct(
8-
"Provider '{$provider}' does not implement this method.");
8+
"Provider '{$class}' does not implement this method.");
99
}
1010

1111
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
final class PhabricatorPhortuneConfigOptions
4+
extends PhabricatorApplicationConfigOptions {
5+
6+
public function getName() {
7+
return pht("Phortune");
8+
}
9+
10+
public function getDescription() {
11+
return pht("Configure payments and billing.");
12+
}
13+
14+
public function getOptions() {
15+
return array(
16+
$this->newOption('phortune.stripe.publishable-key', 'string', null)
17+
->setLocked(true)
18+
->setDescription(pht('Stripe publishable key.')),
19+
$this->newOption('phortune.stripe.secret-key', 'string', null)
20+
->setHidden(true)
21+
->setDescription(pht('Stripe secret key.')),
22+
$this->newOption('phortune.balanced.marketplace-uri', 'string', null)
23+
->setLocked(true)
24+
->setDescription(pht('Balanced Marketplace URI.')),
25+
$this->newOption('phortune.balanced.secret-key', 'string', null)
26+
->setHidden(true)
27+
->setDescription(pht('Balanced secret key.')),
28+
);
29+
}
30+
31+
}

src/applications/phortune/option/PhabricatorStripeConfigOptions.php

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<?php
2+
3+
final class PhortuneBalancedPaymentProvider extends PhortunePaymentProvider {
4+
5+
public function isEnabled() {
6+
return $this->getMarketplaceURI() &&
7+
$this->getSecretKey();
8+
}
9+
10+
public function getProviderType() {
11+
return 'balanced';
12+
}
13+
14+
public function getProviderDomain() {
15+
return 'balancedpayments.com';
16+
}
17+
18+
public function getPaymentMethodDescription() {
19+
return pht('Add Credit or Debit Card');
20+
}
21+
22+
public function getPaymentMethodIcon() {
23+
return celerity_get_resource_uri('/rsrc/image/phortune/balanced.png');
24+
}
25+
26+
public function getPaymentMethodProviderDescription() {
27+
return pht('Processed by Balanced');
28+
}
29+
30+
31+
public function canHandlePaymentMethod(PhortunePaymentMethod $method) {
32+
$type = $method->getMetadataValue('type');
33+
return ($type === 'balanced.account');
34+
}
35+
36+
protected function executeCharge(
37+
PhortunePaymentMethod $method,
38+
PhortuneCharge $charge) {
39+
throw new PhortuneNotImplementedException($this);
40+
}
41+
42+
private function getMarketplaceURI() {
43+
return PhabricatorEnv::getEnvConfig('phortune.balanced.marketplace-uri');
44+
}
45+
46+
private function getSecretKey() {
47+
return PhabricatorEnv::getEnvConfig('phortune.balanced.secret-key');
48+
}
49+
50+
51+
/* -( Adding Payment Methods )--------------------------------------------- */
52+
53+
54+
public function canCreatePaymentMethods() {
55+
return true;
56+
}
57+
58+
59+
/**
60+
* @phutil-external-symbol class Balanced\Settings
61+
* @phutil-external-symbol class Balanced\Marketplace
62+
* @phutil-external-symbol class RESTful\Exceptions\HTTPError
63+
*/
64+
public function createPaymentMethodFromRequest(
65+
AphrontRequest $request,
66+
PhortunePaymentMethod $method) {
67+
68+
$card_errors = $request->getStr('cardErrors');
69+
$balanced_data = $request->getStr('balancedCardData');
70+
71+
$errors = array();
72+
if ($card_errors) {
73+
$raw_errors = json_decode($card_errors);
74+
$errors = $this->parseRawCreatePaymentMethodErrors($raw_errors);
75+
}
76+
77+
if (!$errors) {
78+
$data = json_decode($balanced_data, true);
79+
if (!is_array($data)) {
80+
$errors[] = pht('An error occurred decoding card data.');
81+
}
82+
}
83+
84+
if (!$errors) {
85+
$root = dirname(phutil_get_library_root('phabricator'));
86+
require_once $root.'/externals/httpful/bootstrap.php';
87+
require_once $root.'/externals/restful/bootstrap.php';
88+
require_once $root.'/externals/balanced-php/bootstrap.php';
89+
90+
$account_phid = $method->getAccountPHID();
91+
$author_phid = $method->getAuthorPHID();
92+
$description = $account_phid.':'.$author_phid;
93+
94+
try {
95+
96+
Balanced\Settings::$api_key = $this->getSecretKey();
97+
$buyer = Balanced\Marketplace::mine()->createBuyer(
98+
null,
99+
$data['uri'],
100+
array(
101+
'description' => $description,
102+
));
103+
104+
} catch (RESTful\Exceptions\HTTPError $error) {
105+
// NOTE: This exception doesn't print anything meaningful if it escapes
106+
// to top level. Replace it with something slightly readable.
107+
throw new Exception($error->response->body->description);
108+
}
109+
110+
$exp_string = $data['expiration_year'].'-'.$data['expiration_month'];
111+
$epoch = strtotime($exp_string);
112+
113+
$method
114+
->setName($data['brand'].' / '.$data['last_four'])
115+
->setExpiresEpoch($epoch)
116+
->setMetadata(
117+
array(
118+
'type' => 'balanced.account',
119+
'balanced.accountURI' => $buyer->uri,
120+
'balanced.cardURI' => $data['uri'],
121+
));
122+
}
123+
124+
return $errors;
125+
}
126+
127+
public function renderCreatePaymentMethodForm(
128+
AphrontRequest $request,
129+
array $errors) {
130+
131+
$ccform = id(new PhortuneCreditCardForm())
132+
->setUser($request->getUser())
133+
->setCardNumberError(isset($errors['number']) ? pht('Invalid') : true)
134+
->setCardCVCError(isset($errors['cvc']) ? pht('Invalid') : true)
135+
->setCardExpirationError(isset($errors['exp']) ? pht('Invalid') : null)
136+
->addScript('https://js.balancedpayments.com/v1/balanced.js');
137+
138+
Javelin::initBehavior(
139+
'balanced-payment-form',
140+
array(
141+
'balancedMarketplaceURI' => $this->getMarketplaceURI(),
142+
'formID' => $ccform->getFormID(),
143+
));
144+
145+
return $ccform->buildForm();
146+
}
147+
148+
private function parseRawCreatePaymentMethodErrors(array $raw_errors) {
149+
$errors = array();
150+
151+
foreach ($raw_errors as $error) {
152+
switch ($error) {
153+
case 'number':
154+
$errors[$error] = pht('Card number is incorrect or invalid.');
155+
break;
156+
case 'cvc':
157+
$errors[$error] = pht('CVC code is incorrect or invalid.');
158+
break;
159+
case 'exp':
160+
$errors[$error] = pht('Card expiration date is incorrect.');
161+
break;
162+
default:
163+
$errors[] = $error;
164+
break;
165+
}
166+
}
167+
168+
return $errors;
169+
}
170+
171+
}

src/applications/phortune/provider/PhortuneStripePaymentProvider.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ protected function executeCharge(
5959
}
6060

6161
private function getPublishableKey() {
62-
return PhabricatorEnv::getEnvConfig('stripe.publishable-key');
62+
return PhabricatorEnv::getEnvConfig('phortune.stripe.publishable-key');
6363
}
6464

6565
private function getSecretKey() {
66-
return PhabricatorEnv::getEnvConfig('stripe.secret-key');
66+
return PhabricatorEnv::getEnvConfig('phortune.stripe.secret-key');
6767
}
6868

6969

@@ -90,18 +90,22 @@ public function createPaymentMethodFromRequest(
9090
if ($card_errors) {
9191
$raw_errors = json_decode($card_errors);
9292
$errors = $this->parseRawCreatePaymentMethodErrors($raw_errors);
93-
} else if (!$stripe_token) {
94-
$errors[] = pht('There was an unknown error processing your card.');
9593
}
9694

97-
$secret_key = $this->getSecretKey();
95+
if (!$errors) {
96+
if (!$stripe_token) {
97+
$errors[] = pht('There was an unknown error processing your card.');
98+
}
99+
}
98100

99101
if (!$errors) {
100102
$root = dirname(phutil_get_library_root('phabricator'));
101103
require_once $root.'/externals/stripe-php/lib/Stripe.php';
102104

103105
try {
104106
// First, make sure the token is valid.
107+
$secret_key = $this->getSecretKey();
108+
105109
$info = id(new Stripe_Token())->retrieve($stripe_token, $secret_key);
106110

107111
$account_phid = $method->getAccountPHID();

0 commit comments

Comments
 (0)