diff --git a/app/plugins/bookmarks/models/bookmarks.php b/app/plugins/bookmarks/models/bookmarks.php
index 726e4a6..39b77f3 100644
--- a/app/plugins/bookmarks/models/bookmarks.php
+++ b/app/plugins/bookmarks/models/bookmarks.php
@@ -12,9 +12,8 @@
*/
namespace TheTempusProject\Models;
-use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Bedrock\Functions\Check;
-use TheTempusProject\Canary\Canary as Debug;
+use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Filters;
diff --git a/app/plugins/bookmarks/models/bookmarkviews.php b/app/plugins/bookmarks/models/bookmarkviews.php
index e25d50f..624b336 100644
--- a/app/plugins/bookmarks/models/bookmarkviews.php
+++ b/app/plugins/bookmarks/models/bookmarkviews.php
@@ -12,7 +12,6 @@
*/
namespace TheTempusProject\Models;
-use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel;
diff --git a/app/plugins/bookmarks/models/folders.php b/app/plugins/bookmarks/models/folders.php
index 218f6c2..62a6367 100644
--- a/app/plugins/bookmarks/models/folders.php
+++ b/app/plugins/bookmarks/models/folders.php
@@ -12,9 +12,8 @@
*/
namespace TheTempusProject\Models;
-use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Bedrock\Functions\Check;
-use TheTempusProject\Canary\Canary as Debug;
+use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Houdini\Classes\Filters;
diff --git a/app/plugins/donate/controllers/donate.php b/app/plugins/donate/controllers/donate.php
new file mode 100644
index 0000000..e6b2267
--- /dev/null
+++ b/app/plugins/donate/controllers/donate.php
@@ -0,0 +1,84 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Controllers;
+
+use TheTempusProject\Hermes\Functions\Redirect;
+use TheTempusProject\Bedrock\Functions\Check;
+use TheTempusProject\Bedrock\Functions\Input;
+use TheTempusProject\Bedrock\Functions\Session;
+use TheTempusProject\Houdini\Classes\Issues;
+use TheTempusProject\Houdini\Classes\Views;
+use TheTempusProject\Classes\Controller;
+use TheTempusProject\Classes\Forms;
+use TheTempusProject\Models\Bugreport as BugreportModel;
+use TheTempusProject\TheTempusProject as App;
+use TheTempusProject\Bedrock\Classes\Config;
+use TheTempusProject\Hermes\Functions\Route as Routes;
+
+class Donate extends Controller {
+ protected static $bugreport;
+
+ public function index() {
+ self::$title = 'Donate - {SITENAME}';
+ self::$pageDescription = 'Donations help support our cause and our efforts. Please use this page to select an amount and give today.';
+
+ if ( !Input::exists() ) {
+ return Views::view( 'donate.donate' );
+ }
+
+ if ( !Forms::check( 'donate' ) ) {
+ Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] );
+ return Views::view( 'donate.donate' );
+ }
+
+ try {
+ $api_key = Config::getValue( 'donations/stripeSecret' );
+ $stripe = new \Stripe\StripeClient( $api_key );
+ $session = $stripe->checkout->sessions->create([
+ 'payment_method_types' => ['card'],
+ 'line_items' => [[
+ 'price_data' => [
+ 'currency' => 'usd',
+ 'product_data' => [
+ 'name' => 'Donation',
+ ],
+ 'unit_amount' => Input::post("amount"), // Amount in cents ($10)
+ ],
+ 'quantity' => 1,
+ ]],
+ 'mode' => 'payment',
+ 'metadata' => [
+ 'note' => Input::post("comment"),
+ ],
+ 'success_url' => Routes::getAddress() . 'donate/success?session_id={CHECKOUT_SESSION_ID}',
+ 'cancel_url' => Routes::getAddress() . 'donate/continue',
+ ]);
+
+ // Redirect to the Checkout session
+ header('Location: ' . $session->url);
+ exit;
+ } catch (\Stripe\Exception\ApiErrorException $e) {
+ echo "Error creating Checkout session: " . $e->getMessage();
+ }
+ }
+
+ public function success() {
+ self::$title = 'Thank you!';
+ Views::view( 'donate.success' );
+ }
+
+ public function continue() {
+ self::$title = 'Thank you!';
+ Views::view( 'donate.fail' );
+ }
+}
diff --git a/app/plugins/donate/forms.php b/app/plugins/donate/forms.php
new file mode 100644
index 0000000..7f9e1a9
--- /dev/null
+++ b/app/plugins/donate/forms.php
@@ -0,0 +1,43 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Plugins\Donate;
+
+use TheTempusProject\Bedrock\Functions\Input;
+use TheTempusProject\Classes\Forms;
+
+class DonateForms extends Forms {
+ /**
+ * Adds these functions to the form list.
+ */
+ public function __construct() {
+ self::addHandler( 'donate', __CLASS__, 'donate' );
+ }
+
+ /**
+ * Validates the bug report form.
+ *
+ * @return {bool}
+ */
+ public static function donate() {
+ if ( !Input::exists( 'amount' ) ) {
+ self::addUserError( 'You must specify an amount' );
+ return false;
+ }
+ if ( !self::token() ) {
+ return false;
+ }
+ return true;
+ }
+}
+
+new DonateForms;
diff --git a/app/plugins/donate/models/donations.php b/app/plugins/donate/models/donations.php
new file mode 100644
index 0000000..391ebdf
--- /dev/null
+++ b/app/plugins/donate/models/donations.php
@@ -0,0 +1,35 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Models;
+
+use TheTempusProject\Classes\DatabaseModel;
+use TheTempusProject\Plugins\Bugreport as Plugin;
+
+class Donations extends DatabaseModel {
+ public $tableName = 'donations';
+ public $databaseMatrix = [
+ [ 'amount', 'int', '11' ],
+ [ 'createdAt', 'int', '10' ],
+ [ 'user_d', 'int', '10' ],
+ [ 'comment', 'text', '' ],
+ ];
+ public $plugin;
+
+ /**
+ * The model constructor.
+ */
+ public function __construct() {
+ parent::__construct();
+ $this->plugin = new Plugin;
+ }
+}
diff --git a/app/plugins/donate/plugin.php b/app/plugins/donate/plugin.php
new file mode 100644
index 0000000..2e6a48d
--- /dev/null
+++ b/app/plugins/donate/plugin.php
@@ -0,0 +1,60 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Plugins;
+
+use TheTempusProject\TheTempusProject as App;
+use TheTempusProject\Classes\Plugin;
+use Stripe\StripeClient;
+use TheTempusProject\Bedrock\Classes\Config;
+use TheTempusProject\Hermes\Functions\Route as Routes;
+use TheTempusProject\Models\Memberships;
+
+class Donate extends Plugin {
+ public static $stripe;
+ private static $loaded = false;
+ public $pluginName = 'TP Donations';
+ public $configName = 'donations';
+ public $pluginAuthor = 'JoeyK';
+ public $pluginWebsite = 'https://TheTempusProject.com';
+ public $modelVersion = '1.0';
+ public $pluginVersion = '3.0';
+ public $pluginDescription = 'A simple plugin which adds a site wide donation system.';
+ public $permissionMatrix = [
+ 'controlMemberships' => [
+ 'pretty' => 'Can donate.',
+ 'default' => true,
+ ],
+ ];
+ public $main_links = [
+ [
+ 'text' => 'Donate!',
+ 'url' => '{ROOT_URL}donate',
+ ],
+ ];
+ public $configMatrix = [
+ 'stripePublishable' => [
+ 'type' => 'text',
+ 'pretty' => 'Stripe Publishable key',
+ 'default' => 'pk_xxxxxxxxxxxxxxx',
+ ],
+ 'stripeSecret' => [
+ 'type' => 'text',
+ 'pretty' => 'Stripe Secret key',
+ 'default' => 'sk_xxxxxxxxxxxxxxx',
+ ],
+ ];
+
+ public function __construct( $load = false ) {
+ parent::__construct( $load );
+ }
+}
diff --git a/app/plugins/donate/views/donate.html b/app/plugins/donate/views/donate.html
new file mode 100644
index 0000000..69f6e58
--- /dev/null
+++ b/app/plugins/donate/views/donate.html
@@ -0,0 +1,37 @@
+
+
+
+
+
Support Our Projects
+
Your funding helps provide more time and resources, enables exciting new projects, and ensures continued updates and improvements to all of our services.
+
By contributing, you're supporting these platforms:
+
+ AllTheBookmarks.com - Your one-stop bookmark management service.
+ ForgottenPlanner.com - Keep your plans on track.
+ TempusToolkit.com - Time management made easier.
+ WoWditions.com - Enhancing your World of Warcraft experience.
+ WhatsThatUploadSite.com - Simplified upload site discovery.
+ PollingDownTheStreet.com - Find your local polling station effortlessly.
+
+
Every donation helps us grow and continue providing value to our community. Thank you for your support!
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/plugins/donate/views/fail.html b/app/plugins/donate/views/fail.html
new file mode 100644
index 0000000..c32e583
--- /dev/null
+++ b/app/plugins/donate/views/fail.html
@@ -0,0 +1,4 @@
+
+
Thanks for taking the time to explore donation.
+
Now may not be the right time, but we thank you anyways!
+
\ No newline at end of file
diff --git a/app/plugins/donate/views/success.html b/app/plugins/donate/views/success.html
new file mode 100644
index 0000000..458ea88
--- /dev/null
+++ b/app/plugins/donate/views/success.html
@@ -0,0 +1,5 @@
+
+
Thanks for supporting!
+
Its people like you who keep the pixels on around here.
+ Every Dollar counts, so thank you again.
+
\ No newline at end of file
diff --git a/app/plugins/members/controllers/admin/invoices.php b/app/plugins/members/controllers/admin/invoices.php
new file mode 100644
index 0000000..7d5baba
--- /dev/null
+++ b/app/plugins/members/controllers/admin/invoices.php
@@ -0,0 +1,50 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Controllers\Admin;
+
+use TheTempusProject\Houdini\Classes\Views;
+use TheTempusProject\Houdini\Classes\Navigation;
+use TheTempusProject\Houdini\Classes\Components;
+use TheTempusProject\Classes\AdminController;
+use TheTempusProject\Models\MembershipRecords as MemberModel;
+
+class Invoices extends AdminController {
+ public static $memberships;
+
+ public function __construct() {
+ parent::__construct();
+ self::$title = 'Admin - Memberships';
+ self::$memberships = new MemberModel;
+ $view = Navigation::activePageSelect( 'nav.admin', '/admin/member' );
+ Components::set( 'ADMINNAV', $view );
+ }
+
+ public function index( $data = null ) {
+ Views::view( 'members.admin.list', self::$memberships->list() );
+ }
+
+ public function create( $data = null ) {
+ }
+
+ public function edit( $data = null ) {
+ }
+
+ public function view( $data = null ) {
+ }
+
+ public function delete( $data = null ) {
+ }
+
+ public function preview( $data = null ) {
+ }
+}
diff --git a/app/plugins/members/controllers/admin/members.php b/app/plugins/members/controllers/admin/members.php
new file mode 100644
index 0000000..53dd9ef
--- /dev/null
+++ b/app/plugins/members/controllers/admin/members.php
@@ -0,0 +1,40 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Controllers\Admin;
+
+use TheTempusProject\Houdini\Classes\Views;
+use TheTempusProject\Houdini\Classes\Navigation;
+use TheTempusProject\Houdini\Classes\Components;
+use TheTempusProject\Classes\AdminController;
+use TheTempusProject\Plugins\Members as MemberModel;
+use TheTempusProject\Houdini\Classes\Issues;
+use TheTempusProject\Bedrock\Functions\Input;
+
+class Members extends AdminController {
+ public function __construct() {
+ parent::__construct();
+ $view = Navigation::activePageSelect( 'nav.admin', '/admin/member' );
+ Components::set( 'ADMINNAV', $view );
+ }
+
+ public function index( $data = null ) {
+ self::$title = 'Admin - Membership Webhooks';
+
+ if ( !Input::exists( 'submit' ) ) {
+ return Views::view( 'members.admin.webhooks' );
+ }
+ MemberModel::webhookSetup();
+ Issues::add( 'success', 'Webhooks Generated' );
+ Issues::add( 'error', 'Now, LEAVE!' );
+ }
+}
diff --git a/app/plugins/members/controllers/admin/products.php b/app/plugins/members/controllers/admin/products.php
new file mode 100644
index 0000000..30bdfbc
--- /dev/null
+++ b/app/plugins/members/controllers/admin/products.php
@@ -0,0 +1,97 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Controllers\Admin;
+
+use TheTempusProject\Houdini\Classes\Views;
+use TheTempusProject\Houdini\Classes\Navigation;
+use TheTempusProject\Houdini\Classes\Components;
+use TheTempusProject\Classes\AdminController;
+use TheTempusProject\Models\MembershipProducts;
+use TheTempusProject\Bedrock\Functions\Input;
+use TheTempusProject\Classes\Forms;
+use TheTempusProject\Houdini\Classes\Issues;
+use TheTempusProject\Bedrock\Functions\Check;
+
+class Products extends AdminController {
+ public static $products;
+
+ public function __construct() {
+ parent::__construct();
+ self::$title = 'Admin - Membership Products';
+ self::$products = new MembershipProducts;
+ $view = Navigation::activePageSelect( 'nav.admin', '/admin/products' );
+ Components::set( 'ADMINNAV', $view );
+ }
+
+ public function index( $data = null ) {
+ Views::view( 'members.admin.products.list', self::$products->list() );
+ }
+
+ public function create( $data = null ) {
+ if ( !Input::exists( 'submit' ) ) {
+ return Views::view( 'members.admin.products.create' );
+ }
+ if ( !Forms::check( 'newMembershipProduct' ) ) {
+ Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
+ return $this->index();
+ }
+ $result = self::$products->create( Input::post( 'name' ), Input::post( 'description' ), Input::post( 'monthly_price' ), Input::post( 'yearly_price' ) );
+ if ( $result ) {
+ Issues::add( 'success', 'Your product has been created.' );
+ return $this->index();
+ } else {
+ Issues::add( 'error', [ 'There was an unknown error submitting your data.' => Check::userErrors() ] );
+ return $this->index();
+ }
+ }
+
+ public function edit( $id = null ) {
+ if ( !Input::exists( 'submit' ) ) {
+ return Views::view( 'members.admin.products.edit', self::$posts->findById( $id ) );
+ }
+ if ( !Forms::check( 'editMembershipProduct' ) ) {
+ Issues::add( 'error', [ 'There was an error with your form.' => Check::userErrors() ] );
+ return $this->index();
+ }
+
+ if ( self::$posts->updatePost( $id, Input::post( 'title' ), Input::post( 'blogPost' ), Input::post( 'submit' ) ) === true ) {
+ Issues::add( 'success', 'Post Updated.' );
+ return $this->index();
+ }
+ Issues::add( 'error', 'There was an error with your request.' );
+ $this->index();
+ }
+
+ public function view( $id = null ) {
+ $data = self::$products->findById( $id );
+ if ( $data !== false ) {
+ return Views::view( 'blog.admin.view', $data );
+ }
+ Issues::add( 'error', 'Post not found.' );
+ $this->index();
+ }
+
+ public function delete( $data = null ) {
+ if ( $data == null ) {
+ if ( Input::exists( 'MP_' ) ) {
+ $data = Input::post( 'MP_' );
+ }
+ }
+ if ( !self::$products->delete( (array) $data ) ) {
+ Issues::add( 'error', 'There was an error with your request.' );
+ } else {
+ Issues::add( 'success', 'Post has been deleted' );
+ }
+ $this->index();
+ }
+}
diff --git a/app/plugins/members/controllers/admin/records.php b/app/plugins/members/controllers/admin/records.php
new file mode 100644
index 0000000..069fe2f
--- /dev/null
+++ b/app/plugins/members/controllers/admin/records.php
@@ -0,0 +1,50 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Controllers\Admin;
+
+use TheTempusProject\Houdini\Classes\Views;
+use TheTempusProject\Houdini\Classes\Navigation;
+use TheTempusProject\Houdini\Classes\Components;
+use TheTempusProject\Classes\AdminController;
+use TheTempusProject\Models\Memberships as MemberModel;
+
+class Records extends AdminController {
+ public static $memberships;
+
+ public function __construct() {
+ parent::__construct();
+ self::$title = 'Admin - Memberships';
+ self::$memberships = new MemberModel;
+ $view = Navigation::activePageSelect( 'nav.admin', '/admin/member' );
+ Components::set( 'ADMINNAV', $view );
+ }
+
+ public function index( $data = null ) {
+ Views::view( 'members.admin.memberships.list', self::$memberships->list() );
+ }
+
+ public function create( $data = null ) {
+ }
+
+ public function edit( $data = null ) {
+ }
+
+ public function view( $data = null ) {
+ }
+
+ public function delete( $data = null ) {
+ }
+
+ public function preview( $data = null ) {
+ }
+}
diff --git a/app/plugins/members/controllers/api/stripe.php b/app/plugins/members/controllers/api/stripe.php
new file mode 100644
index 0000000..4f08745
--- /dev/null
+++ b/app/plugins/members/controllers/api/stripe.php
@@ -0,0 +1,188 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Controllers\Api;
+
+use Stripe\StripeClient;
+use Stripe\Event;
+use TheTempusProject\Models\User;
+use TheTempusProject\Controllers\StripeApiController;
+use TheTempusProject\Houdini\Classes\Views;
+use TheTempusProject\Bedrock\Classes\Config;
+use TheTempusProject\Canary\Bin\Canary as Debug;
+use TheTempusProject\Models\MembershipCustomers;
+use TheTempusProject\Models\Memberships;
+
+class Stripe extends StripeApiController {
+ public static $stripe;
+ public static $customers;
+ public static $memberships;
+
+ public function __construct() {
+ parent::__construct();
+ $api_key = Config::getValue( 'memberships/stripeSecret' );
+ self::$stripe = new StripeClient($api_key);
+ }
+
+ public function webhook() {
+ try {
+ $payload = @file_get_contents('php://input');
+ $payload = json_decode( $payload, true );
+ if ( ! is_array( $payload ) ) {
+ throw new \Exception("Error Processing Request", 1);
+ }
+ $event = null;
+ $event = Event::constructFrom( $payload );
+
+ $eventData = $event->data->object;
+
+ // $event->type gives the event type obv
+ switch ($event->type) {
+ // case 'invoice.paid':
+ // Debug::error( 'processing: ' . $event->type );
+ // "id": "in_1QSDcJGsigymNdIJo0Z1a20K",
+ // "customer": "cus_RKtOtR7X7CwPRU",
+ // "charge": "ch_3QSDcJGsigymNdIJ0Smb7Rmx",
+ // "subscription": "sub_1QSDcJGsigymNdIJWGw7Zrv9",
+ // break;
+
+ // case 'charge.succeeded':
+ // Debug::error( 'processing: ' . $event->type );
+ // "id": "ch_3QSDcJGsigymNdIJ0Smb7Rmx",
+ // "invoice": "in_1QSDcJGsigymNdIJo0Z1a20K",
+ // "status": "succeeded",
+ // break;
+
+ case 'customer.subscription.updated':
+ case 'customer.subscription.paused':
+ case 'customer.subscription.resumed':
+ case 'customer.subscription.deleted':
+ Debug::error( 'processing: ' . $event->type );
+
+ self::$memberships = new Memberships;
+ $membership_id = self::$memberships->findBySubscriptionID( $eventData->id );
+ if ( empty( $membership_id ) ) {
+ Debug::error( 'membership not found' );
+
+ self::$customers = new MembershipCustomers;
+ $customer = self::$customers->findByCustomerID( $eventData->customer );
+ if ( empty( $customer ) ) {
+ Debug::error( 'customer not found' );
+ Debug::v( $eventData->customer );
+ break;
+ }
+
+ $result = self::$memberships->create(
+ $eventData->customer,
+ $eventData->id,
+ $eventData->plan->id,
+ $eventData->current_period_start,
+ $eventData->current_period_end,
+ $eventData->status,
+ $customer->local_user,
+ 'frequency'
+ );
+ } else {
+ $result = self::$memberships->update( $membership_id->ID, $eventData->current_period_start, $eventData->current_period_end, $eventData->status );
+ }
+
+ if ( empty( $result ) ) {
+ Debug::error( 'membership not updated' );
+ Debug::v( $result );
+ }
+ break;
+ case 'customer.subscription.created':
+ Debug::error( 'processing: ' . $event->type );
+
+ self::$memberships = new Memberships;
+ $membership_id = self::$memberships->findBySubscriptionID( $eventData->id );
+ if ( ! empty( $membership_id ) ) {
+ Debug::error( 'subscription already created' );
+ break;
+ }
+ self::$customers = new MembershipCustomers;
+ $customer = self::$customers->findByCustomerID( $eventData->customer );
+ if ( empty( $customer ) ) {
+ Debug::error( 'customer not found' );
+ Debug::v( $eventData->customer );
+ break;
+ }
+ Debug::error( 'processing: ' . $event->type );
+
+ $result = self::$memberships->create(
+ $eventData->customer,
+ $eventData->id,
+ $eventData->plan->id,
+ $eventData->current_period_start,
+ $eventData->current_period_end,
+ $eventData->status,
+ $customer->local_user,
+ 'frequency'
+ );
+ Debug::error( 'processing: ' . $event->type );
+
+ if ( empty( $result ) ) {
+ Debug::error( 'membership not made' );
+ Debug::v( $result );
+ }
+ Debug::error( 'done processing: ' . var_export($result,true) );
+ break;
+ // case 'invoice.created':
+ // Debug::error( 'processing: ' . $event->type );
+ // happens when the payment first starts
+
+ // "id": "in_1QSDcJGsigymNdIJo0Z1a20K",
+ // "customer": "cus_RKtOtR7X7CwPRU",
+ // "status": "open",
+ // "total": 888,
+ // break;
+ // case 'checkout.session.completed':
+ // Debug::error( 'processing: ' . $event->type );
+ // new customer has completed first checkout
+ // add thier record or update iit
+
+ // "invoice": "in_1QSDcJGsigymNdIJo0Z1a20K",
+ // "mode": "subscription",
+ // "status": "complete",
+ // "customer": "cus_RKtOtR7X7CwPRU",
+ // break;
+ // case 'payment_intent.succeeded':
+ // Debug::error( 'processing: ' . $event->type );
+ // $paymentIntent = $event->data->object;
+ // break;
+ // case 'payment_method.attached':
+ // Debug::error( 'processing: ' . $event->type );
+ // $paymentMethod = $event->data->object;
+ // break;
+ default:
+ Debug::error( 'Skipped Event:' . $event->type );
+ break;
+ }
+
+
+ $responseType = 'success';
+ $response = true;
+ } catch(\UnexpectedValueException $e) {
+ Debug::error( 'UnexpectedValueException' );
+ Debug::v( $e );
+ http_response_code(400);
+ $responseType = 'error';
+ $response = 'UnexpectedValueException';
+ } catch(\Exception $e) {
+ Debug::error( 'Exception' );
+ Debug::v( $e );
+ http_response_code(400);
+ $responseType = 'error';
+ $response = 'Exception';
+ }
+ Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]);
+ }
+}
\ No newline at end of file
diff --git a/app/plugins/members/controllers/member.php b/app/plugins/members/controllers/member.php
index a43366f..8a800d9 100644
--- a/app/plugins/members/controllers/member.php
+++ b/app/plugins/members/controllers/member.php
@@ -18,19 +18,223 @@ use TheTempusProject\Classes\Controller;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session;
+use TheTempusProject\Models\MembershipCustomers;
+use TheTempusProject\Models\MembershipProducts;
+use TheTempusProject\Bedrock\Classes\Config;
+use TheTempusProject\Bedrock\Functions\Input;
+use Stripe\Checkout\Session as StripeSession;
+use TheTempusProject\Hermes\Functions\Route as Routes;
+use TheTempusProject\Houdini\Classes\Navigation;
+use TheTempusProject\Models\Memberships;
+use TheTempusProject\Houdini\Classes\Components;
class Member extends Controller {
+ public static $customers;
+ public static $products;
+
public function __construct() {
parent::__construct();
Template::noIndex();
- if ( !App::$isMember ) {
- Session::flash( 'error', 'You do not have permission to view this page.' );
- return Redirect::home();
- }
+ $api_key = Config::getValue( 'memberships/stripeSecret' );
+ self::$customers = new MembershipCustomers;
+ self::$products = new MembershipProducts;
}
public function index() {
self::$title = 'Members Area';
+ if ( !App::$isMember ) {
+ Session::flash( 'error', 'You do not have permission to view this page.' );
+ return Redirect::home();
+ }
Views::view( 'members.members' );
}
-}
+
+ public function managepayment( $id = null ) {
+
+
+
+ $api_key = Config::getValue( 'memberships/stripeSecret' );
+ $stripe = new \Stripe\StripeClient( $api_key );
+
+ $customer = self::$customers->findOrCreate( App::$activeUser->ID );
+ if ( empty( $customer ) ) {
+ Session::flash( 'error', 'no customer.' );
+ return Redirect::to( 'member/manage' );
+ }
+
+ $session = $stripe->billingPortal->sessions->create([
+ 'customer' => $customer,
+ 'return_url' => Routes::getAddress() . 'member/manage',
+ ]);
+
+
+
+
+
+
+
+
+ header('Location: ' . $session->url);
+ exit;
+ }
+
+ public function cancelconfirm( $id = null ) {
+ $memberships = new Memberships;
+ $result = $memberships->cancel( $id );
+ // dv( $result );
+ if ( ! empty( $result ) ) {
+ Session::flash( 'success', 'Your Membership has been paused.' );
+ Redirect::to( 'member/manage' );
+ } else {
+ Session::flash( 'error', 'There was an error canceling your membership' );
+ Redirect::to( 'member/manage' );
+ }
+ }
+
+ public function pauseconfirm( $id = null ) {
+ $memberships = new Memberships;
+ $result = $memberships->cancel( $id );
+ // dv( $result );
+ if ( ! empty( $result ) ) {
+ Session::flash( 'success', 'Your Membership has been paused.' );
+ Redirect::to( 'member/manage' );
+ } else {
+ Session::flash( 'error', 'There was an error canceling your membership' );
+ Redirect::to( 'member/manage' );
+ }
+ }
+
+ public function pause( $id = null ) {
+ self::$title = 'pause Membership';
+ Components::set( 'pauseid', $id );
+ Views::view( 'members.pause' );
+ }
+
+ public function resume( $id = null ) {
+ self::$title = 'resume Membership';
+ Views::view( 'members.resume' );
+ }
+
+ public function cancel( $id = null ) {
+ self::$title = 'Cancel Membership';
+ Components::set( 'cancelid', $id );
+ Views::view( 'members.cancel' );
+ }
+
+ public function manage( $id = null ) {
+ self::$title = 'Manage Membership';
+
+ $menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
+ Navigation::activePageSelect( $menu, null, true, true );
+
+ $memberships = new Memberships;
+ $userMemberships = $memberships->getUserSubs();
+
+ Views::view( 'members.manage', $userMemberships );
+ }
+
+ public function upgrade( $id = null ) {
+ self::$title = 'Upgrade Membership';
+ Views::view( 'members.upgrade' );
+ }
+
+ public function join( $id = null ) {
+ self::$title = 'Join {SIITENAME}!';
+ $product = self::$products->findById( $id );
+ if ( empty( $product ) ) {
+ Session::flash( 'success', 'We aren\'t currently accepting new members, please check back soon!' );
+ return Redirect::home();
+ }
+ Views::view( 'members.landing1', $product );
+ }
+
+ public function signup( $id = null ) {
+ self::$title = 'Sign-up for {SIITENAME}!';
+ $product = self::$products->findById( $id );
+ if ( empty( $product ) ) {
+ Session::flash( 'success', 'We aren\'t currently accepting new members, please check back soon!' );
+ return Redirect::home();
+ }
+ Views::view( 'members.landing2', $product );
+ }
+
+ public function getyearly( $id = null ) {
+ if ( empty( $id ) ) {
+ Issues::add( 'error', 'no id' );
+ return $this->index();
+ }
+ $product = self::$products->findById( $id );
+ if ( empty( $product ) ) {
+ Issues::add( 'error', 'no product' );
+ return $this->index();
+ }
+ $customer = self::$customers->findOrCreate( App::$activeUser->ID );
+ if ( empty( $customer ) ) {
+ Issues::add( 'error', 'no customer' );
+ return $this->index();
+ }
+
+ self::$title = 'Purchase';
+ $price = $product->stripe_price_yearly;
+ $api_key = Config::getValue( 'memberships/stripeSecret' );
+ $stripe = new \Stripe\StripeClient( $api_key );
+ $session = $stripe->checkout->sessions->create([
+ 'payment_method_types' => ['card'],
+ 'customer' => $customer,
+ 'line_items' => [[
+ 'price' => $price,
+ 'quantity' => 1,
+ ]],
+ 'mode' => 'subscription',
+ 'success_url' => Routes::getAddress() . 'member/paymentcomplete?session_id={CHECKOUT_SESSION_ID}',
+ 'cancel_url' => Routes::getAddress() . 'member/paymentcanceled',
+ ]);
+ header('Location: ' . $session->url);
+ exit;
+ }
+
+ public function getmonthly( $id = null ) {
+ if ( empty( $id ) ) {
+ Issues::add( 'error', 'no id' );
+ return $this->index();
+ }
+ $product = self::$products->findById( $id );
+ if ( empty( $product ) ) {
+ Issues::add( 'error', 'no product' );
+ return $this->index();
+ }
+ $customer = self::$customers->findOrCreate( App::$activeUser->ID );
+ if ( empty( $customer ) ) {
+ Issues::add( 'error', 'no customer' );
+ return $this->index();
+ }
+
+ self::$title = 'Purchase';
+ $price = $product->stripe_price_monthly;
+ $api_key = Config::getValue( 'memberships/stripeSecret' );
+ $stripe = new \Stripe\StripeClient( $api_key );
+ $session = $stripe->checkout->sessions->create([
+ 'payment_method_types' => ['card'],
+ 'customer' => $customer,
+ 'line_items' => [[
+ 'price' => $price,
+ 'quantity' => 1,
+ ]],
+ 'mode' => 'subscription',
+ 'success_url' => Routes::getAddress() . 'member/paymentcomplete?session_id={CHECKOUT_SESSION_ID}',
+ 'cancel_url' => Routes::getAddress() . 'member/paymentcanceled',
+ ]);
+ header('Location: ' . $session->url);
+ exit;
+ }
+
+ public function paymentcanceled() {
+ self::$title = '(almost) Members Area';
+ Views::view( 'members.paymentcanceled' );
+ }
+
+ public function paymentcomplete() {
+ self::$title = '(almost) Members Area';
+ Views::view( 'members.paymentcomplete' );
+ }
+}
\ No newline at end of file
diff --git a/app/plugins/members/controllers/stripe_api_controller.php b/app/plugins/members/controllers/stripe_api_controller.php
new file mode 100644
index 0000000..1e8a4ad
--- /dev/null
+++ b/app/plugins/members/controllers/stripe_api_controller.php
@@ -0,0 +1,33 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Controllers;
+
+use TheTempusProject\Houdini\Classes\Template;
+use TheTempusProject\TheTempusProject as App;
+use TheTempusProject\Hermes\Functions\Redirect;
+use TheTempusProject\Bedrock\Functions\Session;
+use TheTempusProject\Classes\Controller;
+
+class StripeApiController extends Controller {
+ public function __construct() {
+ parent::__construct();
+ // if ( ! App::verifyApiRequest() ) {
+ // Session::flash( 'error', 'You do not have permission to view this page.' );
+ // return Redirect::home();
+ // }
+ Template::noFollow();
+ Template::noIndex();
+ Template::addHeader( 'Content-Type: application/json; charset=utf-8' );
+ Template::setTemplate( 'api' );
+ }
+}
diff --git a/app/plugins/members/forms.php b/app/plugins/members/forms.php
new file mode 100644
index 0000000..295ea2c
--- /dev/null
+++ b/app/plugins/members/forms.php
@@ -0,0 +1,47 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Plugins\Subscribe;
+
+use TheTempusProject\Bedrock\Functions\Input;
+use TheTempusProject\Classes\Forms;
+
+class MembershipForms extends Forms {
+ /**
+ * Adds these functions to the form list.
+ */
+ public function __construct() {
+ self::addHandler( 'newMembershipProduct', __CLASS__, 'newMembershipProduct' );
+ self::addHandler( 'editMembershipProduct', __CLASS__, 'editMembershipProduct' );
+ }
+
+ /**
+ * Validates the subscribe form.
+ *
+ * @return {bool}
+ */
+ public static function newMembershipProduct() {
+ // if ( !self::token() ) {
+ // return false;
+ // }
+ return true;
+ }
+ public static function editMembershipProduct() {
+ // if ( !self::token() ) {
+ // return false;
+ // }
+ return true;
+ }
+
+}
+
+new MembershipForms;
diff --git a/app/plugins/members/models/membership_customers.php b/app/plugins/members/models/membership_customers.php
new file mode 100644
index 0000000..f2746e9
--- /dev/null
+++ b/app/plugins/members/models/membership_customers.php
@@ -0,0 +1,104 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Models;
+
+use TheTempusProject\Canary\Bin\Canary as Debug;
+use TheTempusProject\Bedrock\Functions\Check;
+use TheTempusProject\Bedrock\Functions\Sanitize;
+use TheTempusProject\Classes\DatabaseModel;
+use TheTempusProject\TheTempusProject as App;
+use TheTempusProject\Bedrock\Classes\Config;
+use TheTempusProject\Hermes\Functions\Route as Routes;
+
+class MembershipCustomers extends DatabaseModel {
+ public static $stripe;
+ public $tableName = 'membership_customers';
+
+ public $databaseMatrix = [
+ [ 'stripe_customer', 'varchar', '155' ],
+ [ 'local_user', 'varchar', '155' ],
+ // renews?
+ // does this renew periodically?
+ // renewal period?
+ // if periodic, how frequent?
+ // renewal type?
+ // automatic, manual review, paid
+ ];
+
+ public function __construct() {
+ parent::__construct();
+ }
+
+ public function findByUserID( $d ) {
+ $data = self::$db->get( $this->tableName, [ 'local_user', '=', $d ] );
+ if ( ! $data->count() ) {
+ return false;
+ }
+ return $data->first();
+ }
+
+ public function findByCustomerID( $d ) {
+ $data = self::$db->get( $this->tableName, [ 'stripe_customer', '=', $d ] );
+ if ( ! $data->count() ) {
+ return false;
+ }
+ return $data->first();
+ }
+
+ public function create( $user_id ) {
+ $data = self::$db->get( 'users', ['ID', '=', $user_id] );
+ if ( !$data->count() ) {
+ Debug::warn( "customer cannot be created, user not found" );
+ return false;
+ }
+ $user = $data->first();
+ $user_email = $user->email;
+ $user_name = $user->name;
+
+ $api_key = Config::getValue( 'memberships/stripeSecret' );
+ if ( $api_key == 'sk_xxxxxxxxxxxxxxx' || empty($api_key) ) {
+ Debug::error( "No Stripe Key found" );
+ return false;
+ }
+ self::$stripe = new \Stripe\StripeClient( $api_key );
+
+ $customer = self::$stripe->customers->create([
+ 'name' => $user_name,
+ 'email' => $user_email,
+ ]);
+
+ $fields = [
+ 'stripe_customer' => $customer->id,
+ 'local_user' => $user_id,
+ ];
+
+ if ( !self::$db->insert( $this->tableName, $fields ) ) {
+ Debug::error( "Membership Customer: $data not added: $fields" );
+ new customException( 'membershipCustomerCreate' );
+ return false;
+ }
+
+ return $customer;
+ }
+
+ public function findOrCreate( $user_id ) {
+ $user = $this->findByUserID( $user_id );
+ if ( ! empty( $user ) ) {
+ return $user->stripe_customer;
+ }
+ $user = $this->create( $user_id );
+ if ( ! empty( $user ) ) {
+ return $user->id;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/plugins/members/models/membership_invoices.php b/app/plugins/members/models/membership_invoices.php
new file mode 100644
index 0000000..9412be8
--- /dev/null
+++ b/app/plugins/members/models/membership_invoices.php
@@ -0,0 +1,38 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Models;
+
+use TheTempusProject\Canary\Bin\Canary as Debug;
+use TheTempusProject\Bedrock\Functions\Check;
+use TheTempusProject\Bedrock\Functions\Sanitize;
+use TheTempusProject\Classes\DatabaseModel;
+use TheTempusProject\TheTempusProject as App;
+
+class MembershipInvoices extends DatabaseModel {
+ public static $stripe;
+ public $tableName = 'membership_invoices';
+
+ public $databaseMatrix = [
+ [ 'name', 'varchar', '155' ],
+ // renews?
+ // does this renew periodically?
+ // renewal period?
+ // if periodic, how frequent?
+ // renewal type?
+ // automatic, manual review, paid
+ ];
+
+ public function __construct() {
+ parent::__construct();
+ }
+}
\ No newline at end of file
diff --git a/app/plugins/members/models/membership_products.php b/app/plugins/members/models/membership_products.php
new file mode 100644
index 0000000..45375b2
--- /dev/null
+++ b/app/plugins/members/models/membership_products.php
@@ -0,0 +1,206 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+
+namespace TheTempusProject\Models;
+
+use TheTempusProject\Canary\Bin\Canary as Debug;
+use TheTempusProject\Bedrock\Functions\Check;
+use TheTempusProject\Bedrock\Functions\Sanitize;
+use TheTempusProject\Classes\DatabaseModel;
+use TheTempusProject\TheTempusProject as App;
+use TheTempusProject\Bedrock\Classes\Config;
+
+class MembershipProducts extends DatabaseModel {
+ public static $stripe;
+ public $tableName = 'membership_products';
+ public $databaseMatrix = [
+ [ 'name', 'varchar', '128' ],
+ [ 'description', 'text', '' ],
+ [ 'monthly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4399.22 === 439922
+ [ 'yearly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4399.22 === 439922
+ [ 'stripe_product', 'varchar', '64' ], // not-required to create - generated by stripe after
+ [ 'stripe_price_monthly', 'varchar', '64' ], // not-required to create - generated by stripe after
+ [ 'stripe_price_yearly', 'varchar', '64' ], // not-required to create - generated by stripe after
+ ];
+
+ public function __construct() {
+ parent::__construct();
+ $api_key = Config::getValue( 'memberships/stripeSecret' );
+ if ( $api_key == 'sk_xxxxxxxxxxxxxxx' || empty($api_key) ) {
+ Debug::error( "No Stripe Key found" );
+ } else {
+ self::$stripe = new \Stripe\StripeClient( $api_key );
+ }
+ }
+
+ public function create( $name, $description, $monthly_price, $yearly_price ) {
+ if ( empty( self::$stripe ) ) {
+ return false;
+ }
+ $stripe_product = $this->createStripeProduct( $name, $description );
+ $stripe_prices = $this->createStripePrices( $stripe_product->id, $monthly_price, $yearly_price );
+
+ $fields = [
+ 'name' => $name,
+ 'description' => $description,
+ 'monthly_price' => $monthly_price,
+ 'yearly_price' => $yearly_price,
+ 'stripe_product' => $stripe_product->id,
+ 'stripe_price_monthly' => $stripe_prices['monthly']->id,
+ 'stripe_price_yearly' => $stripe_prices['yearly']->id,
+ ];
+
+ if ( !self::$db->insert( $this->tableName, $fields ) ) {
+ Debug::error( "Membership Product: $data not updated: $fields" );
+ new customException( 'membershipProductCreate' );
+ return false;
+ }
+
+ return true;
+ }
+
+ public function createStripeProduct( $name, $description ) {
+ if ( empty( self::$stripe ) ) {
+ return false;
+ }
+ $product = self::$stripe->products->create([
+ 'name' => $name,
+ 'description' => $description,
+ ]);
+ return $product;
+ }
+ public function createStripePrices( $product, $monthly_price, $yearly_price ) {
+ $out = [];
+ $out['monthly'] = $this->createStripeMonthlyPrice( $product, $monthly_price );
+ $out['yearly'] = $this->createStripeYearlyPrice( $product, $yearly_price );
+ return $out;
+ }
+ public function createStripeMonthlyPrice( $product, $monthly_price ) {
+ if ( empty( self::$stripe ) ) {
+ return false;
+ }
+ return self::$stripe->prices->create([
+ 'currency' => 'usd',
+ 'unit_amount' => $monthly_price,
+ 'recurring' => ['interval' => 'month'],
+ 'product' => $product,
+ 'lookup_key' => 'membership-monthly',
+ ]);
+ }
+ public function createStripeYearlyPrice( $product, $yearly_price ) {
+ if ( empty( self::$stripe ) ) {
+ return false;
+ }
+ return self::$stripe->prices->create([
+ 'currency' => 'usd',
+ 'unit_amount' => $yearly_price,
+ 'recurring' => ['interval' => 'year'],
+ 'product' => $product,
+ 'lookup_key' => 'membership-yearly',
+ ]);
+ }
+
+ public function updateProduct( $id, $name, $description, $monthly_price, $yearly_price ) {
+ $product = $this->findById( $id );
+ if ( $product === false ) {
+ return false;
+ }
+ if ( $product->monthly_price != $monthly_price ) {
+ $new_monthly = $this->updateStripeMonthlyPrice( $product->stripe_price_monthly, $monthly_price );
+ }
+ if ( $product->yearly_price != $yearly_price ) {
+ $new_yearly = $this->updateStripeYearlyPrice( $product->stripe_price_yearly, $yearly_price );
+ }
+ if ( ( $product->name != $name ) || ( $product->description != $description ) ) {
+ $this->updateStripeProduct( $product->stripe_product, $name, $description );
+ }
+
+ $fields = [
+ 'name' => $name,
+ 'description' => $description,
+ 'monthly_price' => $monthly_price,
+ 'yearly_price' => $yearly_price,
+ 'stripe_price_monthly' => $new_monthly->id,
+ 'stripe_price_yearly' => $new_yearly->id,
+ ];
+ if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
+ new CustomException( 'membershipProductUpdate' );
+ Debug::error( "membership Product: $id not updated: $fields" );
+
+ return false;
+ }
+ self::$log->admin( "Updated membership Product: $id" );
+ return true;
+ }
+
+ public function updateStripeProduct( $id, $name, $description ) {
+ if ( empty( self::$stripe ) ) {
+ return false;
+ }
+ $product = self::$stripe->products->update(
+ $id,
+ [
+ 'name' => $name,
+ 'description' => $description,
+ ]
+ );
+ return $product;
+ }
+
+ public function updateStripeYearlyPrice( $product, $yearly_price ) {
+ $stripe->prices->update(
+ $yearly->id,
+ ['lookup_key' => ""]
+ );
+ return $this->createStripeYearlyPrice( $product, $yearly_price );
+ }
+ public function updateStripeMonthlyPrice( $product, $monthly_price ) {
+ $stripe->prices->update(
+ $monthly->id,
+ ['lookup_key' => ""]
+ );
+ return $this->createStripeMonthlyPrice( $product, $monthly_price );
+ }
+
+ public function filter( $postArray, $params = [] ) {
+ foreach ( $postArray as $instance ) {
+ if ( !is_object( $instance ) ) {
+ $instance = $postArray;
+ $end = true;
+ }
+
+ $instance->prettyPriceMonthly = '$' . number_format( $instance->monthly_price / 100, 2 ); // Outputs: $99.49
+ $instance->prettyPriceYearly = '$' . number_format( $instance->yearly_price / 100, 2 ); // Outputs: $99.49
+ $instance->prettySavings = '$' . number_format(
+ ( $instance->yearly_price - ( $instance->monthly_price * 12 ) ) / 100,
+ 2
+ ); // Outputs: $99.49
+
+ $out[] = $instance;
+ if ( !empty( $end ) ) {
+ $out = $out[0];
+ break;
+ }
+ }
+ return $out;
+ }
+
+ public function findByPriceID( $d ) {
+ $data = self::$db->get( $this->tableName, [ 'stripe_price_monthly', '=', $d, 'OR', 'stripe_price_yearly', '=', $d ] );
+
+ if ( ! $data->count() ) {
+ return false;
+ }
+ return $data->first();
+ }
+}
\ No newline at end of file
diff --git a/app/plugins/members/models/memberships.php b/app/plugins/members/models/memberships.php
new file mode 100644
index 0000000..c527303
--- /dev/null
+++ b/app/plugins/members/models/memberships.php
@@ -0,0 +1,170 @@
+
+ * @link https://TheTempusProject.com
+ * @license https://opensource.org/licenses/MIT [MIT LICENSE]
+ */
+namespace TheTempusProject\Models;
+
+use TheTempusProject\Canary\Bin\Canary as Debug;
+use TheTempusProject\Bedrock\Functions\Check;
+use TheTempusProject\Bedrock\Functions\Sanitize;
+use TheTempusProject\Classes\DatabaseModel;
+use TheTempusProject\TheTempusProject as App;
+use TheTempusProject\Models\MembershipProducts;
+use TheTempusProject\Bedrock\Classes\Config;
+use TheTempusProject\Canary\Classes\CustomException;
+
+class Memberships extends DatabaseModel {
+ public static $stripe;
+ public static $products;
+ public $tableName = 'membership_records';
+ public $databaseMatrix = [
+ [ 'stripe_customer', 'varchar', '64' ],
+ [ 'stripe_subscription', 'varchar', '64' ],
+ [ 'subscription_price_id', 'varchar', '64' ],
+ [ 'current_period_end', 'int', '10' ],
+ [ 'current_period_start', 'int', '10' ],
+ [ 'status', 'varchar', '64' ],
+ [ 'local_user_id', 'int', '10' ],
+ [ 'billing_frequency', 'varchar', '16' ],
+ ];
+
+ public function __construct() {
+ parent::__construct();
+ self::$products = new MembershipProducts;
+ $api_key = Config::getValue( 'memberships/stripeSecret' );
+ if ( $api_key == 'sk_xxxxxxxxxxxxxxx' || empty($api_key) ) {
+ Debug::error( "No Stripe Key found" );
+ } else {
+ self::$stripe = new \Stripe\StripeClient( $api_key );
+ }
+ }
+
+ public function filter( $postArray, $params = [] ) {
+ foreach ( $postArray as $instance ) {
+ if ( !is_object( $instance ) ) {
+ $instance = $postArray;
+ $end = true;
+ }
+ $instance->name = self::$user->getUsername( $instance->local_user_id );
+ $priceData = self::$products->findByPriceID( $instance->subscription_price_id );
+ if ( $priceData ) {
+ if ( $priceData->stripe_price_monthly == $instance->subscription_price_id ) {
+ $price = $priceData->monthly_price;
+ } else {
+ $price = $priceData->yearly_price;
+ }
+ $instance->productName = $priceData->name;
+ $instance->prettyPrice = '$' . number_format( $price / 100, 2 ); // Outputs: $99.49
+ } else {
+ $instance->prettyPrice = "unknown";
+ $instance->productName = "unknown";
+ }
+ $out[] = $instance;
+ if ( !empty( $end ) ) {
+ $out = $out[0];
+ break;
+ }
+ }
+ return $out;
+ }
+
+ public function getUserSubs( $limit = null ) {
+ $whereClause = ['local_user_id', '=', App::$activeUser->ID ];
+ if ( empty( $limit ) ) {
+ $postData = self::$db->getPaginated( $this->tableName, $whereClause );
+ } else {
+ $postData = self::$db->getPaginated( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
+ }
+ if ( !$postData->count() ) {
+ Debug::info( 'No user subs found.' );
+ return false;
+ }
+ return $this->filter( $postData->results() );
+ }
+
+ public function findByUserID( $d ) {
+ $data = self::$db->get( $this->tableName, [ 'local_user_id', '=', $d ] );
+ if ( ! $data->count() ) {
+ return false;
+ }
+ return $data->first();
+ }
+
+ public function findActiveByUserID( $d ) {
+ $data = self::$db->get( $this->tableName, [ 'local_user_id', '=', $d, 'AND', 'status', '=', 'active' ] );
+ if ( ! $data->count() ) {
+ return false;
+ }
+ return $data->first();
+ }
+
+ public function cancel( $id ) {
+ $data = self::$db->get( $this->tableName, [ 'ID', '=', $id, 'AND', 'local_user_id', '=', App::$activeUser->ID, 'AND', 'status', '=', 'active' ] );
+ if ( ! $data->count() ) {
+ return false;
+ }
+ $membershipID = $data->first()->stripe_subscription;
+ $out = false;
+ try {
+ $out = self::$stripe->subscriptions->cancel($membershipID, []);
+ } catch(\Exception $e) {
+ Debug::error( 'Exception' );
+ Debug::v( $e );
+ }
+ return $out;
+ }
+
+ public function findBySubscriptionID( $d ) {
+ $data = self::$db->get( $this->tableName, [ 'stripe_subscription', '=', $d ] );
+ if ( ! $data->count() ) {
+ return false;
+ }
+ return $data->first();
+ }
+
+ public function create( $customer, $subscription, $price, $start, $end, $status, $user_id, $frequency ) {
+ $fields = [
+ 'stripe_customer' => $customer,
+ 'stripe_subscription' => $subscription,
+ 'subscription_price_id' => $price,
+ 'current_period_end' => $end,
+ 'current_period_start' => $start,
+ 'status' => $status,
+ 'local_user_id' => $user_id,
+ 'billing_frequency' => $frequency,
+ ];
+
+ if ( !self::$db->insert( $this->tableName, $fields ) ) {
+ Debug::error( "Memberships: not created: $fields" );
+ new CustomException( 'membershipsCreate' );
+ return false;
+ }
+
+ return true;
+ }
+
+ public function update( $id, $start, $end, $status ) {
+ $fields = [
+ 'current_period_end' => $end,
+ 'current_period_start' => $start,
+ 'status' => $status,
+ ];
+
+ if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
+ Debug::error( "Memberships: not updated: " );
+ Debug::v( $fields );
+ new CustomException( 'membershipsUpdate' );
+ return false;
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/app/plugins/members/plugin.php b/app/plugins/members/plugin.php
index 1b52d5b..11c4f8b 100644
--- a/app/plugins/members/plugin.php
+++ b/app/plugins/members/plugin.php
@@ -1,10 +1,10 @@
* @link https://TheTempusProject.com
@@ -14,10 +14,17 @@ namespace TheTempusProject\Plugins;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Classes\Plugin;
+use Stripe\StripeClient;
+use TheTempusProject\Bedrock\Classes\Config;
+use TheTempusProject\Hermes\Functions\Route as Routes;
+use TheTempusProject\Models\Memberships;
class Members extends Plugin {
+ public static $stripe;
+ public static $memberships;
+ private static $loaded = false;
public $pluginName = 'TP Membership';
- public $configName = 'membership';
+ public $configName = 'memberships';
public $pluginAuthor = 'JoeyK';
public $pluginWebsite = 'https://TheTempusProject.com';
public $modelVersion = '1.0';
@@ -35,8 +42,21 @@ class Members extends Plugin {
];
public $admin_links = [
[
- 'text' => ' Memberships',
- 'url' => '{ROOT_URL}admin/member',
+ 'text' => ' Memberships',
+ 'url' => [
+ [
+ 'text' => ' Products',
+ 'url' => '{ROOT_URL}admin/products',
+ ],
+ [
+ 'text' => ' Subscriptions',
+ 'url' => '{ROOT_URL}admin/records',
+ ],
+ [
+ 'text' => ' Invoices',
+ 'url' => '{ROOT_URL}admin/invoices',
+ ],
+ ],
],
];
public $main_links = [
@@ -45,6 +65,11 @@ class Members extends Plugin {
'url' => '{ROOT_URL}member/index',
'filter' => 'member',
],
+ [
+ 'text' => 'Become a Member',
+ 'url' => '{ROOT_URL}member/join/1',
+ 'filter' => 'nonmember',
+ ],
];
public $resourceMatrix = [
'groups' => [
@@ -54,11 +79,45 @@ class Members extends Plugin {
]
],
];
+ public $configMatrix = [
+ 'stripePublishable' => [
+ 'type' => 'text',
+ 'pretty' => 'Stripe Publishable key',
+ 'default' => 'pk_xxxxxxxxxxxxxxx',
+ ],
+ 'stripeSecret' => [
+ 'type' => 'text',
+ 'pretty' => 'Stripe Secret key',
+ 'default' => 'sk_xxxxxxxxxxxxxxx',
+ ],
+ ];
+
+ public static $webhookEvents = [
+ 'customer.subscription.created',
+ 'customer.subscription.updated',
+ 'customer.subscription.deleted',
+ 'customer.updated',
+ 'customer.deleted',
+ 'payment_method.automatically_updated',
+ 'invoice.payment_action_required',
+ 'invoice.payment_succeeded',
+ 'checkout.session.completed',
+ ];
+
+ public static $userLinks = [
+ "url" => "{ROOT_URL}member/manage",
+ "name" => "Subscriptions"
+ ];
+
public function __construct( $load = false ) {
+ if ( ! self::$loaded ) {
+ App::$userCPlinks[] = (object) self::$userLinks;
+ self::$loaded = true;
+ }
if ( App::$isLoggedIn ) {
- App::$isMember = $this->hasMemberAccess( App::$activeGroup );
+ App::$isMember = $this->groupHasMemberAccess( App::$activeGroup );
if ( empty( App::$isMember ) ) {
- App::$isMember = $this->hasMemberAccess( App::$activeUser );
+ App::$isMember = $this->userHasActiveMembership( App::$activeUser->ID );
}
}
$this->filters[] = [
@@ -67,9 +126,54 @@ class Members extends Plugin {
'replace' => ( App::$isMember ? '$1' : '' ),
'enabled' => true,
];
+ $this->filters[] = [
+ 'name' => 'nonmember',
+ 'find' => '#{NONMEMBER}(.*?){/NONMEMBER}#is',
+ 'replace' => ( App::$isLoggedIn && ! App::$isMember ? '$1' : '' ),
+ 'enabled' => true,
+ ];
+ $api_key = Config::getValue( 'memberships/stripeSecret' );
+ if ( $api_key == 'sk_xxxxxxxxxxxxxxx' || empty($api_key) ) {
+ self::$stripe = false;
+ } else {
+ self::$stripe = new StripeClient( $api_key );
+ }
parent::__construct( $load );
}
- public function hasMemberAccess( $input ) {
+
+ public function groupHasMemberAccess( $activeGroup ) {
+ if ( $activeGroup->memberAccess == true ) {
+ return true;
+ }
+ return false;
+ }
+
+ public function userHasActiveMembership( $user_id ) {
+ self::$memberships = new Memberships;
+ $membership = self::$memberships->findActiveByUserID( $user_id );
+ if ( empty( $membership ) ) {
+ return false;
+ }
return true;
}
+
+ public static function webhookSetup() {
+ $root = Routes::getAddress();
+ // $root = "https://stripe.joeykimsey.com/";
+ $response = self::$stripe->webhookEndpoints->create([
+ 'enabled_events' => self::$webhookEvents,
+ 'url' => $root . 'api/stripe/webhook',
+ ]);
+ return $response;
+ }
+
+ // public static function webhookSetup() {
+ // $root = Routes::getAddress();
+ // $root = "https://stripe.joeykimsey.com/";
+ // $response = self::$stripe->webhookEndpoints->create([
+ // 'enabled_events' => self::$webhookEvents,
+ // 'url' => $root . 'api/stripe/webhook',
+ // ]);
+ // return $response;
+ // }
}
diff --git a/app/plugins/members/views/admin/memberships/list.html b/app/plugins/members/views/admin/memberships/list.html
new file mode 100644
index 0000000..981fe33
--- /dev/null
+++ b/app/plugins/members/views/admin/memberships/list.html
@@ -0,0 +1,44 @@
+Memberships
+{PAGINATION}
+
\ No newline at end of file
diff --git a/app/plugins/members/views/admin/products/create.html b/app/plugins/members/views/admin/products/create.html
new file mode 100644
index 0000000..c14a158
--- /dev/null
+++ b/app/plugins/members/views/admin/products/create.html
@@ -0,0 +1,34 @@
+Create Membership Product
+
\ No newline at end of file
diff --git a/app/plugins/members/views/admin/products/edit.html b/app/plugins/members/views/admin/products/edit.html
new file mode 100644
index 0000000..e211248
--- /dev/null
+++ b/app/plugins/members/views/admin/products/edit.html
@@ -0,0 +1,34 @@
+Edit Membership Product
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/plugins/members/views/admin/products/list.html b/app/plugins/members/views/admin/products/list.html
new file mode 100644
index 0000000..fe4f4af
--- /dev/null
+++ b/app/plugins/members/views/admin/products/list.html
@@ -0,0 +1,41 @@
+Membership Products
+{PAGINATION}
+
+
+ Create
+ Delete
+
\ No newline at end of file
diff --git a/app/plugins/members/views/admin/products/view.html b/app/plugins/members/views/admin/products/view.html
new file mode 100644
index 0000000..536842d
--- /dev/null
+++ b/app/plugins/members/views/admin/products/view.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
Membership Product
+
+
+
+
+
+
+
+ ID:
+ {ID}
+
+
+ Time submitted:
+ {DTC}{time}{/DTC}
+
+
+ IP:
+ {ip}
+
+
+ Email:
+ {email}
+
+
+ Name
+ {name}
+
+
+ Feedback
+
+
+ {feedback}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/plugins/members/views/admin/webhooks.html b/app/plugins/members/views/admin/webhooks.html
new file mode 100644
index 0000000..d895635
--- /dev/null
+++ b/app/plugins/members/views/admin/webhooks.html
@@ -0,0 +1,10 @@
+WARNING: Regenerating existing webhooks makes joey sad, don't do iit!
+
+
+
\ No newline at end of file
diff --git a/app/plugins/members/views/cancel.html b/app/plugins/members/views/cancel.html
new file mode 100644
index 0000000..579b488
--- /dev/null
+++ b/app/plugins/members/views/cancel.html
@@ -0,0 +1,21 @@
+
+
+
+
Are You Sure You Want to Cancel?
+
+ Cancelling your subscription means you'll miss out on exclusive features, updates, and benefits.
+
+
+ Consider staying to continue enjoying the full experience of our service.
+
+
+
+
+
\ No newline at end of file
diff --git a/app/plugins/members/views/landing1.html b/app/plugins/members/views/landing1.html
new file mode 100644
index 0000000..8399ef3
--- /dev/null
+++ b/app/plugins/members/views/landing1.html
@@ -0,0 +1,62 @@
+
+
+
+
Organize Your Bookmarks
+
Keep all your bookmarks organized with custom categories and tags.
+
+
+
Import and Export
+
Seamlessly import and export your bookmarks (paid plans).
+
+
+
Accessible Anywhere
+
Your bookmarks are securely stored and accessible from any device.
+
+
+
+
+
+
Pricing
+
+
+
+
+
Free
+
+
+
Basic bookmark storage
+
No import/export
+
Completely free
+
Sign Up
+
+
+
+
+
+
+
{name} Monthly
+
+
+
Import/export bookmarks
+
Advanced curation tools
+
{prettyPriceMonthly}/month
+
Sign Up
+
+
+
+
+
+
+
{name} Yearly
+
+
+
Save with annual billing
+
All features included
+
{prettyPriceYearly}/year
+
Sign Up
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/plugins/members/views/landing2.html b/app/plugins/members/views/landing2.html
new file mode 100644
index 0000000..67ffce3
--- /dev/null
+++ b/app/plugins/members/views/landing2.html
@@ -0,0 +1,38 @@
+
+ Why Choose Us?
+
+
+
Free Version
+
Basic storage for all your bookmarks, forever free.
+
+
+
Pro Features
+
Unlock powerful import/export and advanced organization.
+
+
+
+
+ Affordable Plans
+
+
+
+
{name} Monthly Plan
+
+
{prettyPriceMonthly}/month
+
All pro features unlocked
+
Get Started
+
+
+
+
+
+
{name} Yearly Plan
+
+
{prettyPriceYearly}/year
+
Save {prettySavings} annually!
+
Sign Up
+
+
+
+
+
\ No newline at end of file
diff --git a/app/plugins/members/views/manage.html b/app/plugins/members/views/manage.html
new file mode 100644
index 0000000..3b7a243
--- /dev/null
+++ b/app/plugins/members/views/manage.html
@@ -0,0 +1,35 @@
+Memberships
+
+
+
+ Name
+ status
+ Price
+ Start
+ End
+
+
+
+
+
+ {LOOP}
+
+ {productName}
+ {status}
+ {prettyPrice}
+ {DTC=date}{current_period_start}{/DTC}
+ {DTC=date}{current_period_end}{/DTC}
+
+
+
+ {/LOOP}
+ {ALT}
+
+
+ No results to show.
+
+
+ {/ALT}
+
+
+Manage Payment Method
\ No newline at end of file
diff --git a/app/plugins/members/views/pause.html b/app/plugins/members/views/pause.html
new file mode 100644
index 0000000..ec667e1
--- /dev/null
+++ b/app/plugins/members/views/pause.html
@@ -0,0 +1,21 @@
+
+
+
+
Are You Sure You Want to Pause?
+
+ Pausing your subscription means you'll miss out on exclusive features, updates, and benefits.
+
+
+ Consider staying to continue enjoying the full experience of our service.
+
+
+
+
+
\ No newline at end of file
diff --git a/app/plugins/members/views/paymentcanceled.html b/app/plugins/members/views/paymentcanceled.html
new file mode 100644
index 0000000..ec98c3c
--- /dev/null
+++ b/app/plugins/members/views/paymentcanceled.html
@@ -0,0 +1,4 @@
+
+
Take your time, its not a sprint, its a marathon.
+
Its ok, no-one wants to checkout too fast, its embarrassing.
+
\ No newline at end of file
diff --git a/app/plugins/members/views/paymentcomplete.html b/app/plugins/members/views/paymentcomplete.html
new file mode 100644
index 0000000..962ac77
--- /dev/null
+++ b/app/plugins/members/views/paymentcomplete.html
@@ -0,0 +1,5 @@
+
+
Thanks for joining!
+
Its people like you who keep the pixels on around here.
+ Its people like me who tell you life is unfair and it could take up to an hour for your membership to activate.
+
\ No newline at end of file
diff --git a/app/plugins/members/views/upgrade.html b/app/plugins/members/views/upgrade.html
new file mode 100644
index 0000000..f28404e
--- /dev/null
+++ b/app/plugins/members/views/upgrade.html
@@ -0,0 +1,27 @@
+
+
+
+
Upgrade to a Yearly Plan
+
+ Save more and enjoy uninterrupted access to all features with our yearly plan!
+
+
+ Upgrading now means you'll save X% compared to the monthly plan.
+ Stay committed and make the most of our service.
+
+
+
+
+ Upgrade to Yearly
+
+
+
+
+ Stay on Monthly
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 38180ab..f1868db 100644
--- a/composer.json
+++ b/composer.json
@@ -20,11 +20,12 @@
],
"require":
{
+ "components/jquery": "1.9.*",
"fortawesome/font-awesome": "4.7",
+ "stripe/stripe-php": "^16.3",
"thetempusproject/bedrock": "1.0.10",
"thetempusproject/canary": "1.0.5",
"thetempusproject/houdini": "1.0.8",
- "components/jquery": "1.9.*",
"twbs/bootstrap": "3.3.7"
},
"autoload":
diff --git a/composer.lock b/composer.lock
index 7060596..f0820a1 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "4d87b4a533236913cbbc0e75664016de",
+ "content-hash": "017241adb07073e9ec4d3fa64ae5a66c",
"packages": [
{
"name": "components/jquery",
@@ -240,6 +240,65 @@
"abandoned": "oomphinc/composer-installers-extender",
"time": "2013-08-31T23:46:48+00:00"
},
+ {
+ "name": "stripe/stripe-php",
+ "version": "v16.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/stripe/stripe-php.git",
+ "reference": "48af6bc64ca8157b3fdce100e856069963bac466"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/stripe/stripe-php/zipball/48af6bc64ca8157b3fdce100e856069963bac466",
+ "reference": "48af6bc64ca8157b3fdce100e856069963bac466",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "php": ">=5.6.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "3.5.0",
+ "phpstan/phpstan": "^1.2",
+ "phpunit/phpunit": "^5.7 || ^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Stripe\\": "lib/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Stripe and contributors",
+ "homepage": "https://github.com/stripe/stripe-php/contributors"
+ }
+ ],
+ "description": "Stripe PHP Library",
+ "homepage": "https://stripe.com/",
+ "keywords": [
+ "api",
+ "payment processing",
+ "stripe"
+ ],
+ "support": {
+ "issues": "https://github.com/stripe/stripe-php/issues",
+ "source": "https://github.com/stripe/stripe-php/tree/v16.3.0"
+ },
+ "time": "2024-11-20T23:30:16+00:00"
+ },
{
"name": "symfony/process",
"version": "v3.4.47",
@@ -540,5 +599,5 @@
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
- "plugin-api-version": "2.6.0"
+ "plugin-api-version": "2.3.0"
}