Skip to content

Commit c109375

Browse files
committed
Initial Stripe integration.
1 parent 7f2d8c5 commit c109375

29 files changed

+2535
-1323
lines changed

mu-plugins/jquery.org/lib/Stripe.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
// Tested on PHP 5.2, 5.3
4+
5+
// This snippet (and some of the curl code) due to the Facebook SDK.
6+
if (!function_exists('curl_init')) {
7+
throw new Exception('Stripe needs the CURL PHP extension.');
8+
}
9+
if (!function_exists('json_decode')) {
10+
throw new Exception('Stripe needs the JSON PHP extension.');
11+
}
12+
13+
// Stripe singleton
14+
require(dirname(__FILE__) . '/Stripe/Stripe.php');
15+
16+
// Utilities
17+
require(dirname(__FILE__) . '/Stripe/Util.php');
18+
require(dirname(__FILE__) . '/Stripe/Util/Set.php');
19+
20+
// Errors
21+
require(dirname(__FILE__) . '/Stripe/Error.php');
22+
require(dirname(__FILE__) . '/Stripe/ApiError.php');
23+
require(dirname(__FILE__) . '/Stripe/ApiConnectionError.php');
24+
require(dirname(__FILE__) . '/Stripe/AuthenticationError.php');
25+
require(dirname(__FILE__) . '/Stripe/CardError.php');
26+
require(dirname(__FILE__) . '/Stripe/InvalidRequestError.php');
27+
28+
// Plumbing
29+
require(dirname(__FILE__) . '/Stripe/Object.php');
30+
require(dirname(__FILE__) . '/Stripe/ApiRequestor.php');
31+
require(dirname(__FILE__) . '/Stripe/ApiResource.php');
32+
require(dirname(__FILE__) . '/Stripe/SingletonApiResource.php');
33+
require(dirname(__FILE__) . '/Stripe/List.php');
34+
35+
// Stripe API Resources
36+
require(dirname(__FILE__) . '/Stripe/Account.php');
37+
require(dirname(__FILE__) . '/Stripe/Charge.php');
38+
require(dirname(__FILE__) . '/Stripe/Customer.php');
39+
require(dirname(__FILE__) . '/Stripe/Invoice.php');
40+
require(dirname(__FILE__) . '/Stripe/InvoiceItem.php');
41+
require(dirname(__FILE__) . '/Stripe/Plan.php');
42+
require(dirname(__FILE__) . '/Stripe/Token.php');
43+
require(dirname(__FILE__) . '/Stripe/Coupon.php');
44+
require(dirname(__FILE__) . '/Stripe/Event.php');
45+
require(dirname(__FILE__) . '/Stripe/Transfer.php');
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
class Stripe_Account extends Stripe_SingletonApiResource
4+
{
5+
public static function constructFrom($values, $apiKey=null)
6+
{
7+
$class = get_class();
8+
return self::scopedConstructFrom($class, $values, $apiKey);
9+
}
10+
11+
public static function retrieve($apiKey=null)
12+
{
13+
$class = get_class();
14+
return self::_scopedSingletonRetrieve($class, $apiKey);
15+
}
16+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
class Stripe_ApiConnectionError extends Stripe_Error
4+
{
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
class Stripe_ApiError extends Stripe_Error
4+
{
5+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
<?php
2+
3+
class Stripe_ApiRequestor
4+
{
5+
public $apiKey;
6+
7+
public function __construct($apiKey=null)
8+
{
9+
$this->_apiKey = $apiKey;
10+
}
11+
12+
public static function apiUrl($url='')
13+
{
14+
$apiBase = Stripe::$apiBase;
15+
return "$apiBase$url";
16+
}
17+
18+
public static function utf8($value)
19+
{
20+
if (is_string($value))
21+
return utf8_encode($value);
22+
else
23+
return $value;
24+
}
25+
26+
private static function _encodeObjects($d)
27+
{
28+
if ($d instanceof Stripe_ApiResource) {
29+
return $d->id;
30+
} else if ($d === true) {
31+
return 'true';
32+
} else if ($d === false) {
33+
return 'false';
34+
} else if (is_array($d)) {
35+
$res = array();
36+
foreach ($d as $k => $v)
37+
$res[$k] = self::_encodeObjects($v);
38+
return $res;
39+
} else {
40+
return $d;
41+
}
42+
}
43+
44+
public static function encode($d)
45+
{
46+
return http_build_query($d, null, '&');
47+
}
48+
49+
public function request($meth, $url, $params=null)
50+
{
51+
if (!$params)
52+
$params = array();
53+
list($rbody, $rcode, $myApiKey) = $this->_requestRaw($meth, $url, $params);
54+
$resp = $this->_interpretResponse($rbody, $rcode);
55+
return array($resp, $myApiKey);
56+
}
57+
58+
public function handleApiError($rbody, $rcode, $resp)
59+
{
60+
if (!is_array($resp) || !isset($resp['error']))
61+
throw new Stripe_ApiError("Invalid response object from API: $rbody (HTTP response code was $rcode)", $rcode, $rbody, $resp);
62+
$error = $resp['error'];
63+
switch ($rcode) {
64+
case 400:
65+
case 404:
66+
throw new Stripe_InvalidRequestError(isset($error['message']) ? $error['message'] : null,
67+
isset($error['param']) ? $error['param'] : null,
68+
$rcode, $rbody, $resp);
69+
case 401:
70+
throw new Stripe_AuthenticationError(isset($error['message']) ? $error['message'] : null, $rcode, $rbody, $resp);
71+
case 402:
72+
throw new Stripe_CardError(isset($error['message']) ? $error['message'] : null,
73+
isset($error['param']) ? $error['param'] : null,
74+
isset($error['code']) ? $error['code'] : null,
75+
$rcode, $rbody, $resp);
76+
default:
77+
throw new Stripe_ApiError(isset($error['message']) ? $error['message'] : null, $rcode, $rbody, $resp);
78+
}
79+
}
80+
81+
private function _requestRaw($meth, $url, $params)
82+
{
83+
$myApiKey = $this->_apiKey;
84+
if (!$myApiKey)
85+
$myApiKey = Stripe::$apiKey;
86+
if (!$myApiKey)
87+
throw new Stripe_AuthenticationError('No API key provided. (HINT: set your API key using "Stripe::setApiKey(<API-KEY>)". You can generate API keys from the Stripe web interface. See https://stripe.com/api for details, or email support@stripe.com if you have any questions.');
88+
89+
$absUrl = $this->apiUrl($url);
90+
$params = self::_encodeObjects($params);
91+
$langVersion = phpversion();
92+
$uname = php_uname();
93+
$ua = array('bindings_version' => Stripe::VERSION,
94+
'lang' => 'php',
95+
'lang_version' => $langVersion,
96+
'publisher' => 'stripe',
97+
'uname' => $uname);
98+
$headers = array('X-Stripe-Client-User-Agent: ' . json_encode($ua),
99+
'User-Agent: Stripe/v1 PhpBindings/' . Stripe::VERSION,
100+
'Authorization: Bearer ' . $myApiKey);
101+
list($rbody, $rcode) = $this->_curlRequest($meth, $absUrl, $headers, $params);
102+
return array($rbody, $rcode, $myApiKey);
103+
}
104+
105+
private function _interpretResponse($rbody, $rcode)
106+
{
107+
try {
108+
$resp = json_decode($rbody, true);
109+
} catch (Exception $e) {
110+
throw new Stripe_ApiError("Invalid response body from API: $rbody (HTTP response code was $rcode)", $rcode, $rbody);
111+
}
112+
113+
if ($rcode < 200 || $rcode >= 300) {
114+
$this->handleApiError($rbody, $rcode, $resp);
115+
}
116+
return $resp;
117+
}
118+
119+
private function _curlRequest($meth, $absUrl, $headers, $params)
120+
{
121+
$curl = curl_init();
122+
$meth = strtolower($meth);
123+
$opts = array();
124+
if ($meth == 'get') {
125+
$opts[CURLOPT_HTTPGET] = 1;
126+
if (count($params) > 0) {
127+
$encoded = self::encode($params);
128+
$absUrl = "$absUrl?$encoded";
129+
}
130+
} else if ($meth == 'post') {
131+
$opts[CURLOPT_POST] = 1;
132+
$opts[CURLOPT_POSTFIELDS] = self::encode($params);
133+
} else if ($meth == 'delete') {
134+
$opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
135+
if (count($params) > 0) {
136+
$encoded = self::encode($params);
137+
$absUrl = "$absUrl?$encoded";
138+
}
139+
} else {
140+
throw new Stripe_ApiError("Unrecognized method $meth");
141+
}
142+
143+
$absUrl = self::utf8($absUrl);
144+
$opts[CURLOPT_URL] = $absUrl;
145+
$opts[CURLOPT_RETURNTRANSFER] = true;
146+
$opts[CURLOPT_CONNECTTIMEOUT] = 30;
147+
$opts[CURLOPT_TIMEOUT] = 80;
148+
$opts[CURLOPT_RETURNTRANSFER] = true;
149+
$opts[CURLOPT_HTTPHEADER] = $headers;
150+
if (!Stripe::$verifySslCerts)
151+
$opts[CURLOPT_SSL_VERIFYPEER] = false;
152+
153+
curl_setopt_array($curl, $opts);
154+
$rbody = curl_exec($curl);
155+
156+
$errno = curl_errno($curl);
157+
if ($errno == CURLE_SSL_CACERT ||
158+
$errno == CURLE_SSL_PEER_CERTIFICATE ||
159+
$errno == 77 // CURLE_SSL_CACERT_BADFILE (constant not defined in PHP though)
160+
) {
161+
array_push($headers, 'X-Stripe-Client-Info: {"ca":"using Stripe-supplied CA bundle"}');
162+
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
163+
curl_setopt($curl, CURLOPT_CAINFO,
164+
dirname(__FILE__) . '/../data/ca-certificates.crt');
165+
$rbody = curl_exec($curl);
166+
}
167+
168+
if ($rbody === false) {
169+
$errno = curl_errno($curl);
170+
$message = curl_error($curl);
171+
curl_close($curl);
172+
$this->handleCurlError($errno, $message);
173+
}
174+
175+
$rcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
176+
curl_close($curl);
177+
return array($rbody, $rcode);
178+
}
179+
180+
public function handleCurlError($errno, $message)
181+
{
182+
$apiBase = Stripe::$apiBase;
183+
switch ($errno) {
184+
case CURLE_COULDNT_CONNECT:
185+
case CURLE_COULDNT_RESOLVE_HOST:
186+
case CURLE_OPERATION_TIMEOUTED:
187+
$msg = "Could not connect to Stripe ($apiBase). Please check your internet connection and try again. If this problem persists, you should check Stripe's service status at https://twitter.com/stripestatus, or let us know at support@stripe.com.";
188+
break;
189+
case CURLE_SSL_CACERT:
190+
case CURLE_SSL_PEER_CERTIFICATE:
191+
$msg = "Could not verify Stripe's SSL certificate. Please make sure that your network is not intercepting certificates. (Try going to $apiBase in your browser.) If this problem persists, let us know at support@stripe.com.";
192+
break;
193+
default:
194+
$msg = "Unexpected error communicating with Stripe. If this problem persists, let us know at support@stripe.com.";
195+
}
196+
197+
$msg .= "\n\n(Network error [errno $errno]: $message)";
198+
throw new Stripe_ApiConnectionError($msg);
199+
}
200+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
abstract class Stripe_ApiResource extends Stripe_Object
4+
{
5+
protected static function _scopedRetrieve($class, $id, $apiKey=null)
6+
{
7+
$instance = new $class($id, $apiKey);
8+
$instance->refresh();
9+
return $instance;
10+
}
11+
12+
public function refresh()
13+
{
14+
$requestor = new Stripe_ApiRequestor($this->_apiKey);
15+
$url = $this->instanceUrl();
16+
17+
list($response, $apiKey) = $requestor->request('get', $url);
18+
$this->refreshFrom($response, $apiKey);
19+
return $this;
20+
}
21+
22+
public static function className($class)
23+
{
24+
// Useful for namespaces: Foo\Stripe_Charge
25+
if ($postfix = strrchr($class, '\\'))
26+
$class = substr($postfix, 1);
27+
if (substr($class, 0, strlen('Stripe')) == 'Stripe')
28+
$class = substr($class, strlen('Stripe'));
29+
$class = str_replace('_', '', $class);
30+
$name = urlencode($class);
31+
$name = strtolower($name);
32+
return $name;
33+
}
34+
35+
public static function classUrl($class)
36+
{
37+
$base = self::className($class);
38+
return "/v1/${base}s";
39+
}
40+
41+
public function instanceUrl()
42+
{
43+
$id = $this['id'];
44+
$class = get_class($this);
45+
if (!$id) {
46+
throw new Stripe_InvalidRequestError("Could not determine which URL to request: $class instance has invalid ID: $id", null);
47+
}
48+
$id = Stripe_ApiRequestor::utf8($id);
49+
$base = self::classUrl($class);
50+
$extn = urlencode($id);
51+
return "$base/$extn";
52+
}
53+
54+
private static function _validateCall($method, $params=null, $apiKey=null)
55+
{
56+
if ($params && !is_array($params))
57+
throw new Stripe_Error("You must pass an array as the first argument to Stripe API method calls. (HINT: an example call to create a charge would be: \"StripeCharge::create(array('amount' => 100, 'currency' => 'usd', 'card' => array('number' => 4242424242424242, 'exp_month' => 5, 'exp_year' => 2015)))\")");
58+
if ($apiKey && !is_string($apiKey))
59+
throw new Stripe_Error('The second argument to Stripe API method calls is an optional per-request apiKey, which must be a string. (HINT: you can set a global apiKey by "Stripe::setApiKey(<apiKey>)")');
60+
}
61+
62+
protected static function _scopedAll($class, $params=null, $apiKey=null)
63+
{
64+
self::_validateCall('all', $params, $apiKey);
65+
$requestor = new Stripe_ApiRequestor($apiKey);
66+
$url = self::classUrl($class);
67+
list($response, $apiKey) = $requestor->request('get', $url, $params);
68+
return Stripe_Util::convertToStripeObject($response, $apiKey);
69+
}
70+
71+
protected static function _scopedCreate($class, $params=null, $apiKey=null)
72+
{
73+
self::_validateCall('create', $params, $apiKey);
74+
$requestor = new Stripe_ApiRequestor($apiKey);
75+
$url = self::classUrl($class);
76+
list($response, $apiKey) = $requestor->request('post', $url, $params);
77+
return Stripe_Util::convertToStripeObject($response, $apiKey);
78+
}
79+
80+
protected function _scopedSave($class)
81+
{
82+
self::_validateCall('save');
83+
if ($this->_unsavedValues) {
84+
$requestor = new Stripe_ApiRequestor($this->_apiKey);
85+
$params = array();
86+
foreach ($this->_unsavedValues->toArray() as $k)
87+
$params[$k] = $this->$k;
88+
$url = $this->instanceUrl();
89+
list($response, $apiKey) = $requestor->request('post', $url, $params);
90+
$this->refreshFrom($response, $apiKey);
91+
}
92+
return $this;
93+
}
94+
95+
protected function _scopedDelete($class, $params=null)
96+
{
97+
self::_validateCall('delete');
98+
$requestor = new Stripe_ApiRequestor($this->_apiKey);
99+
$url = $this->instanceUrl();
100+
list($response, $apiKey) = $requestor->request('delete', $url, $params);
101+
$this->refreshFrom($response, $apiKey);
102+
return $this;
103+
}
104+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
class Stripe_AuthenticationError extends Stripe_Error
4+
{
5+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
class Stripe_CardError extends Stripe_Error
4+
{
5+
public function __construct($message, $param, $code, $http_status=null, $http_body=null, $json_body=null)
6+
{
7+
parent::__construct($message, $http_status, $http_body, $json_body);
8+
$this->param = $param;
9+
$this->code = $code;
10+
}
11+
}

0 commit comments

Comments
 (0)