param( [string]$AuthUrl = 'http://127.0.0.1:9502', [string]$IamUrl = 'http://127.0.0.1:9501', [string]$FileUrl = 'http://127.0.0.1:9506', [string]$OrderUrl = 'http://127.0.0.1:9504', [string]$WarehouseUrl = 'http://127.0.0.1:9505', [string]$ImagePath, [long]$PublicReceivingWarehouseId = 0, [long]$PublicDestinationWarehouseId = 0, [string]$PlatformPassword = 'ChangeMe123!' ) $ErrorActionPreference = 'Stop' function Invoke-Json($method, $url, $body = $null, $token = $null, $extraHeaders = @{}) { $headers = @{}; if ($token) { $headers.Authorization = "Bearer $token" } foreach ($name in $extraHeaders.Keys) { $headers[$name] = $extraHeaders[$name] } $args = @{ Method=$method; Uri=$url; Headers=$headers; TimeoutSec=20 } if ($null -ne $body) { $args.ContentType='application/json'; $args.Body=$body | ConvertTo-Json -Depth 12 } Invoke-RestMethod @args } function Invoke-Status($method, $url, $body, $token) { try { Invoke-Json $method $url $body $token | Out-Null; return 200 } catch { return [int]$_.Exception.Response.StatusCode } } function Assert-Equal($name, $actual, $expected) { if ($actual -ne $expected) { throw "$name expected=$expected actual=$actual" } } function Assert-Success($name, $response) { if ($null -eq $response -or [string]$response.code -notin @('200', '0')) { throw "$name failed code=$($response.code) message=$($response.message)" } } $stamp = Get-Date -Format 'MMddHHmmss' $username = "phase1_$stamp"; $password = 'Phase1Test123!' $registration = Invoke-Json POST "$AuthUrl/api/auth/register" @{ username=$username; password=$password; displayName='Phase One Customer'; companyName="Phase One Enterprise $stamp" legalPerson='Test Legal Person'; legalPhone='13800138000'; contactEmail="phase1_$stamp@example.com" countryCode='CN'; address='No. 1 Test Road, Nanshan, Shenzhen' } Assert-Equal 'registration status' $registration.data.tenantStatus 'PROFILE_REQUIRED' Assert-Equal 'registration next step' $registration.data.nextStep 'LOGIN_AND_COMPLETE_QUALIFICATIONS' $tenantId = [long]$registration.data.tenantId $merchantLogin = Invoke-Json POST "$AuthUrl/api/auth/login" @{username=$username;password=$password} Assert-Success 'merchant login' $merchantLogin $merchantToken = $merchantLogin.data.accessToken $platformLogin = Invoke-Json POST "$AuthUrl/api/auth/login" @{username='platform_admin';password=$PlatformPassword} Assert-Success 'platform login' $platformLogin $platformToken = $platformLogin.data.accessToken $partner = Invoke-Json POST "$WarehouseUrl/api/platform/v1/partners?tenantId=$tenantId" @{ partnerCode="E2E-PARTNER-$stamp"; partnerName="Phase One Partner $stamp"; partnerType='SHIPPER' types=@('SHIPPER','CONSIGNEE'); contactName='Test Partner'; contactPhone='13800138000' contactEmail="partner_$stamp@example.com"; countryCode='CN'; province='Guangdong'; city='Shenzhen' district='Nanshan'; addressLine='No. 1 Test Road'; postalCode='518000'; status='ACTIVE'; remark='Phase one E2E' } $platformToken Assert-Success 'create tenant partner' $partner $partnerId = [long]$partner.data $warehouse = Invoke-Json POST "$WarehouseUrl/api/platform/v1/warehouse-admin" @{ tenantId=$tenantId; warehouseCode="E2E-WH-$stamp"; warehouseName="Phase One Receiving Warehouse $stamp" warehouseType='DOMESTIC'; countryCode='CN'; timezone='Asia/Shanghai'; contactName='Test Warehouse' contactPhone='13800138000'; contactEmail="warehouse_$stamp@example.com"; status='ACTIVE'; remark='Phase one E2E' address=@{addressType='WAREHOUSE';countryCode='CN';stateProvince='Guangdong';city='Shenzhen';district='Nanshan';postalCode='518000';addressLine1='No. 1 Test Road';addressLine2=$null;latitude=$null;longitude=$null} capabilities=@(@{capabilityCode='RECEIVING';capabilityName='Receiving';enabled=$true;cutoffTime='16:00:00';description='Phase one E2E receiving'}) } $platformToken Assert-Success 'create tenant receiving warehouse' $warehouse $warehouseId = [long]$warehouse.data if ($PublicReceivingWarehouseId -gt 0) { $warehouseId = $PublicReceivingWarehouseId } $order = @{ customerId=$partnerId; customerName="Phase One Partner $stamp"; transportMode='SEA'; tradeTerm='FOB' destinationCountryCode='US'; destinationWarehouseId=$(if ($PublicDestinationWarehouseId -gt 0) {$PublicDestinationWarehouseId} else {$null}); destinationWarehouseName='ONT8' expectedShipDate='2026-08-15'; customerRemark='Phase one E2E'; pickupMethod='PICKUP' receivingWarehouseId=$warehouseId; receivingWarehouseName="Phase One Receiving Warehouse $stamp"; channelCode='SEA-US-FBA' channelName='US Sea Channel'; carrierId=9900001; carrierName='模拟海运船司'; taxMode='BUY_DECLARATION'; containerMode='LCL'; customsInspection=$false destinationType='FBA'; amazonWarehouseCode='ONT8'; shipmentId="FBA$stamp"; referenceId="REF$stamp" sender=@{companyName="Phase One Enterprise $stamp";contactName='Test Sender';phone='13800138000';email="p$stamp@example.com";countryCode='CN';province='Guangdong';city='Shenzhen';district='Nanshan';addressLine='No. 1 Test Road';postalCode='518000'} receiver=@{companyName='Amazon';contactName='ONT8';phone='1234567890';email='ont8@example.com';countryCode='US';province='CA';city='Moreno Valley';district='Riverside';addressLine='24300 Nandina Ave';postalCode='92551'} cargoes=@(@{cargoName='Bluetooth Speaker';hsCode='8518210000';packageType='CARTON';packageCount=2;grossWeightKg=20.5;volumeCbm=0.2;lengthCm=50;widthCm=40;heightCm=30;declaredValue=200;currency='USD';dangerousGoods=$false;cargoRemark='Phase one test';skuNo="SKU$stamp";upcNo='123456789012';cargoNameEn='Bluetooth Speaker';customsHsCode='8518210000';unitsPerPackage=10;totalQuantity=20;netWeightKg=18;unitDeclaredValue=10;material='Plastic';usage='Audio';model='P1';brand='Demo'}) } $draftKey = "phase1-draft-$stamp" $submitKey = "phase1-submit-$stamp" $draft = Invoke-Json POST "$OrderUrl/api/merchant/v1/orders/first-leg" $order $merchantToken @{'Idempotency-Key'=$draftKey} Assert-Success 'pre-approval draft create' $draft $orderId = [long]$draft.data $preApproval = Invoke-Json POST "$OrderUrl/api/merchant/v1/orders/first-leg/$orderId/submit" $null $merchantToken @{'Idempotency-Key'=$submitKey} Assert-Equal 'pre-approval submit code' $preApproval.code 'A0403' $creditCode = "91$((Get-Date).ToString('yyyyMMddHHmmss'))A1" $qualificationTypes = @('BUSINESS_LICENSE', 'LEGAL_PERSON_ID', 'FREIGHT_FORWARDER_CERTIFICATE', 'BANK_ACCOUNT_LICENSE') $uploads = @{} foreach ($qualificationType in $qualificationTypes) { $query = "qualificationType=$qualificationType&replace=false" if ($qualificationType -eq 'BUSINESS_LICENSE') { $query += "&certificateNo=$creditCode" } $uploadJson = & curl.exe -sS -X POST "$FileUrl/api/merchant/v1/files/qualifications?$query" ` -H "Authorization: Bearer $merchantToken" -F "file=@$ImagePath;type=image/png" if ($LASTEXITCODE -ne 0) { throw "qualification upload transport failed: $qualificationType" } $upload = $uploadJson | ConvertFrom-Json Assert-Success "upload $qualificationType" $upload Assert-Equal "upload $qualificationType tenant status" $upload.data.tenantStatus 'PROFILE_REQUIRED' $uploads[$qualificationType] = $upload } $merchantQualifications = Invoke-Json GET "$IamUrl/api/merchant/v1/iam/tenant/qualifications" $null $merchantToken Assert-Success 'merchant qualification list' $merchantQualifications $pendingTypes = @($merchantQualifications.data | Where-Object { $_.status -eq 'PENDING' } | ForEach-Object { $_.qualificationType } | Sort-Object -Unique) Assert-Equal 'pending qualification count' $pendingTypes.Count 4 foreach ($qualificationType in $qualificationTypes) { if ($qualificationType -notin $pendingTypes) { throw "missing pending qualification: $qualificationType" } } $submitted = Invoke-Json POST "$IamUrl/api/merchant/v1/iam/tenant/qualifications/submit-review" $null $merchantToken Assert-Success 'submit enterprise review' $submitted Assert-Equal 'submitted tenant status' $submitted.data.status 'PENDING' $reviews = Invoke-Json GET "$IamUrl/api/platform/v1/iam/reviews" $null $platformToken Assert-Success 'platform review list' $reviews $review = @($reviews.data) | Where-Object { [long]$_.tenantId -eq $tenantId } | Select-Object -First 1 if ($null -eq $review) { throw 'pending review not found' } $qualifications = Invoke-Json GET "$IamUrl/api/platform/v1/iam/tenants/$tenantId/qualifications" $null $platformToken Assert-Success 'platform qualification list' $qualifications Assert-Equal 'qualification count' @($qualifications.data).Count 4 $fileName = Split-Path $uploads['BUSINESS_LICENSE'].data.documentUrl -Leaf $preview = Invoke-WebRequest -UseBasicParsing -Uri "$FileUrl/api/platform/v1/files/qualifications/$tenantId/$fileName" ` -Headers @{Authorization="Bearer $platformToken"} -TimeoutSec 20 Assert-Equal 'platform preview status' $preview.StatusCode 200 $approval = Invoke-Json POST "$IamUrl/api/platform/v1/iam/reviews/$($review.id)/approve" @{comment='Phase one E2E approved'} $platformToken Assert-Success 'approve enterprise review' $approval Assert-Equal 'registered owner retained' $approval.data.administratorCreated $false $approvedQualifications = Invoke-Json GET "$IamUrl/api/merchant/v1/iam/tenant/qualifications" $null $merchantToken Assert-Success 'approved qualification list' $approvedQualifications $approvedTypes = @($approvedQualifications.data | Where-Object { $_.status -eq 'APPROVED' } | ForEach-Object { $_.qualificationType } | Sort-Object -Unique) Assert-Equal 'approved qualification count' $approvedTypes.Count 4 $merchantProfile = Invoke-Json GET "$IamUrl/api/merchant/v1/iam/tenant/profile" $null $merchantToken Assert-Success 'merchant profile after approval' $merchantProfile Assert-Equal 'active tenant status' $merchantProfile.data.status 'ACTIVE' $approvedSubmit = Invoke-Json POST "$OrderUrl/api/merchant/v1/orders/first-leg/$orderId/submit" $null $merchantToken @{'Idempotency-Key'=$submitKey} Assert-Success 'approved order submit' $approvedSubmit $detail = Invoke-Json GET "$OrderUrl/api/merchant/v1/orders/first-leg/$orderId" $null $merchantToken Assert-Equal 'submitted order status' $detail.data.order.status 'SUBMITTED' [pscustomobject]@{username=$username;tenantId=$tenantId;registerStatus='PROFILE_REQUIRED';partnerId=$partnerId;warehouseId=$warehouseId;draftCreatedBeforeApproval=$true;preApprovalSubmitCode=$preApproval.code;pendingQualificationCount=4;reviewId=$review.id;previewHttp=200;approvedQualificationCount=4;orderId=$orderId;finalOrderStatus='SUBMITTED'} | ConvertTo-Json