├── README.md ├── cart_app_controller.php ├── cart_app_helper.php ├── cart_app_model.php ├── config ├── authorize_net.php ├── config.php ├── emart_cart.php ├── google.php ├── paypal.php └── template.php ├── controllers ├── cart_orders_controller.php ├── cart_payments_controller.php ├── cart_products_controller.php └── components │ ├── cart_session.php │ └── instant_payment_notification.php ├── models ├── behaviors │ ├── credit_card.php │ └── payment_gateway.php ├── cart_credit_card.php ├── cart_order.php ├── cart_order_line_item.php ├── cart_payment.php ├── cart_payment_line_item.php ├── cart_product.php └── datasources │ ├── aktive_merchant_source.php │ ├── instant_payment_notification_source.php │ └── payment_gateway │ ├── authorize_net.php │ ├── google_checkout.php │ ├── paypal.php │ └── paypal_old.php ├── tests ├── cases │ ├── controllers │ │ └── cart_products_controller.test.php │ └── models │ │ └── cart_product.test.php └── fixtures │ └── cart_product_fixture.php ├── vendors └── shells │ └── cart.php └── views ├── elements └── cart_box.ctp └── helpers ├── button.php └── cart.php /README.md: -------------------------------------------------------------------------------- 1 | # "One Plugin to rule them all" 2 | 3 | This is an attempt to become the defacto solution for shopping cart 4 | implementation in CakePHP. Not to say this in of itself will be the 5 | best shopping cart around, but in fact give developers the resource 6 | to build their own custom shopping cart Application to their own 7 | specifications, allowing you to extrapolate from that point forwards. 8 | 9 | View the [Online Wiki](https://github.com/ProLoser/CakePHP-Cart/wiki) to see the latest version of the documentation -------------------------------------------------------------------------------- /cart_app_controller.php: -------------------------------------------------------------------------------- 1 | array( 4 | 'server' => 'https://secure.authorize.net/gateway/transact.dll', 5 | 'x_login' => 'username', 6 | 'x_version' => '3.1', 7 | 'x_delim_char' => '|', 8 | 'x_delim_data' => 'TRUE', 9 | 'x_url' => 'FALSE', 10 | 'x_type' => 'AUTH_CAPTURE', 11 | 'x_method' => 'CC', 12 | 'x_tran_key' => 'password', 13 | 'x_relay_response' => 'FALSE', 14 | 'x_card_num' => '', 15 | 'x_card_code' => '', 16 | 'x_exp_date' => '', 17 | 'x_description' => '', 18 | 'x_amount' => 'amount', 19 | 'x_tax' => '', 20 | 'x_freight' => '', 21 | 'x_first_name' => '', 22 | 'x_last_name' => '', 23 | 'x_address' => 'address1', 24 | 'x_city' => 'city', 25 | 'x_state' => 'state', 26 | 'x_zip' => 'zip', 27 | 'x_country' => 'country', 28 | 'x_email' => 'email', 29 | 'x_phone' => 'phone1', 30 | 'x_ship_to_first_name' => '', 31 | 'x_ship_to_last_name' => '', 32 | 'x_ship_to_address' => '', 33 | 'x_ship_to_city' => '', 34 | 'x_ship_to_state' => '', 35 | 'x_ship_to_zip' => '', 36 | 'x_ship_to_country' => '', 37 | ), 38 | 'testing' => array( 39 | 'server' => 'https://certification.authorize.net/gateway/transact.dll', 40 | //'DEBUGGING' => 1, //$DEBUGGING = 1; // Display additional information to track down problems 41 | //'TESTING' => 1, //$TESTING = 1; // Set the testing flag so that transactions are not live 42 | ), 43 | ); -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | array( 4 | // Server URL for cart submission 5 | 'server' => 'http://emartcart.com/cart/odb/cart.odb', 6 | // cart field => cart-value-slot or value-default 7 | 'CartCfg' => 'password', 8 | 'bName' => 'username', 9 | 'FixedQty' => '1', 10 | 'NoGiftCerts' => '1', 11 | ), 12 | ); -------------------------------------------------------------------------------- /config/google.php: -------------------------------------------------------------------------------- 1 | array( 4 | // Server URL for cart submission 5 | 'server' => 'https://www.paypal.com/cgi-bin/webscr', 6 | // cart field => cart-value-slot or value-default 7 | 'transaction' => 'txn_id', 8 | 'status' => 'payment_status', 9 | 'currency_code' => 'USD', 10 | 'address' => '', 11 | 'item_name' => 'ISV Payment', 12 | 'invoice' => 'invoice', 13 | 'notify_url' => 'notify_url', 14 | 'complete' => '/', 15 | 'amount' => 'amount', 16 | 'address1' => 'address1', 17 | 'address2' => 'address2', 18 | 'city' => 'city', 19 | 'country' => 'country', 20 | 'email' => 'email', 21 | 'first_name' => 'first_name', 22 | 'last_name' => 'last_name', 23 | 'night_phone_a' => 'phone1', 24 | 'night_phone_b' => 'phone2', 25 | 'state' => 'state', 26 | 'zip' => 'zip', 27 | // {n} will be used for an incremental counter int 28 | 'on{n}' => 'option{n}label', 29 | 'os{n}' => 'option{n}value', 30 | 'cmd' => '_xclick', 31 | 'no_note', 32 | 'business' => 'username', 33 | 'return', 34 | 'responses' => array( 35 | 'verified' => 'VERIFIED', 36 | ), 37 | ), 38 | // Testing settings are automatically merged with defaults 39 | 'testing' => array( 40 | 'server' => 'https://www.sandbox.paypal.com/cgi-bin/webscr', 41 | ), 42 | ); -------------------------------------------------------------------------------- /config/paypal.php: -------------------------------------------------------------------------------- 1 | array( 5 | // Server URL for cart submission 6 | 'server' => 'https://www.paypal.com/cgi-bin/webscr', 7 | 'responses' => array( 8 | 'verified' => 'VERIFIED', 9 | ), 10 | // Default values 11 | 'fields' => array( 12 | 13 | ), 14 | // Other Options 15 | 'ipn' => array( 16 | 'email_fieldname' => 'receiver_email', 17 | 'testing_fieldname' => 'test_ipn', 18 | ), 19 | ), 20 | // Testing settings are automatically merged with defaults 21 | 'testing' => array( 22 | 'server' => 'https://www.sandbox.paypal.com/cgi-bin/webscr', 23 | ), 24 | 'fieldmap' => array( 25 | // cart field => cart-value-slot or value-default 26 | 'login' => 'business', 27 | 'transaction' => 'txn_id', 28 | 'status' => 'payment_status', 29 | 'currency_code' => 'currency_code', 30 | 'item_name' => 'item_name', 31 | 'invoice' => 'invoice', 32 | 'notify_url' => 'notify_url', 33 | 'complete_url' => 'complete_url', 34 | 'cancel_url' => 'cancel_url', 35 | 'return_url' => 'return', 36 | 'amount' => 'amount', 37 | 'address1' => 'address1', 38 | 'address2' => 'address2', 39 | 'city' => 'city', 40 | 'country' => 'country', 41 | 'email' => 'email', 42 | 'first_name' => 'first_name', 43 | 'last_name' => 'last_name', 44 | 'phone1' => 'night_phone_a', 45 | 'phone2' => 'night_phone_b', 46 | 'state' => 'state', 47 | 'zip' => 'zip', 48 | 'action' => 'cmd', 49 | // {n} will be used for an incremental counter int 50 | 'on{n}' => 'option{n}label', 51 | 'os{n}' => 'option{n}value', 52 | 'no_note', 53 | ), 54 | ); -------------------------------------------------------------------------------- /config/template.php: -------------------------------------------------------------------------------- 1 | '] = array( 8 | 'defaults' => array( 9 | // Server URL for cart submission 10 | 'server' => 'https://www.paypal.com/cgi-bin/webscr', 11 | 'responses' => array( 12 | 'verified' => 'VERIFIED', 13 | ), 14 | // Default field values 15 | 'fields' => array( 16 | 'currency_code' => 'USD', 17 | '' => '', 18 | '' => '', 19 | '' => '', 20 | ), 21 | ), 22 | // Testing settings are automatically merged with defaults 23 | 'testing' => array( 24 | 'server' => 'https://www.sandbox.paypal.com/cgi-bin/webscr', 25 | ), 26 | // Used to map plugin-field-names to gateway-specific names 27 | 'fieldmap' => array( 28 | // cart field => cart-value-slot or value-default 29 | 'transaction' => 'txn_id', 30 | 'status' => 'payment_status', 31 | 'currency_code' => 'USD', 32 | 'address' => '', 33 | 'item_name' => 'ISV Payment', 34 | 'invoice' => 'invoice', 35 | 'notify_url' => 'notify_url', 36 | 'complete_url' => 'complete_url', 37 | 'cancel_url' => 'cancel_url', 38 | 'amount' => 'amount', 39 | 'address1' => 'address1', 40 | 'address2' => 'address2', 41 | 'city' => 'city', 42 | 'country' => 'country', 43 | 'email' => 'email', 44 | 'first_name' => 'first_name', 45 | 'last_name' => 'last_name', 46 | 'night_phone_a' => 'phone1', 47 | 'night_phone_b' => 'phone2', 48 | 'state' => 'state', 49 | 'zip' => 'zip', 50 | // {n} will be used for an incremental counter int 51 | 'on{n}' => 'option{n}label', 52 | 'os{n}' => 'option{n}value', 53 | 'cmd' => '_xclick', 54 | 'no_note', 55 | 'business' => 'username', 56 | 'return', 57 | ), 58 | ); -------------------------------------------------------------------------------- /controllers/cart_orders_controller.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /controllers/cart_payments_controller.php: -------------------------------------------------------------------------------- 1 | CartPayment->processIpn($_POST); 20 | } 21 | } 22 | ?> -------------------------------------------------------------------------------- /controllers/cart_products_controller.php: -------------------------------------------------------------------------------- 1 | CartSession->addItem($id); 8 | } 9 | 10 | } 11 | ?> -------------------------------------------------------------------------------- /controllers/components/cart_session.php: -------------------------------------------------------------------------------- 1 | controller =& $controller; 87 | 88 | if (isset($options['useTable'])) { 89 | $this->useTable = $options['useTable']; 90 | } else { 91 | $this->useTable = $this->controller->{$this->controller->modelClass}->useTable; 92 | } 93 | 94 | if (isset($options['nameField'])) { 95 | $this->nameField = $options['nameField']; 96 | } else { 97 | $this->nameField = $this->controller->{$this->controller->modelClass}->displayField; 98 | } 99 | if (isset($options['priceField'])) { 100 | $this->priceField = $options['priceField']; 101 | } 102 | if (isset($options['taxableField'])) { 103 | $this->taxableField = $options['taxableField']; 104 | } 105 | } 106 | //called after Controller::beforeFilter() 107 | function startup(&$controller) { 108 | } 109 | //called after Controller::beforeRender() 110 | function beforeRender(&$controller) { 111 | } 112 | //called after Controller::render() 113 | function shutdown(&$controller) { 114 | } 115 | //called before Controller::redirect() 116 | function beforeRedirect(&$controller, $url, $status=null, $exit=true) { 117 | } 118 | 119 | /** 120 | * Creates a fresh new cart with empty information 121 | * 122 | * @param boolean $full Specifies wether or not to retain the user 123 | * Billing and Shipping info 124 | * @return boolean Session->write() success 125 | * @access public 126 | */ 127 | function resetCart($full = true) { 128 | $data = null; 129 | if ($data = $this->Session->read('Order') && !$full) { 130 | unset($data['LineItem'], $data['Billing'], $data['Shipping'], $data['Total']); 131 | return $this->Session->write('Order', $data); 132 | } else { 133 | return $this->Session->delete('Order'); 134 | } 135 | } 136 | 137 | /** 138 | * Add's an item to the cart, requires a Product.id number or data 139 | * 140 | * @param int/array $product The product ID from the table, or a custom 141 | * array that has the ID field if useTable = true 142 | * @return boolean 143 | * @access public 144 | */ 145 | function addItem($data, $quantity = 1, $attribs = array()) { 146 | // Handles the 3 possible ways to pass a product: 147 | // $product = 3, Merges database data with custom data 148 | // $product['Model']['id'] = 3, Merges database data with custom data 149 | // $product['id'] = 3, Custom products only, for use without a table 150 | $lineItem = array(); 151 | if ($this->useTable) { 152 | if (is_array($data)) { 153 | $product = $this->controller->{$this->controller->modelClass}->find('first', array('recursive' => -1, 'conditions'=>array('id'=>$data['id']))); 154 | if (!$product) { 155 | return false; 156 | } 157 | $lineItem['Product'] = array_merge($data, $product[$this->controller->modelClass]); 158 | } else { 159 | if ($this->Session->check('Order.LineItem.' . $data)) { 160 | $lineItem = $this->Session->read('Order.LineItem.' . $data); 161 | } else { 162 | $product = $this->controller->{$this->controller->modelClass}->find('first', array('recursive' => -1, 'conditions'=>array('id'=>$data))); 163 | if (!$product) { 164 | return false; 165 | } 166 | $lineItem['Product'] = $product['Product']; 167 | } 168 | } 169 | } else { 170 | $lineItem['Product'] = $product = $data; 171 | } 172 | 173 | // Get the list of line items in the cart 174 | $lineItems = array(); 175 | if ($this->Session->check('Order.LineItem')) { 176 | $lineItems = $this->Session->read('Order.LineItem'); 177 | } 178 | 179 | // Check to see if the item is already in the cart 180 | if(!empty($lineItems[$lineItem['Product']['id']])) { 181 | // The item already exists, just edit it 182 | $match = null; 183 | foreach($lineItems[$lineItem['Product']['id']]['Selection'] as $count => $selection) { 184 | if ($selection['attributes'] == $attribs) { 185 | $match = $count; 186 | } 187 | } 188 | 189 | if ($match !== null) { 190 | $quantity = $lineItems[$lineItem['Product']['id']]['Selection'][$match]['quantity'] += $quantity; 191 | $returnValue = $this->updateItem($lineItem['Product']['id'], $match, $quantity); 192 | } else { 193 | $returnValue = $this->_addItemNew($lineItem, $attribs, $quantity, $lineItems); 194 | } 195 | } else { 196 | $returnValue = $this->_addItemNew($lineItem, $attribs, $quantity, $lineItems); 197 | } 198 | 199 | return $returnValue; 200 | } 201 | 202 | /** 203 | * Private method to avoid code duplication. Used in $this->addItem() 204 | * 205 | * @param $lineItem 206 | * @param $attribs 207 | * @param $quantity 208 | * @param $lineItems 209 | * @return boolean 210 | * @access protected 211 | */ 212 | function _addItemNew($lineItem, $attribs = array(), $quantity, $lineItems) { 213 | // Apply LineItem specific attributes 214 | $selection = array(); 215 | $selection['name'] = $lineItem['Product'][$this->nameField]; 216 | $selection['price'] = $lineItem['Product'][$this->priceField]; 217 | $selection['description'] = ($this->descField) ? $lineItem['Product'][$this->descField] : null; 218 | $selection['taxable'] = ($this->taxableField) ? $lineItem['Product'][$this->taxableField] : true; 219 | $selection['quantity'] = $quantity; 220 | $selection['subtotal'] = $quantity * $selection['price']; 221 | $selection['attributes'] = $attribs; 222 | 223 | // Add the item to the main list 224 | $lineItem['Selection'][] = $selection; 225 | $lineItems[$lineItem['Product']['id']] = $lineItem; 226 | 227 | $returnValue = $this->Session->write("Order.LineItem", $lineItems); 228 | $this->calcTotal(); 229 | 230 | return $returnValue; 231 | } 232 | 233 | /** 234 | * Updates a specific cart item. If quantity is passed at 0 or less, item is deleted. 235 | * If $product information is passed, the Product array is updated (does not change lineItem price) 236 | * TODO: I am unsure if I should have multiple actions perform at once (product info + quantity + price). 237 | * 238 | * @param int $id LineItem id number 239 | * @param int $quantity 240 | * @param array $product LineItem Product array 241 | * @return boolean 242 | * @access public 243 | */ 244 | function updateItem($id, $selectionCount, $quantity = false, $product = null) { 245 | $returnValue = null; 246 | 247 | // @TODO Handle the $product parameter 248 | /* if (!empty($product)) { 249 | $returnValue = $this->Session->write("Order.LineItem.$id.Product", $product); 250 | }*/ 251 | 252 | if ($quantity <= 0) { 253 | $returnValue = $this->removeItem($id, $selectionCount); 254 | } 255 | if ($quantity) { 256 | $selection = $this->Session->read("Order.LineItem.$id.Selection"); 257 | 258 | $selection[$selectionCount]['subtotal'] = $quantity * $selection[$selectionCount]['price']; 259 | $selection[$selectionCount]['quantity'] = $quantity; 260 | 261 | $returnValue = $this->Session->write("Order.LineItem.$id.Selection", $selection); 262 | $this->calcTotal(); 263 | } 264 | return $returnValue; 265 | } 266 | 267 | /** 268 | * Removes a LineItem from the shopping cart 269 | * 270 | * @param int $id LineItem id number 271 | * @return boolean 272 | * @access public 273 | */ 274 | // TODO Updated removeItem to work with selections 275 | function removeItem($id, $selection) { 276 | $returnValue = $this->Session->delete("Order.LineItem.$id.Selection.$selection"); 277 | 278 | $selectionArray = $this->Session->read("Order.LineItem.$id.Selection"); 279 | 280 | if (empty($selectionArray)) { 281 | $this->Session->delete("Order.LineItem.$id"); 282 | } 283 | 284 | $lineItems = $this->Session->read("Order.LineItem"); 285 | 286 | if (empty($lineItems)) { 287 | $this->resetCart(); 288 | } else { 289 | $this->calcTotal(); 290 | } 291 | 292 | return $returnValue; 293 | } 294 | 295 | /** 296 | * Returns the Order information for checkout 297 | * 298 | * @param 299 | * @return array() Returns the currently stored shopping cart session with calculations 300 | * @access public 301 | */ 302 | function getOrderDetails() { 303 | if ($this->Session->check('Order')) { 304 | return $this->Session->read('Order'); 305 | } else { 306 | return false; 307 | } 308 | } 309 | 310 | /* 311 | * The setPaymentInfo function is a wrapper function to simply fill in both 312 | * shipping and billing information at once. If only $billingData is passed 313 | * it is copied over to shipping. 314 | * 315 | * @param array $billingData 316 | * @param array $shippingData 317 | * @return void 318 | * @access public 319 | */ 320 | function setPaymentInfo($billingData, $shippingData = null) { 321 | $this->setBillInfo($billingData); 322 | 323 | if ($shippingData) { 324 | $this->setShipInfo($shippingData); 325 | } else { 326 | $this->setShipInfo($billingData); 327 | } 328 | } 329 | 330 | /** 331 | * Stores the customer billing address info 332 | * 333 | * @param array $data 334 | * @return boolean 335 | * @access public 336 | */ 337 | function setBillInfo($data) { 338 | return $this->Session->write('Order.Billing', $data); 339 | } 340 | 341 | /** 342 | * Stores the customer shipping address info 343 | * 344 | * @param array $data 345 | * @return boolean 346 | * @access public 347 | */ 348 | function setShipInfo($data) { 349 | return $this->Session->write('Order.Shipping', $data); 350 | } 351 | 352 | /** 353 | * Checks to make sure tax rate is a decimal and stores for use in calcTotal() 354 | * 355 | * @param decimal $taxRate 356 | * @return void 357 | * @access public 358 | */ 359 | function setTaxRate($taxRate) { 360 | // Ensures that percent values are changed to decimal 361 | if ($taxRate > 1) 362 | $taxRate = $taxRate / 100; 363 | 364 | $this->taxRate = $taxRate; 365 | $this->calcTotal(); 366 | } 367 | 368 | /** 369 | * Checks to make sure shipping rate is a decimal if it's not a flat fee and 370 | * stores for use in calcTotal() 371 | * 372 | * @param decimal $shipRate 373 | * @param boolean $flat Wether or not the shipping rate is a flat fee 374 | * @return void 375 | * @access public 376 | */ 377 | function setShipRate($shipRate, $flat = true) { 378 | // Ensures that percent values are changed to decimal 379 | if (!$flat && $shipRate > 1) 380 | $shipRate = $shipRate / 100; 381 | 382 | $this->shipRate = $shipRate; 383 | $this->calcTotal(); 384 | } 385 | 386 | /** 387 | * Conveniance method for handling all shipping in one call 388 | * 389 | * @param decimal $price 390 | * @param boolean $info 391 | * @return void 392 | * @access public 393 | */ 394 | function setShipping($price, $info) { 395 | $this->setShipRate($price); 396 | $this->setShipInfo($info); 397 | } 398 | 399 | /** 400 | * This function takes the order out of the session, goes row by row totalling up 401 | * the prices into $subtotal, applying tax to individual products that are taxable = true, 402 | * and then adds it all together, including shipping, which it stores back into the 403 | * Order session under Order.Totals 404 | * 405 | * @return boolean 406 | * @access public 407 | */ 408 | function calcTotal() { 409 | $order = $this->Session->read('Order'); 410 | $subtotal = 0; 411 | 412 | foreach ($order['LineItem'] as $id => $lineItem) { 413 | 414 | $lineQuantity = 0; 415 | $lineTotal = 0; 416 | foreach ($lineItem['Selection'] as $count => $selection) { 417 | $selectionTotal = $selection['price'] * $selection['quantity']; 418 | /* if ($selection['taxable']) 419 | $selectionTotal += $selectionTotal * $this->taxRate;*/ 420 | $subtotal += $selectionTotal; 421 | 422 | $lineQuantity += $selection['quantity']; 423 | $lineTotal += $selectionTotal; 424 | } 425 | 426 | $totals = array( 427 | 'quantity' => $lineQuantity, 428 | 'subtotal' => $lineTotal, 429 | 'numAttribs' => count($selection['attributes']), 430 | ); 431 | 432 | $this->Session->write('Order.LineItem.' . $id . '.Totals', $totals); 433 | } 434 | 435 | $tax = sprintf('%.2f', $subtotal * $this->taxRate); 436 | 437 | $shipping = 0; 438 | if ($this->shipRate > 0) { 439 | $shipping = ($this->shipFlat) ? $this->shipRate : $subtotal * $this->shipRate; 440 | } elseif (!empty($order['Totals']) && $order['Totals'] > 0) { 441 | $shipping = $order['Totals']['shipping']; 442 | } 443 | 444 | $total = $subtotal + $tax + $shipping; 445 | 446 | $data = array( 447 | 'subtotal' => $subtotal, 448 | 'tax' => $tax, 449 | 'shipping' => $shipping, 450 | 'total' => $total, 451 | ); 452 | 453 | return $this->Session->write('Order.Totals', $data); 454 | } 455 | 456 | } 457 | ?> 458 | -------------------------------------------------------------------------------- /controllers/components/instant_payment_notification.php: -------------------------------------------------------------------------------- 1 | __settings[$controller->name])) { 22 | $this->__settings[$controller->name] = $settings; 23 | } 24 | } 25 | 26 | /** 27 | * Loads the datasource map and configuration into the helper for reference 28 | * 29 | * @param string $dbConfig name of db configuration to reference for the datasource 30 | * @author Dean Sofer 31 | */ 32 | function load($dbConfig) { 33 | if (empty($dbConfig)) 34 | return false; 35 | App::Import('ConnectionManager'); 36 | $ds = ConnectionManager::getDataSource($settings); 37 | $this->config = $ds->config; 38 | $this->map = $ds->map; 39 | } 40 | 41 | /** 42 | * Returns a payment url with all the details about the payment as $_GET variables. 43 | * Useful if you want to save a transaction before payment seamlessly to the user. 44 | * 45 | * WHAT? Paypal buttons POST to paypal, you can't save the form beforehand. 46 | * This is a workaround. Refer to https://github.com/ProLoser/CakePHP-Cart/wiki/Examples 47 | * 48 | * @param string $data 49 | */ 50 | public function paymentUrl($data, $urls = array()) { 51 | // TODO This is the paypal-specific keys used for each value. Needs to be abstracted 52 | $urlAliases = array( 53 | 'complete' => 'return', 54 | 'notify' => 'notify_url', 55 | 'cancel' => 'cancel_return', 56 | 'error' => '', 57 | ); 58 | $url = $this->map['server']; 59 | $params = array( 60 | 'cmd' => '_xclick', 61 | 'business' => $this->config['email'], 62 | 'currency_code' => $this->config['currency'], 63 | ); 64 | foreach ($urlAliases as $alias => $key) { 65 | if (isset($urls[$alias])) { 66 | if (is_array($urls[$alias])) { 67 | $urls[$alias] = Router::url($urls[$alias]); 68 | } 69 | $params[$key] = $urls[$alias]; 70 | } 71 | } 72 | $url .= '?' . http_build_query(array_merge($params, $data)); 73 | return $url; 74 | } 75 | } 76 | ?> -------------------------------------------------------------------------------- /models/behaviors/credit_card.php: -------------------------------------------------------------------------------- 1 | 'lib'.DS.'merchant.php')); 42 | } 43 | 44 | /** 45 | * Before validate callback 46 | * 47 | * @param object $model Model using this behavior 48 | * @return boolean True if validate operation should continue, false to abort 49 | * @access public 50 | */ 51 | function beforeValidate(&$model) { 52 | //$this->creditCard = new Merchant_Billing_CreditCard($model->data); 53 | return true; 54 | } 55 | 56 | /** 57 | * Return an array of possible credit card types 58 | * 59 | * @param boolean $formatted false if set to true will create a Select-element friendly array 60 | * @return array 61 | * @author Dean 62 | */ 63 | public function creditCardTypes(&$model, $formatted = false) { 64 | $ccTypes = Merchant_Billing_CreditCard::CARD_COMPANIES(); 65 | if (!$formatted) { 66 | return $ccTypes; 67 | } else { 68 | $ccTypes = array_keys($ccTypes); 69 | foreach ($ccTypes as $ccType) { 70 | if ($ccType === 'master') { 71 | $ccInflected[] = 'Mastercard'; 72 | } else { 73 | $ccInflected[] = Inflector::humanize($ccType); 74 | } 75 | } 76 | $creditCards = array_combine($ccTypes, $ccInflected); 77 | return $creditCards; 78 | } 79 | } 80 | 81 | public function validMonth(&$model, $check) { 82 | return Merchant_Billing_CreditCard::valid_month(array_pop($check)); 83 | } 84 | 85 | public function validExpiryYear(&$model, $check) { 86 | return Merchant_Billing_CreditCard::valid_expiry_year(array_pop($check)); 87 | } 88 | 89 | public function validCardNumber(&$model, $check) { 90 | return Merchant_Billing_CreditCard::valid_number(array_pop($check)); 91 | } 92 | 93 | public function validCardType(&$model, $check) { 94 | $ccTypes = $this->creditCardTypes($model); 95 | return isset($ccTypes[array_pop($check)]); 96 | } 97 | 98 | public function matchCardType(&$model, $check, $numberField) { 99 | return Merchant_Billing_CreditCard::matching_type($this->data[$model->alias][$numberField], array_pop($check)); 100 | } 101 | 102 | /** 103 | * Pulls out a aktive-friendly data array from a flat data array 104 | * 105 | * @param string $data 106 | * @return void 107 | * @author Dean 108 | */ 109 | public function formatData(&$model, $data = null) { 110 | if (!$data) { 111 | $data = $model->data; 112 | } 113 | $reference = array( 114 | 'description', 115 | 'address' => array( 116 | 'address1' => '1234 Street', 117 | 'zip' => '98004', 118 | 'state' => 'WA', 119 | 'city' => 'Yorba Linda', 120 | 'country' => 'United States', 121 | ), 122 | 'credit_card' => array( 123 | 'first_name' => 'John', 124 | 'last_name' => 'Doe', 125 | 'number' => '4007000000027', 126 | 'month' => '12', 127 | 'year' => '2012', 128 | 'verification_value' => '123', 129 | 'type' => 'visa', 130 | ), 131 | ); 132 | if (isset($data['description'])) { 133 | $result['description'] = $data['description']; 134 | } 135 | $result['address'] = array_intersect_key($data, $reference['address']); 136 | $result['credit_card'] = array_intersect_key($data, $reference['credit_card']); 137 | return $result; 138 | } 139 | } -------------------------------------------------------------------------------- /models/behaviors/payment_gateway.php: -------------------------------------------------------------------------------- 1 | array( 24 | 'complete' => 'http://example.com/complete', // Arrays are also allowed 25 | 'cancel' => 'http://example.com/cancel', 26 | 'error' => 'http://example.com/error', 27 | 'notify' => 'http://example.com/ipn', 28 | ) 29 | ); 30 | 31 | /** 32 | * Settings for the behavior on every model 33 | * 34 | * @var string 35 | */ 36 | var $settings = array(); 37 | 38 | /** 39 | * Initialize behavior settings 40 | * 41 | * @param string $Model 42 | * @param string $settings 43 | * @return void 44 | * @author Dean 45 | */ 46 | function setup(&$Model, $settings = array()) { 47 | $this->settings[$Model->name] = array_merge($this->defaults, $settings); 48 | } 49 | 50 | /** 51 | * If the developer declared the trigger in the model, call it 52 | * 53 | * @param object $Model instance of model 54 | * @param string $trigger name of trigger to call 55 | * @access protected 56 | */ 57 | function _callback(&$Model, $trigger, $parameters = array()) { 58 | if (method_exists($Model, $trigger)) { 59 | return call_user_func_array(array($Model, $trigger), $parameters); 60 | } 61 | } 62 | 63 | /** 64 | * Used for setting the 'cancel_return_url' and/or 'error_return_url' for PaypalExpress 65 | * 66 | * @param object $Model 67 | * @param mixed $urls array('cancel' => 'http://localhost/cancel') OR 'complete' 68 | * @param mixed $value used when setting only 1 url, provide the value url specified for preceeding argument 69 | * @return void 70 | * @author Dean 71 | */ 72 | public function setUrls(&$Model, $urls, $value = null) { 73 | if (!is_array($urls) && $value) { 74 | $this->settings[$Model->name]['urls'][$urls] = $value; 75 | } else { 76 | $this->settings[$Model->name]['urls'] = $urls; 77 | } 78 | } 79 | 80 | /** 81 | * Returns an instance of the payment gateway 82 | * 83 | * @return $PaymentGatewayDatasource instance for calling methods 84 | * @author Dean 85 | */ 86 | public function loadGateway(&$Model) { 87 | App::import('Model', 'ConnectionManager', false); 88 | return ConnectionManager::getDataSource($Model->useDbConfig); 89 | } 90 | 91 | public function purchase(&$Model, $amount, $data) { 92 | $data['amount'] = $amount; 93 | $continue = $this->_callback($Model, 'beforePurchase', array($data)); 94 | if ($continue === false) { 95 | return false; 96 | } elseif ($continue) { 97 | $data = $continue; 98 | } 99 | $gateway = $this->loadGateway($Model); 100 | $gateway->setUrls($this->settings[$Model->name]['urls']); 101 | $success = $gateway->purchase($data['amount'], $data); 102 | if (!$success) { 103 | $Model->error = $gateway->error; 104 | } 105 | $this->_callback($Model, 'afterPurchase', array($data, $success)); 106 | return $success; 107 | } 108 | 109 | /** 110 | * Verifies POST data given by the instant payment notification 111 | * 112 | * @param array $data Most likely directly $_POST given by the controller. 113 | * @return boolean true | false depending on if data received is actually valid from paypal and not from some script monkey 114 | */ 115 | public function ipn(&$Model, $data) { 116 | $continue = $this->_callback($Model, 'beforeIpn', array($data)); 117 | if ($continue === false) { 118 | return false; 119 | } elseif ($continue) { 120 | $data = $continue; 121 | } 122 | if(!empty($data)){ 123 | $gateway = $this->loadGateway($Model); 124 | $gateway->setUrls($this->settings[$Model->name]['urls']); 125 | $success = $gateway->ipn($data); 126 | $this->_callback($Model, 'afterIpn', array($data, $success)); 127 | return true; 128 | } 129 | return false; 130 | } 131 | 132 | /** 133 | * builds the associative array for paypalitems only if it was a cart upload 134 | * 135 | * @param raw post data sent back from paypal 136 | * @return array of cakePHP friendly association array. 137 | */ 138 | public function extractLineItems(&$Model, $data) { 139 | $gateway = $this->loadGateway($Model); 140 | return $gateway->extractLineItems($data); 141 | } 142 | } 143 | ?> -------------------------------------------------------------------------------- /models/cart_credit_card.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /models/cart_order.php: -------------------------------------------------------------------------------- 1 | array( 16 | 'className' => 'Cart.CartOrderLineItem', 17 | ), 18 | 'CartPayment' => array( 19 | 'className' => 'Cart.CartPayment', 20 | ), 21 | ); 22 | } 23 | ?> -------------------------------------------------------------------------------- /models/cart_order_line_item.php: -------------------------------------------------------------------------------- 1 | array( 16 | 'className' => 'Cart.CartOrder', 17 | ), 18 | ); 19 | 20 | } 21 | ?> -------------------------------------------------------------------------------- /models/cart_payment.php: -------------------------------------------------------------------------------- 1 | array( 16 | 'className' => 'Cart.CartPaymentLineItem', 17 | ), 18 | ); 19 | var $belongsTo = array( 20 | 'CartOrder' => array( 21 | 'className' => 'Cart.CartOrder', 22 | ), 23 | ); 24 | 25 | /** 26 | * On IPN post-back the data is checked against the payment gateway and then saved to the database 27 | * 28 | * @param string $data 29 | * @param string $gatewayConfig 30 | * @return void 31 | * @author Dean 32 | */ 33 | public function processIpn($data) { 34 | if ($this->ipn($data)) { 35 | $payment['CartPayment'] = $data; 36 | if (isset($data['invoice'])) { 37 | $payment['CartPayment']['cart_order_id'] = $data['invoice']; 38 | } 39 | $payment['CartPaymentLineItem'] = $this->extractLineItems($data); 40 | $this->Payment->saveAll($payment); 41 | } 42 | } 43 | } 44 | ?> -------------------------------------------------------------------------------- /models/cart_payment_line_item.php: -------------------------------------------------------------------------------- 1 | array( 13 | 'className' => 'Cart.CartPayment', 14 | ), 15 | ); 16 | } 17 | ?> -------------------------------------------------------------------------------- /models/cart_product.php: -------------------------------------------------------------------------------- 1 | array('notempty'), 13 | 'taxable' => array('boolean'), 14 | 'price' => array('decimal'), 15 | 'visible' => array('boolean'), 16 | 'active' => array('boolean') 17 | ); 18 | 19 | } 20 | ?> -------------------------------------------------------------------------------- /models/datasources/aktive_merchant_source.php: -------------------------------------------------------------------------------- 1 | '', 31 | 'cancel_return_url' => '', 32 | ); 33 | 34 | /** 35 | * Gateway Object 36 | * 37 | * @var string 38 | */ 39 | var $gateway; 40 | 41 | /** 42 | * Holds the error message if there was one 43 | * 44 | * @var string 45 | */ 46 | var $error = ''; 47 | 48 | /** 49 | * constructer. Load the HttpSocket into the Http var. 50 | */ 51 | function __construct($config){ 52 | parent::__construct($config); 53 | $this->_load(); 54 | } 55 | 56 | /** 57 | * Loads a gateway object with the passed options 58 | * 59 | * @param string $options 60 | * @return void 61 | * @author Dean 62 | */ 63 | function _load($options = array()) { 64 | App::Import('Vendor', 'AktiveMerchant', array('file' => 'aktive_merchant'.DS.'lib'.DS.'merchant.php')); 65 | if (isset($this->config['testing']) && $this->config['testing']) { 66 | Merchant_Billing_Base::mode('test'); 67 | } 68 | $gatewayClass = 'Merchant_Billing_' . $this->config['gateway']; 69 | $this->gateway = new $gatewayClass($this->config); 70 | } 71 | 72 | /** 73 | * Creates and returns a credit card object if it is valid 74 | * 75 | * $data = array( 76 | * 'first_name' => 'John', 77 | * 'last_name' => 'Doe', 78 | * 'number' => '5105105105105100', 79 | * 'month' => '12', 80 | * 'year' => '2012', 81 | * 'verification_value' => '123', 82 | * 'type' => 'master', 83 | * ); 84 | * 85 | * @param string $data 86 | * @return $creditCard Merchant_Billing_CreditCard or false if the card is invalid 87 | */ 88 | public function creditCard($data) { 89 | if ($data instanceof Merchant_Billing_CreditCard) { 90 | if ($data->is_valid()) { 91 | return $data; 92 | } else { 93 | return false; 94 | } 95 | } else { 96 | if (isset($data['credit_card'])) { 97 | $data = $data['credit_card']; 98 | } 99 | $creditCard = new Merchant_Billing_CreditCard($data); 100 | if ($creditCard->is_valid()) { 101 | return $creditCard; 102 | } else { 103 | return false; 104 | } 105 | } 106 | } 107 | 108 | /** 109 | * Submits a purchase to the active payment gateway 110 | * 111 | * @param decimal $amount 112 | * @param array $data requires 'description' and 'address' keys to be properly filled 113 | * @return mixed $success Returns the REF ID# if successful, else false 114 | */ 115 | public function purchase($amount, $data) { 116 | try { 117 | // Paypal Express works in 2 separate parts 118 | if ($this->config['gateway'] == 'PaypalExpress') { 119 | if (!isset($_GET['token'])) { 120 | $this->_startPurchase($amount, $data); 121 | } else { 122 | $response = $this->_completePurchase(); 123 | } 124 | } else { 125 | $creditCard = $this->creditCard($data); 126 | if (!$creditCard) { 127 | $this->error = 'Invalid Credit Card'; 128 | return false; 129 | } 130 | 131 | $options = array( 132 | 'description' => $data['description'], 133 | 'address' => $data['address'], 134 | ); 135 | 136 | $response = $this->gateway->purchase($amount, $creditCard, $options); 137 | } 138 | if ($response->success()) { 139 | return $response->authorization(); 140 | } else { 141 | $this->error = $response->message(); 142 | $this->log('Cart.AktiveMerchantSource: ' . $response->message(), 'cart'); 143 | return false; 144 | } 145 | } catch (Exception $e) { 146 | $this->error = $e->getMessage(); 147 | $this->log('Cart.AktiveMerchantSource: ' . $e->getMessage(), 'cart'); 148 | return false; 149 | } 150 | } 151 | 152 | /** 153 | * PaypalExpress: Creates the transaction and sends the user to paypal to login 154 | * 155 | * @param string $data 156 | * @return void 157 | * @author Dean 158 | */ 159 | protected function _startPurchase($amount, $data) { 160 | $pageURL = Router::url(null, true); 161 | $options = array( 162 | 'description' => $data['description'], 163 | 'return_url' => $pageURL, 164 | ); 165 | $response = $this->gateway->setup_purchase($amount, array_merge($this->urls, $options)); 166 | die(header('Location: ' . $this->gateway->url_for_token($response->token()))); 167 | } 168 | 169 | /** 170 | * PaypalExpress: When the user returns from paypal after logging in, the transaction is finalized 171 | * 172 | * @return void 173 | * @author Dean 174 | */ 175 | protected function _completePurchase() { 176 | $response = $this->gateway->get_details_for($_GET['token'], $_GET['PayerID']); 177 | return $this->gateway->purchase($response->amount()); 178 | } 179 | 180 | /** 181 | * Changes the keys for the urls to be compatible with the AktiveMerchant vendor 182 | * 183 | * @return array $urls 184 | */ 185 | public function setUrls($urls) { 186 | if (isset($urls['complete'])) 187 | $this->urls['return_url'] = $urls['complete']; 188 | if (isset($urls['cancel'])) 189 | $this->urls['cancel_return_url'] = $urls['cancel']; 190 | } 191 | 192 | } 193 | ?> -------------------------------------------------------------------------------- /models/datasources/instant_payment_notification_source.php: -------------------------------------------------------------------------------- 1 | map = $this->_map(); 38 | App::import('HttpSocket'); 39 | $this->Http = new HttpSocket(); 40 | Configure::load('Cart.config'); 41 | } 42 | 43 | /** 44 | * Returns the proper settings map from the config files 45 | * 46 | * @param string $mode null override 'defaults' or 'testing' detection by passing the specific settings name 47 | * @return array $settings 48 | * @author Dean 49 | */ 50 | protected function _map($mode = null) { 51 | $config = explode('.', $this->config['driver']); 52 | $config[1] = Inflector::underscore($config[1]); 53 | Configure::load(implode('.', $config)); 54 | $map = Configure::read($this->config['driver']); 55 | if (isset($this->config['testing']) && isset($map['testing'])) { 56 | $map = array_merge($map['defaults'], $map['testing']); 57 | } elseif ($mode && $mode != 'defaults') { 58 | $map = array_merge($map['defaults'], $map[$mode]); 59 | } else { 60 | $map = $map['defaults']; 61 | } 62 | return $map; 63 | } 64 | 65 | /** 66 | * Iterates through the post-back data of the IPN and converts the Order Information to a Cake-friendly array 67 | * TODO: currently broken due to an inconsistent config file 68 | * 69 | * @param string $data 70 | * @param boolean $reverse false Set to true to go from GeneralFormat -> GatewayFormat 71 | * @return mixed $lineItems a formatted array of line items from the ipn post-back data 72 | * @author Dean 73 | */ 74 | public function uniform($data, $reverse = false) { 75 | $result = array(); 76 | foreach ($this->settings as $gatewayField => $generalField) { 77 | if ($reverse) { 78 | // Uses the default value if the general field isn't found 79 | if (isset($data[$generalField])) { 80 | $result[$gatewayField] = $data[$generalField]; 81 | } elseif (!in_array($generalField, $this->map, true)) { 82 | $result[$gatewayField] = $generalField; 83 | } 84 | unset($data[$generalField]); 85 | } elseif (in_array($generalField, $this->map, true)) { 86 | $result[$generalField] = $data[$gatewayField]; 87 | unset($data[$gatewayField]); 88 | } 89 | } 90 | return $result; 91 | } 92 | 93 | /** 94 | * Checks with the server to confirm if the notification is legitimate 95 | * 96 | * @param mixed $data 97 | * @return boolean 98 | * @author Dean 99 | */ 100 | public function ipn($data = null) { 101 | if (empty($data)) { 102 | $data = $_POST; 103 | } 104 | $response = $this->submit($data); 105 | 106 | return ($this->checkResponse($response) && $this->checkEmail($data)); 107 | } 108 | 109 | /** 110 | * Submits the data for verification 111 | * 112 | * @param string $data 113 | * @return void 114 | * @author Dean 115 | */ 116 | public function submit($data) { 117 | return $this->Http->post($this->map['server'], $data); 118 | } 119 | 120 | /** 121 | * Checks Paypal postback to see if it is valid 122 | * 123 | * @param string $response 124 | * @return boolean 125 | * @author Dean 126 | */ 127 | public function checkResponse($response) { 128 | if ($response == $this->map['responses']['verified']) { 129 | return true; 130 | } 131 | if (!$response) { 132 | $this->log('HTTP Error in PaymentGatewayDatasource::checkResponse while posting back to gateway', 'cart'); 133 | } 134 | return false; 135 | } 136 | 137 | /** 138 | * Checks to see if the email in the ipn data matches the database config 139 | * 140 | * @param string $response 141 | * @return boolean 142 | * @author Dean 143 | */ 144 | public function checkEmail($data) { 145 | if ($data[$this->map['ipn']['email_fieldname']] == $this->config['email']) { 146 | return true; 147 | } 148 | return false; 149 | } 150 | 151 | /** 152 | * Checks to see if the testing flag is set in the ipn data 153 | * 154 | * @param string $response 155 | * @return boolean 156 | * @author Dean 157 | */ 158 | public function checkTesting($data) { 159 | if (empty($data[$this->map['ipn']['testing_fieldname']])) { 160 | return false; 161 | } 162 | return true; 163 | } 164 | } 165 | ?> -------------------------------------------------------------------------------- /models/datasources/payment_gateway/authorize_net.php: -------------------------------------------------------------------------------- 1 | settings; 14 | 15 | // setup variables 16 | $ccexp = $ccexpmonth . '/' . $ccexpyear; 17 | 18 | $DEBUGGING = 1; // Display additional information to track down problems 19 | $TESTING = 1; // Set the testing flag so that transactions are not live 20 | $ERROR_RETRIES = 2; // Number of transactions to post if soft errors occur 21 | 22 | $data = array( 23 | "x_login" => $settings['login'], 24 | "x_version" => "3.1", 25 | "x_delim_char" => "|", 26 | "x_delim_data" => "TRUE", 27 | "x_url" => "FALSE", 28 | "x_type" => "AUTH_CAPTURE", 29 | "x_method" => "CC", 30 | "x_tran_key" => $settings['password'], 31 | "x_relay_response" => "FALSE", 32 | "x_card_num" => str_replace(" ", "", $ccnum), 33 | "x_card_code" => $ccver, 34 | "x_exp_date" => $ccexp, 35 | "x_description" => $desc, 36 | "x_amount" => $amount, 37 | "x_tax" => $tax, 38 | "x_freight" => $shipping, 39 | "x_first_name" => $billinginfo["fname"], 40 | "x_last_name" => $billinginfo["lname"], 41 | "x_address" => $billinginfo["address"], 42 | "x_city" => $billinginfo["city"], 43 | "x_state" => $billinginfo["state"], 44 | "x_zip" => $billinginfo["zip"], 45 | "x_country" => $billinginfo["country"], 46 | "x_email" => $email, 47 | "x_phone" => $phone, 48 | "x_ship_to_first_name" => $shippinginfo["fname"], 49 | "x_ship_to_last_name" => $shippinginfo["lname"], 50 | "x_ship_to_address" => $shippinginfo["address"], 51 | "x_ship_to_city" => $shippinginfo["city"], 52 | "x_ship_to_state" => $shippinginfo["state"], 53 | "x_ship_to_zip" => $shippinginfo["zip"], 54 | "x_ship_to_country" => $shippinginfo["country"], 55 | ); 56 | 57 | return parent::verify($data); 58 | } 59 | 60 | 61 | /** 62 | * Verifies POST data given by the paypal instant payment notification 63 | * 64 | * @param array $data Most likely directly $_POST given by the controller. 65 | * @return boolean $valid depending on if data received is actually valid from paypal and not from some script monkey 66 | */ 67 | function verify($data) { 68 | 69 | 70 | return parent::verify($data); 71 | } 72 | 73 | /** 74 | * Scans the returned response from $this->verify() and gives an understandable response 75 | * 76 | * @param string $response 77 | * @return boolean 78 | * @author Dean 79 | */ 80 | public function checkResponse($response) { 81 | // Parse through response string 82 | 83 | $h = substr_count($response, "|"); 84 | $h++; 85 | $responsearray = array(); 86 | 87 | for ($j=1; $j <= $h; $j++) { 88 | 89 | $p = strpos($response, "|"); 90 | 91 | if ($p === false) { // note: three equal signs 92 | // x_delim_char is obviously not found in the last go-around 93 | // This is final response string 94 | $responsearray[$j] = $response; 95 | } 96 | else { 97 | $p++; 98 | // get one portion of the response at a time 99 | $pstr = substr($response, 0, $p); 100 | 101 | // this prepares the text and returns one value of the submitted 102 | // and processed name/value pairs at a time 103 | // for AIM-specific interpretations of the responses 104 | // please consult the AIM Guide and look up 105 | // the section called Gateway Response API 106 | $pstr_trimmed = substr($pstr, 0, -1); // removes "|" at the end 107 | 108 | if($pstr_trimmed==""){ 109 | $pstr_trimmed=""; 110 | } 111 | 112 | $responsearray[$j] = $pstr_trimmed; 113 | 114 | // remove the part that we identified and work with the rest of the string 115 | $response = substr($response, $p); 116 | 117 | } // end if $p === false 118 | 119 | } // end parsing for loop 120 | 121 | return $responsearray; 122 | } 123 | 124 | } 125 | ?> -------------------------------------------------------------------------------- /models/datasources/payment_gateway/google_checkout.php: -------------------------------------------------------------------------------- 1 | array( 24 | 'id' => array( 25 | 'type' => 'integer', 26 | 'null' => true, 27 | 'key' => 'primary', 28 | 'length' => 11, 29 | ), 30 | 'text' => array( 31 | 'type' => 'string', 32 | 'null' => true, 33 | 'key' => 'primary', 34 | 'length' => 140 35 | ), 36 | 'status' => array( 37 | 'type' => 'string', 38 | 'null' => true, 39 | 'key' => 'primary', 40 | 'length' => 140 41 | ), 42 | ) 43 | ); 44 | public function __construct($config) { 45 | $auth = "{$config['login']}:{$config['password']}"; 46 | $this->connection = new HttpSocket( 47 | "http://{$auth}@twitter.com/" 48 | ); 49 | parent::__construct($config); 50 | } 51 | public function listSources() { 52 | return array('tweets'); 53 | } 54 | public function read($model, $queryData = array()) { 55 | if (!isset($queryData['conditions']['username'])) { 56 | $queryData['conditions']['username'] = $this->config['login']; 57 | } 58 | $url = "/statuses/user_timeline/"; 59 | $url .= "{$queryData['conditions']['username']}.json"; 60 | 61 | $response = json_decode($this->connection->get($url), true); 62 | $results = array(); 63 | 64 | foreach ($response as $record) { 65 | $record = array('Tweet' => $record); 66 | $record['User'] = $record['Tweet']['user']; 67 | unset($record['Tweet']['user']); 68 | $results[] = $record; 69 | } 70 | return $results; 71 | } 72 | public function create($model, $fields = array(), $values = array()) { 73 | $data = array_combine($fields, $values); 74 | $result = $this->connection->post('/statuses/update.json', $data); 75 | $result = json_decode($result, true); 76 | if (isset($result['id']) && is_numeric($result['id'])) { 77 | $model->setInsertId($result['id']); 78 | return true; 79 | } 80 | return false; 81 | } 82 | public function describe($model) { 83 | return $this->_schema['tweets']; 84 | } 85 | } 86 | ?> -------------------------------------------------------------------------------- /models/datasources/payment_gateway/paypal.php: -------------------------------------------------------------------------------- 1 | config['testing'])) 21 | || (!isset($data['test_ipn']) && empty($this->config['testing']))) 22 | && parent::ipn($data) && $this->_checkEmail($data) 23 | ) { 24 | return true; 25 | } else { 26 | return false; 27 | } 28 | } 29 | 30 | protected function _checkEmail($data) { 31 | return ($data['receiver_email'] == $this->config['email']); 32 | } 33 | 34 | /** 35 | * Iterates through the post data and extracts all the lineItems in the order if any 36 | * 37 | * @param string $data 38 | * @return array $results 39 | * @author Dean 40 | */ 41 | public function extractLineItems($data) { 42 | $results = array(); 43 | // TODO Does 1 cart item return a different array? 44 | if (isset($data['item_name'])) { 45 | $results[0]['item_name'] = $data['item_name']; 46 | $results[0]['item_number'] = $data['item_number']; 47 | $results[0]['quantity'] = $data['quantity']; 48 | $results[0]['mc_shipping'] = $data['mc_shipping']; 49 | $results[0]['mc_handling'] = $data['mc_handling']; 50 | $results[0]['mc_gross'] = $data['mc_gross']; 51 | $results[0]['tax'] = $data['tax']; 52 | } 53 | if (isset($data['num_cart_items']) && $data['num_cart_items'] > 1) { 54 | for ($i = 1; $i <= $data['num_cart_items']; $i++) { 55 | $results[$i]['item_name'] = $data["item_name$i"]; 56 | $results[$i]['item_number'] = $data["item_number$i"]; 57 | $results[$i]['quantity'] = $data["quantity$i"]; 58 | $results[$i]['mc_shipping'] = $data["mc_shipping$i"]; 59 | $results[$i]['mc_handling'] = $data["mc_handling$i"]; 60 | $results[$i]['mc_gross'] = $data["mc_gross$i"]; 61 | $results[$i]['tax'] = $data["tax$i"]; 62 | } 63 | } 64 | return $results; 65 | } 66 | } -------------------------------------------------------------------------------- /models/datasources/payment_gateway/paypal_old.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/CakePHP-Cart/c0503fa7d3c6d8115f0acd60e9c412bf63217335/models/datasources/payment_gateway/paypal_old.php -------------------------------------------------------------------------------- /tests/cases/controllers/cart_products_controller.test.php: -------------------------------------------------------------------------------- 1 | CartProducts = new TestCartProducts(); 15 | $this->CartProducts->constructClasses(); 16 | } 17 | 18 | function testCartProductsControllerInstance() { 19 | $this->assertTrue(is_a($this->CartProducts, 'CartProductsController')); 20 | } 21 | 22 | function endTest() { 23 | unset($this->CartProducts); 24 | } 25 | } 26 | ?> -------------------------------------------------------------------------------- /tests/cases/models/cart_product.test.php: -------------------------------------------------------------------------------- 1 | CartProduct =& ClassRegistry::init('CartProduct'); 12 | } 13 | 14 | function testCartProductInstance() { 15 | $this->assertTrue(is_a($this->CartProduct, 'CartProduct')); 16 | } 17 | 18 | function testCartProductFind() { 19 | $this->CartProduct->recursive = -1; 20 | $results = $this->CartProduct->find('first'); 21 | $this->assertTrue(!empty($results)); 22 | 23 | $expected = array('CartProduct' => array( 24 | 'id' => 1, 25 | 'created' => '2009-07-21 14:05:10', 26 | 'modified' => '2009-07-21 14:05:10', 27 | 'name' => 'Lorem ipsum dolor sit amet', 28 | 'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida,phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam,vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit,feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', 29 | 'taxable' => 1, 30 | 'price' => 1, 31 | 'visible' => 1, 32 | 'active' => 1 33 | )); 34 | $this->assertEqual($results, $expected); 35 | } 36 | } 37 | ?> -------------------------------------------------------------------------------- /tests/fixtures/cart_product_fixture.php: -------------------------------------------------------------------------------- 1 | array('type'=>'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'), 10 | 'created' => array('type'=>'datetime', 'null' => false, 'default' => NULL), 11 | 'modified' => array('type'=>'datetime', 'null' => false, 'default' => NULL), 12 | 'name' => array('type'=>'string', 'null' => false, 'default' => NULL, 'length' => 128), 13 | 'description' => array('type'=>'text', 'null' => false, 'default' => NULL), 14 | 'taxable' => array('type'=>'boolean', 'null' => false, 'default' => NULL), 15 | 'price' => array('type'=>'float', 'null' => false, 'default' => NULL, 'length' => '10,2'), 16 | 'visible' => array('type'=>'boolean', 'null' => false, 'default' => NULL), 17 | 'active' => array('type'=>'boolean', 'null' => false, 'default' => NULL), 18 | 'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1)) 19 | ); 20 | var $records = array(array( 21 | 'id' => 1, 22 | 'created' => '2009-07-21 14:05:10', 23 | 'modified' => '2009-07-21 14:05:10', 24 | 'name' => 'Lorem ipsum dolor sit amet', 25 | 'description' => 'Lorem ipsum dolor sit amet, aliquet feugiat. Convallis morbi fringilla gravida,phasellus feugiat dapibus velit nunc, pulvinar eget sollicitudin venenatis cum nullam,vivamus ut a sed, mollitia lectus. Nulla vestibulum massa neque ut et, id hendrerit sit,feugiat in taciti enim proin nibh, tempor dignissim, rhoncus duis vestibulum nunc mattis convallis.', 26 | 'taxable' => 1, 27 | 'price' => 1, 28 | 'visible' => 1, 29 | 'active' => 1 30 | )); 31 | } 32 | ?> -------------------------------------------------------------------------------- /vendors/shells/cart.php: -------------------------------------------------------------------------------- 1 | init($session); 3 | if (!isset($currency)) $currency = 'USD'; 4 | ?> 5 |
6 |
hasItems()):?> style="display: none"> 7 | hasItems()): ?> 8 |

link('Shopping Cart', array('controller' => 'products', 'action' => 'view_cart')); ?>

9 | Item(s): getValue('quantity'); ?> 10 | currency($cart->getValue('subtotal'), $currency); ?> 11 | 12 | Loading Cart... 13 | 14 |
15 | -------------------------------------------------------------------------------- /views/helpers/button.php: -------------------------------------------------------------------------------- 1 | load($settings); 18 | parent::__construct(); 19 | } 20 | 21 | function load($settings) { 22 | if (empty($settings) || !empty($this->settings)) 23 | return false; 24 | App::Import('ConnectionManager'); 25 | $ds = ConnectionManager::getDataSource($settings); 26 | $this->config = $ds->config; 27 | $config = explode('.', $this->config['driver']); 28 | $config[1] = Inflector::underscore($config[1]); 29 | Configure::load(implode('.', $config)); 30 | $this->settings = Configure::read($this->config['driver']); 31 | $this->fieldMap = $this->settings['fieldmap']; 32 | if (!empty($this->config['testing']) && $this->config['testing']) { 33 | $this->settings = array_merge($this->settings['defaults'], $this->settings['testing']); 34 | } else { 35 | $this->settings = $this->settings['defaults']; 36 | } 37 | } 38 | 39 | /** 40 | * An array containing the names of helpers this controller uses. The array elements should 41 | * not contain the "Helper" part of the classname. 42 | * 43 | * @var mixed A single name as a string or a list of names as an array. 44 | * @access protected 45 | */ 46 | var $helpers = array('Html', 'Form'); 47 | 48 | /** 49 | * function button will create a complete form button to Pay Now, Donate, Add to Cart, or Subscribe using the paypal service. 50 | * Configuration for the button is in /config/paypal_ip_config.php 51 | * 52 | * for this to work the option 'item_name' and 'amount' must be set in the array options or default config options. 53 | * 54 | * Example: 55 | * $paypal->button('Pay Now', array('amount' => '12.00', 'item_name' => 'test item')); 56 | * $paypal->button('Subscribe', array('type' => 'subscribe', 'amount' => '60.00', 'term' => 'month', 'period' => '2')); 57 | * $paypal->button('Donate', array('type' => 'donate', 'amount' => '60.00')); 58 | * $paypal->button('Add To Cart', array('type' => 'addtocart', 'amount' => '15.00')); 59 | * $paypal->button('View Cart', array('type' => 'viewcart')); 60 | * $paypal->button('Unsubscribe', array('type' => 'unsubscribe')); 61 | * $paypal->button('Checkout', array( 62 | * 'type' => 'cart', 63 | * 'items' => array( 64 | * array('item_name' => 'Item 1', 'amount' => '120', 'quantity' => 2, 'item_number' => '1234'), 65 | * array('item_name' => 'Item 2', 'amount' => '50'), 66 | * array('item_name' => 'Item 3', 'amount' => '80', 'quantity' => 3), 67 | * ) 68 | * )); 69 | * Test Example: 70 | * $paypal->button('Pay Now', array('test' => true, 'amount' => '12.00', 'item_name' => 'test item')); 71 | * 72 | * @access public 73 | * @param String $title takes the title of the paypal button (default "Pay Now" or "Subscribe" depending on option['type']) 74 | * @param Array $options takes an options array defaults to (configuration in /config/paypal_ipn_config.php) 75 | * 76 | * helper_options: 77 | * test: true|false switches default settings in /config/paypal_ipn_config.php between settings and testSettings 78 | * type: 'paynow', 'addtocart', 'donate', 'unsubscribe', 'cart', or 'subscribe' (default 'paynow') 79 | * 80 | * You may pass in api name value pairs to be passed directly to the paypal form link. Refer to paypal.com for a complete list. 81 | * some paypal API examples: 82 | * amount: float value 83 | * notify_url: string url 84 | * item_name: string name of product. 85 | * etc... 86 | */ 87 | function button($title = null, $options = array()){ 88 | if(is_array($title)){ 89 | $options = $title; 90 | $title = isset($options['label']) ? $options['label'] : null; 91 | } 92 | $options = array_merge($this->settings, $options); 93 | $options['type'] = (isset($options['type'])) ? $options['type'] : "paynow"; 94 | 95 | switch($options['type']){ 96 | case 'subscribe': //Subscribe 97 | $options['cmd'] = '_xclick-subscriptions'; 98 | $default_title = 'Subscribe'; 99 | $options['no_note'] = 1; 100 | $options['no_shipping'] = 1; 101 | $options['src'] = 1; 102 | $options['sra'] = 1; 103 | $options = $this->__subscriptionOptions($options); 104 | break; 105 | case 'addtocart': //Add To Cart 106 | $options['cmd'] = '_cart'; 107 | $options['add'] = '1'; 108 | $default_title = 'Add To Cart'; 109 | break; 110 | case 'viewcart': //View Cart 111 | $options['cmd'] = '_cart'; 112 | $options['display'] = '1'; 113 | $default_title = 'View Cart'; 114 | break; 115 | case 'donate': //Doante 116 | $options['cmd'] = '_donations'; 117 | $default_title = 'Donate'; 118 | break; 119 | case 'unsubscribe': //Unsubscribe 120 | $options['cmd'] = '_subscr-find'; 121 | $options['alias'] = $options['business']; 122 | $default_title = 'Unsubscribe'; 123 | break; 124 | case 'cart': //upload cart 125 | $options['cmd'] = '_cart'; 126 | $options['upload'] = 1; 127 | $default_title = 'Checkout'; 128 | $options = $this->__uploadCartOptions($options); 129 | break; 130 | default: //Pay Now 131 | $options['cmd'] = '_xclick'; 132 | $default_title = 'Pay Now'; 133 | break; 134 | } 135 | 136 | $title = (empty($title)) ? $default_title : $title; 137 | $retval = "
"; 138 | unset($options['server']); 139 | $retval .= "
"; 140 | foreach($options as $name => $value){ 141 | $retval .= $this->__hiddenNameValue($name, $value); 142 | } 143 | $retval .= "
"; 144 | $retval .= $this->__submitButton($title); 145 | 146 | return $retval; 147 | } 148 | 149 | /** 150 | * __hiddenNameValue constructs the name value pair in a hidden input html tag 151 | * @access private 152 | * @param String name is the name of the hidden html element. 153 | * @param String value is the value of the hidden html element. 154 | * @access private 155 | * @return Html form button and close form 156 | */ 157 | function __hiddenNameValue($name, $value){ 158 | return ""; 159 | } 160 | 161 | /** 162 | * __submitButton constructs the submit button from the provided text 163 | * @param String text | text is the label of the submit button. Can use plain text or image url. 164 | * @access private 165 | * @return Html form button and close form 166 | */ 167 | function __submitButton($text){ 168 | return $this->Form->end(array('label' => $text)); 169 | } 170 | 171 | /** 172 | * __subscriptionOptions conversts human readable subscription terms 173 | * into paypal terms if need be 174 | * @access private 175 | * @param array options | human readable options into paypal API options 176 | * INT period //paypal api period of term, 2, 3, 1 177 | * String term //paypal API term //month, year, day, week 178 | * Float amount //paypal API amount to charge for perioud of term. 179 | * @return array options 180 | */ 181 | function __subscriptionOptions($options = array()){ 182 | //Period... every 1, 2, 3, etc.. Term 183 | if(isset($options['period'])){ 184 | $options['p3'] = $options['period']; 185 | unset($options['period']); 186 | } 187 | //Mount billed 188 | if(isset($options['amount'])){ 189 | $options['a3'] = $options['amount']; 190 | unset($options['amount']); 191 | } 192 | //Terms, Month(s), Day(s), Week(s), Year(s) 193 | if(isset($options['term'])){ 194 | switch($options['term']){ 195 | case 'month': $options['t3'] = 'M'; break; 196 | case 'year': $options['t3'] = 'Y'; break; 197 | case 'day': $options['t3'] = 'D'; break; 198 | case 'week': $options['t3'] = 'W'; break; 199 | default: $options['t3'] = $options['term']; 200 | } 201 | unset($options['term']); 202 | } 203 | 204 | return $options; 205 | } 206 | 207 | /** 208 | * __uploadCartOptions converts an array of items into paypal friendly name/value pairs 209 | * @access private 210 | * @param array of options that will be returned with proper paypal friendly name/value pairs for items 211 | * @return array options 212 | */ 213 | function __uploadCartOptions($options = array()){ 214 | if(isset($options['items']) && is_array($options['items'])){ 215 | $count = 1; 216 | foreach($options['items'] as $item){ 217 | foreach($item as $key => $value){ 218 | $options[$key.'_'.$count] = $value; 219 | } 220 | $count++; 221 | } 222 | unset($options['items']); 223 | } 224 | return $options; 225 | } 226 | } -------------------------------------------------------------------------------- /views/helpers/cart.php: -------------------------------------------------------------------------------- 1 | session = $session; 7 | } 8 | 9 | function hasItems() { 10 | $returnValue = false; 11 | 12 | if ($this->calculateQuantity() > 0) { 13 | $returnValue = true; 14 | } 15 | 16 | return $returnValue; 17 | } 18 | 19 | function getValue($key) { 20 | $returnValue = 0; 21 | 22 | /* App::import('Component', 'Session'); 23 | $this->session = new SessionComponent();*/ 24 | 25 | if ($key == 'quantity') { 26 | $returnValue = $this->calculateQuantity(); 27 | } else { 28 | if ($this->session->check('Order.Totals.' . $key)) { 29 | $returnValue = $this->session->read('Order.Totals.' . $key); 30 | } 31 | } 32 | 33 | return $returnValue; 34 | } 35 | 36 | function calculateQuantity() { 37 | $quantity = 0; 38 | 39 | if ($this->session->check('Order')) { 40 | foreach ($this->session->read('Order.LineItem') as $item) { 41 | $quantity += $item['Totals']['quantity']; 42 | } 43 | } 44 | 45 | return $quantity; 46 | } 47 | } 48 | ?> --------------------------------------------------------------------------------