Add Stripe invoice generation and customer management

Introduced functionality for creating and managing invoices using Stripe APIs, replacing the previous payment link system. Added customer account creation and retrieval, invoice line-item handling, and extended database object structures to support invoices. Updated order routing to include Stripe customer and invoice details.
This commit is contained in:
Jepp9350
2025-02-19 17:25:19 +01:00
parent ea6c675826
commit 53b61654bf
10 changed files with 287 additions and 27 deletions
+8
View File
@@ -7,6 +7,7 @@ require_once WD . '/modules/stripe/endpoints/stripe_endpoint_customers.php';
require_once WD . '/modules/stripe/endpoints/stripe_endpoint_payment_link.php';
require_once WD . '/modules/stripe/endpoints/stripe_endpoint_product.php';
require_once WD . '/modules/stripe/endpoints/stripe_endpoint_prices.php';
require_once WD . '/modules/stripe/endpoints/stripe_endpoint_invoice.php';
// Require all helper classes
@@ -19,6 +20,7 @@ require_once WD . '/modules/stripe/helpers/stripe_price.php';
use Exception;
use interfaces\stripe_i;
use stripe\endpoints\stripe_endpoint_customers;
use stripe\endpoints\stripe_endpoint_invoice;
use stripe\endpoints\stripe_endpoint_payment_link;
use stripe\endpoints\stripe_endpoint_prices;
use stripe\endpoints\stripe_endpoint_product;
@@ -56,6 +58,11 @@ class stripe implements stripe_i
* @var stripe_endpoint_prices
*/
public stripe_endpoint_prices $prices;
/**
* Invoice endpoint
* @var stripe_endpoint_invoice
*/
public stripe_endpoint_invoice $invoice;
/**
* Stripe client
* @var StripeClient
@@ -69,6 +76,7 @@ class stripe implements stripe_i
$this->payment_link = new stripe_endpoint_payment_link();
$this->product = new stripe_endpoint_product();
$this->prices = new stripe_endpoint_prices();
$this->invoice = new stripe_endpoint_invoice();
}
/**
@@ -19,9 +19,11 @@ class stripe_endpoint_customers
* @throws ApiErrorException
* @throws Exception
*/
public function create(array $data): Customer
public function create(string $email): Customer
{
return self::getClient()->customers->create($data);
return self::getClient()->customers->create([
'email' => $email
]);
}
/**
@@ -0,0 +1,99 @@
<?php
namespace stripe\endpoints;
use Exception;
use objects\orders_o;
use Stripe\Exception\ApiErrorException;
use stripe\helpers\stripe_line_item;
use stripe\helpers\stripe_line_items;
use Stripe\Invoice;
use traits\stripe_endpoint_t;
class stripe_endpoint_invoice
{
use stripe_endpoint_t;
/**
* Retrieve a payment link
* @param string $id
* @return Invoice
* @throws ApiErrorException
* @throws Exception
*/
public function retrieve(string $id): Invoice
{
return self::getClient()->invoices->retrieve($id);
}
/**
* @throws ApiErrorException
* @throws Exception
*/
public function generate(int $order_id, string $stripe_customer_id): Invoice
{
$order = (new orders_o())->select($order_id);
$order->requireSelected();
// Get the order items
$order_items = $order->getOrderItems($order_id);
// Create a new line items object
$line_items = new stripe_line_items('invoice');
// Loop through the order items
foreach ( $order_items as $order_item ) {
// Add the order item to the line items
$line_items->add(
new stripe_line_item(
$order_item['product_id'],
$order_item['price'],
$order_item['quantity']
)
);
}
// Create the payment link
return $this->create(
$line_items,
$stripe_customer_id,
[
'order_id' => $order_id,
'customer_id' => $stripe_customer_id
]
);
}
/**
* Create a new payment link
* @param stripe_line_items $line_items
* @param string $stripe_customer_id
* @param array $metadata Additional metadata
* @return Invoice
* @throws ApiErrorException
* @throws Exception
*/
public function create(stripe_line_items $line_items, string $stripe_customer_id, array $metadata = []): Invoice
{
$invoice = self::getClient()->invoices->create([
'customer' => $stripe_customer_id,
'collection_method' => 'send_invoice',
'days_until_due' => 30,
'metadata' => $metadata,
'auto_advance' => true,
//'payment_settings' => [
// 'payment_method_types' => [
// 'card',
// 'paypal'
// ],
//],
]);
// Add the line items to the invoice
foreach ( $line_items->get() as $line_item ) {
self::getClient()->invoiceItems->create([
'customer' => $stripe_customer_id,
'invoice' => $invoice->id,
'price' => $line_item['price'],
]);
}
// Finalize the invoice
return $invoice->finalizeInvoice();
}
}
@@ -98,8 +98,14 @@ class stripe_line_item
);
}
public function get(): array
public function get($type = 'payment_link'): array
{
if ($type === 'invoice') {
return [
'price' => (string)$this->price_id,
'amount' => (int)$this->quantity,
];
}
return [
//'product' => (int)$this->product_id,
'price' => (string)$this->price_id,
@@ -8,6 +8,22 @@ class stripe_line_items
* @var array $line_items
*/
protected array $line_items = [];
protected array $types = [
'invoice',
'payment_link'
];
protected string $type;
/**
* @throws \Exception
*/
public function __construct($type = 'payment_link')
{
if (!in_array($type, $this->types)) {
throw new \Exception('Invalid type');
}
$this->type = $type;
}
public function __toString(): string
{
@@ -20,7 +36,9 @@ class stripe_line_items
$line_items = [];
/** @var stripe_line_item|array $line_item */
foreach ( $this->line_items as $line_item ) {
$line_items[] = is_array($line_item) ? $line_item : $line_item->get();
$line_items[] = is_array($line_item)
? $line_item
: $line_item->get($this->type);
}
return $line_items;
}
+2 -2
View File
@@ -302,10 +302,10 @@ class orders_o extends db
* Set the stripe invoicing for an order
* @throws \Exception
*/
public function setStripeInvoicing(string $payment_link_id, string $email, string $url): void
public function setStripeInvoicing(string $invoice_id, string $stripe_customer_id, string $url): void
{
self::requireSelected();
$this->stripe_module_orders->add($this->id, $email, $payment_link_id, $url);
$this->stripe_module_orders->add($this->id, $invoice_id, $stripe_customer_id, $url);
self::objectChanged();
}
@@ -0,0 +1,104 @@
<?php
namespace objects;
use classes\db;
use classes\object_property;
use classes\stripe;
use Exception;
use Stripe\Exception\ApiErrorException;
use traits\db_object_t;
class stripe_module_customers_o extends db
{
use db_object_t;
public object_property $email;
public object_property $customer_id;
public object_property $created_at;
private bool $paid;
public function structure(): void
{
$this->setTable('stripe_module_customers');
}
/**
* Add a customer to the database
* @param string $email The customer's email address
* @param string $customer_id The Stripe customer ID
* @return void
* @throws Exception If the object was not created successfully
*/
public function add(string $email, string $customer_id): void
{
$tmp_id = self::add_object([
'email' => $email,
'customer_id' => $customer_id,
]);
$this->id = $tmp_id;
self::getObjectProperties();
self::objectChanged();
}
public function getObjectProperties(): void
{
$this->email = new object_property($this->table, $this->id, 'email', 'string', false);
$this->customer_id = new object_property($this->table, $this->id, 'customer_id', 'string', false);
$this->created_at = new object_property($this->table, $this->id, 'created_at', 'timestamp', false);
}
public function objectChanged(): void
{
//TODO: Add cache invalidation
}
/**
* @throws ApiErrorException
*/
public function asArray(): array
{
$object = self::retrieveCustomer();
return [
'id' => (int)$this->id,
'email' => $this->email->value(),
'customer_id' => $this->customer_id->value(),
'created_at' => $this->created_at->value(),
'object' => $object
];
}
/**
* @throws ApiErrorException
* @throws Exception
*/
public function retrieveCustomer(): object
{
// Validate the object
self::requireSelected();
// Return the payment link
return (new stripe())->customers->retrieve($this->customer_id->value());
}
/**
* Check if a customer has an account
* @param string $email
* @return bool
*/
public function doesCustomerHaveAccount(string $email): bool
{
return (bool)self::getFieldsWhere(['email' => $email], ['id']);
}
/**
* Get the customer ID from the email address
* @param string $email
* @return string
*/
public function getCustomerId(string $email): string
{
return self::getFieldsWhere(['email' => $email], ['customer_id'])[0]['customer_id'];
}
}
@@ -13,13 +13,13 @@ class stripe_module_orders_o extends db
{
use db_object_t;
public object_property $payment_link_id;
public object_property $email;
public object_property $invoice_id;
public object_property $customer_id;
public object_property $email_sent;
public object_property $url;
public object_property $created_at;
private bool $paid;
public function structure(): void
{
@@ -35,12 +35,12 @@ class stripe_module_orders_o extends db
* @return void
* @throws Exception If the object was not created successfully
*/
public function add(int $order_id, string $email, string $payment_link_id, string $url): void
public function add(int $order_id, string $invoice_id, string $stripe_customer_id, string $url): void
{
$tmp_id = self::add_object([
'id' => $order_id,
'email' => $email,
'payment_link_id' => $payment_link_id,
'invoice_id' => $invoice_id,
'customer_id' => $stripe_customer_id,
'url' => $url,
]);
$this->id = $tmp_id;
@@ -50,8 +50,8 @@ class stripe_module_orders_o extends db
public function getObjectProperties(): void
{
$this->payment_link_id = new object_property($this->table, $this->id, 'payment_link_id', 'string', false);
$this->email = new object_property($this->table, $this->id, 'email', 'string', false);
$this->invoice_id = new object_property($this->table, $this->id, 'invoice_id', 'string', false);
$this->customer_id = new object_property($this->table, $this->id, 'customer_id', 'string', false);
$this->email_sent = new object_property($this->table, $this->id, 'email_sent', 'timestamp', false);
$this->url = new object_property($this->table, $this->id, 'url', 'string', false);
$this->created_at = new object_property($this->table, $this->id, 'created_at', 'timestamp', false);
@@ -67,14 +67,17 @@ class stripe_module_orders_o extends db
*/
public function asArray(): array
{
$object = self::retrievePaymentLink();
return [
'id' => (int)$this->id,
'payment_link_id' => (string)$this->payment_link_id->value(),
'email' => (string)$this->email->value(),
'email_sent' => $this->email_sent->value() ? (string)$this->email_sent->value() : null,
'invoice_id' => (string)$this->invoice_id->value(),
'customer_id' => (string)$this->customer_id->value(),
'url' => (string)$this->url->value(),
'created_at' => (string)$this->created_at->value(),
'object' => self::retrievePaymentLink()
'paid' => $object->paid,
'status' => $object->status,
'amount_due' => $object->amount_due,
'amount_paid' => $object->amount_paid,
];
}
@@ -87,7 +90,7 @@ class stripe_module_orders_o extends db
// Validate the object
self::requireSelected();
// Return the payment link
return (new stripe())->payment_link->retrieve($this->payment_link_id->value());
return (new stripe())->invoice->retrieve($this->invoice_id->value());
}
}
@@ -9,6 +9,7 @@ use classes\router;
use classes\stripe;
use objects\logs_o;
use objects\orders_o;
use objects\stripe_module_customers_o;
use traits\route_t;
class moduleStripeRoute
@@ -89,21 +90,34 @@ class moduleStripeRoute
$order->requireSelected();
// Log the action
(new logs_o())->add('modules_stripe', 'global', 1, $user->id, 'MODULES_STRIPE', 'User sent an invoice');
$result = (new stripe())->payment_link->generate(
(int)self::fromRequest('order_id'),
(string)self::fromRequest('email')
);
// Create a customer account, if it doesn't exist
$hasCustomerAccount = (new stripe_module_customers_o())->doesCustomerHaveAccount((string)self::fromRequest('email'));
if (!$hasCustomerAccount) {
$customer = (new stripe())->customers->create((string)self::fromRequest('email'));
(new stripe_module_customers_o())->add((string)self::fromRequest('email'), $customer->id);
} else {
// Get the customer account by email
$customer = (new stripe())->customers->retrieve(
// Get the customer ID
(new stripe_module_customers_o())
->getCustomerId(
(string)self::fromRequest('email')
)
);
}
// Create an invoice
$invoice = (new stripe())->invoice->generate((int)self::fromRequest('order_id'), $customer->id);
// Set the order stripe invoice details
$order->setStripeInvoicing($result->id, (string)self::fromRequest('email'), $result->url);
$order->setStripeInvoicing($invoice->id, $customer->id, $invoice->hosted_invoice_url);
$email = new email();
$email->sendStripeInvoiceEmail(
(string)self::fromRequest('email'),
null,
$result->url,
$invoice->hosted_invoice_url,
(int)self::fromRequest('order_id')
);
// Return the result
$response->success((object)$result);
$response->success((object)$invoice);
} else {
(new logs_o())->add('modules_stripe', 'global', 0, 0, 'MODULES_STRIPE', 'User tried to send an invoice without a valid session');
$response->error('Invalid session', 400);
@@ -8,6 +8,7 @@ use objects\departments_o;
use objects\economic_module_orders;
use objects\logs_o;
use objects\orders_o;
use objects\stripe_module_orders_o;
use objects\users_o;
use traits\route_t;
@@ -35,6 +36,11 @@ class ordersRoute
function ($order) {
// Add the invoice status to the order
$order['economic_invoice_module'] = (new economic_module_orders())->getByOrderId($order['id'])->asArray();
// Add the stripe status to the order
$stripe_module_orders = (new stripe_module_orders_o())->select($order['id']);
if ($stripe_module_orders->exists()) {
$order['stripe_invoice_module'] = $stripe_module_orders->asArray();
}
// Add the customer name to the order
$order['customer_name'] = (new users_o())->getCustomerName($order['customer_id']);
/** @var array $order */