For discussion -- Stripe integration

Summary:
various stripe stuff, including

- external stripe library
- payment form
- test controller to play with payment form, sample business logic

My main questions / discussion topics are...

- is the stripe PHP library too big? (ie should I write something more simple just for phabricator?)
-- if its cool, what is the best way to include the client? (ie should I make it a submodule rather than the flat copy here?)
- is the JS I wrote (too) ridiculous?
-- particularly unhappy with the error message stuff being in JS *but* it seemed the best choice given the most juicy error messages come from the stripe JS such that the overall code complexity is lowest this way.
- how should the stripe JS be included?
-- flat copy like I did here?
-- some sort of external?
-- can we just load it off stripe servers at request time? (I like that from the "if stripe is down, stripe is down" perspective)
- wasn't sure if the date control was too silly and should just be baked into the form?
-- for some reason I feel like its good to be prepared to walk away from Stripe / switch providers here, though I think this is on the wrong side of pragmatic

Test Plan: - played around with sample client form

Reviewers: epriestley

Reviewed By: epriestley

CC: aran

Differential Revision: https://secure.phabricator.com/D2096
This commit is contained in:
Bob Trahan
2012-04-04 16:09:29 -07:00
parent 877cb136e8
commit cc586b0afa
42 changed files with 5710 additions and 8 deletions

66
externals/stripe-php/lib/Stripe.php vendored Normal file
View File

@@ -0,0 +1,66 @@
<?php
// Tested on PHP 5.2, 5.3
// This snippet (and some of the curl code) due to the Facebook SDK.
if (!function_exists('curl_init')) {
throw new Exception('Stripe needs the CURL PHP extension.');
}
if (!function_exists('json_decode')) {
throw new Exception('Stripe needs the JSON PHP extension.');
}
abstract class Stripe
{
public static $apiKey;
public static $apiBase = 'https://api.stripe.com/v1';
public static $verifySslCerts = true;
const VERSION = '1.6.3';
public static function getApiKey()
{
return self::$apiKey;
}
public static function setApiKey($apiKey)
{
self::$apiKey = $apiKey;
}
public static function getVerifySslCerts() {
return self::$verifySslCerts;
}
public static function setVerifySslCerts($verify) {
self::$verifySslCerts = $verify;
}
}
// Utilities
require(dirname(__FILE__) . '/Stripe/Util.php');
require(dirname(__FILE__) . '/Stripe/Util/Set.php');
// Errors
require(dirname(__FILE__) . '/Stripe/Error.php');
require(dirname(__FILE__) . '/Stripe/ApiError.php');
require(dirname(__FILE__) . '/Stripe/ApiConnectionError.php');
require(dirname(__FILE__) . '/Stripe/AuthenticationError.php');
require(dirname(__FILE__) . '/Stripe/CardError.php');
require(dirname(__FILE__) . '/Stripe/InvalidRequestError.php');
// Plumbing
require(dirname(__FILE__) . '/Stripe/Object.php');
require(dirname(__FILE__) . '/Stripe/ApiRequestor.php');
require(dirname(__FILE__) . '/Stripe/ApiResource.php');
// Stripe API Resources
require(dirname(__FILE__) . '/Stripe/Charge.php');
require(dirname(__FILE__) . '/Stripe/Customer.php');
require(dirname(__FILE__) . '/Stripe/Invoice.php');
require(dirname(__FILE__) . '/Stripe/InvoiceItem.php');
require(dirname(__FILE__) . '/Stripe/Plan.php');
require(dirname(__FILE__) . '/Stripe/Token.php');
require(dirname(__FILE__) . '/Stripe/Coupon.php');
require(dirname(__FILE__) . '/Stripe/Event.php');

View File

@@ -0,0 +1,5 @@
<?php
class Stripe_ApiConnectionError extends Stripe_Error
{
}

View File

@@ -0,0 +1,5 @@
<?php
class Stripe_ApiError extends Stripe_Error
{
}

View File

@@ -0,0 +1,197 @@
<?php
class Stripe_ApiRequestor
{
public $apiKey;
public function __construct($apiKey=null)
{
$this->_apiKey = $apiKey;
}
public static function apiUrl($url='')
{
$apiBase = Stripe::$apiBase;
return "$apiBase$url";
}
public static function utf8($value)
{
if (is_string($value))
return utf8_encode($value);
else
return $value;
}
private static function _encodeObjects($d)
{
if ($d instanceof Stripe_ApiRequestor) {
return $d->id;
} else if ($d === true) {
return 'true';
} else if ($d === false) {
return 'false';
} else if (is_array($d)) {
$res = array();
foreach ($d as $k => $v)
$res[$k] = self::_encodeObjects($v);
return $res;
} else {
return $d;
}
}
public static function encode($d)
{
return http_build_query($d, null, '&');
}
public function request($meth, $url, $params=null)
{
if (!$params)
$params = array();
list($rbody, $rcode, $myApiKey) = $this->_requestRaw($meth, $url, $params);
$resp = $this->_interpretResponse($rbody, $rcode);
return array($resp, $myApiKey);
}
public function handleApiError($rbody, $rcode, $resp)
{
if (!is_array($resp) || !isset($resp['error']))
throw new Stripe_ApiError("Invalid response object from API: $rbody (HTTP response code was $rcode)", $rcode, $rbody, $resp);
$error = $resp['error'];
switch ($rcode) {
case 400:
case 404:
throw new Stripe_InvalidRequestError(isset($error['message']) ? $error['message'] : null,
isset($error['param']) ? $error['param'] : null,
$rcode, $rbody, $resp);
case 401:
throw new Stripe_AuthenticationError(isset($error['message']) ? $error['message'] : null, $rcode, $rbody, $resp);
case 402:
throw new Stripe_CardError(isset($error['message']) ? $error['message'] : null,
isset($error['param']) ? $error['param'] : null,
isset($error['code']) ? $error['code'] : null,
$rcode, $rbody, $resp);
default:
throw new Stripe_ApiError(isset($error['message']) ? $error['message'] : null, $rcode, $rbody, $resp);
}
}
private function _requestRaw($meth, $url, $params)
{
$myApiKey = $this->_apiKey;
if (!$myApiKey)
$myApiKey = Stripe::$apiKey;
if (!$myApiKey)
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.');
$absUrl = $this->apiUrl($url);
$params = self::_encodeObjects($params);
$langVersion = phpversion();
$uname = php_uname();
$ua = array('bindings_version' => Stripe::VERSION,
'lang' => 'php',
'lang_version' => $langVersion,
'publisher' => 'stripe',
'uname' => $uname);
$headers = array('X-Stripe-Client-User-Agent: ' . json_encode($ua),
'User-Agent: Stripe/v1 PhpBindings/' . Stripe::VERSION);
list($rbody, $rcode) = $this->_curlRequest($meth, $absUrl, $headers, $params, $myApiKey);
return array($rbody, $rcode, $myApiKey);
}
private function _interpretResponse($rbody, $rcode)
{
try {
$resp = json_decode($rbody, true);
} catch (Exception $e) {
throw new Stripe_ApiError("Invalid response body from API: $rbody (HTTP response code was $rcode)", $rcode, $rbody);
}
if ($rcode < 200 || $rcode >= 300) {
$this->handleApiError($rbody, $rcode, $resp);
}
return $resp;
}
private function _curlRequest($meth, $absUrl, $headers, $params, $myApiKey)
{
$curl = curl_init();
$meth = strtolower($meth);
$opts = array();
if ($meth == 'get') {
$opts[CURLOPT_HTTPGET] = 1;
if (count($params) > 0) {
$encoded = self::encode($params);
$absUrl = "$absUrl?$encoded";
}
} else if ($meth == 'post') {
$opts[CURLOPT_POST] = 1;
$opts[CURLOPT_POSTFIELDS] = self::encode($params);
} else if ($meth == 'delete') {
$opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
if (count($params) > 0) {
$encoded = self::encode($params);
$absUrl = "$absUrl?$encoded";
}
} else {
throw new Stripe_ApiError("Unrecognized method $meth");
}
$absUrl = self::utf8($absUrl);
$opts[CURLOPT_URL] = $absUrl;
$opts[CURLOPT_RETURNTRANSFER] = true;
$opts[CURLOPT_CONNECTTIMEOUT] = 30;
$opts[CURLOPT_TIMEOUT] = 80;
$opts[CURLOPT_RETURNTRANSFER] = true;
$opts[CURLOPT_HTTPHEADER] = $headers;
$opts[CURLOPT_USERPWD] = $myApiKey . ':';
if (!Stripe::$verifySslCerts)
$opts[CURLOPT_SSL_VERIFYPEER] = false;
curl_setopt_array($curl, $opts);
$rbody = curl_exec($curl);
$errno = curl_errno($curl);
if ($errno == CURLE_SSL_CACERT || $errno == CURLE_SSL_PEER_CERTIFICATE) {
array_push($headers, 'X-Stripe-Client-Info: {"ca":"using Stripe-supplied CA bundle"}');
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_CAINFO,
dirname(__FILE__) . '/../data/ca-certificates.crt');
$rbody = curl_exec($curl);
}
if ($rbody === false) {
$errno = curl_errno($curl);
$message = curl_error($curl);
curl_close($curl);
$this->handleCurlError($errno, $message);
}
$rcode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
return array($rbody, $rcode);
}
public function handleCurlError($errno, $message)
{
$apiBase = Stripe::$apiBase;
switch ($errno) {
case CURLE_COULDNT_CONNECT:
case CURLE_COULDNT_RESOLVE_HOST:
case CURLE_OPERATION_TIMEOUTED:
$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.";
break;
case CURLE_SSL_CACERT:
case CURLE_SSL_PEER_CERTIFICATE:
$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.";
break;
default:
$msg = "Unexpected error communicating with Stripe. If this problem persists, let us know at support@stripe.com.";
}
$msg .= "\n\n(Network error: $message)";
throw new Stripe_ApiConnectionError($msg);
}
}

View File

@@ -0,0 +1,97 @@
<?php
abstract class Stripe_ApiResource extends Stripe_Object
{
protected static function _scopedRetrieve($class, $id, $apiKey=null)
{
$instance = new $class($id, $apiKey);
$instance->refresh();
return $instance;
}
public function refresh()
{
$requestor = new Stripe_ApiRequestor($this->_apiKey);
$url = $this->instanceUrl();
list($response, $apiKey) = $requestor->request('get', $url);
$this->refreshFrom($response, $apiKey);
return $this;
}
public static function classUrl($class)
{
// Useful for namespaces: Foo\Stripe_Charge
if ($postfix = strrchr($class, '\\'))
$class = substr($postfix, 1);
if (substr($class, 0, strlen('Stripe')) == 'Stripe')
$class = substr($class, strlen('Stripe'));
$class = str_replace('_', '', $class);
$name = urlencode($class);
$name = strtolower($name);
return "/${name}s";
}
public function instanceUrl()
{
$id = $this['id'];
$class = get_class($this);
if (!$id) {
throw new Stripe_InvalidRequestError("Could not determine which URL to request: $class instance has invalid ID: $id");
}
$id = Stripe_ApiRequestor::utf8($id);
$base = self::classUrl($class);
$extn = urlencode($id);
return "$base/$extn";
}
private static function _validateCall($method, $params=null, $apiKey=null)
{
if ($params && !is_array($params))
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)))\")");
if ($apiKey && !is_string($apiKey))
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>)")');
}
protected static function _scopedAll($class, $params=null, $apiKey=null)
{
self::_validateCall('all', $params, $apiKey);
$requestor = new Stripe_ApiRequestor($apiKey);
$url = self::classUrl($class);
list($response, $apiKey) = $requestor->request('get', $url, $params);
return Stripe_Util::convertToStripeObject($response, $apiKey);
}
protected static function _scopedCreate($class, $params=null, $apiKey=null)
{
self::_validateCall('create', $params, $apiKey);
$requestor = new Stripe_ApiRequestor($apiKey);
$url = self::classUrl($class);
list($response, $apiKey) = $requestor->request('post', $url, $params);
return Stripe_Util::convertToStripeObject($response, $apiKey);
}
protected function _scopedSave($class)
{
self::_validateCall('save');
if ($this->_unsavedValues) {
$requestor = new Stripe_ApiRequestor($this->_apiKey);
$params = array();
foreach ($this->_unsavedValues->toArray() as $k)
$params[$k] = $this->$k;
$url = $this->instanceUrl();
list($response, $apiKey) = $requestor->request('post', $url, $params);
$this->refreshFrom($response, $apiKey);
}
return $this;
}
protected function _scopedDelete($class, $params=null)
{
self::_validateCall('delete');
$requestor = new Stripe_ApiRequestor($this->_apiKey);
$url = $this->instanceUrl();
list($response, $apiKey) = $requestor->request('delete', $url, $params);
$this->refreshFrom($response, $apiKey);
return $this;
}
}

View File

@@ -0,0 +1,5 @@
<?php
class Stripe_AuthenticationError extends Stripe_Error
{
}

View File

@@ -0,0 +1,11 @@
<?php
class Stripe_CardError extends Stripe_Error
{
public function __construct($message, $param, $code, $http_status=null, $http_body=null, $json_body=null)
{
parent::__construct($message, $http_status, $http_body, $json_body);
$this->param = $param;
$this->code = $code;
}
}

View File

@@ -0,0 +1,46 @@
<?php
class Stripe_Charge extends Stripe_ApiResource
{
public static function constructFrom($values, $apiKey=null)
{
$class = get_class();
return self::scopedConstructFrom($class, $values, $apiKey);
}
public static function retrieve($id, $apiKey=null)
{
$class = get_class();
return self::_scopedRetrieve($class, $id, $apiKey);
}
public static function all($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedAll($class, $params, $apiKey);
}
public static function create($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedCreate($class, $params, $apiKey);
}
public function refund($params=null)
{
$requestor = new Stripe_ApiRequestor($this->_apiKey);
$url = $this->instanceUrl() . '/refund';
list($response, $apiKey) = $requestor->request('post', $url, $params);
$this->refreshFrom($response, $apiKey);
return $this;
}
public function capture($params=null)
{
$requestor = new Stripe_ApiRequestor($this->_apiKey);
$url = $this->instanceUrl() . '/capture';
list($response, $apiKey) = $requestor->request('post', $url, $params);
$this->refreshFrom($response, $apiKey);
return $this;
}
}

View File

@@ -0,0 +1,34 @@
<?php
class Stripe_Coupon extends Stripe_ApiResource
{
public static function constructFrom($values, $apiKey=null)
{
$class = get_class();
return self::scopedConstructFrom($class, $values, $apiKey);
}
public static function retrieve($id, $apiKey=null)
{
$class = get_class();
return self::_scopedRetrieve($class, $id, $apiKey);
}
public static function create($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedCreate($class, $params, $apiKey);
}
public function delete($params=null)
{
$class = get_class();
return self::_scopedDelete($class, $params);
}
public static function all($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedAll($class, $params, $apiKey);
}
}

View File

@@ -0,0 +1,94 @@
<?php
class Stripe_Customer extends Stripe_ApiResource
{
public static function constructFrom($values, $apiKey=null)
{
$class = get_class();
return self::scopedConstructFrom($class, $values, $apiKey);
}
public static function retrieve($id, $apiKey=null)
{
$class = get_class();
return self::_scopedRetrieve($class, $id, $apiKey);
}
public static function all($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedAll($class, $params, $apiKey);
}
public static function create($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedCreate($class, $params, $apiKey);
}
public function save()
{
$class = get_class();
return self::_scopedSave($class);
}
public function delete($params=null)
{
$class = get_class();
return self::_scopedDelete($class, $params);
}
public function addInvoiceItem($params=null)
{
if (!$params)
$params = array();
$params['customer'] = $this->id;
$ii = Stripe_InvoiceItem::create($params, $this->_apiKey);
return $ii;
}
public function invoices($params=null)
{
if (!$params)
$params = array();
$params['customer'] = $this->id;
$invoices = Stripe_Invoice::all($params, $this->_apiKey);
return $invoices;
}
public function invoiceItems($params=null)
{
if (!$params)
$params = array();
$params['customer'] = $this->id;
$iis = Stripe_InvoiceItem::all($params, $this->_apiKey);
return $iis;
}
public function charges($params=null)
{
if (!$params)
$params = array();
$params['customer'] = $this->id;
$charges = Stripe_Charge::all($params, $this->_apiKey);
return $charges;
}
public function updateSubscription($params=null)
{
$requestor = new Stripe_ApiRequestor($this->_apiKey);
$url = $this->instanceUrl() . '/subscription';
list($response, $apiKey) = $requestor->request('post', $url, $params);
$this->refreshFrom(array('subscription' => $response), $apiKey, true);
return $this->subscription;
}
public function cancelSubscription($params=null)
{
$requestor = new Stripe_ApiRequestor($this->_apiKey);
$url = $this->instanceUrl() . '/subscription';
list($response, $apiKey) = $requestor->request('delete', $url, $params);
$this->refreshFrom(array('subscription' => $response), $apiKey, true);
return $this->subscription;
}
}

View File

@@ -0,0 +1,27 @@
<?php
class Stripe_Error extends Exception
{
public function __construct($message=null, $http_status=null, $http_body=null, $json_body=null)
{
parent::__construct($message);
$this->http_status = $http_status;
$this->http_body = $http_body;
$this->json_body = $json_body;
}
public function getHttpStatus()
{
return $this->http_status;
}
public function getHttpBody()
{
return $this->http_body;
}
public function getJsonBody()
{
return $this->json_body;
}
}

View File

@@ -0,0 +1,22 @@
<?php
class Stripe_Event extends Stripe_ApiResource
{
public static function constructFrom($values, $apiKey=null)
{
$class = get_class();
return self::scopedConstructFrom($class, $values, $apiKey);
}
public static function retrieve($id, $apiKey=null)
{
$class = get_class();
return self::_scopedRetrieve($class, $id, $apiKey);
}
public static function all($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedAll($class, $params, $apiKey);
}
}

View File

@@ -0,0 +1,10 @@
<?php
class Stripe_InvalidRequestError extends Stripe_Error
{
public function __construct($message, $param, $http_status=null, $http_body=null, $json_body=null)
{
parent::__construct($message, $http_status, $http_body, $json_body);
$this->param = $param;
}
}

View File

@@ -0,0 +1,30 @@
<?php
class Stripe_Invoice extends Stripe_ApiResource
{
public static function constructFrom($values, $apiKey=null)
{
$class = get_class();
return self::scopedConstructFrom($class, $values, $apiKey);
}
public static function retrieve($id, $apiKey=null)
{
$class = get_class();
return self::_scopedRetrieve($class, $id, $apiKey);
}
public static function all($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedAll($class, $params, $apiKey);
}
public static function upcoming($params=null, $apiKey=null)
{
$requestor = new Stripe_ApiRequestor($apiKey);
$url = self::classUrl(get_class()) . '/upcoming';
list($response, $apiKey) = $requestor->request('get', $url, $params);
return Stripe_Util::convertToStripeObject($response, $apiKey);
}
}

View File

@@ -0,0 +1,40 @@
<?php
class Stripe_InvoiceItem extends Stripe_ApiResource
{
public static function constructFrom($values, $apiKey=null)
{
$class = get_class();
return self::scopedConstructFrom($class, $values, $apiKey);
}
public static function retrieve($id, $apiKey=null)
{
$class = get_class();
return self::_scopedRetrieve($class, $id, $apiKey);
}
public static function all($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedAll($class, $params, $apiKey);
}
public static function create($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedCreate($class, $params, $apiKey);
}
public function save()
{
$class = get_class();
return self::_scopedSave($class);
}
public function delete($params=null)
{
$class = get_class();
return self::_scopedDelete($class, $params);
}
}

View File

@@ -0,0 +1,142 @@
<?php
class Stripe_Object implements ArrayAccess
{
public static $_permanentAttributes;
public static function init()
{
self::$_permanentAttributes = new Stripe_Util_Set(array('_apiKey'));
}
protected $_apiKey;
protected $_values;
protected $_unsavedValues;
protected $_transientValues;
public function __construct($id=null, $apiKey=null)
{
$this->_apiKey = $apiKey;
$this->_values = array();
$this->_unsavedValues = new Stripe_Util_Set();
$this->_transientValues = new Stripe_Util_Set();
if ($id)
$this->id = $id;
}
// Standard accessor magic methods
public function __set($k, $v)
{
// TODO: may want to clear from $_transientValues. (Won't be user-visible.)
$this->_values[$k] = $v;
if (!self::$_permanentAttributes->includes($k))
$this->_unsavedValues->add($k);
}
public function __isset($k)
{
return isset($this->_values[$k]);
}
public function __unset($k)
{
unset($this->_values[$k]);
$this->_transientValues->add($k);
$this->_unsavedValues->discard($k);
}
public function __get($k)
{
if (isset($this->_values[$k])) {
return $this->_values[$k];
} else if ($this->_transientValues->includes($k)) {
$class = get_class($this);
$attrs = join(', ', array_keys($this->_values));
error_log("Stripe Notice: Undefined property of $class instance: $k. HINT: The $k attribute was set in the past, however. It was then wiped when refreshing the object with the result returned by Stripe's API, probably as a result of a save(). The attributes currently available on this object are: $attrs");
return null;
} else {
$class = get_class($this);
error_log("Stripe Notice: Undefined property of $class instance: $k");
return null;
}
}
// ArrayAccess methods
public function offsetSet($k, $v)
{
$this->$k = $v;
}
public function offsetExists($k)
{
return isset($this->$k);
}
public function offsetUnset($k)
{
unset($this->$k);
}
public function offsetGet($k)
{
return isset($this->_values[$k]) ? $this->_values[$k] : null;
}
// This unfortunately needs to be public to be used in Util.php
public static function scopedConstructFrom($class, $values, $apiKey=null)
{
$obj = new $class(isset($values['id']) ? $values['id'] : null, $apiKey);
$obj->refreshFrom($values, $apiKey);
return $obj;
}
public static function constructFrom($values, $apiKey=null)
{
$class = get_class();
return self::scopedConstructFrom($class, $values, $apiKey);
}
public function refreshFrom($values, $apiKey, $partial=false)
{
$this->_apiKey = $apiKey;
// Wipe old state before setting new. This is useful for e.g. updating a
// customer, where there is no persistent card parameter. Mark those values
// which don't persist as transient
if ($partial)
$removed = new Stripe_Util_Set();
else
$removed = array_diff(array_keys($this->_values), array_keys($values));
foreach ($removed as $k) {
if (self::$_permanentAttributes->includes($k))
continue;
unset($this->$k);
}
foreach ($values as $k => $v) {
if (self::$_permanentAttributes->includes($k))
continue;
$this->_values[$k] = Stripe_Util::convertToStripeObject($v, $apiKey);
$this->_transientValues->discard($k);
$this->_unsavedValues->discard($k);
}
}
public function __toJSON()
{
if (defined('JSON_PRETTY_PRINT'))
return json_encode($this->__toArray(true), JSON_PRETTY_PRINT);
else
return json_encode($this->__toArray(true));
}
public function __toString()
{
return $this->__toJSON();
}
public function __toArray($recursive=false)
{
if ($recursive)
return Stripe_Util::convertStripeObjectToArray($this->_values);
else
return $this->_values;
}
}
Stripe_Object::init();

View File

@@ -0,0 +1,40 @@
<?php
class Stripe_Plan extends Stripe_ApiResource
{
public static function constructFrom($values, $apiKey=null)
{
$class = get_class();
return self::scopedConstructFrom($class, $values, $apiKey);
}
public static function retrieve($id, $apiKey=null)
{
$class = get_class();
return self::_scopedRetrieve($class, $id, $apiKey);
}
public static function create($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedCreate($class, $params, $apiKey);
}
public function delete($params=null)
{
$class = get_class();
return self::_scopedDelete($class, $params);
}
public function save()
{
$class = get_class();
return self::_scopedSave($class);
}
public static function all($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedAll($class, $params, $apiKey);
}
}

View File

@@ -0,0 +1,22 @@
<?php
class Stripe_Token extends Stripe_ApiResource
{
public static function constructFrom($values, $apiKey=null)
{
$class = get_class();
return self::scopedConstructFrom($class, $values, $apiKey);
}
public static function retrieve($id, $apiKey=null)
{
$class = get_class();
return self::_scopedRetrieve($class, $id, $apiKey);
}
public static function create($params=null, $apiKey=null)
{
$class = get_class();
return self::_scopedCreate($class, $params, $apiKey);
}
}

View File

@@ -0,0 +1,59 @@
<?php
abstract class Stripe_Util
{
public static function isList($array)
{
if (!is_array($array))
return false;
// TODO: this isn't actually correct in general, but it's correct given Stripe's responses
foreach (array_keys($array) as $k) {
if (!is_numeric($k))
return false;
}
return true;
}
public static function convertStripeObjectToArray($values)
{
$results = array();
foreach ($values as $k => $v) {
// FIXME: this is an encapsulation violation
if (Stripe_Object::$_permanentAttributes->includes($k)) {
continue;
}
if ($v instanceof Stripe_Object) {
$results[$k] = $v->__toArray(true);
}
else if (is_array($v)) {
$results[$k] = self::convertStripeObjectToArray($v);
}
else {
$results[$k] = $v;
}
}
return $results;
}
public static function convertToStripeObject($resp, $apiKey)
{
$types = array('charge' => 'Stripe_Charge',
'customer' => 'Stripe_Customer',
'invoice' => 'Stripe_Invoice',
'invoiceitem' => 'Stripe_InvoiceItem', 'event' => 'Stripe_Event');
if (self::isList($resp)) {
$mapped = array();
foreach ($resp as $i)
array_push($mapped, self::convertToStripeObject($i, $apiKey));
return $mapped;
} else if (is_array($resp)) {
if (isset($resp['object']) && is_string($resp['object']) && isset($types[$resp['object']]))
$class = $types[$resp['object']];
else
$class = 'Stripe_Object';
return Stripe_Object::scopedConstructFrom($class, $resp, $apiKey);
} else {
return $resp;
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
class Stripe_Util_Set
{
private $_elts;
public function __construct($members=array())
{
$this->_elts = array();
foreach ($members as $item)
$this->_elts[$item] = true;
}
public function includes($elt)
{
return isset($this->_elts[$elt]);
}
public function add($elt)
{
$this->_elts[$elt] = true;
}
public function discard($elt)
{
unset($this->_elts[$elt]);
}
// TODO: make Set support foreach
public function toArray()
{
return array_keys($this->_elts);
}
}

File diff suppressed because it is too large Load Diff