const test = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const path = require('node:path') const root = path.resolve(__dirname, '../..') const read = (file) => fs.readFileSync(path.join(root, file), 'utf8') test('orders expose four states, seven product categories and retain 22-digit IDs', () => { const domain = read('src/Core/DomainModel.php') const repository = read('src/Payment/OrderRepositoryCore.php') const order = read('src/Payment/Order.php') for (const status of ['pending', 'paid', 'refunded', 'closed']) { assert.match(domain, new RegExp(`ORDER_STATUS_${status.toUpperCase()}\\s*=\\s*'${status}'`)) } assert.ok(repository.includes("ORDER_NUMBER_PATTERN = '/^\\d{20}$|^\\d{22}$/';")) for (const label of ['付费阅读', '付费下载', '付费图片', '付费视频', '购买会员', '余额充值', '购买积分']) { assert.ok(domain.includes(label), `missing product category ${label}`) } assert.match(repository, /\$row\['status'\] === 'refunding'[\s\S]*?Order::STATUS_PAID/) assert.doesNotMatch(order, /STATUS_REFUNDING\s*=/) }) test('balance and points mutations use user meta, guard debits in SQL and cap history at 50', () => { const assets = read('src/Credits/MetaAssetService.php') const ledger = read('src/Ledger/LedgerService.php') assert.match(assets, /MAX_RECORDS\s*=\s*50/) assert.match(assets, /SELECT GET_LOCK\(%s, 5\)/) assert.match(assets, /FOR UPDATE/) assert.match(assets, /CAST\(meta_value AS SIGNED\) >= %d/) assert.match(assets, /CAST\(meta_value AS DECIMAL\(20,2\)\) >= CAST\(%s AS DECIMAL\(20,2\)\)/) assert.match(assets, /array_slice\(\$history, 0, self::MAX_RECORDS - 1\)/) assert.match(assets, /'balance_record'/) assert.match(assets, /'points_record'/) assert.match(ledger, /post_meta_command\(\$command, \$guard, \$business_mutation\)/) assert.match(ledger, /AssetType::BALANCE, AssetType::POINTS/) }) test('free points keep a bounded daily summary alongside the recent points history', () => { const freePoints = read('src/Credits/PointsFree.php') const assets = read('src/Credits/MetaAssetService.php') assert.match(freePoints, /META_DETAIL\s*=\s*'free_points_detail'/) assert.match(freePoints, /remaining_today\(\$user_id\) < \$points/) assert.match(freePoints, /array_slice\(\$detail, 0, 50, true\)/) assert.match(freePoints, /LedgerService::get_instance\(\)->credit\([\s\S]*?self::bump_detail\(\$user_id, \$points\)/) assert.match(assets, /'points_record'/) }) test('after-sale submissions are order-locked and only completed by their own refund application', () => { const afterSale = read('src/Payment/AfterSaleService.php') const schema = read('src/Core/DBMigratorPart2.php') const repository = read('src/Payment/OrderRepositoryCore.php') assert.match(schema, /CREATE TABLE \{\$prefix\}aksrt_order_aftersale[\s\S]*?gateway_request_id[\s\S]*?gateway_result[\s\S]*?completed_at/) assert.match(afterSale, /GET_LOCK\(%s, 5\)[\s\S]*?get_user_order\(\$user_id, \$order_number\)[\s\S]*?find_active\(\$order_number\)/) assert.match(afterSale, /Order::STATUS_PAID/) assert.match(afterSale, /\$gateway->refund\(\$order_number, \(string\) \$row\['refund_amount'\], \$request_id\)/) assert.match(afterSale, /query_refund\(\$request_id, \(string\) \$row\['order_number'\]\)/) assert.match(afterSale, /sanitize_key\(\(string\) \(\$gateway_result\['status'\] \?\? ''\)\) !== 'manual'/) assert.match(afterSale, /\$decision === 'reject'[\s\S]*?compensate_revoke\(\$row, \$order, \$revoke, \$note\)/) assert.match(afterSale, /Order::STATUS_REFUNDED/) assert.match(repository, /can_finalize_order_refund\(\(int\) \(\$extra\['after_sale_id'\] \?\? 0\), \$order_number\)/) assert.match(read('src/Payment/Gateways/PayPalGateway.php'), /'status' => 'manual'/) assert.match(read('src/Payment/Gateways/EpayGateway.php'), /'status'\s*=>\s*'manual'/) }) test('withdrawal holds and returns use the selected user-meta or commission pool', () => { const withdrawal = read('src/Payment/Withdrawal.php') const assets = read('src/Credits/MetaAssetService.php') assert.match(withdrawal, /source_balance_decimal\(\$user_id, \$source\)/) assert.match(withdrawal, /private function source_balance_decimal\(int \$user_id, string \$source\)/) assert.match(withdrawal, /\$this->source_asset\(\$source\)/) assert.match(withdrawal, /\$this->source_balance_decimal\(\$user_id, \$refund_source\)/) assert.match(assets, /AssetType::BALANCE->value, AssetType::POINTS->value/) assert.match(assets, /function write_balance\([\s\S]*?\$wpdb->usermeta/) })