diff --git a/services/nginx/app/classes/stripe.php b/services/nginx/app/classes/stripe.php index ce159b9a..7deef11f 100644 --- a/services/nginx/app/classes/stripe.php +++ b/services/nginx/app/classes/stripe.php @@ -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(); } /** diff --git a/services/nginx/app/modules/stripe/endpoints/stripe_endpoint_customers.php b/services/nginx/app/modules/stripe/endpoints/stripe_endpoint_customers.php index 2009025c..6f919633 100644 --- a/services/nginx/app/modules/stripe/endpoints/stripe_endpoint_customers.php +++ b/services/nginx/app/modules/stripe/endpoints/stripe_endpoint_customers.php @@ -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 + ]); } /** diff --git a/services/nginx/app/modules/stripe/endpoints/stripe_endpoint_invoice.php b/services/nginx/app/modules/stripe/endpoints/stripe_endpoint_invoice.php new file mode 100644 index 00000000..6b914a21 --- /dev/null +++ b/services/nginx/app/modules/stripe/endpoints/stripe_endpoint_invoice.php @@ -0,0 +1,99 @@ +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(); + } + +} \ No newline at end of file diff --git a/services/nginx/app/modules/stripe/helpers/stripe_line_item.php b/services/nginx/app/modules/stripe/helpers/stripe_line_item.php index def695af..24cbf6ba 100644 --- a/services/nginx/app/modules/stripe/helpers/stripe_line_item.php +++ b/services/nginx/app/modules/stripe/helpers/stripe_line_item.php @@ -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, diff --git a/services/nginx/app/modules/stripe/helpers/stripe_line_items.php b/services/nginx/app/modules/stripe/helpers/stripe_line_items.php index 0c82621e..28a73d38 100644 --- a/services/nginx/app/modules/stripe/helpers/stripe_line_items.php +++ b/services/nginx/app/modules/stripe/helpers/stripe_line_items.php @@ -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; } diff --git a/services/nginx/app/objects/orders_o.php b/services/nginx/app/objects/orders_o.php index e7641066..9f6accc3 100644 --- a/services/nginx/app/objects/orders_o.php +++ b/services/nginx/app/objects/orders_o.php @@ -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(); } diff --git a/services/nginx/app/objects/stripe_module_customers_o.php b/services/nginx/app/objects/stripe_module_customers_o.php new file mode 100644 index 00000000..46d805f1 --- /dev/null +++ b/services/nginx/app/objects/stripe_module_customers_o.php @@ -0,0 +1,104 @@ +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']; + } + +} \ No newline at end of file diff --git a/services/nginx/app/objects/stripe_module_orders_o.php b/services/nginx/app/objects/stripe_module_orders_o.php index 85c1066a..28c03626 100644 --- a/services/nginx/app/objects/stripe_module_orders_o.php +++ b/services/nginx/app/objects/stripe_module_orders_o.php @@ -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()); } } \ No newline at end of file diff --git a/services/nginx/app/routes/moduleStripeRoute.php b/services/nginx/app/routes/moduleStripeRoute.php index 939c62a4..f7da8dde 100644 --- a/services/nginx/app/routes/moduleStripeRoute.php +++ b/services/nginx/app/routes/moduleStripeRoute.php @@ -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); diff --git a/services/nginx/app/routes/ordersRoute.php b/services/nginx/app/routes/ordersRoute.php index eead50f1..1052b5d8 100644 --- a/services/nginx/app/routes/ordersRoute.php +++ b/services/nginx/app/routes/ordersRoute.php @@ -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 */