/lib/recurly/recurly_js.php

https://github.com/arush/desparation-deprecated · PHP · 228 lines · 179 code · 33 blank · 16 comment · 27 complexity · c266e24e42f866bebf11343d36af6093 MD5 · raw file

  1. <?php
  2. class Recurly_js
  3. {
  4. /**
  5. * Recurly.js Private Key
  6. */
  7. public static $privateKey;
  8. private $data;
  9. const BILLING_INFO_UPDATE = 'billinginfoupdate';
  10. const BILLING_INFO_UPDATED = 'billinginfoupdated';
  11. const SUBSCRIPTION_CREATE = 'subscriptioncreate';
  12. const SUBSCRIPTION_CREATED = 'subscriptioncreated';
  13. const TRANSACTION_CREATE = 'transactioncreate';
  14. const TRANSACTION_CREATED = 'transactioncreated';
  15. function __construct($data)
  16. {
  17. $this->data = $data;
  18. }
  19. public function verifySubscription()
  20. {
  21. if (!isset($this->data['signature'], $this->data['account_code'], $this->data['plan_code'], $this->data['quantity']))
  22. throw new InvalidArgumentException("Signature, account_code, plan_code, and/or quantity not present.");
  23. return $this->_verifyResults(self::SUBSCRIPTION_CREATED, $this->data);
  24. }
  25. public function verifyTransaction()
  26. {
  27. if (!isset($this->data['signature'], $this->data['account_code'], $this->data['amount_in_cents'], $this->data['currency'], $this->data['uuid']))
  28. throw new InvalidArgumentException("Signature, account_code, amount_in_cents, currency, and/or uuid not present.");
  29. return $this->_verifyResults(self::TRANSACTION_CREATED, $this->data);
  30. }
  31. public function verifyBillingInfoUpdated()
  32. {
  33. if (!isset($this->data['signature'], $this->data['account_code']))
  34. throw new InvalidArgumentException("Signature and account_code not present.");
  35. return $this->_verifyResults(self::BILLING_INFO_UPDATED, $this->data);
  36. }
  37. // Create a signature for a one-time transaction for the given $accountCode
  38. public static function signTransaction($amountInCents, $currency, $accountCode = null)
  39. {
  40. if (empty($currency) || strlen($currency) != 3)
  41. throw new InvalidArgumentException("Invalid currency");
  42. if (intval($amountInCents) <= 0)
  43. throw new InvalidArgumentException("Invalid amount in cents");
  44. return self::_generateSignature(self::TRANSACTION_CREATE, array(
  45. 'account_code' => $accountCode,
  46. 'amount_in_cents' => $amountInCents,
  47. 'currency' => $currency
  48. ));
  49. }
  50. // Create a signature for a new subscription
  51. public static function signSubscription($planCode, $accountCode)
  52. {
  53. return self::_generateSignature(self::SUBSCRIPTION_CREATE, array(
  54. 'plan_code' => $planCode,
  55. 'account_code' => $accountCode
  56. ));
  57. }
  58. // Create a signature for a new subscription, accepting optional parameters to
  59. // be signed
  60. public static function signSubscriptionEx($planCode, $accountCode, $extras = NULL)
  61. {
  62. return self::_generateSignatureEx(self::SUBSCRIPTION_CREATE, array(
  63. 'subscription' => array('plan_code' => $planCode),
  64. 'account' => array('account_code' => $accountCode)),
  65. $extras
  66. );
  67. }
  68. // For troubleshooting purposes
  69. public static function subscriptionDigest($planCode, $accountCode, $extras = NULL)
  70. {
  71. return self::_generateSignatureDigest(self::SUBSCRIPTION_CREATE, array(
  72. 'subscription' => array('plan_code' => $planCode),
  73. 'account' => array('account_code' => $accountCode)),
  74. $extras
  75. );
  76. }
  77. // Create a signature for updating billing information for the given $accountCode
  78. public static function signBillingInfoUpdate($accountCode)
  79. {
  80. if (empty($accountCode))
  81. throw new InvalidArgumentException("Account code is required");
  82. return self::_generateSignature(self::BILLING_INFO_UPDATE, array('account_code' => $accountCode));
  83. }
  84. // In its own function so it can be stubbed for testing
  85. function time_difference($timestamp)
  86. {
  87. return (time() - $timestamp);
  88. }
  89. # Validate the parameters are authentic
  90. private function _verifyResults($claim, $values)
  91. {
  92. if(!isset($values['signature'])) {
  93. throw new Recurly_ForgedQueryStringError("Mising signature");
  94. }
  95. $signature = $values['signature'];
  96. unset($values['signature']);
  97. $pos = strpos($signature, '-');
  98. if (!$pos)
  99. throw new Recurly_ForgedQueryStringError("Invalid signature");
  100. $hmac = substr($signature, 0, $pos);
  101. $timestamp = intval(substr($signature, $pos + 1));
  102. $time_diff = $this->time_difference($timestamp);
  103. if ($time_diff > 3600 || $time_diff < -3600)
  104. throw new Recurly_ForgedQueryStringError("Timestamp is too new or too old.");
  105. $expected_signature = self::_generateSignature($claim, $values, $timestamp);
  106. if ($signature != $expected_signature)
  107. throw new Recurly_ForgedQueryStringError("Signature is not authentic.");
  108. }
  109. # Create a signature using the private key
  110. protected static function _generateSignature($claim, $values, $timestamp = null)
  111. {
  112. if (is_null($timestamp)) { $timestamp = time(); }
  113. ksort($values);
  114. $flat_args = array();
  115. foreach($values as $key => $val) {
  116. if (!is_null($val)) {
  117. if (is_array($val)) {
  118. $inner_args = array();
  119. foreach($val as $val_item) {
  120. $inner_array = array();
  121. ksort($val_item);
  122. foreach ($val_item as $inner_key => $inner_val) {
  123. if (!empty($inner_val))
  124. $inner_array[] = $inner_key . ':' . preg_replace('/([\[\],:\\\\])/', '\${1}', $inner_val);
  125. }
  126. $inner_args[] = '[' . implode($inner_array, ',') . ']';
  127. }
  128. $flat_args[] = $key . ':[' . implode($inner_args, ',') . ']';
  129. }
  130. elseif (!empty($val)) {
  131. $flat_args[] = $key . ':' . preg_replace('/([\[\],:\\\\])/', '\${1}', $val);
  132. }
  133. }
  134. }
  135. $message = "[$timestamp,$claim,[" . implode($flat_args, ',') . ']]';
  136. return Recurly_HmacHash::hash(self::$privateKey, $message) . '-' . strval($timestamp);
  137. }
  138. // Create a signature using the private key and accepting optional signed parameters
  139. protected static function _generateSignatureEx($claim, $values, $extras = NULL, $timestamp = null)
  140. {
  141. if (is_null($timestamp)) { $timestamp = time(); }
  142. $message = self::_generateSignatureDigest($claim, $values, $extras, $timestamp);
  143. $extraKeyPaths = '';
  144. if (!is_null($extras)) {
  145. $extraKeyPaths = '+' . self::_getExtraKeypaths($extras);
  146. }
  147. return Recurly_HmacHash::hash(self::$privateKey, $message) . '-' . strval($timestamp) . $extraKeyPaths;
  148. }
  149. // Generates the protected string used to generate the HMAC signature
  150. protected static function _generateSignatureDigest($claim, $values, $extras = NULL, $timestamp = null)
  151. {
  152. if (is_null($timestamp)) { $timestamp = time(); }
  153. $signed_values = $values;
  154. if (!is_null($extras)) {
  155. $signed_values = array_merge_recursive($values, $extras);
  156. }
  157. $digest = self::_generateDigest($signed_values);
  158. return "[$timestamp,$claim,[$digest]]";
  159. }
  160. // Generates a "digest" of parameter values
  161. protected static function _generateDigest($values)
  162. {
  163. $digestVals = array();
  164. ksort($values);
  165. $flat = array();
  166. foreach($values as $key => $val) {
  167. if (!is_null($val)) {
  168. if (is_array($val)) {
  169. $digestVals[] = $key . ':[' . self::_generateDigest($val) . ']';
  170. }
  171. elseif (!empty($val)) {
  172. $digestVals[] = $key . ':' . preg_replace('/([\[\],:\\\\])/', '\\\\${1}', $val);
  173. }
  174. }
  175. }
  176. return implode($digestVals, ',');
  177. }
  178. // Only works with 1 level of nesting for now
  179. protected static function _getExtraKeypaths($extras)
  180. {
  181. ksort($extras);
  182. $keypaths = array();
  183. foreach($extras as $key => $val) {
  184. if (!is_null($val)) {
  185. if (is_array($val)) {
  186. ksort($val);
  187. foreach($val as $inner_key => $inner_val) {
  188. if (!empty($inner_val)) { $keypaths[] = "$key.$inner_key"; }
  189. }
  190. }
  191. }
  192. }
  193. return implode($keypaths, '+');
  194. }
  195. }