243 lines
7.7 KiB
PHP
243 lines
7.7 KiB
PHP
<?php
|
|
|
|
namespace objects;
|
|
|
|
use classes\db;
|
|
use classes\object_property;
|
|
use classes\stripe;
|
|
use Exception;
|
|
use traits\db_object_t;
|
|
|
|
class stripe_payment_intents_o extends db
|
|
{
|
|
use db_object_t;
|
|
|
|
public object_property $order_id;
|
|
public object_property $payment_intent_id;
|
|
public object_property $client_secret;
|
|
public object_property $data;
|
|
public object_property $reader_id;
|
|
public object_property $tax_percentage;
|
|
|
|
public function structure(): void
|
|
{
|
|
$this->setTable('stripe_payment_intents');
|
|
}
|
|
|
|
/**
|
|
* Add a new payment intent
|
|
* @throws Exception
|
|
*/
|
|
public function add(
|
|
int $order_id,
|
|
string $payment_intent_id,
|
|
string $client_secret,
|
|
mixed $data = null,
|
|
?string $reader_id = null,
|
|
int $tax_percentage = null
|
|
): void {
|
|
if (is_object($data) || is_array($data)) {
|
|
$data = json_encode($data);
|
|
}
|
|
|
|
$this->clearOrderPaymentIntents($order_id);
|
|
$tmp_id = self::add_object([
|
|
'order_id' => $order_id,
|
|
'payment_intent_id' => $payment_intent_id,
|
|
'client_secret' => $client_secret,
|
|
'data' => $data,
|
|
'reader_id' => $reader_id,
|
|
'tax_percentage' => ($tax_percentage !== null) ? (int)$tax_percentage : 0,
|
|
]);
|
|
$this->id = $tmp_id;
|
|
self::getObjectProperties();
|
|
self::objectChanged();
|
|
}
|
|
|
|
public function getObjectProperties(): void
|
|
{
|
|
$this->order_id = new object_property($this->table, $this->id, 'order_id', 'int', false);
|
|
$this->payment_intent_id = new object_property($this->table, $this->id, 'payment_intent_id', 'string', false);
|
|
$this->client_secret = new object_property($this->table, $this->id, 'client_secret', 'string', false);
|
|
$this->data = new object_property($this->table, $this->id, 'data', 'string', false);
|
|
$this->reader_id = new object_property($this->table, $this->id, 'reader_id', 'string', false);
|
|
$this->tax_percentage = new object_property($this->table, $this->id, 'tax_percentage', 'int', false);
|
|
}
|
|
|
|
public function objectChanged(): void
|
|
{
|
|
// TODO: Add cache invalidation
|
|
}
|
|
|
|
public function doesOrderHavePaymentIntent(int $order_id): bool
|
|
{
|
|
return count($this->getOrderPaymentIntentRows($order_id)) > 0;
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function selectOrderPaymentIntent(int $order_id): self
|
|
{
|
|
$rows = $this->getOrderPaymentIntentRows($order_id);
|
|
if (count($rows) === 0) {
|
|
throw new Exception('No payment intent found for order id: ' . $order_id);
|
|
}
|
|
|
|
$this->id = (int)$rows[0]['id'];
|
|
self::getObjectProperties();
|
|
$this->deleteDuplicateOrderPaymentIntents($order_id, $this->id);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function getOrderPaymentIntentRows(int $order_id): array
|
|
{
|
|
$rows = self::getFieldsWhere(
|
|
[
|
|
'order_id' => $order_id,
|
|
],
|
|
['id', 'payment_intent_id', 'reader_id', 'tax_percentage']
|
|
);
|
|
|
|
usort($rows, static fn(array $a, array $b): int => ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0)));
|
|
return $rows;
|
|
}
|
|
|
|
public function clearOrderPaymentIntents(int $order_id, ?int $keepId = null): void
|
|
{
|
|
foreach ($this->getOrderPaymentIntentRows($order_id) as $row) {
|
|
$rowId = (int)($row['id'] ?? 0);
|
|
if ($rowId <= 0 || ($keepId !== null && $rowId === $keepId)) {
|
|
continue;
|
|
}
|
|
self::delete_object($this->getTable(), $rowId);
|
|
}
|
|
}
|
|
|
|
public function updateStoredPaymentIntent(mixed $paymentIntent): void
|
|
{
|
|
self::requireSelected();
|
|
|
|
if (is_object($paymentIntent) && method_exists($paymentIntent, 'toJSON')) {
|
|
$encodedData = $paymentIntent->toJSON();
|
|
} elseif (is_array($paymentIntent) || is_object($paymentIntent)) {
|
|
$encodedData = json_encode($paymentIntent);
|
|
} else {
|
|
$encodedData = (string)$paymentIntent;
|
|
}
|
|
|
|
$this->data->set($encodedData);
|
|
|
|
$normalizedClientSecret = null;
|
|
$normalizedReaderId = null;
|
|
$normalizedTaxPercentage = null;
|
|
|
|
if (is_object($paymentIntent) || is_array($paymentIntent)) {
|
|
$metadata = is_array($paymentIntent)
|
|
? ($paymentIntent['metadata'] ?? [])
|
|
: ($paymentIntent->metadata ?? null);
|
|
|
|
$normalizedClientSecret = is_array($paymentIntent)
|
|
? ($paymentIntent['client_secret'] ?? null)
|
|
: ($paymentIntent->client_secret ?? null);
|
|
|
|
if (is_array($metadata)) {
|
|
$normalizedReaderId = $metadata['reader_id'] ?? $metadata['reader'] ?? null;
|
|
$normalizedTaxPercentage = $metadata['tax_percentage'] ?? null;
|
|
} elseif (is_object($metadata)) {
|
|
$normalizedReaderId = $metadata->reader_id ?? $metadata->reader ?? null;
|
|
$normalizedTaxPercentage = $metadata->tax_percentage ?? null;
|
|
}
|
|
}
|
|
|
|
if (is_string($normalizedClientSecret) && $normalizedClientSecret !== '') {
|
|
$this->client_secret->set($normalizedClientSecret);
|
|
}
|
|
if ($normalizedReaderId !== null) {
|
|
$this->reader_id->set((string)$normalizedReaderId);
|
|
}
|
|
if ($normalizedTaxPercentage !== null && is_numeric($normalizedTaxPercentage)) {
|
|
$this->tax_percentage->set((int)$normalizedTaxPercentage);
|
|
}
|
|
|
|
self::objectChanged();
|
|
}
|
|
|
|
public function setReaderId(?string $readerId): void
|
|
{
|
|
self::requireSelected();
|
|
|
|
if ($readerId === null || trim($readerId) === '') {
|
|
$this->reader_id->nullify();
|
|
self::objectChanged();
|
|
return;
|
|
}
|
|
|
|
$this->reader_id->set(trim($readerId));
|
|
self::objectChanged();
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function delete(): void
|
|
{
|
|
self::requireSelected();
|
|
self::cancelPaymentIntent();
|
|
self::deletePermanently();
|
|
}
|
|
|
|
/**
|
|
* @throws Exception
|
|
*/
|
|
public function cancelPaymentIntent(): void
|
|
{
|
|
self::requireSelected();
|
|
$stripe = new stripe();
|
|
|
|
$readerId = trim((string)($this->reader_id->value() ?? ''));
|
|
if ($readerId !== '') {
|
|
try {
|
|
$stripe->readers->sendCancelPaymentIntent($readerId);
|
|
} catch (\Stripe\Exception\InvalidRequestException) {
|
|
// The reader may already be idle or missing. Clearing local state is sufficient.
|
|
}
|
|
$this->reader_id->nullify();
|
|
}
|
|
|
|
$paymentIntentId = trim((string)($this->payment_intent_id->value() ?? ''));
|
|
if ($paymentIntentId === '') {
|
|
self::objectChanged();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$paymentIntent = $stripe->payment_intents->get($paymentIntentId);
|
|
} catch (\Stripe\Exception\InvalidRequestException) {
|
|
self::objectChanged();
|
|
return;
|
|
}
|
|
|
|
$status = strtolower((string)($paymentIntent->status ?? ''));
|
|
if (in_array($status, ['succeeded', 'canceled'], true)) {
|
|
$this->updateStoredPaymentIntent($paymentIntent);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$cancelledPaymentIntent = $stripe->payment_intents->cancel($paymentIntentId);
|
|
$this->updateStoredPaymentIntent($cancelledPaymentIntent);
|
|
} catch (\Stripe\Exception\InvalidRequestException) {
|
|
self::objectChanged();
|
|
}
|
|
}
|
|
|
|
private function deleteDuplicateOrderPaymentIntents(int $order_id, int $keepId): void
|
|
{
|
|
$this->clearOrderPaymentIntents($order_id, $keepId);
|
|
}
|
|
}
|