diff options
author | Camil Staps | 2016-07-28 09:37:48 +0200 |
---|---|---|
committer | Camil Staps | 2016-07-28 09:47:04 +0200 |
commit | 4f84eb2b09bf51eabdc29b5eeec101e0260b1cb7 (patch) | |
tree | 82722787d4018373720c66933f475bb2b1708c92 | |
parent | Split Calculatable in trait and interface (diff) |
Braintree integration: first version
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | classes/Assignment.php | 8 | ||||
-rw-r--r-- | classes/Constants.php | 2 | ||||
-rw-r--r-- | classes/Discount.php | 8 | ||||
-rw-r--r-- | classes/Offer.php | 21 | ||||
-rw-r--r-- | classes/Payment.php | 2 | ||||
-rw-r--r-- | conf.php | 31 | ||||
-rw-r--r-- | conf.private.example.php | 1 | ||||
-rw-r--r-- | css/businessadmin.css | 4 | ||||
-rw-r--r-- | include/offers-overview.php | 5 | ||||
-rw-r--r-- | include/offers.php | 34 | ||||
-rw-r--r-- | include/pay.php | 24 | ||||
-rw-r--r-- | index.php | 39 | ||||
-rw-r--r-- | install/index.php | 6 | ||||
-rw-r--r-- | install/upgrade.php | 18 |
15 files changed, 142 insertions, 67 deletions
@@ -169,6 +169,10 @@ are listed by name and removal time. This way, you never really lose your file. # Changelog +### 0.5 (Jul 28, 2016) + +0.5 Braintree integration. + ### 0.4 (Jul 26, 2016) 0.4.2 Moved `offer.payment_received` to a separate table `payments`. @@ -177,7 +181,7 @@ are listed by name and removal time. This way, you never really lose your file. ### 0.3 (Jul 20, 2016) -0.3 Discounts +0.3 Discounts. ### 0.2 (Feb 10, 2015) diff --git a/classes/Assignment.php b/classes/Assignment.php index 27efb4a..2ceef94 100644 --- a/classes/Assignment.php +++ b/classes/Assignment.php @@ -24,8 +24,8 @@ /** * An interface to the assignment table in the database */ -class Assignment extends Model { - use Calculatable; +class Assignment extends Model implements Calculatable { + use StandardCalculatable; public $table = 'assignment', @@ -50,11 +50,11 @@ class Assignment extends Model { return $pd->text($this->description); } - protected function calculateSubtotal() { + public function calculateSubtotal() { return $this->hours * $this->price_per_hour; } - protected function calculateVAT() { + public function calculateVAT() { return $this->calculateSubtotal() * $this->VAT_percentage / 100; } } diff --git a/classes/Constants.php b/classes/Constants.php index 3ffadd7..fb2435d 100644 --- a/classes/Constants.php +++ b/classes/Constants.php @@ -80,5 +80,5 @@ class Constants { const password_cost = 10; /** @const version Version of BusinessAdmin. Don't change this yourself! */ - const version = '0.4.2'; + const version = '0.5'; } diff --git a/classes/Discount.php b/classes/Discount.php index 7216615..4084d43 100644 --- a/classes/Discount.php +++ b/classes/Discount.php @@ -24,8 +24,8 @@ /** * An interface to the discount table in the database */ -class Discount extends Model { - use Calculatable; +class Discount extends Model implements Calculatable { + use StandardCalculatable; public $table = 'discount', @@ -50,11 +50,11 @@ class Discount extends Model { return $pd->text($this->description); } - protected function calculateSubtotal() { + public function calculateSubtotal() { return - $this->value; } - protected function calculateVAT() { + public function calculateVAT() { return $this->calculateSubtotal() * $this->VAT_percentage / 100; } } diff --git a/classes/Offer.php b/classes/Offer.php index 815f626..8fa9ba7 100644 --- a/classes/Offer.php +++ b/classes/Offer.php @@ -27,7 +27,7 @@ class Offer extends Model{ public $table = 'offer', - $fillable_columns = ['contactId', 'start_date', 'end_date', 'invoice_date', 'accepted', 'invoice_fileId']; + $fillable_columns = ['contactId', 'start_date', 'end_date', 'invoice_date', 'accepted', 'invoice_fileId', 'payment_key']; protected function accessor($key, $value) { switch ($key) { @@ -57,6 +57,25 @@ class Offer extends Model{ } /** + * A random max-63-char string that can be used as payment_key + * + * @return string The random string + */ + public static function getRandomPaymentKey() { + return preg_replace('/[^\w]+/', '', + base64_encode(openssl_random_pseudo_bytes(45))); + } + + /** + * Get whether the offer is eligible for online payment or not + * + * @return bool True iff it is eligible + */ + public function getPaymentEligibility() { + return $this->payment_key != ''; + } + + /** * Get the contact that this offer is linked to * * @return contact The contact diff --git a/classes/Payment.php b/classes/Payment.php index e60539f..5bc08dc 100644 --- a/classes/Payment.php +++ b/classes/Payment.php @@ -27,7 +27,7 @@ class Payment extends Model { public $table = 'payment', - $fillable_columns = ['offerId', 'date']; + $fillable_columns = ['offerId', 'date', 'braintree_id']; /** * Get the offer that this payment is linked to @@ -31,20 +31,16 @@ session_start(); error_reporting(0); ini_set('display_errors', 0); -/** - * Autoload a class if it isn't loaded yet - * - * This function is automatically called by PHP if a class isn't loaded yet. It shouldn't be used manually. - * - * @param string $pClass The name of the class to load - */ -function __autoload($pClass) { - require_once("classes/$pClass.php"); -} +set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path()); -set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__)); +spl_autoload_register(function ($pClass) { + $path = dirname(__FILE__) . "/classes/$pClass.php"; + if (file_exists($path)) { + require_once($path); + } +}); -require_once('./conf.private.php'); +require_once('conf.private.php'); try { $_pdo = new PDO("mysql:host=".DB_HOST.";port=".DB_PORT.";dbname=".DB_NAME.";charset=utf8", DB_USER, DB_PASS); @@ -52,3 +48,14 @@ try { } catch (PDOException $e) { die("Down until PDO error fixed."); } + +if (BRAINTREE_ENABLED) { + require_once('modules/braintree/lib/Braintree.php'); + + Braintree_Configuration::environment(BRAINTREE_ENVIRONMENT); + Braintree_Configuration::merchantId(BRAINTREE_MERCHANT); + Braintree_Configuration::publicKey(BRAINTREE_KEY_PUBLIC); + Braintree_Configuration::privateKey(BRAINTREE_KEY_PRIVATE); +} + +require_once('classes/Calculatable.php'); // Some definitions that are required diff --git a/conf.private.example.php b/conf.private.example.php index 54f4133..a9275b1 100644 --- a/conf.private.example.php +++ b/conf.private.example.php @@ -8,6 +8,7 @@ define('DB_PORT', '3306'); // Braintree settings define('BRAINTREE_ENABLED', true); +define('BRAINTREE_ENVIRONMENT', 'sandbox'); define('BRAINTREE_MERCHANT', ...); define('BRAINTREE_KEY_PUBLIC', ...); define('BRAINTREE_KEY_PRIVATE', ...); diff --git a/css/businessadmin.css b/css/businessadmin.css index e898740..e2c714a 100644 --- a/css/businessadmin.css +++ b/css/businessadmin.css @@ -111,3 +111,7 @@ td .btn.btn-circle:last-child { display: none; } } + +.payment-panel { + margin-top: 5%; +} diff --git a/include/offers-overview.php b/include/offers-overview.php index 1118793..6d9bbc1 100644 --- a/include/offers-overview.php +++ b/include/offers-overview.php @@ -95,7 +95,10 @@ require_once('./login.php'); </table> </td> <td class='col-min-width'> - <a title='" . ($offer->accepted ? "Accepted" : "Not accepted") . "' href='?toggle_accept={$offer->id}' class='btn " . ($offer->accepted ? "btn-success" : "btn-default") . " btn-circle fa fa-check'></a><a title='View' href='?id={$offer->id}' class='btn btn-primary btn-circle fa fa-arrow-right'></a><a title='Delete' href='?delete={$offer->id}' class='btn btn-danger btn-circle fa fa-times'></a> + <a title='" . ($offer->accepted ? "Accepted" : "Not accepted") . "' href='?toggle_accept={$offer->id}' class='btn " . ($offer->accepted ? "btn-success" : "btn-default") . " btn-circle fa fa-check'></a> + <a title='" . ($offer->getPaymentEligibility() ? "Eligible for online payment" : "Not eligible for online payment") . "' href='?toggle_payment_eligibility={$offer->id}' class='btn " . ($offer->getPaymentEligibility() ? "btn-success" : "btn-default") . " btn-circle fa fa-credit-card'></a> + <a title='View' href='?id={$offer->id}' class='btn btn-primary btn-circle fa fa-arrow-right'></a> + <a title='Delete' href='?delete={$offer->id}' class='btn btn-danger btn-circle fa fa-times'></a> </td> </tr>"; } diff --git a/include/offers.php b/include/offers.php index 2aa150d..8be7530 100644 --- a/include/offers.php +++ b/include/offers.php @@ -32,11 +32,12 @@ require('./header.php'); //------------------------------------------------------------------------------ // Check for GET variables // - // ?id=<id> View information of the offer with id <id> - // ?toggle_accept=<id> Toggle the accepted status of the offer with id <id> - // ?generate_invoice=<id> Generate an invoice for the offer with id <id> - // ?trash_invoice=<id> Trash the invoice file - // ?delete=<id> Delete the offer with id <id> + // ?id=<id> View information of the offer with id <id> + // ?toggle_accept=<id> Toggle the accepted status of the offer with id <id> + // ?toggle_payment_eligibility=<id> Toggle the payment eligibility of the offer with id <id> + // ?generate_invoice=<id> Generate an invoice for the offer with id <id> + // ?trash_invoice=<id> Trash the invoice file + // ?delete=<id> Delete the offer with id <id> //------------------------------------------------------------------------------ // The header of the page @@ -69,7 +70,7 @@ require('./header.php'); try { $offer = new Offer($_pdo, $id); $offer->accepted = !$offer->accepted; - echo "<div class='alert alert-success alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The status offer #{$offer->id} has been set to <i>".($offer->accepted ? "accepted" : "unaccepted")."</i>.</div>"; + echo "<div class='alert alert-success alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The status of offer #{$offer->id} has been set to <i>".($offer->accepted ? "accepted" : "unaccepted")."</i>.</div>"; } catch (PDOException $e) { echo "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The status of the offer could not be changed due to a PDO error.</div>"; } catch (Exception $e) { @@ -79,6 +80,27 @@ require('./header.php'); echo "</div>"; } + // Toggle offer payment eligibility + if (isset($_GET['toggle_payment_eligibility'])) { + echo "<div class='col-lg-12'>"; + $id = (int) $_GET['toggle_payment_eligibility']; + try { + $offer = new Offer($_pdo, $id); + if ($offer->getPaymentEligibility()) { + $offer->payment_key = null; + } else { + $offer->payment_key = Offer::getRandomPaymentKey(); + } + echo "<div class='alert alert-success alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The offer #{$offer->id} is now <i>".($offer->getPaymentEligibility() ? "eligible" : "ineligible")."</i> for online payment.</div>"; + } catch (PDOException $e) { + echo "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The online payment eligibility could not be changed due to a PDO error.</div>"; + } catch (Exception $e) { + echo "<div class='alert alert-warning alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The offer with id {$id} could not be found.</div>"; + } + + echo "</div>"; + } + // Generate invoice if (isset($_GET['generate_invoice'])) { echo "<div class='col-lg-12'>"; diff --git a/include/pay.php b/include/pay.php index 596251c..37dd1cc 100644 --- a/include/pay.php +++ b/include/pay.php @@ -42,10 +42,10 @@ require('./header.php'); $notFound = true; } } - if ($notFound || $offerKey != $_offer->key) { - ?> - <div class='form-group alert alert-danger'>The invoice could not be found.</div> - <?php + if ($notFound || $offerKey != $_offer->payment_key) { + echo "<div class='form-group alert alert-danger'>The invoice could not be found.</div>"; + } elseif ($_offer->payment_key == '') { + echo "<div class='form-group alert alert-danger'>This invoice is not eligible for online payment.</div>"; } elseif (isset($_POST['payment_method_nonce'])) { $nonce = $_POST['payment_method_nonce']; $trans = Braintree_Transaction::sale([ @@ -91,23 +91,21 @@ require('./header.php'); foreach ($_offer->getItems() as $item) { $i++; echo '<tr>'; - echo "<td class='col-max-width'> + echo "<td> <b><a href='#collapse-item-$i' data-toggle='collapse'>{$item->title}</a></b> <div class='collapse' id='collapse-item-$i'>{$item->getHTMLDescription()}</div> </td>"; - echo "<td class='col-min-width'>".Constants::invoice_valuta."{$item->calculate(Calculatable::SUBTOTAL)}</td>"; - echo "<td class='col-min-width'>{$item->VAT_percentage}%</td>"; - echo "<td class='col-min-width'>".Constants::invoice_valuta."{$item->calculate(Calculatable::TOTAL)}</td>"; + echo "<td>".Constants::invoice_valuta."{$item->calculate(Calculatable::SUBTOTAL)}</td>"; + echo "<td>{$item->VAT_percentage}%</td>"; + echo "<td>".Constants::invoice_valuta."{$item->calculate(Calculatable::TOTAL)}</td>"; echo '</tr>'; } ?> <tr style="border-top:2px solid #666;"> - <th colspan="3" class="text-right">Subtotal</th> + <th class="text-right">Totals</th> <td><?=$subtotal?></td> - </tr> - <tr> - <th colspan="3" class="text-right">Total</th> - <td><?=$total?></td> + <td></td> + <td><b><?=$total?></b></td> </tr> </table> </div> @@ -36,26 +36,27 @@ $_request = str_replace(Constants::url_internal, '', $_request); // This is the REQUEST_URI switch // The default shows a 404 page $pages = array( - '/' => './include/home.php', - '/clients' => './include/clients.php', - '/clients/new' => './include/clients-new.php', - '/clients/edit' => './include/clients-edit.php', - '/contacts' => './include/contacts.php', - '/contacts/new' => './include/contacts-new.php', - '/contacts/edit' => './include/contacts-edit.php', - '/offers' => './include/offers.php', - '/offers/new' => './include/offers-new.php', - '/offers/edit' => './include/offers-edit.php', - '/assignments' => './include/assignments.php', - '/assignments/new' => './include/assignments-new.php', + '/' => './include/home.php', + '/clients' => './include/clients.php', + '/clients/new' => './include/clients-new.php', + '/clients/edit' => './include/clients-edit.php', + '/contacts' => './include/contacts.php', + '/contacts/new' => './include/contacts-new.php', + '/contacts/edit' => './include/contacts-edit.php', + '/offers' => './include/offers.php', + '/offers/new' => './include/offers-new.php', + '/offers/edit' => './include/offers-edit.php', + '/assignments' => './include/assignments.php', + '/assignments/new' => './include/assignments-new.php', '/assignments/edit' => './include/assignments-edit.php', - '/discounts' => './include/discounts.php', - '/discounts/new' => './include/discounts-new.php', - '/discounts/edit' => './include/discounts-edit.php', - '/about' => './include/about.php', - '/settings' => './include/settings.php', - '/users/new' => './include/users-new.php', - '/ajax/collapse' => './include/ajax-collapse.php' + '/discounts' => './include/discounts.php', + '/discounts/new' => './include/discounts-new.php', + '/discounts/edit' => './include/discounts-edit.php', + '/about' => './include/about.php', + '/settings' => './include/settings.php', + '/users/new' => './include/users-new.php', + '/ajax/collapse' => './include/ajax-collapse.php', + '/pay' => './include/pay.php' ); $_page = null; diff --git a/install/index.php b/install/index.php index dbea375..d40959f 100644 --- a/install/index.php +++ b/install/index.php @@ -81,7 +81,8 @@ if (isset($_GET['create_tables'])) { `end_date` date NOT NULL, `invoice_date` date NOT NULL, `accepted` tinyint(1) unsigned NOT NULL DEFAULT '0', - `invoice_fileId` smallint(5) unsigned DEFAULT NULL, + `invoice_fileId` smallint(5) unsigned DEFAULT NULL, + `payment_key` varchar(63) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `invoice_fileId` (`invoice_fileId`), KEY `contactId` (`contactId`), @@ -93,6 +94,8 @@ if (isset($_GET['create_tables'])) { `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, `offerId` smallint(5) unsigned NOT NULL, `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `braintree_id` varchar(36) DEFAULT NULL, + `braintree_status` varchar(63) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;"); @@ -116,6 +119,7 @@ if (isset($_GET['create_tables'])) { ADD CONSTRAINT `offer_ibfk_1` FOREIGN KEY (`invoice_fileId`) REFERENCES `".Constants::db_prefix."file` (`id`), ADD CONSTRAINT `offer_ibfk_2` FOREIGN KEY (`contactId`) REFERENCES `".Constants::db_prefix."contact` (`id`)"); + $_pdo->query("CREATE UNIQUE INDEX `payment_uniq_1` ON `".Constants::db_prefix."payment` (`offerId`);"); $_pdo->query("ALTER TABLE `payment` ADD CONSTRAINT `payment_ibfk_1` FOREIGN KEY (`offerId`) REFERENCES `offer` (`id`);"); diff --git a/install/upgrade.php b/install/upgrade.php index aea97ce..8ead230 100644 --- a/install/upgrade.php +++ b/install/upgrade.php @@ -97,9 +97,8 @@ if (isset($_GET['upgrade'])) { $offers = $_pdo->query("SELECT `id`,`payment_received` FROM `".Constants::db_prefix."offer` WHERE `payment_received` IS NOT NULL"); $offers = $offers->fetchAll(PDO::FETCH_ASSOC); foreach ($offers as $offer) { - $received = $offer['payment_received']; - $offer = new Offer($_pdo, $offer['id']); - $offer->createPayment($received); + $stmt = $_pdo->prepare("INSERT IGNORE INTO `".Constants::db_prefix."payment` (`offerId`,`date`) VALUES (?,?)"); + $stmt->execute($offer['id'], $offer['payment_received']); } $_pdo->query("ALTER TABLE `".Constants::db_prefix."offer` DROP `payment_received`;"); @@ -108,6 +107,19 @@ if (isset($_GET['upgrade'])) { } } + if (lower_version($_GET['upgrade'], '0.5')) { + try { + $_pdo->query("ALTER TABLE `".Constants::db_prefix."offer` + ADD `payment_key` VARCHAR(63) DEFAULT NULL;"); + $_pdo->query("ALTER TABLE `".Constants::db_prefix."payment` + ADD `braintree_id` VARCHAR(36) DEFAULT NULL, + ADD `braintree_status` VARCHAR (63) NULL DEFAULT NULL;"); + $_pdo->query("CREATE UNIQUE INDEX `payment_uniq_1` ON `".Constants::db_prefix."payment` (`offerId`);"); + } catch (PDOException $e) { + echo "Altering the database structure failed with a PDOException ({$e->getCode()}): {$e->getMessage()}<br/>" . $e->getTraceAsString(); + } + } + echo "<br/>All done."; } ?> |