diff options
-rw-r--r-- | classes/Mail.php | 3 | ||||
-rw-r--r-- | classes/Model.php | 36 | ||||
-rw-r--r-- | classes/Offer.php | 41 | ||||
-rw-r--r-- | classes/Payment.php | 91 | ||||
-rw-r--r-- | conf.php | 6 | ||||
-rw-r--r-- | include/offers-view.php | 40 | ||||
-rw-r--r-- | include/offers.php | 41 |
7 files changed, 148 insertions, 110 deletions
diff --git a/classes/Mail.php b/classes/Mail.php index dcc5cf5..06c2eb6 100644 --- a/classes/Mail.php +++ b/classes/Mail.php @@ -28,5 +28,6 @@ class Mail extends Model { /** {@inheritDoc} */ public $table = 'mail', - $fillable_columns = ['contactId', 'offerId', 'subject']; + $fillable_columns = ['contactId', 'offerId', 'subject'], + $timestamps = ['date']; } diff --git a/classes/Model.php b/classes/Model.php index 6ee7821..3b1c49e 100644 --- a/classes/Model.php +++ b/classes/Model.php @@ -45,15 +45,21 @@ class ModelSetFailedException extends Exception { abstract class Model { /** * @var string $table The database table + * @var string $primary_key The table's primary key * @var string[] $protected_columns Columns that cannot be edited * @var string[] $fillable_columns Columns that can be edited - * @var string $primary_key The table's primary key + * @var string[] $timestamps Columns that are TIMESTAMPs (special treatment in accessor and mutator) + * @var string[] $dates Columns that are DATEs (special treatment in accessor and mutator) + * @var string[] $booleans Columns that are BOOLEANs (special treatment in accessor and mutator) */ public $table = '', + $primary_key = 'id', $protected_columns = ['id'], $fillable_columns = [], - $primary_key = 'id'; + $timestamps = [], + $dates = [], + $booleans = []; /** * @var PDO $pdo A PDO instance for database communication @@ -91,7 +97,7 @@ abstract class Model { */ public function __set($key, $value) { if (!in_array($key, $this->fillable_columns)) { - throw new ModelIllegalAccessException("Column $key cannot be edited."); + throw new ModelIllegalAccessException("Column `{$this->table()}`.`$key` cannot be edited."); } if ($this->data[$key] == $value) { return; @@ -102,7 +108,7 @@ abstract class Model { $this->data[$this->primary_key] ]); if ($stmt->rowCount() != 1) { - throw new ModelEditFailedException(); + throw new ModelEditFailedException("Failed to update `{$this->table()}`.`$key` to '$value'."); } $this->data[$key] = $value; } @@ -124,10 +130,18 @@ abstract class Model { * @param string $key The column * @param string $value The value * - * @return mixed The modified value (default: $value) + * @return mixed The modified value */ protected function accessor($key, $value) { - return $value; + if (is_null($value)) { + return null; + } elseif (in_array($key, $this->booleans)) { + return (bool) $value; + } elseif (in_array($key, $this->dates) || in_array($key, $this->timestamps)) { + return strtotime($value); + } else { + return $value; + } } /** @@ -136,10 +150,16 @@ abstract class Model { * @param string $key The column * @param mixed $value The value * - * @return string The modified value (default: $value casted to string) + * @return string The modified value */ protected function mutator($key, $value) { - return (string) $value; + if (in_array($key, $this->dates) && is_int($value)) { + return date('Y-m-d', $value); + } elseif (in_array($key, $this->timestamps) && is_int($value)) { + return date('Y-m-d H:i:s', $value); + } else { + return (string) $value; + } } /** diff --git a/classes/Offer.php b/classes/Offer.php index 2a48604..202c0e8 100644 --- a/classes/Offer.php +++ b/classes/Offer.php @@ -28,44 +28,9 @@ class Offer extends Model{ /** {@inheritDoc} */ public $table = 'offer', - $fillable_columns = ['contactId', 'start_date', 'end_date', 'invoice_date', 'accepted', 'invoice_fileId', 'payment_key']; - - /** - * {@inheritDoc} - * @param $key {@inheritDoc} - * @param $value {@inheritDoc} - */ - protected function accessor($key, $value) { - switch ($key) { - case 'start_date': - case 'end_date': - case 'invoice_date': - return strtotime($value); - break; - case 'accepted': - return (bool) $value; - case 'invoice_fileId': - return $value == null ? null : (int) $value; - default: - return parent::accessor($key, $value); - } - } - - /** - * {@inheritDoc} - * @param $key {@inheritDoc} - * @param $value {@inheritDoc} - */ - protected function mutator($key, $value) { - switch ($key) { - case 'start_date': - case 'end_date': - case 'invoice_date': - return is_string($value) ? $value : date('Y-m-d', $value); - default: - return parent::mutator($key, $value); - } - } + $fillable_columns = ['contactId', 'start_date', 'end_date', 'invoice_date', 'accepted', 'invoice_fileId', 'payment_key'], + $dates = ['start_date', 'end_date', 'invoice_date'], + $booleans = ['accepted']; /** * A random max-63-char string that can be used as payment_key diff --git a/classes/Payment.php b/classes/Payment.php index 64b65ae..bab55be 100644 --- a/classes/Payment.php +++ b/classes/Payment.php @@ -28,7 +28,8 @@ class Payment extends Model { /** {@inheritDoc} */ public $table = 'payment', - $fillable_columns = ['offerId', 'date', 'braintree_id']; + $fillable_columns = ['offerId', 'date', 'braintree_id', 'braintree_status'], + $timestamps = ['date']; /** * Get the offer that this payment is linked to @@ -40,36 +41,6 @@ class Payment extends Model { } /** - * {@inheritDoc} - * @param $key {@inheritDoc} - * @param $value {@inheritDoc} - */ - protected function accessor($key, $value) { - switch ($key) { - case 'date': - return strtotime($value); - break; - default: - return parent::accessor($key, $value); - } - } - - /** - * {@inheritDoc} - * @param $key {@inheritDoc} - * @param $value {@inheritDoc} - */ - protected function mutator($key, $value) { - switch ($key) { - case 'date': - return is_string($value) ? $value : date('Y-m-d H:i:s', $value); - break; - default: - return parent::mutator($key, $value); - } - } - - /** * Make a mailer to send about this payment * * @return Mailer The mailer @@ -92,4 +63,62 @@ class Payment extends Model { public function send() { return $this->mailer()->send(); } + + /** + * Refresh the Braintree status + * + * @return string|null The new status, or null if this Payment does not have a Braintree id + */ + public function refreshBraintreeStatus() { + if (is_null($this->braintree_id)) { + return null; + } + + $trans = Braintree_Transaction::find($this->braintree_id); + $this->braintree_status = $trans->status; + } + + /** + * Is the Braintree transaction finished? + * + * @return bool|null True iff the Braintree transaction is finished, or null if this Payment does not have a Braintree id + */ + public function isBraintreeFinished() { + if (is_null($this->braintree_id)) { + return null; + } + + return in_array($this->braintree_status, [ + Braintree_Transaction::AUTHORIZATION_EXPIRED, + Braintree_Transaction::GATEWAY_REJECTED, + Braintree_Transaction::FAILED, + Braintree_Transaction::PROCESSOR_DECLINED, + Braintree_Transaction::SETTLED, + Braintree_Transaction::VOIDED, + Braintree_Transaction::SETTLEMENT_DECLINED, + ]); + } + + /** + * Is the Braintree transaction successful? + * + * A transaction can be successful if it is not yet finished. + * + * @return bool|null True iff the Braintree transaction is successful, or null if this Payment does not have a Braintree id + */ + public function isBraintreeSuccessful() { + if (is_null($this->braintree_id)) { + return null; + } + + return in_array($this->braintree_status, [ + Braintree_Transaction::AUTHORIZING, + Braintree_Transaction::AUTHORIZED, + Braintree_Transaction::SETTLED, + Braintree_Transaction::SETTLING, + Braintree_Transaction::SUBMITTED_FOR_SETTLEMENT, + Braintree_Transaction::SETTLEMENT_PENDING, + Braintree_Transaction::SETTLEMENT_CONFIRMED, + ]); + } } @@ -33,6 +33,8 @@ ini_set('display_errors', 0); set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path()); +require_once('conf.private.php'); + spl_autoload_register(function ($pClass) { $path = dirname(__FILE__) . "/classes/$pClass.php"; if (file_exists($path)) { @@ -40,11 +42,11 @@ spl_autoload_register(function ($pClass) { } }); -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); $_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $_pdo->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL); + $_pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); } catch (PDOException $e) { die("Down until PDO error fixed."); } diff --git a/include/offers-view.php b/include/offers-view.php index ec579b6..1455e43 100644 --- a/include/offers-view.php +++ b/include/offers-view.php @@ -57,7 +57,7 @@ $_offer = new Offer($_pdo, $_id); </tr>"; } if (count($assignments) == 0) { - echo "<tr><td colspan='6'>There are no assignments in the database. Why not start with creating one, below?</td></tr>"; + echo "<tr><td colspan='5'>There are no assignments in the database. Why not start with creating one, below?</td></tr>"; } ?> </tbody> @@ -99,7 +99,7 @@ $_offer = new Offer($_pdo, $_id); </tr>"; } if (count($discounts) == 0) { - echo "<tr><td colspan='6'>There are no discounts in the database. Why not start with creating one, below?</td></tr>"; + echo "<tr><td colspan='4'>There are no discounts in the database. Why not start with creating one, below?</td></tr>"; } ?> </tbody> @@ -108,6 +108,42 @@ $_offer = new Offer($_pdo, $_id); </div> </div> <div class="col-md-6"> + <div class="panel panel-default"> + <div class="panel-heading">Payments</div> + <div class="panel-body table-responsive"> + <table class="table table-bordered table-striped"> + <thead> + <tr> + <th>#</th> + <th>Braintree ID</th> + <th>Braintree status</th> + <th>Tools</th> + </tr> + </thead> + <tbody> + <?php + $payment = $_offer->getPayment(); + $payments = [$payment]; + foreach ($payments as $payment) { + echo "<tr> + <td>{$payment->id}</td> + <td>{$payment->braintree_id}</td> + <td>{$payment->braintree_status}</td> + <td class='col-min-width'> + <a title='Refresh' href='?id={$_offer->id}&refresh_payment={$payment->id}' class='btn btn-info btn-circle fa fa-refresh'></a> + </td> + </tr>"; + } + if (count($payments) == 0) { + echo "<tr><td colspan='4'>There are no payments in the database. Why not start with creating one, below?</td></tr>"; + } + ?> + </tbody> + </table> + </div> + </div> +</div> +<div class="col-lg-12"> <div class="panel panel-default" id="panel-timeline"> <div class="panel-heading"> <i class="fa fa-clock-o fa-fw"></i> Timeline diff --git a/include/offers.php b/include/offers.php index e06a4eb..165d88f 100644 --- a/include/offers.php +++ b/include/offers.php @@ -37,7 +37,7 @@ require('./header.php'); // ?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 - // ?send_invoice=<id> Send invoice to contact + // ?refesh_payment=<id> Refresh the Braintree status of a payment // ?delete=<id> Delete the offer with id <id> //------------------------------------------------------------------------------ @@ -48,10 +48,8 @@ require('./header.php'); $offer = new Offer($_pdo, $id); $offer->accepted = !$offer->accepted; $alert = "<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) { - $alert = "<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) { - $alert = "<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>"; + $alert = "<div class='alert alert-warning alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The acceptance status of offer {$id} could not be changed: {$e->getMessage()}.</div>"; } } @@ -66,10 +64,8 @@ require('./header.php'); $offer->payment_key = Offer::getRandomPaymentKey(); } $alert = "<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) { - $alert = "<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) { - $alert = "<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>"; + $alert = "<div class='alert alert-warning alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The payment eligibility of offer {$id} could not be changed: {$e->getMessage()}.</div>"; } } @@ -80,10 +76,8 @@ require('./header.php'); $offer = new Offer($_pdo, $id); $file = $offer->generateInvoice(); $alert = "<div class='alert alert-success alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The invoice for offer #{$offer->id} is generated: <a class='alert-link' href='{$file->getFilenameURI()}' target='_blank'>{$file->filename}</a></div>"; - } catch (PDOException $e) { - $alert = "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The invoice for offer #{$offer->id} could not be generated due to a PDO error.</div>"; } catch (Exception $e) { - $alert = "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The invoice for offer #{$id} could not be generated.</div>"; + $alert = "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The invoice for offer #{$id} could not be generated: {$e->getMessage()}.</div>"; } } @@ -98,27 +92,20 @@ require('./header.php'); } else { $alert = "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The invoice for offer #{$id} could not be trashed.</div>"; } - } catch (PDOException $e) { - $alert = "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The invoice for offer #{$offer->id} could not be trashed due to a PDO error.</div>"; } catch (Exception $e) { - $alert = "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The invoice for offer #{$id} could not be trashed.</div>"; + $alert = "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The invoice for offer #{$id} could not be trashed: {$e->getMessage()}.</div>"; } } - // Send invoice - if (isset($_GET['send_invoice'])) { - $id = (int) $_GET['send_invoice']; + // Refresh payment + if (isset($_GET['refresh_payment'])) { + $id = (int) $_GET['refresh_payment']; try { - $offer = new Offer($_pdo, $id); - if ($offer->send()) { - $alert = "<div class='alert alert-success alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The invoice for offer {$id} has been sent to {$offer->getContact()->email}.</div>"; - } else { - $alert = "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The invoice could not be sent due to an unknown error.</div>"; - } - } catch (PDOException $e) { - $alert = "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The invoice could not be sent due to a PDO error.</div>"; + $payment = new Payment($_pdo, $id); + $payment->refreshBraintreeStatus(); + $alert = "<div class='alert alert-success alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The payment's status has been updated to <b>{$payment->braintree_status}</b>.</div>"; } catch (Exception $e) { - $alert = "<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>"; + $alert = "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The payment could not be refreshed due to an exception: {$e->getMessage()}.</div>"; } } @@ -132,10 +119,8 @@ require('./header.php'); } else { $alert = "<div class='alert alert-warning alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The offer #{$offer->id} could not be removed. Perhaps it's already removed?</div>"; } - } catch (PDOException $e) { - $alert = "<div class='alert alert-danger alert-dismissable'><button type='button' class='close fa fa-times' data-dismiss='alert' aria-hidden='true'></button>The offer could not be removed due to a PDO error.</div>"; } catch (Exception $e) { - $alert = "<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>"; + $alert = "<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 deleted: {$e->getMessage()}.</div>"; } } |