From 17c12810737dc3a717d5cb553ba8475167b44e7a Mon Sep 17 00:00:00 2001 From: liu <2302563948@qq.com> Date: Fri, 4 Sep 2026 23:30:13 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=9E=E8=B0=83=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .phpunit.result.cache | 2 +- .../Mini/OnlinePaymentController.php | 12 +-- app/Services/OnlinePaymentService.php | 6 +- app/Services/WangpuPayService.php | 45 +++++++-- config/services.php | 1 + database/seeders/SysDataSeeder.php | 1 + tests/Feature/MiniOnlinePaymentTest.php | 95 ++++++++++++++----- 7 files changed, 119 insertions(+), 43 deletions(-) diff --git a/.phpunit.result.cache b/.phpunit.result.cache index abafeba..4b676ab 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":2,"defects":{"Tests\\Feature\\ProductPriceTest::test_product_list_returns_price_for_store_level":8,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":8,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":7,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":8,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":8,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":8,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":8,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":8,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":8,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":8,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":8,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":7,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":7,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":7,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":7,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":8,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":7,"Tests\\Feature\\MiniAuthTest::test_phone_binding_matches_store":8,"Tests\\Feature\\MiniAuthTest::test_phone_no_match_stays_pending":8,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":7,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":7,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":7,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":5,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":7,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":7,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":8,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":8,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":7,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":8,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":8,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":8,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":8,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":8,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":8,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":8,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":8,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":8,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":8,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":7,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":8,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":7,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":7,"Tests\\Feature\\ExportTest::test_export_pdf":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":7,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":7,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":7,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":8,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":7,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":8,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":8,"Tests\\Feature\\DebugPermissionTest::test_debug_permission":8,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":7,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":7,"Tests\\Feature\\BillPaymentTest::test_payment_detail_returns_voucher_urls":8,"Tests\\Feature\\ContainerReturnTest::test_press_containers_adds_amount_and_writes_record":7,"Tests\\Feature\\ContainerReturnTest::test_return_containers_deducts_amount":7,"Tests\\Feature\\ContainerReturnTest::test_mixed_sign_containers":7,"Tests\\Feature\\ContainerReturnTest::test_zero_containers_writes_no_record":7,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":7,"Tests\\Feature\\DashboardTest::test_requires_dashboard_analysis_ability":7,"Tests\\Feature\\DashboardTest::test_recon_summary_and_receivable":8,"Tests\\Feature\\DashboardDebugTest::test_debug":8,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":8,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":8,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":8,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":8,"Tests\\Feature\\StoreOrderItemTest::test_add_store_item_uses_level_percent_price":8,"Tests\\Feature\\ExportTest::test_export_suppliers_multi_sheet":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_skips_catalog_sync_when_product_deleted":7,"Tests\\Feature\\ProductImportExportTest::test_export_filters_by_category":8,"Tests\\Feature\\ProductImportExportTest::test_import_creates_products_and_auto_supplier":7,"Tests\\Feature\\ProductImportExportTest::test_import_matches_existing_supplier":7,"Tests\\Feature\\ProductImportExportTest::test_import_errors_abort_all_rows":7,"Tests\\Feature\\ProductImportExportTest::test_import_category_path_resolution":7,"Tests\\Feature\\MiniReportTest::test_report_accumulates_same_product_across_orders":7,"Tests\\Feature\\ExportTest::test_export_suppliers_split_by_market":7,"Tests\\Feature\\MiniOnlinePaymentTest::test_query_syncs_gateway_status":7,"Tests\\Feature\\MiniSpecialTest::test_cart_quantity_attached_for_store":8,"Tests\\Feature\\MiniWechatPaymentTest::test_create_wechat_payment_success":7,"Tests\\Feature\\ExportTest::test_export_lists_all_catalog_products_with_totals_row":7},"times":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.003,"Tests\\Feature\\ProductPriceTest::test_product_list_returns_price_for_store_level":0.09,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":0.007,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":0.01,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":0.007,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.048,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.047,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.007,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.051,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.049,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":0.094,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":0.028,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":0.1,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.101,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":0.076,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":0.022,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":0.028,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":0.021,"Tests\\Feature\\DebugGenerateTest::test_debug_generate":0.047,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":0.019,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":0.022,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.019,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.02,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.036,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.011,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.013,"Tests\\Feature\\StatementTest::test_generate_excludes_cancelled_and_used_items":0.016,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":0.056,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":0.003,"Tests\\Feature\\MiniAuthTest::test_phone_binding_matches_store":0.005,"Tests\\Feature\\MiniAuthTest::test_phone_no_match_stays_pending":0.001,"Tests\\Feature\\MiniAuthTest::test_sys_token_cannot_access_mini":0.006,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":0.044,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":0.092,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":0.017,"Tests\\Feature\\ExportTest::test_export_pdf":0.015,"Tests\\Feature\\ExportTest::test_export_invalid_format_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_requires_export_permission":0.055,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.001,"Tests\\Feature\\MiniAuthTest::test_login_fails_when_wechat_rejects_code":0.003,"Tests\\Feature\\CartTest::test_add_to_cart_succeeds":0.047,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.047,"Tests\\Feature\\CartTest::test_off_shelf_product_rejected":0.046,"Tests\\Feature\\CartTest::test_soft_deleted_product_rejected":0.008,"Tests\\Feature\\CartTest::test_product_without_level_price_rejected":0.006,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.006,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.087,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.051,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.051,"Tests\\Feature\\CartTest::test_empty_cart_returns_empty_items":0.048,"Tests\\Feature\\CartTest::test_update_quantity":0.049,"Tests\\Feature\\CartTest::test_update_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.049,"Tests\\Feature\\CartTest::test_invalid_quantity_rejected":0.047,"Tests\\Feature\\CartTest::test_delete_item":0.048,"Tests\\Feature\\CartTest::test_delete_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.098,"Tests\\Feature\\CartTest::test_unauthenticated_returns_401":0.002,"Tests\\Feature\\CartTest::test_cart_isolated_between_users":0.011,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":0.057,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_fixed_and_percent":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_actual_price":0.009,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":0.046,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":0.008,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":0.007,"Tests\\Feature\\ProductCostPriceTest::test_update_switches_fixed_to_percent":0.01,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":0.01,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":0.009,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":0.008,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":0.007,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_percent_columns":0.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":0.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":0.01,"Tests\\Feature\\PurchaseGenerateTest::test_generate_uses_min_actual_price_across_types":0.014,"Tests\\Feature\\ProductPriceTest::test_store_without_level_sees_products_without_price":0.046,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.009,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_registered_user":0.01,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unregistered_openid":0.002,"Tests\\Feature\\MiniAuthTest::test_register_binds_store_and_issues_token":0.009,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.002,"Tests\\Feature\\MiniAuthTest::test_register_rejects_duplicate_openid":0.003,"Tests\\Feature\\MiniStoreTest::test_info_returns_bound_store":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info":0.046,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.045,"Tests\\Feature\\MiniStoreTest::test_requires_bound_store":0.007,"Tests\\Feature\\MiniStoreTest::test_update_info_validates_length":0.045,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":0.012,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":0.022,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":0.019,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":0.02,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":0.014,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_accepted_orders_fails":0.053,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.103,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.048,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.051,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.053,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.011,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.011,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.012,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":0.013,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":0.01,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.01,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.057,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.061,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.247,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.054,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":0.103,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":0.103,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":0.103,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":0.032,"Tests\\Feature\\PurchaseEditTest::test_update_row_requires_at_least_one_field":0.101,"Tests\\Feature\\PurchaseEditTest::test_update_cell_rejects_negative_quantity":0.113,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":0.098,"Tests\\Feature\\ReconciliationTest::test_build_excludes_unfinished_orders":0.016,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":0.104,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":0.019,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":0.102,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_rejects_missing_params":0.102,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":0.106,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":0.012,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":0.109,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":0.106,"Tests\\Feature\\PurchaseEditTest::test_store_summary_returns_aggregated_items":0.106,"Tests\\Feature\\PurchaseEditTest::test_store_summary_aggregates_multiple_orders_with_weighted_price":0.06,"Tests\\Feature\\PurchaseEditTest::test_store_summary_rejects_missing_or_invalid_params":0.104,"Tests\\Feature\\ExportTest::test_export_invalid_type_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_missing_purchase_rejected":0.01,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_container_summary":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_container_syncs_order_amounts":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_container_rejected_when_purchase_completed":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_container_rejects_incomplete_or_foreign_orders":0.026,"Tests\\Feature\\PurchaseEditTest::test_update_container_validates_negative_and_duplicate_orders":0.018,"Tests\\Feature\\ProductCategoryTest::test_create_child_category_under_top_level":0.007,"Tests\\Feature\\ProductCategoryTest::test_create_category_under_second_level_rejected":0.007,"Tests\\Feature\\ProductCategoryTest::test_update_second_level_move_to_another_top":0.009,"Tests\\Feature\\ProductCategoryTest::test_update_parent_with_children_cannot_move_under_category":0.007,"Tests\\Feature\\ProductCategoryTest::test_update_with_missing_parent_rejected":0.007,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_parent_category_rejected":0.007,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":0.007,"Tests\\Feature\\ProductCategoryTest::test_mini_categories_returns_all_enabled_categories":0.045,"Tests\\Feature\\MiniBillTest::test_bill_list_derives_pay_state_and_settlement_date":0.048,"Tests\\Feature\\MiniBillTest::test_bill_list_summary_aggregates_unpaid":0.047,"Tests\\Feature\\MiniBillTest::test_bill_list_payable_filter":0.048,"Tests\\Feature\\MiniBillTest::test_bill_list_filters_by_bill_date_range":0.051,"Tests\\Feature\\MiniBillTest::test_bill_data_isolated_between_stores":0.088,"Tests\\Feature\\MiniBillTest::test_bill_detail_outputs_bill_items_and_orders":0.052,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":0.052,"Tests\\Feature\\StoreOrderTest::test_order_list_filters_by_order_date_range":0.059,"Tests\\Feature\\StoreOrderTest::test_order_list_rejects_invalid_filters":0.048,"Tests\\Feature\\BillExportTest::test_export_merges_items_across_bills":0.098,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":0.051,"Tests\\Feature\\BillExportTest::test_export_empty_category_rejected":0.071,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":0.048,"Tests\\Feature\\BillExportTest::test_export_invalid_ids_rejected":0.009,"Tests\\Feature\\DebugPermissionTest::test_debug_permission":0.098,"Tests\\Feature\\BillExportTest::test_mini_export_scoped_to_bound_store":0.014,"Tests\\Feature\\BillExportTest::test_mini_export_requires_store_binding":0.005,"Tests\\Feature\\MiniNoticeTest::test_notice_list_includes_personal_and_broadcast":0.046,"Tests\\Feature\\MiniNoticeTest::test_broadcast_read_creates_personal_copy":0.052,"Tests\\Feature\\MiniNoticeTest::test_personal_notice_read_marks_directly":0.046,"Tests\\Feature\\MiniNoticeTest::test_other_users_notice_not_found":0.007,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_only_accept_and_cancel":0.101,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_rejects_delivery_and_completion":0.161,"Tests\\Feature\\StoreOrderTest::test_batch_status_only_accept_and_cancel":0.101,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":0.059,"Tests\\Feature\\BillPaymentTest::test_manual_pay_accumulates_store_total_purchase_amount":0.068,"Tests\\Feature\\BillPaymentTest::test_payment_audit_pass_accumulates_store_total_purchase_amount":0.052,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":0.049,"Tests\\Feature\\MiniProductTest::test_product_detail_guest_sees_null_price":0.045,"Tests\\Feature\\MiniProductTest::test_product_detail_store_user_sees_level_price":0.006,"Tests\\Feature\\MiniProductTest::test_product_detail_percent_price_calculated_from_cost":0.046,"Tests\\Feature\\MiniProductTest::test_product_detail_off_shelf_or_missing_rejected":0.046,"Tests\\Feature\\MiniProductTest::test_product_list_store_user_sees_level_price":0.005,"Tests\\Feature\\MiniProductTest::test_product_list_guest_sees_null_price":0.044,"Tests\\Feature\\BillPaymentTest::test_admin_order_list_and_detail_show_bill_info":0.073,"Tests\\Feature\\BillPaymentTest::test_payment_detail_returns_voucher_urls":0.053,"Tests\\Feature\\StoreOrderTest::test_order_list_preview_includes_image_spec_and_unit":0.056,"Tests\\Feature\\ContainerReturnTest::test_press_containers_adds_amount_and_writes_record":0.063,"Tests\\Feature\\ContainerReturnTest::test_return_containers_deducts_amount":0.06,"Tests\\Feature\\ContainerReturnTest::test_mixed_sign_containers":0.059,"Tests\\Feature\\ContainerReturnTest::test_zero_containers_writes_no_record":0.058,"Tests\\Feature\\ContainerReturnTest::test_non_integer_containers_rejected":0.047,"Tests\\Feature\\ContainerReturnTest::test_manual_create_and_delete_routes_removed":0.007,"Tests\\Feature\\ContainerReturnTest::test_export_matrix_merges_same_day_and_zero_fills":0.09,"Tests\\Feature\\ContainerReturnTest::test_export_filters_by_selected_stores":0.129,"Tests\\Feature\\ContainerReturnTest::test_export_invalid_params_rejected":0.01,"Tests\\Feature\\ContainerReturnTest::test_export_empty_range_rejected":0.006,"Tests\\Feature\\ContainerReturnTest::test_export_requires_export_permission":0.006,"Tests\\Feature\\DashboardTest::test_guest_cannot_access_dashboard":0.002,"Tests\\Feature\\DashboardTest::test_requires_dashboard_analysis_ability":0.009,"Tests\\Feature\\DashboardTest::test_empty_database_returns_zero_structure":0.01,"Tests\\Feature\\DashboardTest::test_overview_excludes_cancelled_orders":0.051,"Tests\\Feature\\DashboardTest::test_week_growth_rate":0.056,"Tests\\Feature\\DashboardTest::test_trend_covers_30_days_with_zero_filling":0.05,"Tests\\Feature\\DashboardTest::test_category_sales_grouped_by_category":0.051,"Tests\\Feature\\DashboardTest::test_top_products_aggregated_across_orders":0.052,"Tests\\Feature\\DashboardTest::test_top_stores_ranked_by_amount":0.092,"Tests\\Feature\\DashboardTest::test_purchase_amount_prefers_actual_over_estimate":0.01,"Tests\\Feature\\DashboardTest::test_recon_summary_and_receivable":0.05,"Tests\\Feature\\DashboardTest::test_latest_orders_limited_and_ordered":0.052,"Tests\\Feature\\DashboardTest::test_archives_count_active_records_only":0.132,"Tests\\Feature\\DashboardDebugTest::test_debug_ability_check":0.044,"Tests\\Feature\\DashboardDebugTest::test_debug":0.045,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_cost_and_notifies_all_stores":0.091,"Tests\\Feature\\ProductPriceTest::test_level_percent_update_notifies_level_stores":0.091,"Tests\\Feature\\ProductPriceTest::test_level_update_without_percent_change_sends_no_notice":0.048,"Tests\\Feature\\ProductPriceTest::test_level_percent_validation":0.011,"Tests\\Feature\\ProductCostPriceTest::test_calc_level_price":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_level_price_edge_cases":0.002,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_level_prices":0.008,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_price":0.007,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_row_update":0.052,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_level_price_columns":0.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_level_price":0.049,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_level_price":0.047,"Tests\\Feature\\CartTest::test_store_level_missing_rejected":0.006,"Tests\\Feature\\StoreOrderTest::test_store_level_missing_rejected":0.045,"Tests\\Feature\\PurchaseEditTest::test_update_row_skips_catalog_sync_when_product_deleted":0.105,"Tests\\Feature\\PurchaseEditTest::test_store_item_add_creates_item_and_recalculates":0.106,"Tests\\Feature\\PurchaseEditTest::test_store_item_update_merges_multi_order_rows":0.06,"Tests\\Feature\\PurchaseEditTest::test_store_item_remove_deletes_and_recalculates":0.104,"Tests\\Feature\\StoreOrderItemTest::test_update_store_item_recalculates_totals":0.062,"Tests\\Feature\\StoreOrderItemTest::test_store_item_edits_rejected_when_purchase_completed":0.061,"Tests\\Feature\\StoreOrderItemTest::test_update_store_item_validates_negative_values":0.068,"Tests\\Feature\\StoreOrderItemTest::test_add_store_item_uses_level_percent_price":0.06,"Tests\\Feature\\StoreOrderItemTest::test_add_store_item_rejected_when_product_deleted":0.058,"Tests\\Feature\\ExportTest::test_export_lists_all_catalog_products_with_totals_row":0.102,"Tests\\Feature\\ExportTest::test_export_supplier_filter":0.101,"Tests\\Feature\\ExportTest::test_export_stores_multi_sheet":0.104,"Tests\\Feature\\ExportTest::test_export_stores_single_store":0.1,"Tests\\Feature\\ExportTest::test_export_stores_without_items_rejected":0.144,"Tests\\Feature\\ExportTest::test_export_suppliers_multi_sheet":0.103,"Tests\\Feature\\ExportTest::test_export_suppliers_single":0.101,"Tests\\Feature\\ExportTest::test_export_suppliers_without_items_rejected":0.102,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_change_reprices_items_by_store_level":0.103,"Tests\\Feature\\PurchaseEditTest::test_update_row_without_cost_change_keeps_item_price":0.104,"Tests\\Feature\\CartTest::test_update_other_stores_item_rejected":0.09,"Tests\\Feature\\CartTest::test_delete_other_stores_item_rejected":0.09,"Tests\\Feature\\CartTest::test_cart_isolated_between_stores":0.089,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_valid_credentials":0.127,"Tests\\Feature\\MiniAuthTest::test_login_rejects_wrong_password":0.123,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unknown_username":0.002,"Tests\\Feature\\MiniAuthTest::test_disabled_store_cannot_login":0.124,"Tests\\Feature\\MiniAuthTest::test_login_validates_required_params":0.002,"Tests\\Feature\\MiniAuthTest::test_login_token_accesses_protected_routes":0.131,"Tests\\Feature\\MiniAuthTest::test_set_password_succeeds":0.208,"Tests\\Feature\\MiniAuthTest::test_set_password_rejects_wrong_old_password":0.127,"Tests\\Feature\\MiniAuthTest::test_set_password_validates_confirmation":0.045,"Tests\\Feature\\BillExportTest::test_mini_export_scoped_to_own_store":0.093,"Tests\\Feature\\MiniNoticeTest::test_other_stores_notice_not_found":0.088,"Tests\\Feature\\MiniProductTest::test_product_detail_store_sees_level_price":0.045,"Tests\\Feature\\MiniProductTest::test_product_list_store_sees_level_price":0.046,"Tests\\Feature\\MiniStoreTest::test_info_returns_current_store":0.044,"Tests\\Feature\\MiniStoreTest::test_disabled_store_rejected":0.047,"Tests\\Feature\\ProductImportExportTest::test_export_downloads_all_products":0.008,"Tests\\Feature\\ProductImportExportTest::test_export_filters_by_category":0.008,"Tests\\Feature\\ProductImportExportTest::test_export_template_mode":0.005,"Tests\\Feature\\ProductImportExportTest::test_export_requires_permission":0.006,"Tests\\Feature\\ProductImportExportTest::test_import_creates_products_and_auto_supplier":0.043,"Tests\\Feature\\ProductImportExportTest::test_import_matches_existing_supplier":0.016,"Tests\\Feature\\ProductImportExportTest::test_import_errors_abort_all_rows":0.017,"Tests\\Feature\\ProductImportExportTest::test_import_category_path_resolution":0.028,"Tests\\Feature\\ProductImportExportTest::test_import_rejects_invalid_header":0.014,"Tests\\Feature\\ProductImportExportTest::test_import_requires_permission":0.012,"Tests\\Feature\\ProductImportExportTest::test_list_filters_by_market":0.01,"Tests\\Feature\\MiniReportTest::test_report_aggregates_total_and_item_percent":0.047,"Tests\\Feature\\MiniReportTest::test_report_accumulates_same_product_across_orders":0.052,"Tests\\Feature\\MiniReportTest::test_report_filters_by_custom_date_range":0.046,"Tests\\Feature\\MiniReportTest::test_report_excludes_cancelled_and_deleted_orders":0.049,"Tests\\Feature\\MiniReportTest::test_report_preset_month_and_last_month":0.049,"Tests\\Feature\\MiniReportTest::test_report_preset_week_and_last_week":0.05,"Tests\\Feature\\MiniReportTest::test_report_defaults_to_current_month":0.047,"Tests\\Feature\\MiniReportTest::test_report_data_isolated_between_stores":0.088,"Tests\\Feature\\MiniReportTest::test_report_empty_period_returns_zero":0.046,"Tests\\Feature\\MiniReportTest::test_report_validates_params":0.051,"Tests\\Feature\\ProductBatchDeleteTest::test_batch_delete_soft_deletes_selected_products":0.008,"Tests\\Feature\\ProductBatchDeleteTest::test_batch_delete_requires_non_empty_ids":0.006,"Tests\\Feature\\ProductBatchDeleteTest::test_batch_delete_forbidden_without_permission":0.007,"Tests\\Feature\\CartTest::test_place_order_clears_ordered_items_from_cart":0.057,"Tests\\Feature\\StoreOrderItemTest::test_admin_order_list_filters_by_order_date_range":0.066,"Tests\\Feature\\StoreOrderItemTest::test_admin_order_list_search_by_purchase_no":0.064,"Tests\\Feature\\ExportTest::test_export_suppliers_split_by_market":0.056,"Tests\\Feature\\BillPaymentTest::test_generate_bill_saves_after_sale_and_remark":0.119,"Tests\\Feature\\ProductCostPriceTest::test_product_price_unit_default_and_editable":0.013,"Tests\\Feature\\StoreOrderTest::test_place_order_blocked_by_overdue_unpaid_bill":0.053,"Tests\\Feature\\StoreOrderTest::test_place_order_allowed_within_payment_cycle":0.057,"Tests\\Feature\\CartTest::test_cart_summary_endpoint":0.052,"Tests\\Feature\\CartTest::test_cart_summary_requires_login":0.002,"Tests\\Feature\\MiniProductTest::test_product_list_appends_cart_quantity_and_summary":0.054,"Tests\\Feature\\MiniProductTest::test_product_list_guest_sees_zero_cart_fields":0.046,"Tests\\Feature\\MiniProductTest::test_product_detail_appends_cart_quantity":0.049,"Tests\\Feature\\MiniProductTest::test_home_appends_cart_summary":0.05,"Tests\\Feature\\MiniOnlinePaymentTest::test_sign_matches_document_example":0.002,"Tests\\Feature\\MiniOnlinePaymentTest::test_create_online_payment_success":0.057,"Tests\\Feature\\MiniOnlinePaymentTest::test_create_online_payment_validation":0.098,"Tests\\Feature\\MiniOnlinePaymentTest::test_create_fails_when_code2session_fails":0.048,"Tests\\Feature\\MiniOnlinePaymentTest::test_create_gateway_failure_releases_bills":0.054,"Tests\\Feature\\MiniOnlinePaymentTest::test_notify_settles_payment":0.055,"Tests\\Feature\\MiniOnlinePaymentTest::test_notify_rejects_invalid_messages":0.06,"Tests\\Feature\\MiniOnlinePaymentTest::test_query_syncs_gateway_status":0.06,"Tests\\Feature\\MiniOnlinePaymentTest::test_query_rejects_other_store_payment":0.088,"Tests\\Feature\\MiniOnlinePaymentTest::test_offline_payment_flow_unaffected":0.048,"Tests\\Feature\\BillPaymentTest::test_generate_bill_positive_after_sale_added_to_total":0.114,"Tests\\Feature\\BillPaymentTest::test_generate_bill_rejects_non_numeric_after_sale":0.048,"Tests\\Feature\\ClientSpecialTest::test_batch_add_creates_specials":0.008,"Tests\\Feature\\ClientSpecialTest::test_batch_add_skips_existing_products":0.009,"Tests\\Feature\\ClientSpecialTest::test_batch_add_requires_non_empty_product_ids":0.006,"Tests\\Feature\\ClientSpecialTest::test_batch_add_forbidden_without_permission":0.007,"Tests\\Feature\\ClientSpecialTest::test_query_filters_by_product_name_keyword":0.007,"Tests\\Feature\\ClientSpecialTest::test_update_changes_sort_and_status":0.006,"Tests\\Feature\\ClientSpecialTest::test_delete_and_batch_delete":0.008,"Tests\\Feature\\ClientSpecialTest::test_batch_delete_requires_non_empty_ids":0.006,"Tests\\Feature\\MiniSpecialTest::test_guest_sees_specials_with_null_price":0.045,"Tests\\Feature\\MiniSpecialTest::test_store_sees_level_price":0.047,"Tests\\Feature\\MiniSpecialTest::test_disabled_special_and_off_shelf_product_excluded":0.045,"Tests\\Feature\\MiniSpecialTest::test_specials_sorted_by_sort_asc":0.087,"Tests\\Feature\\MiniSpecialTest::test_cart_quantity_attached_for_store":0.046,"Tests\\Feature\\MiniOnlinePaymentTest::test_aes_encryption_matches_demo_golden":0.001,"Tests\\Feature\\MiniOnlinePaymentTest::test_notify_envelope_round_trip":0.004,"Tests\\Feature\\MiniOnlinePaymentTest::test_swapped_key_config_fails_with_clear_message":0.004,"Tests\\Feature\\MiniWechatPaymentTest::test_create_wechat_payment_success":0.054,"Tests\\Feature\\MiniWechatPaymentTest::test_create_wechat_payment_gateway_failure_releases_bills":0.054,"Tests\\Feature\\MiniWechatPaymentTest::test_wechat_notify_settles_payment":0.056,"Tests\\Feature\\MiniWechatPaymentTest::test_wechat_notify_rejects_invalid_messages":0.054,"Tests\\Feature\\MiniWechatPaymentTest::test_wechat_notify_with_platform_cert_validates_signature":0.052,"Tests\\Feature\\MiniWechatPaymentTest::test_wechat_query_syncs_status":0.057,"Tests\\Feature\\MiniWechatPaymentTest::test_wangpu_channel_remains_default":0.051,"Tests\\Feature\\BillPaymentTest::test_payment_config_returns_pay_methods":0.048,"Tests\\Feature\\MiniMpPaymentTest::test_mp_scene_wechat_channel":0.08,"Tests\\Feature\\MiniMpPaymentTest::test_mp_scene_wangpu_channel":0.055,"Tests\\Feature\\MiniMpPaymentTest::test_mp_scene_oauth_failure":0.049,"Tests\\Feature\\MiniMpPaymentTest::test_mp_scene_requires_mp_config":0.048,"Tests\\Feature\\MiniMpPaymentTest::test_default_scene_remains_mini":0.054,"Tests\\Feature\\MiniProductTest::test_product_list_ordered_by_category_tree":0.006}} \ No newline at end of file +{"version":2,"defects":{"Tests\\Feature\\ProductPriceTest::test_product_list_returns_price_for_store_level":8,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":8,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":7,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":8,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":8,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":8,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":8,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":8,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":8,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":8,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":8,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":7,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":7,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":7,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":7,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":8,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":7,"Tests\\Feature\\MiniAuthTest::test_phone_binding_matches_store":8,"Tests\\Feature\\MiniAuthTest::test_phone_no_match_stays_pending":8,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":7,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":7,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":7,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":5,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":7,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":7,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":8,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":8,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":7,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":7,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":8,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":8,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":8,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":8,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":8,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":8,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":8,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":8,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":8,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":8,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":8,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":7,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":8,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":7,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":7,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":7,"Tests\\Feature\\ExportTest::test_export_pdf":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":7,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":7,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":7,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":7,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":7,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":7,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":8,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":7,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":8,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":8,"Tests\\Feature\\DebugPermissionTest::test_debug_permission":8,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":7,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":7,"Tests\\Feature\\BillPaymentTest::test_payment_detail_returns_voucher_urls":8,"Tests\\Feature\\ContainerReturnTest::test_press_containers_adds_amount_and_writes_record":7,"Tests\\Feature\\ContainerReturnTest::test_return_containers_deducts_amount":7,"Tests\\Feature\\ContainerReturnTest::test_mixed_sign_containers":7,"Tests\\Feature\\ContainerReturnTest::test_zero_containers_writes_no_record":7,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":7,"Tests\\Feature\\DashboardTest::test_requires_dashboard_analysis_ability":7,"Tests\\Feature\\DashboardTest::test_recon_summary_and_receivable":8,"Tests\\Feature\\DashboardDebugTest::test_debug":8,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":8,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":8,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":8,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":8,"Tests\\Feature\\StoreOrderItemTest::test_add_store_item_uses_level_percent_price":8,"Tests\\Feature\\ExportTest::test_export_suppliers_multi_sheet":7,"Tests\\Feature\\PurchaseEditTest::test_update_row_skips_catalog_sync_when_product_deleted":7,"Tests\\Feature\\ProductImportExportTest::test_export_filters_by_category":8,"Tests\\Feature\\ProductImportExportTest::test_import_creates_products_and_auto_supplier":7,"Tests\\Feature\\ProductImportExportTest::test_import_matches_existing_supplier":7,"Tests\\Feature\\ProductImportExportTest::test_import_errors_abort_all_rows":7,"Tests\\Feature\\ProductImportExportTest::test_import_category_path_resolution":7,"Tests\\Feature\\MiniReportTest::test_report_accumulates_same_product_across_orders":7,"Tests\\Feature\\ExportTest::test_export_suppliers_split_by_market":7,"Tests\\Feature\\MiniOnlinePaymentTest::test_query_syncs_gateway_status":7,"Tests\\Feature\\MiniSpecialTest::test_cart_quantity_attached_for_store":8,"Tests\\Feature\\MiniWechatPaymentTest::test_create_wechat_payment_success":7,"Tests\\Feature\\ExportTest::test_export_lists_all_catalog_products_with_totals_row":7,"Tests\\Feature\\MiniOnlinePaymentTest::test_create_online_payment_success":7,"Tests\\Feature\\MiniMpPaymentTest::test_mp_scene_wechat_channel":8,"Tests\\Feature\\MiniMpPaymentTest::test_default_scene_remains_mini":7},"times":{"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.003,"Tests\\Feature\\ProductPriceTest::test_product_list_returns_price_for_store_level":0.09,"Tests\\Feature\\ProductPriceTest::test_store_without_level_is_rejected":0.007,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_and_notifies_affected_stores":0.01,"Tests\\Feature\\ProductPriceTest::test_price_matrix_structure":0.007,"Tests\\Feature\\StoreOrderTest::test_place_order_snapshots_level_price_and_recalculates":0.048,"Tests\\Feature\\StoreOrderTest::test_client_supplied_amount_is_ignored":0.048,"Tests\\Feature\\StoreOrderTest::test_product_without_level_price_rejected":0.007,"Tests\\Feature\\StoreOrderTest::test_cancel_only_pending_orders":0.052,"Tests\\Feature\\StoreOrderTest::test_summarized_order_cannot_be_cancelled":0.049,"Tests\\Feature\\StoreOrderTest::test_order_data_isolated_between_stores":0.092,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_orders_by_product":0.028,"Tests\\Feature\\PurchaseGenerateTest::test_generate_writes_back_order_status":0.101,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_pending_orders_fails":0.006,"Tests\\Feature\\PurchaseGenerateTest::test_generate_is_idempotent":0.106,"Tests\\Feature\\AllocationTest::test_allocation_conserves_amount_with_tail_correction":0.076,"Tests\\Feature\\AllocationTest::test_allocation_follows_order_ratio":0.022,"Tests\\Feature\\AllocationTest::test_allocation_is_idempotent":0.028,"Tests\\Feature\\AllocationTest::test_allocation_rejected_without_actual_amount":0.021,"Tests\\Feature\\DebugGenerateTest::test_debug_generate":0.047,"Tests\\Feature\\ReconciliationTest::test_build_creates_reconciliation_items":0.019,"Tests\\Feature\\ReconciliationTest::test_build_filters_by_supplier":0.022,"Tests\\Feature\\ReconciliationTest::test_update_item_recalculates_diff_and_header":0.019,"Tests\\Feature\\ReconciliationTest::test_toggle_reconciled_flag":0.02,"Tests\\Feature\\ReconciliationTest::test_settle_creates_settlements_per_store":0.036,"Tests\\Feature\\StatementTest::test_generate_snapshots_payment_cycle":0.011,"Tests\\Feature\\StatementTest::test_statement_isolated_between_stores":0.013,"Tests\\Feature\\StatementTest::test_generate_excludes_cancelled_and_used_items":0.016,"Tests\\Feature\\MiniAuthTest::test_login_creates_user_and_issues_token":0.056,"Tests\\Feature\\MiniAuthTest::test_disabled_user_cannot_login":0.003,"Tests\\Feature\\MiniAuthTest::test_phone_binding_matches_store":0.005,"Tests\\Feature\\MiniAuthTest::test_phone_no_match_stays_pending":0.001,"Tests\\Feature\\MiniAuthTest::test_sys_token_cannot_access_mini":0.005,"Tests\\Feature\\MiniAuthTest::test_mini_token_cannot_access_admin_api":0.044,"Tests\\Feature\\ExportTest::test_export_xlsx_with_encoded_chinese_filename":0.097,"Tests\\Feature\\ExportTest::test_export_category_filters_to_vegetables":0.017,"Tests\\Feature\\ExportTest::test_export_pdf":0.015,"Tests\\Feature\\ExportTest::test_export_invalid_format_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_requires_export_permission":0.056,"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.001,"Tests\\Feature\\MiniAuthTest::test_login_fails_when_wechat_rejects_code":0.003,"Tests\\Feature\\CartTest::test_add_to_cart_succeeds":0.046,"Tests\\Feature\\CartTest::test_duplicate_add_merges_quantity":0.048,"Tests\\Feature\\CartTest::test_off_shelf_product_rejected":0.047,"Tests\\Feature\\CartTest::test_soft_deleted_product_rejected":0.008,"Tests\\Feature\\CartTest::test_product_without_level_price_rejected":0.006,"Tests\\Feature\\CartTest::test_unbound_user_rejected":0.006,"Tests\\Feature\\CartTest::test_store_without_level_rejected":0.085,"Tests\\Feature\\CartTest::test_list_with_prices_amounts_and_totals":0.049,"Tests\\Feature\\CartTest::test_list_marks_off_shelf_item_unbuyable":0.05,"Tests\\Feature\\CartTest::test_empty_cart_returns_empty_items":0.047,"Tests\\Feature\\CartTest::test_update_quantity":0.048,"Tests\\Feature\\CartTest::test_update_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_update_missing_item_rejected":0.047,"Tests\\Feature\\CartTest::test_invalid_quantity_rejected":0.045,"Tests\\Feature\\CartTest::test_delete_item":0.046,"Tests\\Feature\\CartTest::test_delete_other_users_item_rejected":0.009,"Tests\\Feature\\CartTest::test_clear_only_own_cart":0.096,"Tests\\Feature\\CartTest::test_unauthenticated_returns_401":0.002,"Tests\\Feature\\CartTest::test_cart_isolated_between_users":0.011,"Tests\\Feature\\DebugCartListTest::test_dump_cart_list_response":0.057,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_fixed_and_percent":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_actual_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_actual_price":0.009,"Tests\\Feature\\ProductCostPriceTest::test_mini_list_returns_actual_price_without_cost_price":0.047,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_and_percent_pricing":0.008,"Tests\\Feature\\ProductCostPriceTest::test_create_percent_row_without_percent_fails":0.007,"Tests\\Feature\\ProductCostPriceTest::test_update_switches_fixed_to_percent":0.01,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_only_notifies_percent_level_stores":0.01,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_percent_row_update":0.009,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_mixed_row_updates_cost_and_level":0.008,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_empty_row_rejected":0.007,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_percent_columns":0.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_actual_price_for_percent_row":0.007,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_actual_price_for_percent_row":0.01,"Tests\\Feature\\PurchaseGenerateTest::test_generate_uses_min_actual_price_across_types":0.014,"Tests\\Feature\\ProductPriceTest::test_store_without_level_sees_products_without_price":0.046,"Tests\\Feature\\ProductPriceTest::test_unbound_user_can_browse_products_without_price_but_cannot_cart":0.009,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_registered_user":0.01,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unregistered_openid":0.002,"Tests\\Feature\\MiniAuthTest::test_register_binds_store_and_issues_token":0.009,"Tests\\Feature\\MiniAuthTest::test_register_rejects_unknown_store":0.002,"Tests\\Feature\\MiniAuthTest::test_register_rejects_duplicate_openid":0.003,"Tests\\Feature\\MiniStoreTest::test_info_returns_bound_store":0.005,"Tests\\Feature\\MiniStoreTest::test_update_info":0.045,"Tests\\Feature\\MiniStoreTest::test_update_info_ignores_readonly_fields":0.046,"Tests\\Feature\\MiniStoreTest::test_requires_bound_store":0.007,"Tests\\Feature\\MiniStoreTest::test_update_info_validates_length":0.046,"Tests\\Feature\\StoreOrderTest::test_container_update_recalculates_amounts":0.012,"Tests\\Feature\\StoreOrderTest::test_container_update_allowed_in_editable_statuses":0.022,"Tests\\Feature\\StoreOrderTest::test_container_update_rejected_in_other_statuses":0.019,"Tests\\Feature\\StoreOrderTest::test_container_update_heals_legacy_product_amount":0.02,"Tests\\Feature\\StoreOrderTest::test_container_update_validates_negative_numbers":0.014,"Tests\\Feature\\PurchaseGenerateTest::test_generate_without_accepted_orders_fails":0.091,"Tests\\Feature\\PurchaseGenerateTest::test_generate_with_selected_order_ids":0.133,"Tests\\Feature\\StoreOrderItemTest::test_place_order_snapshots_full_product_info":0.048,"Tests\\Feature\\StoreOrderItemTest::test_mini_detail_hides_cost_price":0.051,"Tests\\Feature\\StoreOrderItemTest::test_admin_detail_shows_cost_price_and_supplier":0.053,"Tests\\Feature\\StoreOrderItemTest::test_update_item_recalculates_order_totals":0.011,"Tests\\Feature\\StoreOrderItemTest::test_update_item_rejected_when_completed_or_cancelled":0.011,"Tests\\Feature\\StoreOrderItemTest::test_update_item_validates_negative_price":0.012,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_updates_snapshot_from_product":0.013,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_recalculates_percent_price_with_latest_cost":0.01,"Tests\\Feature\\StoreOrderItemTest::test_sync_item_rejected_when_product_deleted":0.01,"Tests\\Feature\\StoreOrderItemTest::test_order_list_search_by_product_name":0.056,"Tests\\Feature\\StoreOrderTest::test_soft_delete_only_cancelled_orders":0.062,"Tests\\Feature\\StoreOrderTest::test_soft_delete_rejected_for_non_cancelled_orders":0.254,"Tests\\Feature\\StoreOrderTest::test_soft_delete_twice_rejected":0.054,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_store_matrix":0.106,"Tests\\Feature\\PurchaseEditTest::test_update_cell_syncs_order_item_and_totals":0.104,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_syncs_all_order_items":0.105,"Tests\\Feature\\PurchaseEditTest::test_update_row_weight_distributed_by_quantity":0.032,"Tests\\Feature\\PurchaseEditTest::test_update_row_requires_at_least_one_field":0.1,"Tests\\Feature\\PurchaseEditTest::test_update_cell_rejects_negative_quantity":0.099,"Tests\\Feature\\PurchaseGenerateTest::test_generate_aggregates_order_items_into_header":0.1,"Tests\\Feature\\ReconciliationTest::test_build_excludes_unfinished_orders":0.016,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_product_attributes":0.12,"Tests\\Feature\\PurchaseEditTest::test_update_row_syncs_to_product_catalog":0.019,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_returns_items_with_order_info":0.105,"Tests\\Feature\\PurchaseEditTest::test_cell_detail_rejects_missing_params":0.105,"Tests\\Feature\\PurchaseEditTest::test_update_cell_weight_cascades_totals":0.109,"Tests\\Feature\\PurchaseEditTest::test_cell_sync_refreshes_snapshot_and_cascades":0.012,"Tests\\Feature\\PurchaseEditTest::test_edits_rejected_when_purchase_completed":0.108,"Tests\\Feature\\PurchaseEditTest::test_update_row_does_not_touch_manual_weight":0.111,"Tests\\Feature\\PurchaseEditTest::test_store_summary_returns_aggregated_items":0.104,"Tests\\Feature\\PurchaseEditTest::test_store_summary_aggregates_multiple_orders_with_weighted_price":0.061,"Tests\\Feature\\PurchaseEditTest::test_store_summary_rejects_missing_or_invalid_params":0.101,"Tests\\Feature\\ExportTest::test_export_invalid_type_rejected":0.016,"Tests\\Feature\\ExportTest::test_export_missing_purchase_rejected":0.009,"Tests\\Feature\\PurchaseEditTest::test_detail_returns_container_summary":0.022,"Tests\\Feature\\PurchaseEditTest::test_update_container_syncs_order_amounts":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_container_rejected_when_purchase_completed":0.018,"Tests\\Feature\\PurchaseEditTest::test_update_container_rejects_incomplete_or_foreign_orders":0.026,"Tests\\Feature\\PurchaseEditTest::test_update_container_validates_negative_and_duplicate_orders":0.018,"Tests\\Feature\\ProductCategoryTest::test_create_child_category_under_top_level":0.006,"Tests\\Feature\\ProductCategoryTest::test_create_category_under_second_level_rejected":0.007,"Tests\\Feature\\ProductCategoryTest::test_update_second_level_move_to_another_top":0.02,"Tests\\Feature\\ProductCategoryTest::test_update_parent_with_children_cannot_move_under_category":0.012,"Tests\\Feature\\ProductCategoryTest::test_update_with_missing_parent_rejected":0.011,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_parent_category_rejected":0.012,"Tests\\Feature\\ProductCategoryTest::test_create_product_with_leaf_category_ok":0.013,"Tests\\Feature\\ProductCategoryTest::test_mini_categories_returns_all_enabled_categories":0.045,"Tests\\Feature\\MiniBillTest::test_bill_list_derives_pay_state_and_settlement_date":0.048,"Tests\\Feature\\MiniBillTest::test_bill_list_summary_aggregates_unpaid":0.047,"Tests\\Feature\\MiniBillTest::test_bill_list_payable_filter":0.047,"Tests\\Feature\\MiniBillTest::test_bill_list_filters_by_bill_date_range":0.052,"Tests\\Feature\\MiniBillTest::test_bill_data_isolated_between_stores":0.09,"Tests\\Feature\\MiniBillTest::test_bill_detail_outputs_bill_items_and_orders":0.053,"Tests\\Feature\\StoreOrderTest::test_order_list_outputs_status_name_and_item_preview":0.055,"Tests\\Feature\\StoreOrderTest::test_order_list_filters_by_order_date_range":0.06,"Tests\\Feature\\StoreOrderTest::test_order_list_rejects_invalid_filters":0.05,"Tests\\Feature\\BillExportTest::test_export_merges_items_across_bills":0.096,"Tests\\Feature\\BillExportTest::test_export_filters_by_root_category":0.052,"Tests\\Feature\\BillExportTest::test_export_empty_category_rejected":0.07,"Tests\\Feature\\BillExportTest::test_export_requires_export_permission":0.049,"Tests\\Feature\\BillExportTest::test_export_invalid_ids_rejected":0.009,"Tests\\Feature\\DebugPermissionTest::test_debug_permission":0.098,"Tests\\Feature\\BillExportTest::test_mini_export_scoped_to_bound_store":0.014,"Tests\\Feature\\BillExportTest::test_mini_export_requires_store_binding":0.005,"Tests\\Feature\\MiniNoticeTest::test_notice_list_includes_personal_and_broadcast":0.055,"Tests\\Feature\\MiniNoticeTest::test_broadcast_read_creates_personal_copy":0.052,"Tests\\Feature\\MiniNoticeTest::test_personal_notice_read_marks_directly":0.046,"Tests\\Feature\\MiniNoticeTest::test_other_users_notice_not_found":0.007,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_only_accept_and_cancel":0.101,"Tests\\Feature\\StoreOrderTest::test_manual_status_flow_rejects_delivery_and_completion":0.149,"Tests\\Feature\\StoreOrderTest::test_batch_status_only_accept_and_cancel":0.102,"Tests\\Feature\\BillPaymentTest::test_order_status_progresses_via_business_chain":0.057,"Tests\\Feature\\BillPaymentTest::test_manual_pay_accumulates_store_total_purchase_amount":0.067,"Tests\\Feature\\BillPaymentTest::test_payment_audit_pass_accumulates_store_total_purchase_amount":0.052,"Tests\\Feature\\BillPaymentTest::test_payment_audit_reject_does_not_accumulate":0.048,"Tests\\Feature\\MiniProductTest::test_product_detail_guest_sees_null_price":0.045,"Tests\\Feature\\MiniProductTest::test_product_detail_store_user_sees_level_price":0.006,"Tests\\Feature\\MiniProductTest::test_product_detail_percent_price_calculated_from_cost":0.045,"Tests\\Feature\\MiniProductTest::test_product_detail_off_shelf_or_missing_rejected":0.046,"Tests\\Feature\\MiniProductTest::test_product_list_store_user_sees_level_price":0.005,"Tests\\Feature\\MiniProductTest::test_product_list_guest_sees_null_price":0.045,"Tests\\Feature\\BillPaymentTest::test_admin_order_list_and_detail_show_bill_info":0.07,"Tests\\Feature\\BillPaymentTest::test_payment_detail_returns_voucher_urls":0.053,"Tests\\Feature\\StoreOrderTest::test_order_list_preview_includes_image_spec_and_unit":0.052,"Tests\\Feature\\ContainerReturnTest::test_press_containers_adds_amount_and_writes_record":0.061,"Tests\\Feature\\ContainerReturnTest::test_return_containers_deducts_amount":0.057,"Tests\\Feature\\ContainerReturnTest::test_mixed_sign_containers":0.06,"Tests\\Feature\\ContainerReturnTest::test_zero_containers_writes_no_record":0.058,"Tests\\Feature\\ContainerReturnTest::test_non_integer_containers_rejected":0.047,"Tests\\Feature\\ContainerReturnTest::test_manual_create_and_delete_routes_removed":0.006,"Tests\\Feature\\ContainerReturnTest::test_export_matrix_merges_same_day_and_zero_fills":0.089,"Tests\\Feature\\ContainerReturnTest::test_export_filters_by_selected_stores":0.13,"Tests\\Feature\\ContainerReturnTest::test_export_invalid_params_rejected":0.01,"Tests\\Feature\\ContainerReturnTest::test_export_empty_range_rejected":0.007,"Tests\\Feature\\ContainerReturnTest::test_export_requires_export_permission":0.006,"Tests\\Feature\\DashboardTest::test_guest_cannot_access_dashboard":0.002,"Tests\\Feature\\DashboardTest::test_requires_dashboard_analysis_ability":0.006,"Tests\\Feature\\DashboardTest::test_empty_database_returns_zero_structure":0.009,"Tests\\Feature\\DashboardTest::test_overview_excludes_cancelled_orders":0.05,"Tests\\Feature\\DashboardTest::test_week_growth_rate":0.054,"Tests\\Feature\\DashboardTest::test_trend_covers_30_days_with_zero_filling":0.05,"Tests\\Feature\\DashboardTest::test_category_sales_grouped_by_category":0.051,"Tests\\Feature\\DashboardTest::test_top_products_aggregated_across_orders":0.05,"Tests\\Feature\\DashboardTest::test_top_stores_ranked_by_amount":0.091,"Tests\\Feature\\DashboardTest::test_purchase_amount_prefers_actual_over_estimate":0.009,"Tests\\Feature\\DashboardTest::test_recon_summary_and_receivable":0.052,"Tests\\Feature\\DashboardTest::test_latest_orders_limited_and_ordered":0.052,"Tests\\Feature\\DashboardTest::test_archives_count_active_records_only":0.132,"Tests\\Feature\\DashboardDebugTest::test_debug_ability_check":0.044,"Tests\\Feature\\DashboardDebugTest::test_debug":0.045,"Tests\\Feature\\ProductPriceTest::test_batch_price_updates_cost_and_notifies_all_stores":0.091,"Tests\\Feature\\ProductPriceTest::test_level_percent_update_notifies_level_stores":0.091,"Tests\\Feature\\ProductPriceTest::test_level_update_without_percent_change_sends_no_notice":0.048,"Tests\\Feature\\ProductPriceTest::test_level_percent_validation":0.009,"Tests\\Feature\\ProductCostPriceTest::test_calc_level_price":0.001,"Tests\\Feature\\ProductCostPriceTest::test_calc_level_price_edge_cases":0.001,"Tests\\Feature\\ProductCostPriceTest::test_admin_list_contains_cost_price_and_level_prices":0.009,"Tests\\Feature\\ProductCostPriceTest::test_create_product_with_cost_price":0.007,"Tests\\Feature\\ProductCostPriceTest::test_batch_price_cost_row_update":0.053,"Tests\\Feature\\ProductCostPriceTest::test_price_matrix_includes_cost_and_level_price_columns":0.01,"Tests\\Feature\\ProductCostPriceTest::test_mini_order_uses_level_price":0.048,"Tests\\Feature\\ProductCostPriceTest::test_mini_cart_uses_level_price":0.048,"Tests\\Feature\\CartTest::test_store_level_missing_rejected":0.006,"Tests\\Feature\\StoreOrderTest::test_store_level_missing_rejected":0.05,"Tests\\Feature\\PurchaseEditTest::test_update_row_skips_catalog_sync_when_product_deleted":0.104,"Tests\\Feature\\PurchaseEditTest::test_store_item_add_creates_item_and_recalculates":0.107,"Tests\\Feature\\PurchaseEditTest::test_store_item_update_merges_multi_order_rows":0.06,"Tests\\Feature\\PurchaseEditTest::test_store_item_remove_deletes_and_recalculates":0.101,"Tests\\Feature\\StoreOrderItemTest::test_update_store_item_recalculates_totals":0.062,"Tests\\Feature\\StoreOrderItemTest::test_store_item_edits_rejected_when_purchase_completed":0.061,"Tests\\Feature\\StoreOrderItemTest::test_update_store_item_validates_negative_values":0.057,"Tests\\Feature\\StoreOrderItemTest::test_add_store_item_uses_level_percent_price":0.059,"Tests\\Feature\\StoreOrderItemTest::test_add_store_item_rejected_when_product_deleted":0.058,"Tests\\Feature\\ExportTest::test_export_lists_all_catalog_products_with_totals_row":0.101,"Tests\\Feature\\ExportTest::test_export_supplier_filter":0.101,"Tests\\Feature\\ExportTest::test_export_stores_multi_sheet":0.102,"Tests\\Feature\\ExportTest::test_export_stores_single_store":0.1,"Tests\\Feature\\ExportTest::test_export_stores_without_items_rejected":0.141,"Tests\\Feature\\ExportTest::test_export_suppliers_multi_sheet":0.102,"Tests\\Feature\\ExportTest::test_export_suppliers_single":0.103,"Tests\\Feature\\ExportTest::test_export_suppliers_without_items_rejected":0.101,"Tests\\Feature\\PurchaseEditTest::test_update_row_cost_change_reprices_items_by_store_level":0.102,"Tests\\Feature\\PurchaseEditTest::test_update_row_without_cost_change_keeps_item_price":0.104,"Tests\\Feature\\CartTest::test_update_other_stores_item_rejected":0.103,"Tests\\Feature\\CartTest::test_delete_other_stores_item_rejected":0.088,"Tests\\Feature\\CartTest::test_cart_isolated_between_stores":0.088,"Tests\\Feature\\MiniAuthTest::test_login_issues_token_for_valid_credentials":0.127,"Tests\\Feature\\MiniAuthTest::test_login_rejects_wrong_password":0.124,"Tests\\Feature\\MiniAuthTest::test_login_rejects_unknown_username":0.002,"Tests\\Feature\\MiniAuthTest::test_disabled_store_cannot_login":0.126,"Tests\\Feature\\MiniAuthTest::test_login_validates_required_params":0.003,"Tests\\Feature\\MiniAuthTest::test_login_token_accesses_protected_routes":0.141,"Tests\\Feature\\MiniAuthTest::test_set_password_succeeds":0.209,"Tests\\Feature\\MiniAuthTest::test_set_password_rejects_wrong_old_password":0.125,"Tests\\Feature\\MiniAuthTest::test_set_password_validates_confirmation":0.045,"Tests\\Feature\\BillExportTest::test_mini_export_scoped_to_own_store":0.093,"Tests\\Feature\\MiniNoticeTest::test_other_stores_notice_not_found":0.09,"Tests\\Feature\\MiniProductTest::test_product_detail_store_sees_level_price":0.048,"Tests\\Feature\\MiniProductTest::test_product_list_store_sees_level_price":0.045,"Tests\\Feature\\MiniStoreTest::test_info_returns_current_store":0.044,"Tests\\Feature\\MiniStoreTest::test_disabled_store_rejected":0.046,"Tests\\Feature\\ProductImportExportTest::test_export_downloads_all_products":0.008,"Tests\\Feature\\ProductImportExportTest::test_export_filters_by_category":0.007,"Tests\\Feature\\ProductImportExportTest::test_export_template_mode":0.006,"Tests\\Feature\\ProductImportExportTest::test_export_requires_permission":0.007,"Tests\\Feature\\ProductImportExportTest::test_import_creates_products_and_auto_supplier":0.025,"Tests\\Feature\\ProductImportExportTest::test_import_matches_existing_supplier":0.015,"Tests\\Feature\\ProductImportExportTest::test_import_errors_abort_all_rows":0.016,"Tests\\Feature\\ProductImportExportTest::test_import_category_path_resolution":0.026,"Tests\\Feature\\ProductImportExportTest::test_import_rejects_invalid_header":0.014,"Tests\\Feature\\ProductImportExportTest::test_import_requires_permission":0.01,"Tests\\Feature\\ProductImportExportTest::test_list_filters_by_market":0.024,"Tests\\Feature\\MiniReportTest::test_report_aggregates_total_and_item_percent":0.048,"Tests\\Feature\\MiniReportTest::test_report_accumulates_same_product_across_orders":0.047,"Tests\\Feature\\MiniReportTest::test_report_filters_by_custom_date_range":0.047,"Tests\\Feature\\MiniReportTest::test_report_excludes_cancelled_and_deleted_orders":0.048,"Tests\\Feature\\MiniReportTest::test_report_preset_month_and_last_month":0.049,"Tests\\Feature\\MiniReportTest::test_report_preset_week_and_last_week":0.05,"Tests\\Feature\\MiniReportTest::test_report_defaults_to_current_month":0.046,"Tests\\Feature\\MiniReportTest::test_report_data_isolated_between_stores":0.087,"Tests\\Feature\\MiniReportTest::test_report_empty_period_returns_zero":0.046,"Tests\\Feature\\MiniReportTest::test_report_validates_params":0.05,"Tests\\Feature\\ProductBatchDeleteTest::test_batch_delete_soft_deletes_selected_products":0.007,"Tests\\Feature\\ProductBatchDeleteTest::test_batch_delete_requires_non_empty_ids":0.007,"Tests\\Feature\\ProductBatchDeleteTest::test_batch_delete_forbidden_without_permission":0.007,"Tests\\Feature\\CartTest::test_place_order_clears_ordered_items_from_cart":0.058,"Tests\\Feature\\StoreOrderItemTest::test_admin_order_list_filters_by_order_date_range":0.065,"Tests\\Feature\\StoreOrderItemTest::test_admin_order_list_search_by_purchase_no":0.063,"Tests\\Feature\\ExportTest::test_export_suppliers_split_by_market":0.055,"Tests\\Feature\\BillPaymentTest::test_generate_bill_saves_after_sale_and_remark":0.114,"Tests\\Feature\\ProductCostPriceTest::test_product_price_unit_default_and_editable":0.013,"Tests\\Feature\\StoreOrderTest::test_place_order_blocked_by_overdue_unpaid_bill":0.053,"Tests\\Feature\\StoreOrderTest::test_place_order_allowed_within_payment_cycle":0.06,"Tests\\Feature\\CartTest::test_cart_summary_endpoint":0.053,"Tests\\Feature\\CartTest::test_cart_summary_requires_login":0.002,"Tests\\Feature\\MiniProductTest::test_product_list_appends_cart_quantity_and_summary":0.056,"Tests\\Feature\\MiniProductTest::test_product_list_guest_sees_zero_cart_fields":0.048,"Tests\\Feature\\MiniProductTest::test_product_detail_appends_cart_quantity":0.052,"Tests\\Feature\\MiniProductTest::test_home_appends_cart_summary":0.052,"Tests\\Feature\\MiniOnlinePaymentTest::test_sign_matches_document_example":0.002,"Tests\\Feature\\MiniOnlinePaymentTest::test_create_online_payment_success":0.056,"Tests\\Feature\\MiniOnlinePaymentTest::test_create_online_payment_validation":0.103,"Tests\\Feature\\MiniOnlinePaymentTest::test_create_fails_when_code2session_fails":0.048,"Tests\\Feature\\MiniOnlinePaymentTest::test_create_gateway_failure_releases_bills":0.052,"Tests\\Feature\\MiniOnlinePaymentTest::test_notify_settles_payment":0.05,"Tests\\Feature\\MiniOnlinePaymentTest::test_notify_rejects_invalid_messages":0.052,"Tests\\Feature\\MiniOnlinePaymentTest::test_query_syncs_gateway_status":0.062,"Tests\\Feature\\MiniOnlinePaymentTest::test_query_rejects_other_store_payment":0.087,"Tests\\Feature\\MiniOnlinePaymentTest::test_offline_payment_flow_unaffected":0.05,"Tests\\Feature\\BillPaymentTest::test_generate_bill_positive_after_sale_added_to_total":0.112,"Tests\\Feature\\BillPaymentTest::test_generate_bill_rejects_non_numeric_after_sale":0.048,"Tests\\Feature\\ClientSpecialTest::test_batch_add_creates_specials":0.008,"Tests\\Feature\\ClientSpecialTest::test_batch_add_skips_existing_products":0.009,"Tests\\Feature\\ClientSpecialTest::test_batch_add_requires_non_empty_product_ids":0.006,"Tests\\Feature\\ClientSpecialTest::test_batch_add_forbidden_without_permission":0.007,"Tests\\Feature\\ClientSpecialTest::test_query_filters_by_product_name_keyword":0.007,"Tests\\Feature\\ClientSpecialTest::test_update_changes_sort_and_status":0.007,"Tests\\Feature\\ClientSpecialTest::test_delete_and_batch_delete":0.008,"Tests\\Feature\\ClientSpecialTest::test_batch_delete_requires_non_empty_ids":0.006,"Tests\\Feature\\MiniSpecialTest::test_guest_sees_specials_with_null_price":0.046,"Tests\\Feature\\MiniSpecialTest::test_store_sees_level_price":0.045,"Tests\\Feature\\MiniSpecialTest::test_disabled_special_and_off_shelf_product_excluded":0.046,"Tests\\Feature\\MiniSpecialTest::test_specials_sorted_by_sort_asc":0.089,"Tests\\Feature\\MiniSpecialTest::test_cart_quantity_attached_for_store":0.048,"Tests\\Feature\\MiniOnlinePaymentTest::test_aes_encryption_matches_demo_golden":0.001,"Tests\\Feature\\MiniOnlinePaymentTest::test_notify_envelope_round_trip":0.004,"Tests\\Feature\\MiniOnlinePaymentTest::test_swapped_key_config_fails_with_clear_message":0.004,"Tests\\Feature\\MiniWechatPaymentTest::test_create_wechat_payment_success":0.062,"Tests\\Feature\\MiniWechatPaymentTest::test_create_wechat_payment_gateway_failure_releases_bills":0.053,"Tests\\Feature\\MiniWechatPaymentTest::test_wechat_notify_settles_payment":0.061,"Tests\\Feature\\MiniWechatPaymentTest::test_wechat_notify_rejects_invalid_messages":0.055,"Tests\\Feature\\MiniWechatPaymentTest::test_wechat_notify_with_platform_cert_validates_signature":0.053,"Tests\\Feature\\MiniWechatPaymentTest::test_wechat_query_syncs_status":0.057,"Tests\\Feature\\MiniWechatPaymentTest::test_wangpu_channel_remains_default":0.051,"Tests\\Feature\\BillPaymentTest::test_payment_config_returns_pay_methods":0.047,"Tests\\Feature\\MiniMpPaymentTest::test_mp_scene_wechat_channel":0.073,"Tests\\Feature\\MiniMpPaymentTest::test_mp_scene_wangpu_channel":0.056,"Tests\\Feature\\MiniMpPaymentTest::test_mp_scene_oauth_failure":0.075,"Tests\\Feature\\MiniMpPaymentTest::test_mp_scene_requires_mp_config":0.09,"Tests\\Feature\\MiniMpPaymentTest::test_default_scene_remains_mini":0.063,"Tests\\Feature\\MiniProductTest::test_product_list_ordered_by_category_tree":0.006,"Tests\\Feature\\MiniOnlinePaymentTest::test_notify_sign_matches_doc_golden":0.002,"Tests\\Feature\\MiniOnlinePaymentTest::test_notify_verify_rejects_bad_sign":0.002}} \ No newline at end of file diff --git a/app/Http/Controllers/Mini/OnlinePaymentController.php b/app/Http/Controllers/Mini/OnlinePaymentController.php index ef212eb..f981d66 100644 --- a/app/Http/Controllers/Mini/OnlinePaymentController.php +++ b/app/Http/Controllers/Mini/OnlinePaymentController.php @@ -105,20 +105,20 @@ class OnlinePaymentController extends BaseMiniController } /** - * 旺铺支付结果后台通知(公开路由,解密信封后幂等结账) + * 旺铺支付结果后台通知(公开路由,MD5 验签后幂等结账) * - * 通知报文与网关应答同为加密信封(data + signature,商户私钥可解), - * 解密成功即视为合法通知;应答 {"code":"00"} 视为通知成功,否则网关按 2^n 分钟重试 7 次; + * 通知报文为明文表单(不涉及加解密),sign 之外非空数据元 ASCII 升序拼接 + 专用加签Key 做 MD5, + * 验签通过即视为核心平台合法通知;应答 {"code":"00"} 视为通知成功,否则网关按 2^n 分钟重试 7 次; * 重复通知必须幂等(settle 内部行锁 + 状态判断)。 */ #[PostRoute('/payment/notify', authorize: false)] public function notify(Request $request): JsonResponse { try { - $params = $this->wangpu->decryptNotify($request->all()); + $params = $this->wangpu->verifyNotify($request->all()); } catch (Throwable $e) { - Log::warning('旺铺支付通知解密失败', ['error' => $e->getMessage()]); - return $this->notifyAck('01', '报文解密失败'); + Log::warning('旺铺支付通知验签失败', ['error' => $e->getMessage()]); + return $this->notifyAck('01', '报文验签失败'); } try { diff --git a/app/Services/OnlinePaymentService.php b/app/Services/OnlinePaymentService.php index bf5a276..83adae7 100644 --- a/app/Services/OnlinePaymentService.php +++ b/app/Services/OnlinePaymentService.php @@ -160,8 +160,10 @@ class OnlinePaymentService $payment->pay_params = $gatewayData; $payment->save(); - $wxjsapistr = $gatewayData['wxjsapistr'] ?? '{}'; - $wxjsapiData = json_decode($wxjsapistr); + // 调起支付参数:旺铺渠道在应答 wxjsapistr(JSON 串)中;微信渠道下单应答本身即调起参数 + $wxjsapiData = $channel === self::CHANNEL_WANGPU + ? json_decode((string) ($gatewayData['wxjsapistr'] ?? '{}')) + : $gatewayData; return [$payment, $wxjsapiData]; } diff --git a/app/Services/WangpuPayService.php b/app/Services/WangpuPayService.php index 57d13f2..869b48d 100644 --- a/app/Services/WangpuPayService.php +++ b/app/Services/WangpuPayService.php @@ -14,7 +14,11 @@ use Illuminate\Support\Str; * - 业务参数 JSON 序列化后,用随机 16 位密钥做 AES-128-ECB 加密(base64)放入 data; * - AES 密钥用旺铺平台公钥 RSA 加密(base64)放入 signature; * - 整体以 JSON 信封 POST:{serialNo, version, timestamp, data, signature, extras, organizNo}; - * - 应答/通知反向解密:商户私钥解 signature 得 AES 密钥,再用其解 data 得业务报文。 + * 应答报文反向解密:商户私钥解 signature 得 AES 密钥,再用其解 data 得业务报文。 + * + * 支付/退款后台通知为明文表单报文,不涉及加解密,仅 MD5 验签: + * sign 之外所有非空数据元按名称 ASCII 升序拼成 key=value&... 串,末尾拼接 &key=加签Key, + * MD5(utf-8)后转大写与报文 sign 比对,一致即视为核心平台合法通知。 * * 配置优先级:后台「系统设置 → 旺铺支付」(site_config wangpu.*) > config/services.php(env) */ @@ -68,20 +72,43 @@ class WangpuPayService } /** - * 解密支付结果后台通知(报文 RSA+AES 双重加密,解密成功即视为合法通知) + * 验证支付结果后台通知签名(明文表单报文 + MD5 验签,验签通过即视为合法通知) * - * @param array $envelope 通知信封(含 data/signature) - * @return array 解密后的业务报文(mer_order_id/order_status/order_amt/trade_no 等) - * @throws RepositoryException 报文解密失败 + * @param array $params 通知表单报文(含 sign) + * @return array 原样返回的通知报文(mer_order_id/order_status/order_amt/trade_no 等) + * @throws RepositoryException 缺少签名 / 加签Key未配置 / 验签失败 */ - public function decryptNotify(array $envelope): array + public function verifyNotify(array $params): array { - Log::driver('pay')->info('旺铺支付通知接收', ['envelope' => $envelope]); - $params = $this->decryptEnvelope($envelope); - Log::driver('pay')->info('旺铺支付通知解密', ['params' => $params]); + Log::driver('pay')->info('旺铺支付通知接收', ['params' => $params]); + $sign = strtoupper(trim((string) ($params['sign'] ?? ''))); + if ($sign === '' || ! hash_equals($this->sign($params), $sign)) { + Log::driver('pay')->warning('旺铺支付通知验签失败', ['params' => $params]); + throw new RepositoryException('通知报文验签失败'); + } return $params; } + /** + * 通知报文 MD5 加签:剔除 sign 与空值 → 按参数名 ASCII 升序拼 key=value&... → 末尾拼 &key=加签Key → MD5 大写 + * + * @param array $params 通知报文 + * @throws RepositoryException 加签Key未配置 + */ + protected function sign(array $params): string + { + unset($params['sign']); + $params = array_filter($params, static fn (mixed $value): bool => $value !== null && $value !== ''); + ksort($params, SORT_STRING); + $str = implode('&', array_map(static fn (string $key, mixed $value): string => $key . '=' . $value, array_keys($params), $params)); + + $signKey = $this->config('sign_key'); + if ($signKey === '') { + throw new RepositoryException('旺铺支付未配置通知加签Key,请联系管理员'); + } + return strtoupper(md5($str . '&key=' . $signKey)); + } + /** * 支付结果后台通知地址(统一下单时上送的 notifyurl) */ diff --git a/config/services.php b/config/services.php index 69b0dbb..f6d1815 100644 --- a/config/services.php +++ b/config/services.php @@ -82,6 +82,7 @@ return [ 'sub_appid' => env('WANGPU_SUB_APPID', ''), // 下单微信子 appid(默认取小程序 appid) 'payway_code' => env('WANGPU_PAYWAY_CODE', 'WECHAT_MINI'), // 支付方式代码(小程序主扫必填) 'mp_payway_code' => env('WANGPU_MP_PAYWAY_CODE', ''), // 公众号场景支付方式代码(留空回退 payway_code) + 'sign_key' => env('WANGPU_SIGN_KEY', ''), // 支付/退款后台通知 MD5 专用加签Key(旺铺分配) ], /* diff --git a/database/seeders/SysDataSeeder.php b/database/seeders/SysDataSeeder.php index dbd000a..7ce8c40 100644 --- a/database/seeders/SysDataSeeder.php +++ b/database/seeders/SysDataSeeder.php @@ -45,6 +45,7 @@ class SysDataSeeder extends Seeder ['id' => 18, 'group_id' => 5, 'key' => 'sub_appid', 'title' => '旺铺下单子appid', 'describe' => '下单微信子 appid(sub_appid),留空则取小程序自身 appid', 'values' => '', 'type' => 'Input','options' => "", 'sort' => 6, 'created_at' => $date, 'updated_at' => $date], ['id' => 19, 'group_id' => 5, 'key' => 'payway_code', 'title' => '旺铺支付方式代码', 'describe' => '支付方式代码 payway_code(小程序主扫必填,如 WECHAT_MINI),见旺铺数据词典', 'values' => 'WECHAT_MINI', 'type' => 'Input','options' => "", 'sort' => 7, 'created_at' => $date, 'updated_at' => $date], ['id' => 34, 'group_id' => 5, 'key' => 'mp_payway_code', 'title' => '旺铺公众号支付方式代码', 'describe' => '公众号 H5 场景支付方式代码 payway_code(如 WECHAT_JSPAY),留空则回退小程序支付方式代码', 'values' => 'WECHAT_JSPAY', 'type' => 'Input','options' => "", 'sort' => 9, 'created_at' => $date, 'updated_at' => $date], + ['id' => 35, 'group_id' => 5, 'key' => 'sign_key', 'title' => '旺铺通知加签Key', 'describe' => '支付/退款后台通知 MD5 专用加签Key(旺铺分配),用于校验通知报文签名 sign,请勿泄露', 'values' => '', 'type' => 'Input','options' => "", 'sort' => 10, 'created_at' => $date, 'updated_at' => $date], ['id' => 20, 'group_id' => 5, 'key' => 'private_key', 'title' => '商户RSA私钥', 'describe' => '商户 RSA 私钥(base64 单行,不含 PEM 头尾),用于解密网关应答/支付通知报文,请勿泄露', 'values' => '', 'type' => 'TextArea','options' => "", 'sort' => 8, 'created_at' => $date, 'updated_at' => $date], // 微信官方支付(wxpay):微信支付 APIv3 ['id' => 22, 'group_id' => 6, 'key' => 'mch_id', 'title' => '微信支付商户号', 'describe' => '微信支付商户号 mch_id(微信商户平台分配)', 'values' => '', 'type' => 'Input','options' => "", 'sort' => 0, 'created_at' => $date, 'updated_at' => $date], diff --git a/tests/Feature/MiniOnlinePaymentTest.php b/tests/Feature/MiniOnlinePaymentTest.php index 791f8c8..643aca3 100644 --- a/tests/Feature/MiniOnlinePaymentTest.php +++ b/tests/Feature/MiniOnlinePaymentTest.php @@ -17,7 +17,8 @@ use ReflectionMethod; * 小程序在线支付(旺铺网关行业版):下单 → 调起支付 → 后台通知/主动查询结账 * * 通讯协议与官方示例 demo/IndexController.php 一致: - * 业务报文 AES-128-ECB 加密(随机 16 位密钥)+ RSA 公钥加密 AES 密钥,JSON 信封传输。 + * 下单/查询的业务报文 AES-128-ECB 加密(随机 16 位密钥)+ RSA 公钥加密 AES 密钥,JSON 信封传输; + * 支付结果后台通知为明文表单报文,仅 MD5 验签(ASCII 升序拼接 + 专用加签Key)。 * * - Http::fake 模拟微信 code2session 与旺铺网关,不触网 * - 结账幂等:重复通知/查询不重复累加门店总采购金额 @@ -44,6 +45,7 @@ class MiniOnlinePaymentTest extends ProcurementTestCase 'services.wangpu.public_key' => self::TEST_PUBLIC_KEY, 'services.wangpu.private_key' => self::TEST_PRIVATE_KEY, 'services.wangpu.payway_code' => 'WECHAT_MINI', + 'services.wangpu.sign_key' => 'notify-test-key', ]); } @@ -106,6 +108,14 @@ class MiniOnlinePaymentTest extends ProcurementTestCase 'order_id' => 'WP202608270001', 'tradeNo' => 'T20260827001', 'user_openid' => $openid, + 'wxjsapistr' => (string) json_encode([ + 'appId' => 'wx-mini-test', + 'timeStamp' => '1724745600', + 'nonceStr' => 'nonce001', + 'package' => 'prepay_id=wp-prepay-001', + 'signType' => 'RSA', + 'paySign' => 'sign001', + ]), ]), 200 ), @@ -151,10 +161,10 @@ class MiniOnlinePaymentTest extends ProcurementTestCase return $payment; } - /** 构造加密的支付成功通知信封 */ - private function encryptedNotifyParams(PaymentModel $payment, array $overrides = []): array + /** 构造带 MD5 签名的支付成功通知表单报文(加签算法与官方文档一致,测试侧独立实现) */ + private function signedNotifyParams(PaymentModel $payment, array $overrides = []): array { - return $this->gatewayEnvelope(array_merge([ + $params = array_merge([ 'mer_order_id' => $payment->payment_no, 'order_status' => '1', 'order_amt' => (string) $payment->amount, @@ -166,7 +176,13 @@ class MiniOnlinePaymentTest extends ProcurementTestCase 'mer_no' => 'mer001', 'device_no' => 'dev001', 'order_title' => '账单合并付款', - ], $overrides)); + ], $overrides); + + $signParams = array_filter($params, static fn ($value): bool => $value !== null && $value !== ''); + ksort($signParams, SORT_STRING); + $str = implode('&', array_map(static fn ($k, $v) => $k . '=' . $v, array_keys($signParams), $signParams)); + $params['sign'] = strtoupper(md5($str . '&key=' . config('services.wangpu.sign_key'))); + return $params; } /** AES-128-ECB 加密 golden test:与官方示例 demo/functions.php encryption 算法输出一致 */ @@ -186,19 +202,41 @@ class MiniOnlinePaymentTest extends ProcurementTestCase ); } - /** 信封加解密回环:网关侧加密(demo 算法)→ 服务解密还原业务报文 */ - public function test_notify_envelope_round_trip(): void + /** 通知 MD5 验签 golden test:官方文档示例报文 + SignKey 直算签名一致(含中文、空值剔除) */ + public function test_notify_sign_matches_doc_golden(): void { + config(['services.wangpu.sign_key' => '07714583f82b4db8b675b32cd5e0969743']); + $params = [ - 'mer_order_id' => 'ZF202608270001', + 'mer_order_id' => 'CBC92E5GTL000083202004121010143', + 'trade_no' => '11420200410120144102483', + 'mer_no' => '2001071119360E5Riu', + 'order_amt' => '0.01', + 'payway_code' => 'QR_WECHAT_BARPAY', + 'order_id' => '202004101201444525348059', 'order_status' => '1', - 'order_amt' => '150.50', - 'order_title' => '账单合并付款', // 中文报文 + 'order_title' => '住宿酒店', + 'mer_code' => 'W00000000001381', + 'device_no' => 'CBC92E5GTL000083', + 'order_time' => '2020-04-10 12:01:44', + 'trade_time' => '2020-04-10 12:01:47', + 'gateway_mer_order_id' => '2020041012014445269', + 'fee' => '', // 空值不参与签名(平台不下发空值数据元) + 'sign' => 'A31998F2E0549E0A80B2A4B3A0473784', // 文档示例签名值 ]; - $decrypted = app(WangpuPayService::class)->decryptNotify($this->gatewayEnvelope($params)); + $verified = app(WangpuPayService::class)->verifyNotify($params); - $this->assertSame($params, $decrypted); + $this->assertSame($params, $verified); + } + + /** 通知验签失败(签名缺失/错误)抛异常 */ + public function test_notify_verify_rejects_bad_sign(): void + { + $service = app(WangpuPayService::class); + + $this->expectException(RepositoryException::class); + $service->verifyNotify(['mer_order_id' => 'ZF202608270001', 'sign' => 'INVALIDSIGN']); } /** 密钥配置错误:公钥栏误填私钥时给出明确中文报错(而非 openssl 警告) */ @@ -228,7 +266,9 @@ class MiniOnlinePaymentTest extends ProcurementTestCase $paymentNo = $response->json('data.payment_no'); $this->assertSame('150.50', $response->json('data.amount')); - $this->assertSame('WP202608270001', $response->json('data.pay_params.order_id')); + // 调起支付参数取自网关应答 wxjsapistr(小程序 wx.requestPayment 直接透传) + $this->assertSame('wx-mini-test', $response->json('data.pay_params.appId')); + $this->assertSame('prepay_id=wp-prepay-001', $response->json('data.pay_params.package')); $payment = PaymentModel::where('payment_no', $paymentNo)->first(); $this->assertSame(PaymentModel::TYPE_ONLINE, $payment->pay_type); @@ -336,7 +376,7 @@ class MiniOnlinePaymentTest extends ProcurementTestCase $this->assertSame(0, $bill->fresh()->payment_id, '账单释放可重新付款'); } - /** 支付成功通知:解密信封 → 幂等结账(账单置已支付 + 累加门店总采购金额 + 通知门店) */ + /** 支付成功通知:验签 → 幂等结账(账单置已支付 + 累加门店总采购金额 + 通知门店) */ public function test_notify_settles_payment(): void { $store = StoreModel::factory()->create(); @@ -344,7 +384,7 @@ class MiniOnlinePaymentTest extends ProcurementTestCase $bill2 = $this->makeBill($store, '50.00'); $payment = $this->makeOnlinePayment($store, '150.00', $bill1, $bill2); - $this->postJson('/mini/payment/notify', $this->encryptedNotifyParams($payment)) + $this->postJson('/mini/payment/notify', $this->signedNotifyParams($payment)) ->assertJsonPath('code', '00'); $payment->refresh(); @@ -366,34 +406,39 @@ class MiniOnlinePaymentTest extends ProcurementTestCase ); // 重复通知幂等:仍应答成功,金额不重复累加 - $this->postJson('/mini/payment/notify', $this->encryptedNotifyParams($payment)) + $this->postJson('/mini/payment/notify', $this->signedNotifyParams($payment)) ->assertJsonPath('code', '00'); $this->assertSame('150.00', (string) $store->fresh()->total_purchase_amount); $this->assertSame(1, NoticeModel::where('store_id', $store->id)->count()); } - /** 通知解密失败 / 金额不一致 / 订单号不存在 / 非支付成功状态:应答失败且不结账 */ + /** 通知验签失败 / 金额不一致 / 订单号不存在 / 非支付成功状态:应答失败且不结账 */ public function test_notify_rejects_invalid_messages(): void { $store = StoreModel::factory()->create(); $bill = $this->makeBill($store, '100.00'); $payment = $this->makeOnlinePayment($store, '100.00', $bill); - // 信封 signature 非法 → 解密失败 - $badEnvelope = $this->encryptedNotifyParams($payment); - $badEnvelope['signature'] = 'INVALIDSIGN'; - $this->postJson('/mini/payment/notify', $badEnvelope)->assertJsonPath('code', '01'); + // 签名非法 → 验签失败 + $badSign = $this->signedNotifyParams($payment); + $badSign['sign'] = 'INVALIDSIGN'; + $this->postJson('/mini/payment/notify', $badSign)->assertJsonPath('code', '01'); - // 金额不一致(防篡改) - $this->postJson('/mini/payment/notify', $this->encryptedNotifyParams($payment, ['order_amt' => '99.99'])) + // 篡改金额后未重签 → 验签失败 + $tampered = $this->signedNotifyParams($payment); + $tampered['order_amt'] = '99.99'; + $this->postJson('/mini/payment/notify', $tampered)->assertJsonPath('code', '01'); + + // 金额不一致(防篡改,签名正确但金额与支付单不符) + $this->postJson('/mini/payment/notify', $this->signedNotifyParams($payment, ['order_amt' => '99.99'])) ->assertJsonPath('code', '01'); // 订单号不存在 - $this->postJson('/mini/payment/notify', $this->encryptedNotifyParams($payment, ['mer_order_id' => 'ZF000000000000'])) + $this->postJson('/mini/payment/notify', $this->signedNotifyParams($payment, ['mer_order_id' => 'ZF000000000000'])) ->assertJsonPath('code', '01'); // 非支付成功状态 - $this->postJson('/mini/payment/notify', $this->encryptedNotifyParams($payment, ['order_status' => '0'])) + $this->postJson('/mini/payment/notify', $this->signedNotifyParams($payment, ['order_status' => '0'])) ->assertJsonPath('code', '01'); // 均未结账