# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: transaction-full.spec.ts >> 权益购买 >> 购买权益 - 无 client_id 应报错
- Location: tests/transaction-full.spec.ts:389:7

# Error details

```
Error: expect(received).not.toBe(expected) // Object.is equality

Expected: not 500
```

# Test source

```ts
  298 |     expect(res.status()).toBe(201);
  299 |   });
  300 | 
  301 |   test('会员 - member_card 支付', async ({ request }) => {
  302 |     const res = await request.post(`${BASE}/nail/pos/transactions`, {
  303 |       headers: headers(),
  304 |       data: makeTransaction({
  305 |         client_id: ADA_ID,
  306 |         items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
  307 |         method: 'member_card',
  308 |         split_payments: [{ method: 'member_card', amount_cents: 8800 }],
  309 |       }),
  310 |     });
  311 |     expect(res.status()).toBe(201);
  312 |   });
  313 | 
  314 |   test('会员 - 消费后 total_spent 和 visits 更新', async ({ request }) => {
  315 |     // 先查当前数据
  316 |     const before = await request.get(`${BASE}/nail/clients/${ADA_ID}`, { headers: headers() });
  317 |     const beforeData = await before.json();
  318 |     const beforeSpent = beforeData.total_spent_cents;
  319 |     const beforeVisits = beforeData.total_visits;
  320 | 
  321 |     // 做一单
  322 |     await request.post(`${BASE}/nail/pos/transactions`, {
  323 |       headers: headers(),
  324 |       data: makeTransaction({
  325 |         client_id: ADA_ID,
  326 |         items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
  327 |         split_payments: [{ method: 'cash', amount_cents: 8800 }],
  328 |       }),
  329 |     });
  330 | 
  331 |     // 验证数据更新
  332 |     const after = await request.get(`${BASE}/nail/clients/${ADA_ID}`, { headers: headers() });
  333 |     const afterData = await after.json();
  334 |     expect(afterData.total_spent_cents).toBe(beforeSpent + 8800);
  335 |     expect(afterData.total_visits).toBe(beforeVisits + 1);
  336 |   });
  337 | 
  338 |   test('会员 - debt 不为负', async ({ request }) => {
  339 |     const res = await request.get(`${BASE}/nail/clients/${ADA_ID}`, { headers: headers() });
  340 |     const data = await res.json();
  341 |     expect(data.debt_amount_cents).toBeGreaterThanOrEqual(0);
  342 |   });
  343 | });
  344 | 
  345 | // ═══════════════════════════════════════════════════
  346 | // 三、权益购买
  347 | // ═══════════════════════════════════════════════════
  348 | test.describe('权益购买', () => {
  349 | 
  350 |   test('购买储值方案 - 入门储值100', async ({ request }) => {
  351 |     const res = await request.post(`${BASE}/nail/pos/transactions`, {
  352 |       headers: headers(),
  353 |       data: makeTransaction({
  354 |         client_id: ADA_ID,
  355 |         goods_items: [{ goods_type: 'stored_value', goods_id: SV_PLAN_100, price_cents: 10000, name: '入门储值100', validity: { type: 'no_limit' } }],
  356 |         split_payments: [{ method: 'cash', amount_cents: 10000 }],
  357 |       }),
  358 |     });
  359 |     expect(res.status()).toBe(201);
  360 |     const data = await res.json();
  361 |     expect(data.status).toBe('completed');
  362 |     expect(data.total_cents).toBe(10000);
  363 |   });
  364 | 
  365 |   test('购买储值方案 - 储值200送10', async ({ request }) => {
  366 |     const res = await request.post(`${BASE}/nail/pos/transactions`, {
  367 |       headers: headers(),
  368 |       data: makeTransaction({
  369 |         client_id: ADA_ID,
  370 |         goods_items: [{ goods_type: 'stored_value', goods_id: SV_PLAN_200, price_cents: 20000, name: '储值200送10', validity: { type: 'no_limit' } }],
  371 |         split_payments: [{ method: 'cash', amount_cents: 20000 }],
  372 |       }),
  373 |     });
  374 |     expect(res.status()).toBe(201);
  375 |   });
  376 | 
  377 |   test('购买次卡 - 纯色美甲5次卡', async ({ request }) => {
  378 |     const res = await request.post(`${BASE}/nail/pos/transactions`, {
  379 |       headers: headers(),
  380 |       data: makeTransaction({
  381 |         client_id: ADA_ID,
  382 |         goods_items: [{ goods_type: 'visit_card', goods_id: VC_PURE_5, price_cents: 35800, name: '纯色美甲5次卡', validity: { type: 'purchase_day', unit: 'day', value: 365 } }],
  383 |         split_payments: [{ method: 'cash', amount_cents: 35800 }],
  384 |       }),
  385 |     });
  386 |     expect(res.status()).toBe(201);
  387 |   });
  388 | 
  389 |   test('购买权益 - 无 client_id 应报错', async ({ request }) => {
  390 |     const res = await request.post(`${BASE}/nail/pos/transactions`, {
  391 |       headers: headers(),
  392 |       data: makeTransaction({
  393 |         goods_items: [{ goods_type: 'stored_value', goods_id: SV_PLAN_100, price_cents: 10000, name: '入门储值100', validity: { type: 'no_limit' } }],
  394 |         split_payments: [{ method: 'cash', amount_cents: 10000 }],
  395 |       }),
  396 |     });
  397 |     // 购买权益必须关联会员，不应该成功
> 398 |     expect(res.status()).not.toBe(500);
      |                              ^ Error: expect(received).not.toBe(expected) // Object.is equality
  399 |   });
  400 | 
  401 |   test('购买权益 - 无效 goods_id 应报错', async ({ request }) => {
  402 |     const res = await request.post(`${BASE}/nail/pos/transactions`, {
  403 |       headers: headers(),
  404 |       data: makeTransaction({
  405 |         client_id: ADA_ID,
  406 |         goods_items: [{ goods_type: 'stored_value', goods_id: 'invalid_plan_id', price_cents: 10000, name: '假方案', validity: { type: 'no_limit' } }],
  407 |         split_payments: [{ method: 'cash', amount_cents: 10000 }],
  408 |       }),
  409 |     });
  410 |     expect(res.status()).not.toBe(500);
  411 |     expect(res.status()).toBeGreaterThanOrEqual(400);
  412 |   });
  413 | });
  414 | 
  415 | // ═══════════════════════════════════════════════════
  416 | // 四、储值卡核销
  417 | // ═══════════════════════════════════════════════════
  418 | test.describe('储值卡核销', () => {
  419 | 
  420 |   test('储值卡全额抵扣 - 不应 500', async ({ request }) => {
  421 |     const res = await request.post(`${BASE}/nail/pos/transactions`, {
  422 |       headers: headers(),
  423 |       data: makeTransaction({
  424 |         client_id: ADA_ID,
  425 |         items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
  426 |         method: 'stored_value',
  427 |         split_payments: [{ method: 'stored_value', amount_cents: 8800 }],
  428 |         stored_value_holding_id: 'test_holding',
  429 |         stored_value_splits: [{ holding_id: 'test_holding', amount_cents: 8800 }],
  430 |       }),
  431 |     });
  432 |     // 可能是 400（holding 不存在），但绝不应该是 500
  433 |     expect(res.status()).not.toBe(500);
  434 |   });
  435 | 
  436 |   test('储值卡部分抵扣（储值 + 现金混合）- 不应 500', async ({ request }) => {
  437 |     const res = await request.post(`${BASE}/nail/pos/transactions`, {
  438 |       headers: headers(),
  439 |       data: makeTransaction({
  440 |         client_id: ADA_ID,
  441 |         items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
  442 |         method: 'split',
  443 |         split_payments: [
  444 |           { method: 'stored_value', amount_cents: 5000 },
  445 |           { method: 'cash', amount_cents: 3800 },
  446 |         ],
  447 |         stored_value_holding_id: 'test_holding',
  448 |         stored_value_splits: [{ holding_id: 'test_holding', amount_cents: 5000 }],
  449 |       }),
  450 |     });
  451 |     expect(res.status()).not.toBe(500);
  452 |   });
  453 | 
  454 |   test('储值卡 + 小费 - 不应 500', async ({ request }) => {
  455 |     const res = await request.post(`${BASE}/nail/pos/transactions`, {
  456 |       headers: headers(),
  457 |       data: makeTransaction({
  458 |         client_id: ADA_ID,
  459 |         items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
  460 |         tip_cents: 880,
  461 |         method: 'stored_value',
  462 |         split_payments: [{ method: 'stored_value', amount_cents: 9680 }],
  463 |         stored_value_holding_id: 'test_holding',
  464 |         stored_value_splits: [{ holding_id: 'test_holding', amount_cents: 9680 }],
  465 |       }),
  466 |     });
  467 |     expect(res.status()).not.toBe(500);
  468 |   });
  469 | 
  470 |   test('储值卡余额不足 - 应返回 400 不是 500', async ({ request }) => {
  471 |     const res = await request.post(`${BASE}/nail/pos/transactions`, {
  472 |       headers: headers(),
  473 |       data: makeTransaction({
  474 |         client_id: ADA_ID,
  475 |         items: [serviceItem(SVC_SHELL, '韩式贝壳片', 22800)],
  476 |         method: 'stored_value',
  477 |         split_payments: [{ method: 'stored_value', amount_cents: 22800 }],
  478 |         stored_value_holding_id: 'test_holding',
  479 |         stored_value_splits: [{ holding_id: 'test_holding', amount_cents: 22800 }],
  480 |       }),
  481 |     });
  482 |     expect(res.status()).not.toBe(500);
  483 |   });
  484 | 
  485 |   test('储值卡 - 假 holding_id 应返回 4xx', async ({ request }) => {
  486 |     const res = await request.post(`${BASE}/nail/pos/transactions`, {
  487 |       headers: headers(),
  488 |       data: makeTransaction({
  489 |         client_id: ADA_ID,
  490 |         items: [serviceItem(SVC_PURE, '纯色美甲', 8800)],
  491 |         method: 'stored_value',
  492 |         split_payments: [{ method: 'stored_value', amount_cents: 8800 }],
  493 |         stored_value_holding_id: 'fake_holding_id_12345',
  494 |         stored_value_splits: [{ holding_id: 'fake_holding_id_12345', amount_cents: 8800 }],
  495 |       }),
  496 |     });
  497 |     expect(res.status()).toBeGreaterThanOrEqual(400);
  498 |     expect(res.status()).toBeLessThan(500);
```