Initial add Stripe Auto-payment with saved card

This commit is contained in:
wrongecho 2025-01-12 20:56:38 +00:00
parent e7f19dc69d
commit a15081ac67
7 changed files with 440 additions and 24 deletions

View File

@ -2404,10 +2404,16 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) {
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.7.5'");
}
// if (CURRENT_DATABASE_VERSION == '1.7.5') {
// // Insert queries here required to update to DB version 1.7.6
if (CURRENT_DATABASE_VERSION == '1.7.5') {
mysqli_query($mysqli, "CREATE TABLE `client_stripe` (`client_id` INT(11) NOT NULL, `stripe_id` VARCHAR(255) NOT NULL, `stripe_pm` varchar(255) NULL) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci; ");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.7.6'");
}
// if (CURRENT_DATABASE_VERSION == '1.7.6') {
// // Insert queries here required to update to DB version 1.7.7
// // Then, update the database to the next sequential version
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.7.6'");
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.7.7'");
// }
} else {

View File

@ -5,4 +5,4 @@
* It is used in conjunction with database_updates.php
*/
DEFINE("LATEST_DATABASE_VERSION", "1.7.5");
DEFINE("LATEST_DATABASE_VERSION", "1.7.6");

11
db.sql
View File

@ -342,6 +342,17 @@ CREATE TABLE `client_notes` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `client_stripe`
--
DROP TABLE IF EXISTS `client_stripe`;
CREATE TABLE IF NOT EXISTS `client_stripe` (
`client_id` int(11) NOT NULL,
`stripe_id` varchar(255) NOT NULL,
`stripe_pm` varchar(255) NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- Table structure for table `client_tags`
--

View File

@ -0,0 +1,23 @@
// Initialize Stripe.js
const stripe = Stripe('pk_test_51OTpmkHRGkC845Mqz0zM2A1pjnnXwOyD5tyPzWnRwVthuizNjuBIjoYgMHBMLQBuegrUXQpIyX4yr1fNMo7QzCs500bBnFJgEr');
initialize();
// Fetch Checkout Session and retrieve the client secret
async function initialize() {
const fetchClientSecret = async () => {
const response = await fetch("/portal/portal_post.php?create_stripe_checkout", {
method: "POST",
});
const { clientSecret } = await response.json();
return clientSecret;
};
// Initialize Checkout
const checkout = await stripe.initEmbeddedCheckout({
fetchClientSecret,
});
// Mount Checkout
checkout.mount('#checkout');
}

126
portal/autopay.php Normal file
View File

@ -0,0 +1,126 @@
<?php
/*
* Client Portal
* Auto-pay configuration for PTC/finance contacts
*/
require_once "inc_portal.php";
if ($session_contact_primary == 0 && !$session_contact_is_billing_contact) {
header("Location: portal_post.php?logout");
exit();
}
// Initialize stripe
require_once '../vendor/stripe-php-10.5.0/init.php';
// Get Stripe vars
$stripe_vars = mysqli_fetch_array(mysqli_query($mysqli, "SELECT config_stripe_enable, config_stripe_publishable, config_stripe_secret FROM settings WHERE company_id = 1"));
$config_stripe_enable = intval($stripe_vars['config_stripe_enable']);
$config_stripe_publishable = nullable_htmlentities($stripe_vars['config_stripe_publishable']);
$config_stripe_secret = nullable_htmlentities($stripe_vars['config_stripe_secret']);
// Get client's StripeID from database
$stripe_client_details = mysqli_fetch_array(mysqli_query($mysqli, "SELECT * FROM client_stripe WHERE client_id = $session_client_id LIMIT 1"));
if ($stripe_client_details) {
$stripe_id = sanitizeInput($stripe_client_details['stripe_id']);
$stripe_pm = sanitizeInput($stripe_client_details['stripe_pm']);
}
// Stripe not enabled in settings
if (!$config_stripe_enable || !$config_stripe_publishable || !$config_stripe_secret) {
echo "Stripe payment error - Stripe is not enabled, please talk to your helpdesk for further information.";
include_once 'portal_footer.php';
exit();
}
?>
<h3>AutoPay</h3>
<div class="row">
<div class="col-md-10">
<!-- Setup pt1: Stripe ID not found / auto-payment not configured -->
<?php if (!$stripe_client_details || empty($stripe_id)) { ?>
<b>Save card details</b><br>
In order to set up automatic payments, you must create a customer record in Stripe.<br>
First, you must authorize Stripe to store your card details for the purpose of automatic payment.
<br><br>
<div class="col-5">
<form action="portal_post.php" method="POST">
<div class="form-group">
<div class="custom-control custom-checkbox">
<input class="custom-control-input" type="checkbox" id="consent" name="consent" value="1" required>
<label for="consent" class="custom-control-label">
I grant consent for automatic payments
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="form-control btn-success" name="create_stripe_customer">Create Stripe Customer Record</button>
</div>
</form>
</div>
<?php }
// Setup pt2: Stripe ID found / payment may be configured -->
elseif (empty($stripe_pm)) { ?>
<b>Save card details</b><br>
Please add the payment details you would like to save.<br>
By adding payment details here, you grant consent for future automatic payments of invoices.<br><br>
<script src="https://js.stripe.com/v3/"></script>
<script src="../js/autopay_setup_stripe.js"></script>
<input type="hidden" id="stripe_publishable_key" value="<?php echo $config_stripe_publishable ?>">
<div id="checkout">
<!-- Checkout will insert the payment form here -->
</div>
<?php }
// Manage the saved card
else { ?>
<b>Manage saved card details</b>
<?php
// Initialize
$stripe = new \Stripe\StripeClient($config_stripe_secret);
$payment_method = $stripe->customers->retrievePaymentMethod(
$stripe_id,
$stripe_pm,
[]
);
$card_name = nullable_htmlentities($payment_method->billing_details->name);
$card_brand = nullable_htmlentities($payment_method->card->display_brand);
$card_last4 = nullable_htmlentities($payment_method->card->last4);
$card_expires = nullable_htmlentities($payment_method->card->exp_month) . "/" . nullable_htmlentities($payment_method->card->exp_year);
?>
<ul><li><?php echo "$card_brand card ending in $card_last4 issued to $card_name, expires $card_expires"; ?></li></ul>
<hr>
<b>Actions</b><br>
- <a href="portal_post.php?stripe_remove_card&pm=<?php echo $stripe_pm; ?>">Remove saved card</a>
<?php } ?>
</div>
</div>
<?php
require_once "portal_footer.php";

View File

@ -50,28 +50,34 @@ header("X-Frame-Options: DENY"); // Legacy
</li>
<?php if (($session_contact_primary == 1 || $session_contact_is_billing_contact) && $config_module_enable_accounting == 1) { ?>
<li class="nav-item">
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "invoices.php") {echo "active";} ?>" href="invoices.php">Invoices</a>
</li>
<li class="nav-item">
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "quotes.php") {echo "active";} ?>" href="quotes.php">Quotes</a>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle <?php echo (in_array(basename($_SERVER['PHP_SELF']), ['invoices.php', 'quotes.php']) ? 'active' : ''); ?>" href="#" id="navbarDropdown1" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Finance
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown1">
<a class="dropdown-item" href="invoices.php">Invoices</a>
<a class="dropdown-item" href="quotes.php">Quotes</a>
<!-- <a class="dropdown-item" href="autopay.php">Auto Payment</a>-->
</div>
</li>
<?php } ?>
<?php if ($config_module_enable_itdoc && ($session_contact_primary == 1 || $session_contact_is_technical_contact)) { ?>
<li class="nav-item">
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "documents.php") {echo "active";} ?>" href="documents.php">Documents</a>
</li>
<li class="nav-item">
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "contacts.php") {echo "active";} ?>" href="contacts.php">Contacts</a>
</li>
<li class="nav-item">
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "domains.php") {echo "active";} ?>" href="domains.php">Domains</a>
</li>
<li class="nav-item">
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "certificates.php") {echo "active";} ?>" href="certificates.php">Certificates</a>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle <?php echo (in_array(basename($_SERVER['PHP_SELF']), ['documents.php', 'contacts.php', 'domains.php', 'certificates.php']) ? 'active' : ''); ?>" href="#" id="navbarDropdown1" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Technical
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown1">
<a class="dropdown-item" href="contacts.php">Contacts</a>
<a class="dropdown-item" href="documents.php">Documents</a>
<a class="dropdown-item" href="domains.php">Domains</a>
<a class="dropdown-item" href="certificates.php">Certificates</a>
<a class="dropdown-item" href="ticket_view_all.php">All tickets</a>
</div>
</li>
<?php } ?>
</ul>
</ul><!-- End left nav -->
<ul class="nav navbar-nav pull-right">
<li class="nav-item dropdown">
@ -100,7 +106,6 @@ header("X-Frame-Options: DENY"); // Legacy
<img src="<?php echo "../uploads/clients/$session_client_id/$session_contact_photo"; ?>" alt="..." height="50" width="50" class="img-circle img-responsive">
<?php } else { ?>
<span class="fa-stack fa-2x rounded-left">
<i class="fa fa-circle fa-stack-2x text-secondary"></i>
<span class="fa fa-stack-1x text-white"><?php echo $session_contact_initials; ?></span>

View File

@ -4,8 +4,11 @@
* Process GET/POST requests
*/
require_once "inc_portal.php";
require_once '../config.php';
require_once '../get_settings.php';
require_once '../functions.php';
require_once 'check_login.php';
require_once 'portal_functions.php';
if (isset($_POST['add_ticket'])) {
@ -327,6 +330,12 @@ if (isset($_POST['edit_profile'])) {
}
if (isset($_POST['add_contact'])) {
if ($session_contact_primary == 0 && !$session_contact_is_technical_contact) {
header("Location: portal_post.php?logout");
exit();
}
$contact_name = sanitizeInput($_POST['contact_name']);
$contact_email = sanitizeInput($_POST['contact_email']);
$contact_technical = intval($_POST['contact_technical']);
@ -368,6 +377,12 @@ if (isset($_POST['add_contact'])) {
}
if (isset($_POST['edit_contact'])) {
if ($session_contact_primary == 0 && !$session_contact_is_technical_contact) {
header("Location: portal_post.php?logout");
exit();
}
$contact_id = intval($_POST['contact_id']);
$contact_name = sanitizeInput($_POST['contact_name']);
$contact_email = sanitizeInput($_POST['contact_email']);
@ -413,3 +428,233 @@ if (isset($_POST['edit_contact'])) {
customAction('contact_update', $contact_id);
}
if (isset($_POST['create_stripe_customer'])) {
if ($session_contact_primary == 0 && !$session_contact_is_billing_contact) {
header("Location: portal_post.php?logout");
exit();
}
if (!$config_stripe_enable) {
header("Location: autopay.php");
exit();
}
// Initialize stripe
require_once '../vendor/stripe-php-10.5.0/init.php';
// Get Stripe vars
$stripe_vars = mysqli_fetch_array(mysqli_query($mysqli, "SELECT config_stripe_enable, config_stripe_publishable, config_stripe_secret FROM settings WHERE company_id = 1"));
$config_stripe_enable = intval($stripe_vars['config_stripe_enable']);
$config_stripe_publishable = nullable_htmlentities($stripe_vars['config_stripe_publishable']);
$config_stripe_secret = nullable_htmlentities($stripe_vars['config_stripe_secret']);
// Get client's StripeID from database (should be none)
$stripe_client_details = mysqli_fetch_array(mysqli_query($mysqli, "SELECT stripe_id FROM client_stripe WHERE client_id = $session_client_id LIMIT 1"));
if (!$stripe_client_details) {
// Initiate Stripe
$stripe = new \Stripe\StripeClient($config_stripe_secret);
// Create customer
$customer = $stripe->customers->create([
'name' => $session_client_name,
'email' => $session_contact_email,
'metadata' => [
'itflow_client_id' => $session_client_id,
'consent' => $session_contact_name
]
]);
// Get & Store customer ID
$stripe_id = sanitizeInput($customer->id);
mysqli_query($mysqli, "INSERT INTO client_stripe SET client_id = $session_client_id, stripe_id = '$stripe_id'");
// Logging
logAction("Stripe", "Create", "$session_contact_name created Stripe customer for $session_client_name as $stripe_id and authorised future automatic payments", $session_client_id, $session_client_id);
$_SESSION['alert_message'] = "Stripe customer created, thank you for your consent";
} else {
$_SESSION['alert_type'] = "danger";
$_SESSION['alert_message'] = "Stripe customer already exists";
}
header('Location: autopay.php');
}
if (isset($_GET['create_stripe_checkout'])) {
if ($session_contact_primary == 0 && !$session_contact_is_billing_contact) {
header("Location: portal_post.php?logout");
exit();
}
if (!$config_stripe_enable) {
header("Location: autopay.php");
exit();
}
// Initialize stripe
require_once '../vendor/stripe-php-10.5.0/init.php';
// Get Stripe vars
$stripe_vars = mysqli_fetch_array(mysqli_query($mysqli, "SELECT config_stripe_enable, config_stripe_publishable, config_stripe_secret FROM settings WHERE company_id = 1"));
$config_stripe_enable = intval($stripe_vars['config_stripe_enable']);
$config_stripe_publishable = nullable_htmlentities($stripe_vars['config_stripe_publishable']);
$config_stripe_secret = nullable_htmlentities($stripe_vars['config_stripe_secret']);
// Currency
$client_currency_details = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT client_currency_code FROM clients WHERE client_id = $session_client_id LIMIT 1"));
$client_currency = $client_currency_details['client_currency_code'];
$stripe = new \Stripe\StripeClient($config_stripe_secret);
$return_url = "$config_base_url/portal/portal_post.php?stripe_save_card&session_id={CHECKOUT_SESSION_ID}";
$checkout_session = $stripe->checkout->sessions->create([
'currency' => $client_currency,
'mode' => 'setup',
'ui_mode' => 'embedded',
'return_url' => $return_url,
]);
echo json_encode(array('clientSecret' => $checkout_session->client_secret));
}
if (isset($_GET['stripe_save_card'])) {
if ($session_contact_primary == 0 && !$session_contact_is_billing_contact) {
header("Location: portal_post.php?logout");
exit();
}
if (!$config_stripe_enable) {
header("Location: autopay.php");
exit();
}
// Get session ID from URL
$checkout_session_id = sanitizeInput($_GET['session_id']);
// Initialize stripe
require_once '../vendor/stripe-php-10.5.0/init.php';
// Get Stripe vars
$stripe_vars = mysqli_fetch_array(mysqli_query($mysqli, "SELECT config_stripe_enable, config_stripe_publishable, config_stripe_secret FROM settings WHERE company_id = 1"));
$config_stripe_enable = intval($stripe_vars['config_stripe_enable']);
$config_stripe_publishable = nullable_htmlentities($stripe_vars['config_stripe_publishable']);
$config_stripe_secret = nullable_htmlentities($stripe_vars['config_stripe_secret']);
// Get client's StripeID from database
$stripe_client_details = mysqli_fetch_array(mysqli_query($mysqli, "SELECT stripe_id FROM client_stripe WHERE client_id = $session_client_id LIMIT 1"));
$client_stripe_id = sanitizeInput($stripe_client_details['stripe_id']);
// Initialize
$stripe = new \Stripe\StripeClient($config_stripe_secret);
// Retrieve checkout session
$checkout_session = $stripe->checkout->sessions->retrieve($checkout_session_id,[]);
// Get setup intent
$setup_intent_id = $checkout_session->setup_intent;
// Retrieve the setup intent details
$setup_intent = $stripe->setupIntents->retrieve($setup_intent_id, []);
// Get the payment method token
$payment_method = sanitizeInput($setup_intent->payment_method);
// Attach the payment method to the client in Stripe
$stripe->paymentMethods->attach($payment_method, ['customer' => $client_stripe_id]);
// Update ITFlow
mysqli_query($mysqli, "UPDATE client_stripe SET stripe_pm = '$payment_method' WHERE client_id = $session_client_id LIMIT 1");
// Get some card details for the email/logging
$payment_method_details = $stripe->paymentMethods->retrieve($payment_method);
$card_info = sanitizeInput($payment_method_details->card->display_brand) . " " . sanitizeInput($payment_method_details->card->last4);
// Send email confirmation
$sql_settings = mysqli_query($mysqli, "SELECT * FROM settings WHERE company_id = 1");
$row = mysqli_fetch_array($sql_settings);
$config_smtp_host = $row['config_smtp_host'];
$config_smtp_port = intval($row['config_smtp_port']);
$config_smtp_encryption = $row['config_smtp_encryption'];
$config_smtp_username = $row['config_smtp_username'];
$config_smtp_password = $row['config_smtp_password'];
$config_invoice_from_name = sanitizeInput($row['config_invoice_from_name']);
$config_invoice_from_email = sanitizeInput($row['config_invoice_from_email']);
$config_invoice_paid_notification_email = sanitizeInput($row['config_invoice_paid_notification_email']);
$config_base_url = sanitizeInput($config_base_url);
if (!empty($config_smtp_host)) {
$subject = "Payment method saved";
$body = "Hello $session_contact_name,<br><br>Were writing to confirm that your payment details have been securely stored with Stripe, our trusted payment processor.<br><br>By agreeing to save your payment information, you have authorized us to automatically bill your card ($card_info) for any future invoices. The payment details youve provided are securely stored with Stripe and will be used solely for invoices. We do not have access to your full card details.<br><br>You may update or remove your payment information at any time using the portal.<br><br>Thank you for your business!<br><br>~<br>$company_name - Billing<br>$config_invoice_from_email<br>$company_phone";
$data = [
[
'from' => $config_invoice_from_email,
'from_name' => $config_invoice_from_name,
'recipient' => $session_contact_email,
'recipient_name' => $session_contact_name,
'subject' => $subject,
'body' => $body,
]
];
$mail = addToMailQueue($mysqli, $data);
}
// Logging
logAction("Stripe", "Update", "$session_contact_name added saved card ($card_info) for future automatic payments (PM: $payment_method)", $session_client_id, $session_client_id);
// Redirect
$_SESSION['alert_message'] = "Card saved - thank you";
header('Location: autopay.php');
}
if (isset($_GET['stripe_remove_card'])) {
if ($session_contact_primary == 0 && !$session_contact_is_billing_contact) {
header("Location: portal_post.php?logout");
exit();
}
if (!$config_stripe_enable) {
header("Location: autopay.php");
exit();
}
$payment_method = sanitizeInput($_GET['pm']);
// Initialize stripe
require_once '../vendor/stripe-php-10.5.0/init.php';
// Get Stripe vars
$stripe_vars = mysqli_fetch_array(mysqli_query($mysqli, "SELECT config_stripe_enable, config_stripe_publishable, config_stripe_secret FROM settings WHERE company_id = 1"));
$config_stripe_enable = intval($stripe_vars['config_stripe_enable']);
$config_stripe_publishable = nullable_htmlentities($stripe_vars['config_stripe_publishable']);
$config_stripe_secret = nullable_htmlentities($stripe_vars['config_stripe_secret']);
$stripe = new \Stripe\StripeClient($config_stripe_secret);
// Detach PM
$stripe->paymentMethods->detach($payment_method, []);
// Remove from ITFlow
mysqli_query($mysqli, "UPDATE client_stripe SET stripe_pm = NULL WHERE client_id = $session_client_id LIMIT 1");
//Logging & Redirect
logAction("Stripe", "Update", "$session_contact_name deleted saved card (PM: $payment_method)", $session_client_id, $session_client_id);
$_SESSION['alert_message'] = "Card removed";
header('Location: autopay.php');
}