Skip to content

Commit 5567dfe

Browse files
authored
Merge pull request #1148 from itflow-org/stripe-autopay
Initial add Stripe Auto-payment with saved card
2 parents f774218 + 9a36ad2 commit 5567dfe

13 files changed

+911
-69
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
require_once "includes/inc_all_admin.php";
4+
5+
$stripe_clients_sql = mysqli_query($mysqli, "SELECT * FROM client_stripe LEFT JOIN clients ON client_stripe.client_id = clients.client_id");
6+
?>
7+
8+
<div class="card card-dark">
9+
<div class="card-header py-3">
10+
<h3 class="card-title"><i class="fas fa-fw fa-credit-card mr-2"></i>Online Payment - Client info</h3>
11+
</div>
12+
13+
<div class="card-body">
14+
15+
<table class="table tabled-bordered border border-dark">
16+
<thead class="thead-dark">
17+
<tr>
18+
<th>Client</th>
19+
<th>Stripe Customer ID</th>
20+
<th>Stripe Payment ID</th>
21+
</tr>
22+
</thead>
23+
<tbody>
24+
25+
<?php
26+
while ($row = mysqli_fetch_array($stripe_clients_sql)) {
27+
$client_id = intval($row['client_id']);
28+
$client_name = sanitizeInput($row['client_name']);
29+
$stripe_id = sanitizeInput($row['stripe_id']);
30+
$stripe_pm = sanitizeInput($row['stripe_pm']);
31+
32+
?>
33+
34+
<tr>
35+
<td><?php echo "$client_name ($client_id)" ?></td>
36+
<td><?php echo $stripe_id; ?></td>
37+
<td><?php echo $stripe_pm ?></td>
38+
</tr>
39+
40+
<?php } ?>
41+
42+
</tbody>
43+
</table>
44+
45+
</div>
46+
47+
</div>
48+
49+
<?php
50+
require_once "includes/footer.php";
51+

database_updates.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2404,10 +2404,16 @@ function processFile($file_path, $file_name, $mysqli) {
24042404
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.7.5'");
24052405
}
24062406

2407-
// if (CURRENT_DATABASE_VERSION == '1.7.5') {
2408-
// // Insert queries here required to update to DB version 1.7.6
2407+
if (CURRENT_DATABASE_VERSION == '1.7.5') {
2408+
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; ");
2409+
2410+
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.7.6'");
2411+
}
2412+
2413+
// if (CURRENT_DATABASE_VERSION == '1.7.6') {
2414+
// // Insert queries here required to update to DB version 1.7.7
24092415
// // Then, update the database to the next sequential version
2410-
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.7.6'");
2416+
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '1.7.7'");
24112417
// }
24122418

24132419
} else {

database_version.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
* It is used in conjunction with database_updates.php
66
*/
77

8-
DEFINE("LATEST_DATABASE_VERSION", "1.7.5");
8+
DEFINE("LATEST_DATABASE_VERSION", "1.7.6");

db.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,17 @@ CREATE TABLE `client_notes` (
342342
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
343343
/*!40101 SET character_set_client = @saved_cs_client */;
344344

345+
--
346+
-- Table structure for table `client_stripe`
347+
--
348+
349+
DROP TABLE IF EXISTS `client_stripe`;
350+
CREATE TABLE IF NOT EXISTS `client_stripe` (
351+
`client_id` int(11) NOT NULL,
352+
`stripe_id` varchar(255) NOT NULL,
353+
`stripe_pm` varchar(255) NULL
354+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
355+
345356
--
346357
-- Table structure for table `client_tags`
347358
--

guest/guest_pay_invoice_stripe.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@
279279

280280
// Add Payment to History
281281
mysqli_query($mysqli, "INSERT INTO payments SET payment_date = '$pi_date', payment_amount = $pi_amount_paid, payment_currency_code = '$pi_currency', payment_account_id = $config_stripe_account, payment_method = 'Stripe', payment_reference = 'Stripe - $pi_id', payment_invoice_id = $invoice_id");
282-
mysqli_query($mysqli, "INSERT INTO history SET history_status = 'Paid', history_description = 'Payment added - $ip - $os - $browser', history_invoice_id = $invoice_id");
282+
mysqli_query($mysqli, "INSERT INTO history SET history_status = 'Paid', history_description = 'Online Payment added (client) - $ip - $os - $browser', history_invoice_id = $invoice_id");
283283

284284
// Notify
285285
appNotify("Invoice Paid", "Invoice $invoice_prefix$invoice_number has been paid by $client_name - $ip - $os - $browser", "invoice.php?invoice_id=$invoice_id", $pi_client_id);
@@ -313,7 +313,7 @@
313313

314314
if (!empty($config_smtp_host)) {
315315
$subject = "Payment Received - Invoice $invoice_prefix$invoice_number";
316-
$body = "Hello $contact_name,<br><br>We have received your payment in the amount of " . $pi_currency . $pi_amount_paid . " for invoice <a href=\'https://$config_base_url/guest/guest_view_invoice.php?invoice_id=$invoice_id&url_key=$invoice_url_key\'>$invoice_prefix$invoice_number</a>. Please keep this email as a receipt for your records.<br><br>Amount: " . numfmt_format_currency($currency_format, $pi_amount_paid, $invoice_currency_code) . "<br>Balance: " . numfmt_format_currency($currency_format, '0', $invoice_currency_code) . "<br><br>Thank you for your business!<br><br><br>~<br>$company_name - Billing<br>$config_invoice_from_email<br>$company_phone";
316+
$body = "Hello $contact_name,<br><br>We have received online payment for the amount of " . $pi_currency . $pi_amount_paid . " for invoice <a href=\'https://$config_base_url/guest/guest_view_invoice.php?invoice_id=$invoice_id&url_key=$invoice_url_key\'>$invoice_prefix$invoice_number</a>. Please keep this email as a receipt for your records.<br><br>Amount: " . numfmt_format_currency($currency_format, $pi_amount_paid, $invoice_currency_code) . "<br><br>Thank you for your business!<br><br><br>~<br>$company_name - Billing<br>$config_invoice_from_email<br>$company_phone";
317317

318318
$data = [
319319
[
@@ -330,7 +330,7 @@
330330
// Email the internal notification address too
331331
if (!empty($config_invoice_paid_notification_email)) {
332332
$subject = "Payment Received - $client_name - Invoice $invoice_prefix$invoice_number";
333-
$body = "Hello, <br><br>This is a notification that an invoice has been paid in ITFlow. Below is a copy of the receipt sent to the client:-<br><br>--------<br><br>Hello $contact_name,<br><br>We have received your payment in the amount of " . $pi_currency . $pi_amount_paid . " for invoice <a href=\'https://$config_base_url/guest/guest_view_invoice.php?invoice_id=$invoice_id&url_key=$invoice_url_key\'>$invoice_prefix$invoice_number</a>. Please keep this email as a receipt for your records.<br><br>Amount: " . numfmt_format_currency($currency_format, $pi_amount_paid, $invoice_currency_code) . "<br>Balance: " . numfmt_format_currency($currency_format, '0', $invoice_currency_code) . "<br><br>Thank you for your business!<br><br><br>~<br>$company_name - Billing<br>$config_invoice_from_email<br>$company_phone";
333+
$body = "Hello, <br><br>This is a notification that an invoice has been paid in ITFlow. Below is a copy of the receipt sent to the client:-<br><br>--------<br><br>Hello $contact_name,<br><br>We have received online payment for the amount of " . $pi_currency . $pi_amount_paid . " for invoice <a href=\'https://$config_base_url/guest/guest_view_invoice.php?invoice_id=$invoice_id&url_key=$invoice_url_key\'>$invoice_prefix$invoice_number</a>. Please keep this email as a receipt for your records.<br><br>Amount: " . numfmt_format_currency($currency_format, $pi_amount_paid, $invoice_currency_code) . "<br><br>Thank you for your business!<br><br><br>~<br>$company_name - Billing<br>$config_invoice_from_email<br>$company_phone";
334334

335335
$data[] = [
336336
'from' => $config_invoice_from_email,

invoice.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,17 @@
145145
$json_products = json_encode($products);
146146
}
147147

148+
// Payment with saved card (auto-pay)
149+
if ($config_stripe_enable) {
150+
$stripe_client_details = mysqli_fetch_array(mysqli_query($mysqli, "SELECT * FROM client_stripe WHERE client_id = $client_id LIMIT 1"));
151+
if ($stripe_client_details) {
152+
$stripe_id = sanitizeInput($stripe_client_details['stripe_id']);
153+
$stripe_pm = sanitizeInput($stripe_client_details['stripe_pm']);
154+
}
155+
}
156+
157+
158+
148159
?>
149160

150161
<ol class="breadcrumb d-print-none">
@@ -197,6 +208,11 @@
197208
<a class="btn btn-success" href="#" data-toggle="modal" data-target="#addPaymentModal">
198209
<i class="fa fa-fw fa-credit-card mr-2"></i>Add Payment
199210
</a>
211+
<?php if ($invoice_status !== 'Partial' && $config_stripe_enable && $stripe_id && $stripe_pm) { ?>
212+
<a class="btn btn-primary confirm-link" href="post.php?add_payment_stripe&invoice_id=<?php echo $invoice_id; ?>&csrf_token=<?php echo $_SESSION['csrf_token']; ?>">
213+
<i class="fa fa-fw fa-credit-card mr-2"></i>Pay via saved card
214+
</a>
215+
<?php } ?>
200216
<?php } ?>
201217

202218
<?php if (($invoice_status == 'Sent' || $invoice_status == 'Viewed') && $invoice_amount == 0 && $invoice_status !== 'Non-Billable') { ?>

js/autopay_setup_stripe.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Initialize Stripe.js
2+
const stripe = Stripe('pk_test_51OTpmkHRGkC845Mqz0zM2A1pjnnXwOyD5tyPzWnRwVthuizNjuBIjoYgMHBMLQBuegrUXQpIyX4yr1fNMo7QzCs500bBnFJgEr');
3+
4+
initialize();
5+
6+
// Fetch Checkout Session and retrieve the client secret
7+
async function initialize() {
8+
const fetchClientSecret = async () => {
9+
const response = await fetch("/portal/portal_post.php?create_stripe_checkout", {
10+
method: "POST",
11+
});
12+
const { clientSecret } = await response.json();
13+
return clientSecret;
14+
};
15+
16+
// Initialize Checkout
17+
const checkout = await stripe.initEmbeddedCheckout({
18+
fetchClientSecret,
19+
});
20+
21+
// Mount Checkout
22+
checkout.mount('#checkout');
23+
}

portal/autopay.php

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
/*
3+
* Client Portal
4+
* Auto-pay configuration for PTC/finance contacts
5+
*/
6+
7+
require_once "inc_portal.php";
8+
9+
if ($session_contact_primary == 0 && !$session_contact_is_billing_contact) {
10+
header("Location: portal_post.php?logout");
11+
exit();
12+
}
13+
14+
// Initialize stripe
15+
require_once '../vendor/stripe-php-10.5.0/init.php';
16+
17+
// Get Stripe vars
18+
$stripe_vars = mysqli_fetch_array(mysqli_query($mysqli, "SELECT config_stripe_enable, config_stripe_publishable, config_stripe_secret FROM settings WHERE company_id = 1"));
19+
$config_stripe_enable = intval($stripe_vars['config_stripe_enable']);
20+
$config_stripe_publishable = nullable_htmlentities($stripe_vars['config_stripe_publishable']);
21+
$config_stripe_secret = nullable_htmlentities($stripe_vars['config_stripe_secret']);
22+
23+
// Get client's StripeID from database
24+
$stripe_client_details = mysqli_fetch_array(mysqli_query($mysqli, "SELECT * FROM client_stripe WHERE client_id = $session_client_id LIMIT 1"));
25+
if ($stripe_client_details) {
26+
$stripe_id = sanitizeInput($stripe_client_details['stripe_id']);
27+
$stripe_pm = sanitizeInput($stripe_client_details['stripe_pm']);
28+
}
29+
30+
// Stripe not enabled in settings
31+
if (!$config_stripe_enable || !$config_stripe_publishable || !$config_stripe_secret) {
32+
echo "Stripe payment error - Stripe is not enabled, please talk to your helpdesk for further information.";
33+
include_once 'portal_footer.php';
34+
exit();
35+
}
36+
37+
?>
38+
39+
<h3>AutoPay</h3>
40+
<div class="row">
41+
42+
<div class="col-md-10">
43+
44+
<!-- Setup pt1: Stripe ID not found / auto-payment not configured -->
45+
<?php if (!$stripe_client_details || empty($stripe_id)) { ?>
46+
47+
<b>Save card details</b><br>
48+
In order to set up automatic payments, you must create a customer record in Stripe.<br>
49+
First, you must authorize Stripe to store your card details for the purpose of automatic payment.
50+
<br><br>
51+
52+
<div class="col-5">
53+
<form action="portal_post.php" method="POST">
54+
55+
<div class="form-group">
56+
<div class="custom-control custom-checkbox">
57+
<input class="custom-control-input" type="checkbox" id="consent" name="consent" value="1" required>
58+
<label for="consent" class="custom-control-label">
59+
I grant consent for automatic payments
60+
</label>
61+
</div>
62+
</div>
63+
64+
<div class="form-group">
65+
<button type="submit" class="form-control btn-success" name="create_stripe_customer">Create Stripe Customer Record</button>
66+
</div>
67+
</form>
68+
</div>
69+
70+
<?php }
71+
72+
// Setup pt2: Stripe ID found / payment may be configured -->
73+
elseif (empty($stripe_pm)) { ?>
74+
75+
<b>Save card details</b><br>
76+
Please add the payment details you would like to save.<br>
77+
By adding payment details here, you grant consent for future automatic payments of invoices.<br><br>
78+
79+
<script src="https://js.stripe.com/v3/"></script>
80+
<script src="../js/autopay_setup_stripe.js"></script>
81+
<input type="hidden" id="stripe_publishable_key" value="<?php echo $config_stripe_publishable ?>">
82+
<div id="checkout">
83+
<!-- Checkout will insert the payment form here -->
84+
</div>
85+
86+
<?php }
87+
88+
// Manage the saved card
89+
else { ?>
90+
91+
<b>Manage saved card details</b>
92+
93+
<?php
94+
95+
try {
96+
// Initialize
97+
$stripe = new \Stripe\StripeClient($config_stripe_secret);
98+
99+
// Get payment method info (last 4 digits etc)
100+
$payment_method = $stripe->customers->retrievePaymentMethod(
101+
$stripe_id,
102+
$stripe_pm,
103+
[]
104+
);
105+
106+
} catch (Exception $e) {
107+
$error = $e->getMessage();
108+
error_log("Stripe payment error - encountered exception when fetching payment method info for $stripe_pm: $error");
109+
logApp("Stripe", "error", "Exception when fetching payment method info for $stripe_pm: $error");
110+
}
111+
112+
$card_name = nullable_htmlentities($payment_method->billing_details->name);
113+
$card_brand = nullable_htmlentities($payment_method->card->display_brand);
114+
$card_last4 = nullable_htmlentities($payment_method->card->last4);
115+
$card_expires = nullable_htmlentities($payment_method->card->exp_month) . "/" . nullable_htmlentities($payment_method->card->exp_year);
116+
117+
?>
118+
119+
<ul><li><?php echo "$card_name - $card_brand card ending in $card_last4, expires $card_expires"; ?></li></ul>
120+
121+
<hr>
122+
<b>Actions</b><br>
123+
- <a href="portal_post.php?stripe_remove_card&pm=<?php echo $stripe_pm; ?>">Remove saved card</a>
124+
125+
<?php } ?>
126+
127+
128+
</div>
129+
130+
</div>
131+
132+
133+
<?php
134+
require_once "portal_footer.php";

portal/index.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
require_once "inc_portal.php";
1010

11-
1211
?>
1312
<div class="col-md-2 offset-1">
1413
<a href="ticket_add.php" class="btn btn-primary btn-block">New ticket</a>

portal/portal_header.php

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,34 @@
5050
</li>
5151

5252
<?php if (($session_contact_primary == 1 || $session_contact_is_billing_contact) && $config_module_enable_accounting == 1) { ?>
53-
<li class="nav-item">
54-
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "invoices.php") {echo "active";} ?>" href="invoices.php">Invoices</a>
55-
</li>
56-
<li class="nav-item">
57-
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "quotes.php") {echo "active";} ?>" href="quotes.php">Quotes</a>
53+
<li class="nav-item dropdown">
54+
<a class="nav-link dropdown-toggle <?php echo in_array(basename($_SERVER['PHP_SELF']), ['invoices.php', 'quotes.php', 'autopay.php']) ? 'active' : ''; ?>" href="#" id="navbarDropdown1" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
55+
Finance
56+
</a>
57+
<div class="dropdown-menu" aria-labelledby="navbarDropdown1">
58+
<a class="dropdown-item" href="invoices.php">Invoices</a>
59+
<a class="dropdown-item" href="quotes.php">Quotes</a>
60+
<a class="dropdown-item" href="autopay.php">Auto Payment</a>
61+
</div>
5862
</li>
5963
<?php } ?>
64+
6065
<?php if ($config_module_enable_itdoc && ($session_contact_primary == 1 || $session_contact_is_technical_contact)) { ?>
61-
<li class="nav-item">
62-
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "documents.php") {echo "active";} ?>" href="documents.php">Documents</a>
63-
</li>
64-
<li class="nav-item">
65-
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "contacts.php") {echo "active";} ?>" href="contacts.php">Contacts</a>
66-
</li>
67-
<li class="nav-item">
68-
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "domains.php") {echo "active";} ?>" href="domains.php">Domains</a>
69-
</li>
70-
<li class="nav-item">
71-
<a class="nav-link <?php if (basename($_SERVER['PHP_SELF']) == "certificates.php") {echo "active";} ?>" href="certificates.php">Certificates</a>
66+
<li class="nav-item dropdown">
67+
<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="navbarDropdown2" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
68+
Technical
69+
</a>
70+
<div class="dropdown-menu" aria-labelledby="navbarDropdown2">
71+
<a class="dropdown-item" href="contacts.php">Contacts</a>
72+
<a class="dropdown-item" href="documents.php">Documents</a>
73+
<a class="dropdown-item" href="domains.php">Domains</a>
74+
<a class="dropdown-item" href="certificates.php">Certificates</a>
75+
<a class="dropdown-item" href="ticket_view_all.php">All tickets</a>
76+
</div>
7277
</li>
7378
<?php } ?>
74-
</ul>
79+
80+
</ul><!-- End left nav -->
7581

7682
<ul class="nav navbar-nav pull-right">
7783
<li class="nav-item dropdown">
@@ -100,7 +106,6 @@
100106
<img src="<?php echo "../uploads/clients/$session_client_id/$session_contact_photo"; ?>" alt="..." height="50" width="50" class="img-circle img-responsive">
101107

102108
<?php } else { ?>
103-
104109
<span class="fa-stack fa-2x rounded-left">
105110
<i class="fa fa-circle fa-stack-2x text-secondary"></i>
106111
<span class="fa fa-stack-1x text-white"><?php echo $session_contact_initials; ?></span>

0 commit comments

Comments
 (0)