├── Stripe ├── ApiError.php ├── ApiConnectionError.php ├── AuthenticationError.php ├── RateLimitError.php ├── InvalidRequestError.php ├── CardError.php ├── Account.php ├── Balance.php ├── Error.php ├── AttachedObject.php ├── Util │ └── Set.php ├── Event.php ├── Token.php ├── SingletonApiResource.php ├── Refund.php ├── BalanceTransaction.php ├── List.php ├── Coupon.php ├── Plan.php ├── InvoiceItem.php ├── ApplicationFee.php ├── Transfer.php ├── Subscription.php ├── Stripe.php ├── Card.php ├── Recipient.php ├── Invoice.php ├── Util.php ├── Charge.php ├── Customer.php ├── ApiResource.php ├── Object.php └── ApiRequestor.php ├── README.md ├── Stripe.php └── PaymentStripe.module /Stripe/ApiError.php: -------------------------------------------------------------------------------- 1 | param = $param; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Stripe/CardError.php: -------------------------------------------------------------------------------- 1 | param = $param; 11 | $this->code = $code; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Stripe/Account.php: -------------------------------------------------------------------------------- 1 | httpStatus = $httpStatus; 11 | $this->httpBody = $httpBody; 12 | $this->jsonBody = $jsonBody; 13 | } 14 | 15 | public function getHttpStatus() 16 | { 17 | return $this->httpStatus; 18 | } 19 | 20 | public function getHttpBody() 21 | { 22 | return $this->httpBody; 23 | } 24 | 25 | public function getJsonBody() 26 | { 27 | return $this->jsonBody; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Stripe/AttachedObject.php: -------------------------------------------------------------------------------- 1 | _values), array_keys($properties)); 14 | // Don't unset, but rather set to null so we send up '' for deletion. 15 | foreach ($removed as $k) { 16 | $this->$k = null; 17 | } 18 | 19 | foreach ($properties as $k => $v) { 20 | $this->$k = $v; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Stripe/Util/Set.php: -------------------------------------------------------------------------------- 1 | _elts = array(); 10 | foreach ($members as $item) 11 | $this->_elts[$item] = true; 12 | } 13 | 14 | public function includes($elt) 15 | { 16 | return isset($this->_elts[$elt]); 17 | } 18 | 19 | public function add($elt) 20 | { 21 | $this->_elts[$elt] = true; 22 | } 23 | 24 | public function discard($elt) 25 | { 26 | unset($this->_elts[$elt]); 27 | } 28 | 29 | // TODO: make Set support foreach 30 | public function toArray() 31 | { 32 | return array_keys($this->_elts); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Stripe/Event.php: -------------------------------------------------------------------------------- 1 | refresh(); 9 | return $instance; 10 | } 11 | 12 | /** 13 | * @param Stripe_SingletonApiResource $class 14 | * @return string The endpoint associated with this singleton class. 15 | */ 16 | public static function classUrl($class) 17 | { 18 | $base = self::className($class); 19 | return "/v1/${base}"; 20 | } 21 | 22 | /** 23 | * @return string The endpoint associated with this singleton API resource. 24 | */ 25 | public function instanceUrl() 26 | { 27 | $class = get_class($this); 28 | $base = self::classUrl($class); 29 | return "$base"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Stripe/Refund.php: -------------------------------------------------------------------------------- 1 | _apiKey); 8 | list($response, $apiKey) = $requestor->request( 9 | 'get', 10 | $this['url'], 11 | $params 12 | ); 13 | return Stripe_Util::convertToStripeObject($response, $apiKey); 14 | } 15 | 16 | public function create($params=null) 17 | { 18 | $requestor = new Stripe_ApiRequestor($this->_apiKey); 19 | list($response, $apiKey) = $requestor->request( 20 | 'post', $this['url'], $params 21 | ); 22 | return Stripe_Util::convertToStripeObject($response, $apiKey); 23 | } 24 | 25 | public function retrieve($id, $params=null) 26 | { 27 | $requestor = new Stripe_ApiRequestor($this->_apiKey); 28 | $base = $this['url']; 29 | $id = Stripe_ApiRequestor::utf8($id); 30 | $extn = urlencode($id); 31 | list($response, $apiKey) = $requestor->request( 32 | 'get', "$base/$extn", $params 33 | ); 34 | return Stripe_Util::convertToStripeObject($response, $apiKey); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Stripe/Coupon.php: -------------------------------------------------------------------------------- 1 | get("PaymentStripe"); 13 | $payment->setCurrency("EUR"); 14 | $payment->setId(123456789); 15 | 16 | $customer = Array(); 17 | $customer['email'] = "antti.peisa@gmail.com"; 18 | $payment->setCustomerData($customer); 19 | 20 | $amount = 1000; // Amount in payment modules always in cents 21 | $payment->addProduct("My product", $amount); 22 | 23 | // In this example we are going to do all in same page 24 | $url = $page->httpUrl; 25 | $payment->setProcessUrl($url . "?step=process"); 26 | $payment->setFailureUrl($url . "?step=fail"); 27 | $payment->setCancelUrl($url . "?step=cancel"); 28 | 29 | switch ($input->get->step) { 30 | case 'process': 31 | if ($payment->processPayment()) { 32 | echo "Thanks, payment successful!"; 33 | } else { 34 | echo "Are you kidding me?"; 35 | } 36 | break; 37 | 38 | case 'fail': 39 | echo "Something went wrong"; 40 | break; 41 | 42 | case 'cancel': 43 | echo "I think you cancelled?"; 44 | break; 45 | 46 | default: 47 | echo $payment->embed(); // Here you could look if instance is PaymentEmbed or PaymentRedirect and choose method based on that 48 | break; 49 | } 50 | ``` 51 | 52 | ## License 53 | GPL 2.0 54 | -------------------------------------------------------------------------------- /Stripe/Plan.php: -------------------------------------------------------------------------------- 1 | _apiKey); 48 | $url = $this->instanceUrl() . '/refund'; 49 | list($response, $apiKey) = $requestor->request('post', $url, $params); 50 | $this->refreshFrom($response, $apiKey); 51 | return $this; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Stripe/Transfer.php: -------------------------------------------------------------------------------- 1 | _apiKey); 47 | $url = $this->instanceUrl() . '/cancel'; 48 | list($response, $apiKey) = $requestor->request('post', $url); 49 | $this->refreshFrom($response, $apiKey); 50 | return $this; 51 | } 52 | 53 | /** 54 | * @return Stripe_Transfer The saved transfer. 55 | */ 56 | public function save() 57 | { 58 | $class = get_class(); 59 | return self::_scopedSave($class); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Stripe/Subscription.php: -------------------------------------------------------------------------------- 1 | _apiKey); 54 | $url = $this->instanceUrl() . '/discount'; 55 | list($response, $apiKey) = $requestor->request('delete', $url); 56 | $this->refreshFrom(array('discount' => null), $apiKey, true); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Stripe/Stripe.php: -------------------------------------------------------------------------------- 1 | id; 72 | $transfers = Stripe_Transfer::all($params, $this->_apiKey); 73 | return $transfers; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Stripe/Invoice.php: -------------------------------------------------------------------------------- 1 | request('get', $url, $params); 52 | return Stripe_Util::convertToStripeObject($response, $apiKey); 53 | } 54 | 55 | /** 56 | * @return Stripe_Invoice The saved invoice. 57 | */ 58 | public function save() 59 | { 60 | $class = get_class(); 61 | return self::_scopedSave($class); 62 | } 63 | 64 | /** 65 | * @return Stripe_Invoice The paid invoice. 66 | */ 67 | public function pay() 68 | { 69 | $requestor = new Stripe_ApiRequestor($this->_apiKey); 70 | $url = $this->instanceUrl() . '/pay'; 71 | list($response, $apiKey) = $requestor->request('post', $url); 72 | $this->refreshFrom($response, $apiKey); 73 | return $this; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Stripe.php: -------------------------------------------------------------------------------- 1 | $v) { 34 | // FIXME: this is an encapsulation violation 35 | if ($k[0] == '_') { 36 | continue; 37 | } 38 | if ($v instanceof Stripe_Object) { 39 | $results[$k] = $v->__toArray(true); 40 | } else if (is_array($v)) { 41 | $results[$k] = self::convertStripeObjectToArray($v); 42 | } else { 43 | $results[$k] = $v; 44 | } 45 | } 46 | return $results; 47 | } 48 | 49 | /** 50 | * Converts a response from the Stripe API to the corresponding PHP object. 51 | * 52 | * @param array $resp The response from the Stripe API. 53 | * @param string $apiKey 54 | * @return Stripe_Object|array 55 | */ 56 | public static function convertToStripeObject($resp, $apiKey) 57 | { 58 | $types = array( 59 | 'card' => 'Stripe_Card', 60 | 'charge' => 'Stripe_Charge', 61 | 'customer' => 'Stripe_Customer', 62 | 'list' => 'Stripe_List', 63 | 'invoice' => 'Stripe_Invoice', 64 | 'invoiceitem' => 'Stripe_InvoiceItem', 65 | 'event' => 'Stripe_Event', 66 | 'transfer' => 'Stripe_Transfer', 67 | 'plan' => 'Stripe_Plan', 68 | 'recipient' => 'Stripe_Recipient', 69 | 'refund' => 'Stripe_Refund', 70 | 'subscription' => 'Stripe_Subscription' 71 | ); 72 | if (self::isList($resp)) { 73 | $mapped = array(); 74 | foreach ($resp as $i) 75 | array_push($mapped, self::convertToStripeObject($i, $apiKey)); 76 | return $mapped; 77 | } else if (is_array($resp)) { 78 | if (isset($resp['object']) 79 | && is_string($resp['object']) 80 | && isset($types[$resp['object']])) { 81 | $class = $types[$resp['object']]; 82 | } else { 83 | $class = 'Stripe_Object'; 84 | } 85 | return Stripe_Object::scopedConstructFrom($class, $resp, $apiKey); 86 | } else { 87 | return $resp; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Stripe/Charge.php: -------------------------------------------------------------------------------- 1 | _apiKey); 58 | $url = $this->instanceUrl() . '/refund'; 59 | list($response, $apiKey) = $requestor->request('post', $url, $params); 60 | $this->refreshFrom($response, $apiKey); 61 | return $this; 62 | } 63 | 64 | /** 65 | * @param array|null $params 66 | * 67 | * @return Stripe_Charge The captured charge. 68 | */ 69 | public function capture($params=null) 70 | { 71 | $requestor = new Stripe_ApiRequestor($this->_apiKey); 72 | $url = $this->instanceUrl() . '/capture'; 73 | list($response, $apiKey) = $requestor->request('post', $url, $params); 74 | $this->refreshFrom($response, $apiKey); 75 | return $this; 76 | } 77 | 78 | /** 79 | * @param array|null $params 80 | * 81 | * @return array The updated dispute. 82 | */ 83 | public function updateDispute($params=null) 84 | { 85 | $requestor = new Stripe_ApiRequestor($this->_apiKey); 86 | $url = $this->instanceUrl() . '/dispute'; 87 | list($response, $apiKey) = $requestor->request('post', $url, $params); 88 | $this->refreshFrom(array('dispute' => $response), $apiKey, true); 89 | return $this->dispute; 90 | } 91 | 92 | /** 93 | * @return Stripe_Charge The updated charge. 94 | */ 95 | public function closeDispute() 96 | { 97 | $requestor = new Stripe_ApiRequestor($this->_apiKey); 98 | $url = $this->instanceUrl() . '/dispute/close'; 99 | list($response, $apiKey) = $requestor->request('post', $url); 100 | $this->refreshFrom($response, $apiKey); 101 | return $this; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /PaymentStripe.module: -------------------------------------------------------------------------------- 1 | 'PaymentStripe', 10 | 'version' => 001, 11 | 'summary' => 'PaymentStripe', 12 | 'singular' => false, 13 | 'requires' => 'PaymentModule', 14 | ); 15 | } 16 | 17 | public function init() { 18 | $this->currenct = $this->defaultCurrency; 19 | } 20 | 21 | public function getTitle() { 22 | return $this->_("Credit Card (Stripe)"); 23 | } 24 | 25 | public function processPayment() { 26 | // Set your secret key: remember to change this to your live secret key in production 27 | // See your keys here https://dashboard.stripe.com/account 28 | Stripe::setApiKey($this->secretKey); 29 | 30 | // Get the credit card details submitted by the form 31 | $token = $this->input->post->stripeToken; 32 | 33 | // Create the charge on Stripe's servers - this will charge the user's card 34 | try { 35 | $charge = Stripe_Charge::create(array( 36 | "amount" => $this->getTotalAmount(), // amount in cents, again 37 | "currency" => $this->currency, 38 | "card" => $token, 39 | "description" => $this->customer->email) 40 | ); 41 | } catch(Stripe_CardError $e) { 42 | $this->setFailureReason($e->getMessage()); 43 | return false; 44 | } 45 | return true; 46 | } 47 | 48 | public function render() { 49 | 50 | if ($this->getTotalAmount() <= 0) throw new WireException("Products are not set"); 51 | if ($this->processUrl == '') throw new WireException("processUrl is not set"); 52 | 53 | 54 | $out = ' 55 |
56 | 71 |
'; 72 | return $out; 73 | } 74 | 75 | public static function getModuleConfigInputfields(array $data) { 76 | $inputfields = new InputfieldWrapper(); 77 | 78 | $field = wire('modules')->get('InputfieldText'); 79 | $field->name = 'seller'; 80 | $field->label = __("Seller name"); 81 | $field->notes = __("Company or seller of the goods - this is shown on stripe payment form"); 82 | if(isset($data['seller'])) $field->value = $data['seller']; 83 | $inputfields->add($field); 84 | 85 | $field = wire('modules')->get('InputfieldText'); 86 | $field->name = 'defaultCurrency'; 87 | $field->label = __("Default currency"); 88 | $field->notes = __("Use this currency by default (always possible to overwrite when using this module from API)"); 89 | if(isset($data['defaultCurrency'])) $field->value = $data['defaultCurrency']; 90 | $inputfields->add($field); 91 | 92 | $field = wire('modules')->get('InputfieldText'); 93 | $field->name = 'secretKey'; 94 | $field->label = __("Secret key"); 95 | $field->notes = __("Your secret key: remember to change this to your live secret key in production. See your keys [here](https://dashboard.stripe.com/account)"); 96 | if(isset($data['secretKey'])) $field->value = $data['secretKey']; 97 | $inputfields->add($field); 98 | 99 | $field = wire('modules')->get('InputfieldText'); 100 | $field->name = 'publicKey'; 101 | $field->label = __("Public key"); 102 | $field->notes = __("Your public key: remember to change this to your live public key in production. See your keys [here](https://dashboard.stripe.com/account)"); 103 | if(isset($data['publicKey'])) $field->value = $data['publicKey']; 104 | $inputfields->add($field); 105 | 106 | $field = wire('modules')->get('InputfieldText'); 107 | $field->name = 'imageUrl'; 108 | $field->label = __("Url to logo"); 109 | $field->notes = __("Company logo or other image to show in Stripe payment form. Relative url from root, like /site/templates/images/logo.gif"); 110 | if(isset($data['imageUrl'])) $field->value = $data['imageUrl']; 111 | $inputfields->add($field); 112 | 113 | return $inputfields; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Stripe/Customer.php: -------------------------------------------------------------------------------- 1 | id; 71 | $ii = Stripe_InvoiceItem::create($params, $this->_apiKey); 72 | return $ii; 73 | } 74 | 75 | /** 76 | * @param array|null $params 77 | * 78 | * @returns array An array of the customer's Stripe_Invoices. 79 | */ 80 | public function invoices($params=null) 81 | { 82 | if (!$params) 83 | $params = array(); 84 | $params['customer'] = $this->id; 85 | $invoices = Stripe_Invoice::all($params, $this->_apiKey); 86 | return $invoices; 87 | } 88 | 89 | /** 90 | * @param array|null $params 91 | * 92 | * @returns array An array of the customer's Stripe_InvoiceItems. 93 | */ 94 | public function invoiceItems($params=null) 95 | { 96 | if (!$params) 97 | $params = array(); 98 | $params['customer'] = $this->id; 99 | $iis = Stripe_InvoiceItem::all($params, $this->_apiKey); 100 | return $iis; 101 | } 102 | 103 | /** 104 | * @param array|null $params 105 | * 106 | * @returns array An array of the customer's Stripe_Charges. 107 | */ 108 | public function charges($params=null) 109 | { 110 | if (!$params) 111 | $params = array(); 112 | $params['customer'] = $this->id; 113 | $charges = Stripe_Charge::all($params, $this->_apiKey); 114 | return $charges; 115 | } 116 | 117 | /** 118 | * @param array|null $params 119 | * 120 | * @returns Stripe_Subscription The updated subscription. 121 | */ 122 | public function updateSubscription($params=null) 123 | { 124 | $requestor = new Stripe_ApiRequestor($this->_apiKey); 125 | $url = $this->instanceUrl() . '/subscription'; 126 | list($response, $apiKey) = $requestor->request('post', $url, $params); 127 | $this->refreshFrom(array('subscription' => $response), $apiKey, true); 128 | return $this->subscription; 129 | } 130 | 131 | /** 132 | * @param array|null $params 133 | * 134 | * @returns Stripe_Subscription The cancelled subscription. 135 | */ 136 | public function cancelSubscription($params=null) 137 | { 138 | $requestor = new Stripe_ApiRequestor($this->_apiKey); 139 | $url = $this->instanceUrl() . '/subscription'; 140 | list($response, $apiKey) = $requestor->request('delete', $url, $params); 141 | $this->refreshFrom(array('subscription' => $response), $apiKey, true); 142 | return $this->subscription; 143 | } 144 | 145 | /** 146 | * @param array|null $params 147 | * 148 | * @returns Stripe_Customer The updated customer. 149 | */ 150 | public function deleteDiscount() 151 | { 152 | $requestor = new Stripe_ApiRequestor($this->_apiKey); 153 | $url = $this->instanceUrl() . '/discount'; 154 | list($response, $apiKey) = $requestor->request('delete', $url); 155 | $this->refreshFrom(array('discount' => null), $apiKey, true); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Stripe/ApiResource.php: -------------------------------------------------------------------------------- 1 | refresh(); 9 | return $instance; 10 | } 11 | 12 | /** 13 | * @returns Stripe_ApiResource The refreshed resource. 14 | */ 15 | public function refresh() 16 | { 17 | $requestor = new Stripe_ApiRequestor($this->_apiKey); 18 | $url = $this->instanceUrl(); 19 | 20 | list($response, $apiKey) = $requestor->request( 21 | 'get', 22 | $url, 23 | $this->_retrieveOptions 24 | ); 25 | $this->refreshFrom($response, $apiKey); 26 | return $this; 27 | } 28 | 29 | /** 30 | * @param string $class 31 | * 32 | * @returns string The name of the class, with namespacing and underscores 33 | * stripped. 34 | */ 35 | public static function className($class) 36 | { 37 | // Useful for namespaces: Foo\Stripe_Charge 38 | if ($postfix = strrchr($class, '\\')) { 39 | $class = substr($postfix, 1); 40 | } 41 | if (substr($class, 0, strlen('Stripe')) == 'Stripe') { 42 | $class = substr($class, strlen('Stripe')); 43 | } 44 | $class = str_replace('_', '', $class); 45 | $name = urlencode($class); 46 | $name = strtolower($name); 47 | return $name; 48 | } 49 | 50 | /** 51 | * @param string $class 52 | * 53 | * @returns string The endpoint URL for the given class. 54 | */ 55 | public static function classUrl($class) 56 | { 57 | $base = self::_scopedLsb($class, 'className', $class); 58 | return "/v1/${base}s"; 59 | } 60 | 61 | /** 62 | * @returns string The full API URL for this API resource. 63 | */ 64 | public function instanceUrl() 65 | { 66 | $id = $this['id']; 67 | $class = get_class($this); 68 | if ($id === null) { 69 | $message = "Could not determine which URL to request: " 70 | . "$class instance has invalid ID: $id"; 71 | throw new Stripe_InvalidRequestError($message, null); 72 | } 73 | $id = Stripe_ApiRequestor::utf8($id); 74 | $base = $this->_lsb('classUrl', $class); 75 | $extn = urlencode($id); 76 | return "$base/$extn"; 77 | } 78 | 79 | private static function _validateCall($method, $params=null, $apiKey=null) 80 | { 81 | if ($params && !is_array($params)) { 82 | $message = "You must pass an array as the first argument to Stripe API " 83 | . "method calls. (HINT: an example call to create a charge " 84 | . "would be: \"StripeCharge::create(array('amount' => 100, " 85 | . "'currency' => 'usd', 'card' => array('number' => " 86 | . "4242424242424242, 'exp_month' => 5, 'exp_year' => 2015)))\")"; 87 | throw new Stripe_Error($message); 88 | } 89 | 90 | if ($apiKey && !is_string($apiKey)) { 91 | $message = 'The second argument to Stripe API method calls is an ' 92 | . 'optional per-request apiKey, which must be a string. ' 93 | . '(HINT: you can set a global apiKey by ' 94 | . '"Stripe::setApiKey()")'; 95 | throw new Stripe_Error($message); 96 | } 97 | } 98 | 99 | protected static function _scopedAll($class, $params=null, $apiKey=null) 100 | { 101 | self::_validateCall('all', $params, $apiKey); 102 | $requestor = new Stripe_ApiRequestor($apiKey); 103 | $url = self::_scopedLsb($class, 'classUrl', $class); 104 | list($response, $apiKey) = $requestor->request('get', $url, $params); 105 | return Stripe_Util::convertToStripeObject($response, $apiKey); 106 | } 107 | 108 | protected static function _scopedCreate($class, $params=null, $apiKey=null) 109 | { 110 | self::_validateCall('create', $params, $apiKey); 111 | $requestor = new Stripe_ApiRequestor($apiKey); 112 | $url = self::_scopedLsb($class, 'classUrl', $class); 113 | list($response, $apiKey) = $requestor->request('post', $url, $params); 114 | return Stripe_Util::convertToStripeObject($response, $apiKey); 115 | } 116 | 117 | protected function _scopedSave($class, $apiKey=null) 118 | { 119 | self::_validateCall('save'); 120 | $requestor = new Stripe_ApiRequestor($apiKey); 121 | $params = $this->serializeParameters(); 122 | 123 | if (count($params) > 0) { 124 | $url = $this->instanceUrl(); 125 | list($response, $apiKey) = $requestor->request('post', $url, $params); 126 | $this->refreshFrom($response, $apiKey); 127 | } 128 | return $this; 129 | } 130 | 131 | protected function _scopedDelete($class, $params=null) 132 | { 133 | self::_validateCall('delete'); 134 | $requestor = new Stripe_ApiRequestor($this->_apiKey); 135 | $url = $this->instanceUrl(); 136 | list($response, $apiKey) = $requestor->request('delete', $url, $params); 137 | $this->refreshFrom($response, $apiKey); 138 | return $this; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Stripe/Object.php: -------------------------------------------------------------------------------- 1 | _apiKey = $apiKey; 31 | $this->_values = array(); 32 | $this->_unsavedValues = new Stripe_Util_Set(); 33 | $this->_transientValues = new Stripe_Util_Set(); 34 | 35 | $this->_retrieveOptions = array(); 36 | if (is_array($id)) { 37 | foreach($id as $key => $value) { 38 | if ($key != 'id') { 39 | $this->_retrieveOptions[$key] = $value; 40 | } 41 | } 42 | $id = $id['id']; 43 | } 44 | 45 | if ($id !== null) { 46 | $this->id = $id; 47 | } 48 | } 49 | 50 | // Standard accessor magic methods 51 | public function __set($k, $v) 52 | { 53 | if ($v === "") { 54 | throw new InvalidArgumentException( 55 | 'You cannot set \''.$k.'\'to an empty string. ' 56 | .'We interpret empty strings as NULL in requests. ' 57 | .'You may set obj->'.$k.' = NULL to delete the property' 58 | ); 59 | } 60 | 61 | if (self::$nestedUpdatableAttributes->includes($k) && isset($this->$k) && is_array($v)) { 62 | $this->$k->replaceWith($v); 63 | } else { 64 | // TODO: may want to clear from $_transientValues. (Won't be user-visible.) 65 | $this->_values[$k] = $v; 66 | } 67 | if (!self::$permanentAttributes->includes($k)) 68 | $this->_unsavedValues->add($k); 69 | } 70 | public function __isset($k) 71 | { 72 | return isset($this->_values[$k]); 73 | } 74 | public function __unset($k) 75 | { 76 | unset($this->_values[$k]); 77 | $this->_transientValues->add($k); 78 | $this->_unsavedValues->discard($k); 79 | } 80 | public function __get($k) 81 | { 82 | if (array_key_exists($k, $this->_values)) { 83 | return $this->_values[$k]; 84 | } else if ($this->_transientValues->includes($k)) { 85 | $class = get_class($this); 86 | $attrs = join(', ', array_keys($this->_values)); 87 | $message = "Stripe Notice: Undefined property of $class instance: $k. " 88 | . "HINT: The $k attribute was set in the past, however. " 89 | . "It was then wiped when refreshing the object " 90 | . "with the result returned by Stripe's API, " 91 | . "probably as a result of a save(). The attributes currently " 92 | . "available on this object are: $attrs"; 93 | error_log($message); 94 | return null; 95 | } else { 96 | $class = get_class($this); 97 | error_log("Stripe Notice: Undefined property of $class instance: $k"); 98 | return null; 99 | } 100 | } 101 | 102 | // ArrayAccess methods 103 | public function offsetSet($k, $v) 104 | { 105 | $this->$k = $v; 106 | } 107 | 108 | public function offsetExists($k) 109 | { 110 | return array_key_exists($k, $this->_values); 111 | } 112 | 113 | public function offsetUnset($k) 114 | { 115 | unset($this->$k); 116 | } 117 | public function offsetGet($k) 118 | { 119 | return array_key_exists($k, $this->_values) ? $this->_values[$k] : null; 120 | } 121 | 122 | public function keys() 123 | { 124 | return array_keys($this->_values); 125 | } 126 | 127 | /** 128 | * This unfortunately needs to be public to be used in Util.php 129 | * 130 | * @param Stripe_Object $class 131 | * @param array $values 132 | * @param string|null $apiKey 133 | * 134 | * @return Stripe_Object The object constructed from the given values. 135 | */ 136 | public static function scopedConstructFrom($class, $values, $apiKey=null) 137 | { 138 | $obj = new $class(isset($values['id']) ? $values['id'] : null, $apiKey); 139 | $obj->refreshFrom($values, $apiKey); 140 | return $obj; 141 | } 142 | 143 | /** 144 | * @param array $values 145 | * @param string|null $apiKey 146 | * 147 | * @return Stripe_Object The object of the same class as $this constructed 148 | * from the given values. 149 | */ 150 | public static function constructFrom($values, $apiKey=null) 151 | { 152 | $class = get_class($this); 153 | return self::scopedConstructFrom($class, $values, $apiKey); 154 | } 155 | 156 | /** 157 | * Refreshes this object using the provided values. 158 | * 159 | * @param array $values 160 | * @param string $apiKey 161 | * @param boolean $partial Defaults to false. 162 | */ 163 | public function refreshFrom($values, $apiKey, $partial=false) 164 | { 165 | $this->_apiKey = $apiKey; 166 | 167 | // Wipe old state before setting new. This is useful for e.g. updating a 168 | // customer, where there is no persistent card parameter. Mark those values 169 | // which don't persist as transient 170 | if ($partial) { 171 | $removed = new Stripe_Util_Set(); 172 | } else { 173 | $removed = array_diff(array_keys($this->_values), array_keys($values)); 174 | } 175 | 176 | foreach ($removed as $k) { 177 | if (self::$permanentAttributes->includes($k)) 178 | continue; 179 | unset($this->$k); 180 | } 181 | 182 | foreach ($values as $k => $v) { 183 | if (self::$permanentAttributes->includes($k) && isset($this[$k])) 184 | continue; 185 | 186 | if (self::$nestedUpdatableAttributes->includes($k) && is_array($v)) { 187 | $this->_values[$k] = Stripe_Object::scopedConstructFrom('Stripe_AttachedObject', $v, $apiKey); 188 | } else { 189 | $this->_values[$k] = Stripe_Util::convertToStripeObject($v, $apiKey); 190 | } 191 | 192 | $this->_transientValues->discard($k); 193 | $this->_unsavedValues->discard($k); 194 | } 195 | } 196 | 197 | /** 198 | * @return array A recursive mapping of attributes to values for this object, 199 | * including the proper value for deleted attributes. 200 | */ 201 | public function serializeParameters() 202 | { 203 | $params = array(); 204 | if ($this->_unsavedValues) { 205 | foreach ($this->_unsavedValues->toArray() as $k) { 206 | $v = $this->$k; 207 | if ($v === NULL) { 208 | $v = ''; 209 | } 210 | $params[$k] = $v; 211 | } 212 | } 213 | 214 | // Get nested updates. 215 | foreach (self::$nestedUpdatableAttributes->toArray() as $property) { 216 | if (isset($this->$property) && $this->$property instanceOf Stripe_Object) { 217 | $params[$property] = $this->$property->serializeParameters(); 218 | } 219 | } 220 | return $params; 221 | } 222 | 223 | // Pretend to have late static bindings, even in PHP 5.2 224 | protected function _lsb($method) 225 | { 226 | $class = get_class($this); 227 | $args = array_slice(func_get_args(), 1); 228 | return call_user_func_array(array($class, $method), $args); 229 | } 230 | protected static function _scopedLsb($class, $method) 231 | { 232 | $args = array_slice(func_get_args(), 2); 233 | return call_user_func_array(array($class, $method), $args); 234 | } 235 | 236 | public function __toJSON() 237 | { 238 | if (defined('JSON_PRETTY_PRINT')) { 239 | return json_encode($this->__toArray(true), JSON_PRETTY_PRINT); 240 | } else { 241 | return json_encode($this->__toArray(true)); 242 | } 243 | } 244 | 245 | public function __toString() 246 | { 247 | return $this->__toJSON(); 248 | } 249 | 250 | public function __toArray($recursive=false) 251 | { 252 | if ($recursive) { 253 | return Stripe_Util::convertStripeObjectToArray($this->_values); 254 | } else { 255 | return $this->_values; 256 | } 257 | } 258 | } 259 | 260 | 261 | Stripe_Object::init(); 262 | -------------------------------------------------------------------------------- /Stripe/ApiRequestor.php: -------------------------------------------------------------------------------- 1 | _apiKey = $apiKey; 23 | } 24 | 25 | /** 26 | * @param string $url The path to the API endpoint. 27 | * 28 | * @returns string The full path. 29 | */ 30 | public static function apiUrl($url='') 31 | { 32 | $apiBase = Stripe::$apiBase; 33 | return "$apiBase$url"; 34 | } 35 | 36 | /** 37 | * @param string|mixed $value A string to UTF8-encode. 38 | * 39 | * @returns string|mixed The UTF8-encoded string, or the object passed in if 40 | * it wasn't a string. 41 | */ 42 | public static function utf8($value) 43 | { 44 | if (is_string($value) 45 | && mb_detect_encoding($value, "UTF-8", TRUE) != "UTF-8") { 46 | return utf8_encode($value); 47 | } else { 48 | return $value; 49 | } 50 | } 51 | 52 | private static function _encodeObjects($d) 53 | { 54 | if ($d instanceof Stripe_ApiResource) { 55 | return self::utf8($d->id); 56 | } else if ($d === true) { 57 | return 'true'; 58 | } else if ($d === false) { 59 | return 'false'; 60 | } else if (is_array($d)) { 61 | $res = array(); 62 | foreach ($d as $k => $v) 63 | $res[$k] = self::_encodeObjects($v); 64 | return $res; 65 | } else { 66 | return self::utf8($d); 67 | } 68 | } 69 | 70 | /** 71 | * @param array $arr An map of param keys to values. 72 | * @param string|null $prefix (It doesn't look like we ever use $prefix...) 73 | * 74 | * @returns string A querystring, essentially. 75 | */ 76 | public static function encode($arr, $prefix=null) 77 | { 78 | if (!is_array($arr)) 79 | return $arr; 80 | 81 | $r = array(); 82 | foreach ($arr as $k => $v) { 83 | if (is_null($v)) 84 | continue; 85 | 86 | if ($prefix && $k && !is_int($k)) 87 | $k = $prefix."[".$k."]"; 88 | else if ($prefix) 89 | $k = $prefix."[]"; 90 | 91 | if (is_array($v)) { 92 | $r[] = self::encode($v, $k, true); 93 | } else { 94 | $r[] = urlencode($k)."=".urlencode($v); 95 | } 96 | } 97 | 98 | return implode("&", $r); 99 | } 100 | 101 | /** 102 | * @param string $method 103 | * @param string $url 104 | * @param array|null $params 105 | * 106 | * @return array An array whose first element is the response and second 107 | * element is the API key used to make the request. 108 | */ 109 | public function request($method, $url, $params=null) 110 | { 111 | if (!$params) 112 | $params = array(); 113 | list($rbody, $rcode, $myApiKey) = $this->_requestRaw($method, $url, $params); 114 | $resp = $this->_interpretResponse($rbody, $rcode); 115 | return array($resp, $myApiKey); 116 | } 117 | 118 | 119 | /** 120 | * @param string $rbody A JSON string. 121 | * @param int $rcode 122 | * @param array $resp 123 | * 124 | * @throws Stripe_InvalidRequestError if the error is caused by the user. 125 | * @throws Stripe_AuthenticationError if the error is caused by a lack of 126 | * permissions. 127 | * @throws Stripe_CardError if the error is the error code is 402 (payment 128 | * required) 129 | * @throws Stripe_ApiError otherwise. 130 | */ 131 | public function handleApiError($rbody, $rcode, $resp) 132 | { 133 | if (!is_array($resp) || !isset($resp['error'])) { 134 | $msg = "Invalid response object from API: $rbody " 135 | ."(HTTP response code was $rcode)"; 136 | throw new Stripe_ApiError($msg, $rcode, $rbody, $resp); 137 | } 138 | 139 | $error = $resp['error']; 140 | $msg = isset($error['message']) ? $error['message'] : null; 141 | $param = isset($error['param']) ? $error['param'] : null; 142 | $code = isset($error['code']) ? $error['code'] : null; 143 | 144 | switch ($rcode) { 145 | case 400: 146 | if ($code == 'rate_limit') { 147 | throw new Stripe_RateLimitError( 148 | $msg, $param, $rcode, $rbody, $resp 149 | ); 150 | } 151 | case 404: 152 | throw new Stripe_InvalidRequestError( 153 | $msg, $param, $rcode, $rbody, $resp 154 | ); 155 | case 401: 156 | throw new Stripe_AuthenticationError($msg, $rcode, $rbody, $resp); 157 | case 402: 158 | throw new Stripe_CardError($msg, $param, $code, $rcode, $rbody, $resp); 159 | default: 160 | throw new Stripe_ApiError($msg, $rcode, $rbody, $resp); 161 | } 162 | } 163 | 164 | private function _requestRaw($method, $url, $params) 165 | { 166 | $myApiKey = $this->_apiKey; 167 | if (!$myApiKey) 168 | $myApiKey = Stripe::$apiKey; 169 | 170 | if (!$myApiKey) { 171 | $msg = 'No API key provided. (HINT: set your API key using ' 172 | . '"Stripe::setApiKey()". You can generate API keys from ' 173 | . 'the Stripe web interface. See https://stripe.com/api for ' 174 | . 'details, or email support@stripe.com if you have any questions.'; 175 | throw new Stripe_AuthenticationError($msg); 176 | } 177 | 178 | $absUrl = $this->apiUrl($url); 179 | $params = self::_encodeObjects($params); 180 | $langVersion = phpversion(); 181 | $uname = php_uname(); 182 | $ua = array('bindings_version' => Stripe::VERSION, 183 | 'lang' => 'php', 184 | 'lang_version' => $langVersion, 185 | 'publisher' => 'stripe', 186 | 'uname' => $uname); 187 | $headers = array('X-Stripe-Client-User-Agent: ' . json_encode($ua), 188 | 'User-Agent: Stripe/v1 PhpBindings/' . Stripe::VERSION, 189 | 'Authorization: Bearer ' . $myApiKey); 190 | if (Stripe::$apiVersion) 191 | $headers[] = 'Stripe-Version: ' . Stripe::$apiVersion; 192 | list($rbody, $rcode) = $this->_curlRequest( 193 | $method, 194 | $absUrl, 195 | $headers, 196 | $params 197 | ); 198 | return array($rbody, $rcode, $myApiKey); 199 | } 200 | 201 | private function _interpretResponse($rbody, $rcode) 202 | { 203 | try { 204 | $resp = json_decode($rbody, true); 205 | } catch (Exception $e) { 206 | $msg = "Invalid response body from API: $rbody " 207 | . "(HTTP response code was $rcode)"; 208 | throw new Stripe_ApiError($msg, $rcode, $rbody); 209 | } 210 | 211 | if ($rcode < 200 || $rcode >= 300) { 212 | $this->handleApiError($rbody, $rcode, $resp); 213 | } 214 | return $resp; 215 | } 216 | 217 | private function _curlRequest($method, $absUrl, $headers, $params) 218 | { 219 | 220 | if (!self::$preFlight) { 221 | self::$preFlight = $this->checkSslCert($this->apiUrl()); 222 | } 223 | 224 | $curl = curl_init(); 225 | $method = strtolower($method); 226 | $opts = array(); 227 | if ($method == 'get') { 228 | $opts[CURLOPT_HTTPGET] = 1; 229 | if (count($params) > 0) { 230 | $encoded = self::encode($params); 231 | $absUrl = "$absUrl?$encoded"; 232 | } 233 | } else if ($method == 'post') { 234 | $opts[CURLOPT_POST] = 1; 235 | $opts[CURLOPT_POSTFIELDS] = self::encode($params); 236 | } else if ($method == 'delete') { 237 | $opts[CURLOPT_CUSTOMREQUEST] = 'DELETE'; 238 | if (count($params) > 0) { 239 | $encoded = self::encode($params); 240 | $absUrl = "$absUrl?$encoded"; 241 | } 242 | } else { 243 | throw new Stripe_ApiError("Unrecognized method $method"); 244 | } 245 | 246 | $absUrl = self::utf8($absUrl); 247 | $opts[CURLOPT_URL] = $absUrl; 248 | $opts[CURLOPT_RETURNTRANSFER] = true; 249 | $opts[CURLOPT_CONNECTTIMEOUT] = 30; 250 | $opts[CURLOPT_TIMEOUT] = 80; 251 | $opts[CURLOPT_RETURNTRANSFER] = true; 252 | $opts[CURLOPT_HTTPHEADER] = $headers; 253 | if (!Stripe::$verifySslCerts) 254 | $opts[CURLOPT_SSL_VERIFYPEER] = false; 255 | 256 | curl_setopt_array($curl, $opts); 257 | $rbody = curl_exec($curl); 258 | 259 | if (!defined('CURLE_SSL_CACERT_BADFILE')) { 260 | define('CURLE_SSL_CACERT_BADFILE', 77); // constant not defined in PHP 261 | } 262 | 263 | $errno = curl_errno($curl); 264 | if ($errno == CURLE_SSL_CACERT || 265 | $errno == CURLE_SSL_PEER_CERTIFICATE || 266 | $errno == CURLE_SSL_CACERT_BADFILE) { 267 | array_push( 268 | $headers, 269 | 'X-Stripe-Client-Info: {"ca":"using Stripe-supplied CA bundle"}' 270 | ); 271 | $cert = $this->caBundle(); 272 | curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); 273 | curl_setopt($curl, CURLOPT_CAINFO, $cert); 274 | $rbody = curl_exec($curl); 275 | } 276 | 277 | if ($rbody === false) { 278 | $errno = curl_errno($curl); 279 | $message = curl_error($curl); 280 | curl_close($curl); 281 | $this->handleCurlError($errno, $message); 282 | } 283 | 284 | $rcode = curl_getinfo($curl, CURLINFO_HTTP_CODE); 285 | curl_close($curl); 286 | return array($rbody, $rcode); 287 | } 288 | 289 | /** 290 | * @param number $errno 291 | * @param string $message 292 | * @throws Stripe_ApiConnectionError 293 | */ 294 | public function handleCurlError($errno, $message) 295 | { 296 | $apiBase = Stripe::$apiBase; 297 | switch ($errno) { 298 | case CURLE_COULDNT_CONNECT: 299 | case CURLE_COULDNT_RESOLVE_HOST: 300 | case CURLE_OPERATION_TIMEOUTED: 301 | $msg = "Could not connect to Stripe ($apiBase). Please check your " 302 | . "internet connection and try again. If this problem persists, " 303 | . "you should check Stripe's service status at " 304 | . "https://twitter.com/stripestatus, or"; 305 | break; 306 | case CURLE_SSL_CACERT: 307 | case CURLE_SSL_PEER_CERTIFICATE: 308 | $msg = "Could not verify Stripe's SSL certificate. Please make sure " 309 | . "that your network is not intercepting certificates. " 310 | . "(Try going to $apiBase in your browser.) " 311 | . "If this problem persists,"; 312 | break; 313 | default: 314 | $msg = "Unexpected error communicating with Stripe. " 315 | . "If this problem persists,"; 316 | } 317 | $msg .= " let us know at support@stripe.com."; 318 | 319 | $msg .= "\n\n(Network error [errno $errno]: $message)"; 320 | throw new Stripe_ApiConnectionError($msg); 321 | } 322 | 323 | private function checkSslCert($url) 324 | { 325 | /* Preflight the SSL certificate presented by the backend. This isn't 100% 326 | * bulletproof, in that we're not actually validating the transport used to 327 | * communicate with Stripe, merely that the first attempt to does not use a 328 | * revoked certificate. 329 | 330 | * Unfortunately the interface to OpenSSL doesn't make it easy to check the 331 | * certificate before sending potentially sensitive data on the wire. This 332 | * approach raises the bar for an attacker significantly. 333 | */ 334 | 335 | if (version_compare(PHP_VERSION, '5.3.0', '<')) { 336 | error_log("Warning: This version of PHP is too old to check SSL certificates correctly. " . 337 | "Stripe cannot guarantee that the server has a certificate which is not blacklisted"); 338 | return true; 339 | } 340 | 341 | $url = parse_url($url); 342 | $port = isset($url["port"]) ? $url["port"] : 443; 343 | $url = "ssl://{$url["host"]}:{$port}"; 344 | 345 | $sslContext = stream_context_create(array( 'ssl' => array( 346 | 'capture_peer_cert' => true, 347 | 'verify_peer' => true, 348 | 'cafile' => $this->caBundle(), 349 | ))); 350 | $result = stream_socket_client($url, $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $sslContext); 351 | if ($errno !== 0) { 352 | throw new Stripe_ApiConnectionError( 353 | "Could not connect to Stripe ($apiBase). Please check your " 354 | . "internet connection and try again. If this problem persists, " 355 | . "you should check Stripe's service status at " 356 | . "https://twitter.com/stripestatus. Reason was: $errstr" 357 | ); 358 | } 359 | 360 | $params = stream_context_get_params($result); 361 | 362 | $cert = $params['options']['ssl']['peer_certificate']; 363 | $cert_data = openssl_x509_parse( $cert ); 364 | 365 | openssl_x509_export($cert, $pem_cert); 366 | 367 | if (self::isBlackListed($pem_cert)) { 368 | throw new Stripe_ApiConnectionError( 369 | "Invalid server certificate. You tried to connect to a server that has a " . 370 | "revoked SSL certificate, which means we cannot securely send data to " . 371 | "that server. Please email support@stripe.com if you need help " . 372 | "connecting to the correct API server." 373 | ); 374 | } 375 | 376 | return true; 377 | } 378 | 379 | /* Checks if a valid PEM encoded certificate is blacklisted 380 | * @return boolean 381 | */ 382 | public static function isBlackListed($certificate) 383 | { 384 | $certificate = trim($certificate); 385 | $lines = explode("\n", $certificate); 386 | 387 | // Kludgily remove the PEM padding 388 | array_shift($lines); array_pop($lines); 389 | 390 | $der_cert = base64_decode(implode("", $lines)); 391 | $fingerprint = sha1($der_cert); 392 | return in_array($fingerprint, self::blacklistedCerts()); 393 | } 394 | 395 | private function caBundle() 396 | { 397 | return dirname(__FILE__) . '/../data/ca-certificates.crt'; 398 | } 399 | } 400 | --------------------------------------------------------------------------------