109 lines
3.7 KiB
SQL
109 lines
3.7 KiB
SQL
-- ============================================================
|
|
-- PAYMENT FLOW HARDENING MIGRATION
|
|
-- Date: 2026-02-15
|
|
-- Purpose: Add idempotency, audit, and integrity constraints
|
|
-- ============================================================
|
|
|
|
-- 1. CREATE PAYMENTS AUDIT TABLE
|
|
-- Stores full webhook payloads for forensic debugging
|
|
|
|
CREATE TABLE IF NOT EXISTS PaymentAudit (
|
|
ID INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
OrderID INT UNSIGNED NULL,
|
|
PaymentIntentID VARCHAR(64) NULL,
|
|
StripeChargeID VARCHAR(64) NULL,
|
|
StripeEventID VARCHAR(64) NOT NULL,
|
|
EventType VARCHAR(64) NOT NULL,
|
|
AmountCents INT UNSIGNED NULL,
|
|
Currency CHAR(3) NULL DEFAULT 'usd',
|
|
RawPayload MEDIUMTEXT NOT NULL,
|
|
ProcessedAt DATETIME NULL,
|
|
ProcessingResult ENUM('success', 'skipped_duplicate', 'skipped_already_paid', 'error') NOT NULL DEFAULT 'success',
|
|
ErrorMessage TEXT NULL,
|
|
CreatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
-- Prevent duplicate event processing
|
|
UNIQUE KEY uk_stripe_event (StripeEventID),
|
|
|
|
-- Fast lookups
|
|
INDEX idx_order (OrderID),
|
|
INDEX idx_payment_intent (PaymentIntentID),
|
|
INDEX idx_created (CreatedAt)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
|
|
-- 2. ADD MISSING COLUMNS TO ORDERS TABLE
|
|
-- (Run these only if columns don't exist)
|
|
|
|
-- Add StripePaymentIntentID if it doesn't exist
|
|
SET @dbname = 'payfrit_dev';
|
|
SET @tablename = 'Orders';
|
|
SET @columnname = 'StripePaymentIntentID';
|
|
SET @preparedStatement = (
|
|
SELECT IF(
|
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = @columnname) > 0,
|
|
'SELECT 1',
|
|
'ALTER TABLE Orders ADD COLUMN StripePaymentIntentID VARCHAR(64) NULL AFTER PaymentStatus'
|
|
)
|
|
);
|
|
PREPARE stmt FROM @preparedStatement;
|
|
EXECUTE stmt;
|
|
DEALLOCATE PREPARE stmt;
|
|
|
|
|
|
-- 3. ADD UNIQUE CONSTRAINT ON StripePaymentIntentID
|
|
-- Each PaymentIntent can only pay ONE order
|
|
|
|
ALTER TABLE Orders
|
|
ADD UNIQUE INDEX uk_stripe_payment_intent (StripePaymentIntentID);
|
|
|
|
-- Note: If this fails with "Duplicate entry", you have data integrity issues to resolve first
|
|
|
|
|
|
-- 4. ADD AMOUNT VERIFICATION COLUMNS (if not present)
|
|
-- Store the expected and actual amounts for verification
|
|
|
|
SET @columnname = 'ExpectedAmountCents';
|
|
SET @preparedStatement = (
|
|
SELECT IF(
|
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = @columnname) > 0,
|
|
'SELECT 1',
|
|
'ALTER TABLE Orders ADD COLUMN ExpectedAmountCents INT UNSIGNED NULL AFTER StripePaymentIntentID'
|
|
)
|
|
);
|
|
PREPARE stmt FROM @preparedStatement;
|
|
EXECUTE stmt;
|
|
DEALLOCATE PREPARE stmt;
|
|
|
|
SET @columnname = 'ReceivedAmountCents';
|
|
SET @preparedStatement = (
|
|
SELECT IF(
|
|
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_SCHEMA = @dbname AND TABLE_NAME = @tablename AND COLUMN_NAME = @columnname) > 0,
|
|
'SELECT 1',
|
|
'ALTER TABLE Orders ADD COLUMN ReceivedAmountCents INT UNSIGNED NULL AFTER ExpectedAmountCents'
|
|
)
|
|
);
|
|
PREPARE stmt FROM @preparedStatement;
|
|
EXECUTE stmt;
|
|
DEALLOCATE PREPARE stmt;
|
|
|
|
|
|
-- 5. ADD UNIQUE CONSTRAINT ON WorkPayoutLedgers.StripePaymentIntentID
|
|
-- Each PaymentIntent should only be linked to ONE ledger entry
|
|
|
|
ALTER TABLE WorkPayoutLedgers
|
|
ADD UNIQUE INDEX uk_ledger_payment_intent (StripePaymentIntentID);
|
|
|
|
|
|
-- 6. ADD INDEX FOR FAST WEBHOOK LOOKUPS
|
|
|
|
ALTER TABLE Orders
|
|
ADD INDEX idx_payment_status (PaymentStatus);
|
|
|
|
-- ============================================================
|
|
-- PRODUCTION (payfrit) - Run the same on production database
|
|
-- Replace @dbname = 'payfrit_dev' with @dbname = 'payfrit'
|
|
-- ============================================================
|