aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCamil Staps2016-07-28 09:37:48 +0200
committerCamil Staps2016-07-28 09:47:04 +0200
commit4f84eb2b09bf51eabdc29b5eeec101e0260b1cb7 (patch)
tree82722787d4018373720c66933f475bb2b1708c92
parentSplit Calculatable in trait and interface (diff)
Braintree integration: first version
-rw-r--r--README.md6
-rw-r--r--classes/Assignment.php8
-rw-r--r--classes/Constants.php2
-rw-r--r--classes/Discount.php8
-rw-r--r--classes/Offer.php21
-rw-r--r--classes/Payment.php2
-rw-r--r--conf.php31
-rw-r--r--conf.private.example.php1
-rw-r--r--css/businessadmin.css4
-rw-r--r--include/offers-overview.php5
-rw-r--r--include/offers.php34
-rw-r--r--include/pay.php24
-rw-r--r--index.php39
-rw-r--r--install/index.php6
-rw-r--r--install/upgrade.php18
15 files changed, 142 insertions, 67 deletions
diff --git a/README.md b/README.md
index c57267f..fd06d18 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/conf.php b/conf.php
index e8ed000..d195a07 100644
--- a/conf.php
+++ b/conf.php
@@ -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>
diff --git a/index.php b/index.php
index a7242b6..b0cdfc5 100644
--- a/index.php
+++ b/index.php
@@ -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.";
}
?>