1, 'next_price' => 1, 'next_invoice' => 1, 'next_invoice_item' => 1, 'customers' => [], 'prices' => [], 'products' => [], 'invoice_items' => [], 'invoices' => [], ]; public static function resetStore(): void { self::writeStore(self::DEFAULT_STATE); } public static function setInvoiceState(string $invoiceId, array $attributes): void { $store = self::readStore(); $invoice = $store['invoices'][$invoiceId] ?? null; if (!$invoice) { return; } $store['invoices'][$invoiceId] = [ ...$invoice, ...$attributes, ]; self::writeStore($store); } public function request($method, $absUrl, $headers, $params, $hasFile, $apiMode = 'v1') { $path = (string)parse_url((string)$absUrl, PHP_URL_PATH); $store = self::readStore(); if ($method === 'post' && $path === '/v1/customers') { $customerId = sprintf('cus_fake_%06d', (int)$store['next_customer']); $store['next_customer']++; $customer = [ 'id' => $customerId, 'object' => 'customer', 'email' => (string)($params['email'] ?? ''), ]; $store['customers'][$customerId] = $customer; self::writeStore($store); return $this->jsonResponse($customer); } if ($method === 'get' && preg_match('#^/v1/customers/(?P[^/]+)$#', $path, $matches)) { $customer = $store['customers'][$matches['id']] ?? null; if (!$customer) { return $this->errorResponse(404, 'resource_missing', 'No such customer.'); } return $this->jsonResponse($customer); } if ($method === 'get' && preg_match('#^/v1/products/(?P[^/]+)$#', $path, $matches)) { $product = $store['products'][$matches['id']] ?? null; if (!$product) { return $this->errorResponse(404, 'resource_missing', 'No such product.'); } return $this->jsonResponse($product); } if ($method === 'post' && $path === '/v1/products') { $productId = (string)($params['id'] ?? sprintf('prod_fake_%06d', count($store['products']) + 1)); $product = [ 'id' => $productId, 'object' => 'product', 'name' => (string)($params['name'] ?? 'Fake Product'), ]; $store['products'][$productId] = $product; self::writeStore($store); return $this->jsonResponse($product); } if ($method === 'post' && $path === '/v1/prices') { $priceId = sprintf('price_fake_%06d', (int)$store['next_price']); $store['next_price']++; $price = [ 'id' => $priceId, 'object' => 'price', 'product' => (string)($params['product'] ?? ''), 'unit_amount' => (int)($params['unit_amount'] ?? 0), 'currency' => strtolower((string)($params['currency'] ?? 'dkk')), ]; $store['prices'][$priceId] = $price; self::writeStore($store); return $this->jsonResponse($price); } if ($method === 'post' && $path === '/v1/invoices') { $invoiceId = sprintf('in_fake_%06d', (int)$store['next_invoice']); $store['next_invoice']++; $invoice = [ 'id' => $invoiceId, 'object' => 'invoice', 'customer' => (string)($params['customer'] ?? ''), 'status' => 'draft', 'paid' => false, 'amount_due' => 0, 'amount_paid' => 0, 'collection_method' => (string)($params['collection_method'] ?? 'send_invoice'), 'hosted_invoice_url' => sprintf('https://stripe.test/invoices/%s', $invoiceId), 'metadata' => is_array($params['metadata'] ?? null) ? $params['metadata'] : [], 'lines' => [], ]; $store['invoices'][$invoiceId] = $invoice; self::writeStore($store); return $this->jsonResponse($invoice); } if ($method === 'post' && $path === '/v1/invoiceitems') { $invoiceId = (string)($params['invoice'] ?? ''); $invoice = $store['invoices'][$invoiceId] ?? null; if (!$invoice) { return $this->errorResponse(404, 'resource_missing', 'No such invoice.'); } $invoiceItemId = sprintf('ii_fake_%06d', (int)$store['next_invoice_item']); $store['next_invoice_item']++; $priceId = (string)($params['price'] ?? ''); $price = $store['prices'][$priceId] ?? ['unit_amount' => 0]; $invoiceItem = [ 'id' => $invoiceItemId, 'object' => 'invoiceitem', 'invoice' => $invoiceId, 'customer' => (string)($params['customer'] ?? ''), 'price' => $priceId, 'amount' => (int)($price['unit_amount'] ?? 0), ]; $store['invoice_items'][$invoiceItemId] = $invoiceItem; $store['invoices'][$invoiceId]['lines'][] = $invoiceItemId; $store['invoices'][$invoiceId]['amount_due'] += (int)($price['unit_amount'] ?? 0); self::writeStore($store); return $this->jsonResponse($invoiceItem); } if ($method === 'post' && preg_match('#^/v1/invoices/(?P[^/]+)/finalize$#', $path, $matches)) { $invoiceId = $matches['id']; $invoice = $store['invoices'][$invoiceId] ?? null; if (!$invoice) { return $this->errorResponse(404, 'resource_missing', 'No such invoice.'); } $invoice['status'] = 'open'; $store['invoices'][$invoiceId] = $invoice; self::writeStore($store); return $this->jsonResponse($invoice); } if ($method === 'post' && preg_match('#^/v1/invoices/(?P[^/]+)/void$#', $path, $matches)) { $invoiceId = $matches['id']; $invoice = $store['invoices'][$invoiceId] ?? null; if (!$invoice) { return $this->errorResponse(404, 'resource_missing', 'No such invoice.'); } $invoice['status'] = 'void'; $invoice['paid'] = false; $store['invoices'][$invoiceId] = $invoice; self::writeStore($store); return $this->jsonResponse($invoice); } if ($method === 'get' && preg_match('#^/v1/invoices/(?P[^/]+)$#', $path, $matches)) { $invoice = $store['invoices'][$matches['id']] ?? null; if (!$invoice) { return $this->errorResponse(404, 'resource_missing', 'No such invoice.'); } return $this->jsonResponse($invoice); } return $this->errorResponse(404, 'resource_missing', 'Unsupported fake Stripe request: ' . $method . ' ' . $path); } private function jsonResponse(array $payload, int $status = 200): array { return [ json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $status, [ 'Content-Type' => 'application/json', 'Request-Id' => 'req_fake_stripe', ], ]; } private function errorResponse(int $status, string $code, string $message): array { return [ json_encode([ 'error' => [ 'type' => 'invalid_request_error', 'code' => $code, 'message' => $message, ], ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $status, [ 'Content-Type' => 'application/json', 'Request-Id' => 'req_fake_stripe_error', ], ]; } private static function readStore(): array { $path = self::storePath(); if (!is_file($path)) { return self::DEFAULT_STATE; } $json = file_get_contents($path); if ($json === false || trim($json) === '') { return self::DEFAULT_STATE; } $decoded = json_decode($json, true); if (!is_array($decoded)) { return self::DEFAULT_STATE; } return [ ...self::DEFAULT_STATE, ...$decoded, ]; } private static function writeStore(array $store): void { file_put_contents( self::storePath(), json_encode($store, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) ); } private static function storePath(): string { $path = trim((string)getenv('STRIPE_FAKE_STORE_PATH')); if ($path !== '') { return $path; } return sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'truckwash-stripe-fake-store.json'; } }