find($order_id); if (empty($order) || $order['pay_status'] != 1) { BaseService::log('onOrderPaid: order not paid or not found', ['order_id' => $order_id], 'warning'); return false; } // 判断是否为票务商品 if (!BaseService::isTicketGoods($order['goods_id'])) { BaseService::log('onOrderPaid: not a ticket goods', ['order_id' => $order_id], 'info'); return true; // 不是票务商品,不报错 } // 查询商品快照(规格信息) $order_goods = \Db::name('OrderGoods') ->where('order_id', $order_id) ->select(); if (empty($order_goods)) { BaseService::log('onOrderPaid: no order goods', ['order_id' => $order_id], 'error'); return false; } // 逐个生成票(每个规格选项 = 一张票) $count = 0; foreach ($order_goods as $og) { $ticket_id = self::issueTicket($order, $og); if ($ticket_id > 0) { $count++; } } BaseService::log('onOrderPaid: success', [ 'order_id' => $order_id, 'tickets_issued' => $count, ]); return $count > 0; } /** * 发放单张票 * * @param array $order 订单数据 * @param array $order_goods 订单商品数据(包含 spec_base_id) * @return int 票ID */ public static function issueTicket($order, $order_goods) { $ticket_code = BaseService::generateUuid(); // 构建 QR 数据 $qr_payload = [ 'id' => 0, // 写入后再更新 'code' => $ticket_code, 'event' => $order['goods_id'], 'seat' => $order_goods['spec_name'] ?? '', // 规格名=座位信息 ]; $qr_data = BaseService::encryptQrData($qr_payload); // 观演人信息(从订单扩展字段读取,由购票页表单写入) $extension_data = json_decode($order['extension_data'] ?? '{}', true); $attendee = $extension_data['attendee'] ?? []; $now = BaseService::now(); $ticket_id = \Db::name(BaseService::table('tickets'))->insertGetId([ 'order_id' => $order['id'], 'order_no' => $order['order_no'], 'goods_id' => $order['goods_id'], 'goods_snapshot' => json_encode([ 'goods_name' => $order['goods_name'] ?? '', 'spec_name' => $order_goods['spec_name'] ?? '', 'price' => $order_goods['goods_price'] ?? 0, ], JSON_UNESCAPED_UNICODE), 'user_id' => $order['user_id'], 'ticket_code' => $ticket_code, 'qr_data' => $qr_data, 'seat_info' => $order_goods['spec_name'] ?? '', 'spec_base_id' => $order_goods['spec_base_id'] ?? 0, 'real_name' => $attendee['real_name'] ?? '', 'phone' => $attendee['phone'] ?? '', 'id_card' => $attendee['id_card'] ?? '', 'verify_status' => 0, // 0=未核销 'issued_at' => $now, 'created_at' => $now, 'updated_at' => $now, ]); // 更新 QR 数据中的 ticket_id if ($ticket_id > 0) { $qr_payload['id'] = $ticket_id; $qr_data_updated = BaseService::encryptQrData($qr_payload); \Db::name(BaseService::table('tickets')) ->where('id', $ticket_id) ->update(['qr_data' => $qr_data_updated]); } return $ticket_id; } /** * 核销票(事务保护 + 悲观锁防并发) * * @param string $ticket_code 票码 * @param int $verifier_id 核销员ID * @return array [code, msg] */ public static function verifyTicket($ticket_code, $verifier_id) { try { return \Db::transaction(function () use ($ticket_code, $verifier_id) { // FOR UPDATE 悲观锁:防止并发核销同一张票 $ticket = \Db::name(BaseService::table('tickets')) ->where('ticket_code', $ticket_code) ->lock(true) ->find(); if (empty($ticket)) { return ['code' => -1, 'msg' => '票码不存在']; } if ($ticket['verify_status'] == 1) { return ['code' => -2, 'msg' => '该票已核销']; } if ($ticket['verify_status'] == 2) { return ['code' => -3, 'msg' => '该票已退款']; } $now = BaseService::now(); // 更新票状态 \Db::name(BaseService::table('tickets')) ->where('id', $ticket['id']) ->update([ 'verify_status' => 1, 'verify_time' => $now, 'verifier_id' => $verifier_id, 'updated_at' => $now, ]); // 写入核销记录 $verifier = \Db::name(BaseService::table('verifiers')) ->where('id', $verifier_id) ->find(); \Db::name(BaseService::table('verifications'))->insert([ 'ticket_id' => $ticket['id'], 'ticket_code' => $ticket_code, 'verifier_id' => $verifier_id, 'verifier_name'=> $verifier['name'] ?? '', 'goods_id' => $ticket['goods_id'], 'created_at' => $now, ]); BaseService::log('verifyTicket: success', [ 'ticket_id' => $ticket['id'], 'verifier_id' => $verifier_id, ]); return [ 'code' => 0, 'msg' => '核销成功', 'data' => [ 'seat_info' => $ticket['seat_info'], 'real_name' => $ticket['real_name'], 'goods_name' => json_decode($ticket['goods_snapshot'] ?? '{}', true)['goods_name'] ?? '', ], ]; }); } catch (\Throwable $e) { BaseService::log('verifyTicket: transaction_error', [ 'ticket_code' => $ticket_code, 'error' => $e->getMessage(), ], 'error'); return ['code' => -999, 'msg' => '核销失败,请重试']; } } /** * 获取用户所有票 */ public static function getUserTickets($user_id, $status = null) { $where = ['user_id' => $user_id]; if ($status !== null) { $where['verify_status'] = $status; } return \Db::name(BaseService::table('tickets')) ->where($where) ->order('created_at', 'desc') ->select(); } /** * 生成 QR 码图片 URL * * @param string $ticket_code * @return string QR码图片URL */ public static function getQrCodeUrl($ticket_code) { $content = base64_encode(json_encode([ 'type' => 'vr_ticket', 'code' => $ticket_code, ])); return ROOT_URL . '?s=index/qrcode/index&content=' . urlencode($content) . '&size=8&level=H'; } }