feat(t219): show pending-payment reconciliation
This commit is contained in:
@@ -380,6 +380,9 @@ func (h *adminHandlers) taskDetail(ctx *gin.Context) {
|
||||
"order_authorizations": orderAuthorizationResponses(
|
||||
detail.OrderAuthorizations,
|
||||
),
|
||||
"order_submissions": adminOrderSubmissionResponses(
|
||||
detail.OrderSubmissions,
|
||||
),
|
||||
"events": events,
|
||||
"assets": []gin.H{
|
||||
assetResponse(detail.Asset),
|
||||
@@ -387,6 +390,50 @@ func (h *adminHandlers) taskDetail(ctx *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func adminOrderSubmissionResponses(
|
||||
submissions []domain.OrderSubmission,
|
||||
) []gin.H {
|
||||
result := make([]gin.H, 0, len(submissions))
|
||||
for _, submission := range submissions {
|
||||
result = append(result, adminOrderSubmissionResponse(submission))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func adminOrderSubmissionResponse(
|
||||
submission domain.OrderSubmission,
|
||||
) gin.H {
|
||||
return gin.H{
|
||||
"id": submission.ID,
|
||||
"authorization_id": submission.AuthorizationID,
|
||||
"dry_run_id": submission.DryRunID,
|
||||
"execution_id": submission.ExecutionID,
|
||||
"status": submission.Status,
|
||||
"expected_title": submission.ExpectedTitle,
|
||||
"expected_sku": submission.ExpectedSKU,
|
||||
"expected_quantity": submission.ExpectedQuantity,
|
||||
"expected_unit_price_cents": submission.ExpectedUnitPriceCents,
|
||||
"expected_total_price_cents": submission.ExpectedTotalPriceCents,
|
||||
"platform_order_no": submission.PlatformOrderNo,
|
||||
"platform_ordered_at": formatOptionalTime(
|
||||
submission.PlatformOrderedAt,
|
||||
),
|
||||
"platform_order_status": submission.PlatformOrderStatus,
|
||||
"reconciliation_evidence_asset_id": submission.
|
||||
ReconciliationEvidenceAssetID,
|
||||
"reconciliation_evidence_sha256": submission.
|
||||
ReconciliationEvidenceSHA256,
|
||||
"manual_reason_code": submission.ManualReasonCode,
|
||||
"fenced_at": formatTime(submission.FencedAt),
|
||||
"reconciled_at": formatOptionalTime(
|
||||
submission.ReconciledAt,
|
||||
),
|
||||
"manual_review_at": formatOptionalTime(
|
||||
submission.ManualReviewAt,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *adminHandlers) createOrderAuthorization(ctx *gin.Context) {
|
||||
var request struct {
|
||||
ExecutionID string `json:"execution_id"`
|
||||
|
||||
@@ -197,9 +197,13 @@ func TestAdminAPIAssetAndTaskLifecycle(t *testing.T) {
|
||||
var detail map[string]any
|
||||
decodeResponse(t, detailResponse, &detail)
|
||||
requirement, _ := detail["original_requirement"].(map[string]any)
|
||||
orderSubmissions, ok := detail["order_submissions"].([]any)
|
||||
if detailResponse.Code != http.StatusOK ||
|
||||
requirement["sku"] != "BLACK-20L" ||
|
||||
requirement["quantity"] != float64(2) {
|
||||
requirement["quantity"] != float64(2) ||
|
||||
!ok ||
|
||||
len(orderSubmissions) != 0 ||
|
||||
detailResponse.Header().Get("Cache-Control") != "no-store" {
|
||||
t.Fatalf(
|
||||
"task detail status/body = %d / %#v",
|
||||
detailResponse.Code,
|
||||
|
||||
@@ -1314,6 +1314,71 @@ func TestDeviceOrderCommandDeliveryAndAcknowledgementAreRecoverable(
|
||||
authorizationStatus,
|
||||
)
|
||||
}
|
||||
adminDetail := performAdminRequest(
|
||||
t,
|
||||
fixture.adminRouter,
|
||||
http.MethodGet,
|
||||
"/api/v1/tasks/"+taskID,
|
||||
"",
|
||||
nil,
|
||||
"",
|
||||
)
|
||||
requireAdminStatus(t, adminDetail, http.StatusOK)
|
||||
if adminDetail.Header().Get("Cache-Control") != "no-store" {
|
||||
t.Fatalf(
|
||||
"admin detail cache control = %q",
|
||||
adminDetail.Header().Get("Cache-Control"),
|
||||
)
|
||||
}
|
||||
var adminDetailBody struct {
|
||||
OrderSubmissions []struct {
|
||||
ID string `json:"id"`
|
||||
AuthorizationID string `json:"authorization_id"`
|
||||
Status string `json:"status"`
|
||||
ExpectedSKU string `json:"expected_sku"`
|
||||
ExpectedQuantity int `json:"expected_quantity"`
|
||||
ExpectedTotalCents int64 `json:"expected_total_price_cents"`
|
||||
PlatformOrderNo string `json:"platform_order_no"`
|
||||
PlatformOrderedAt string `json:"platform_ordered_at"`
|
||||
PlatformOrderStatus string `json:"platform_order_status"`
|
||||
EvidenceAssetID string `json:"reconciliation_evidence_asset_id"`
|
||||
} `json:"order_submissions"`
|
||||
}
|
||||
decodeResponse(t, adminDetail, &adminDetailBody)
|
||||
if len(adminDetailBody.OrderSubmissions) != 1 {
|
||||
t.Fatalf(
|
||||
"admin order submissions = %+v",
|
||||
adminDetailBody.OrderSubmissions,
|
||||
)
|
||||
}
|
||||
adminSubmission := adminDetailBody.OrderSubmissions[0]
|
||||
if adminSubmission.ID != submissionResponse.Submission.ID ||
|
||||
adminSubmission.AuthorizationID != command.ID ||
|
||||
adminSubmission.Status != "RECONCILED" ||
|
||||
adminSubmission.ExpectedSKU != command.OriginalSKU ||
|
||||
adminSubmission.ExpectedQuantity != 2 ||
|
||||
adminSubmission.ExpectedTotalCents != 4300 ||
|
||||
adminSubmission.PlatformOrderNo != "12345678901234567890" ||
|
||||
adminSubmission.PlatformOrderedAt == "" ||
|
||||
adminSubmission.PlatformOrderStatus != "PENDING_PAYMENT" ||
|
||||
adminSubmission.EvidenceAssetID != reconciliationEvidenceID {
|
||||
t.Fatalf("admin submission = %+v", adminSubmission)
|
||||
}
|
||||
detailAfterReconcile, err := fixture.tasks.Get(
|
||||
context.Background(),
|
||||
localAdminSubject,
|
||||
taskID,
|
||||
)
|
||||
if err != nil ||
|
||||
len(detailAfterReconcile.OrderSubmissions) != 1 ||
|
||||
detailAfterReconcile.OrderSubmissions[0].ID !=
|
||||
submissionResponse.Submission.ID {
|
||||
t.Fatalf(
|
||||
"task detail submissions = %+v, error = %v",
|
||||
detailAfterReconcile.OrderSubmissions,
|
||||
err,
|
||||
)
|
||||
}
|
||||
var deliveredEvents, acknowledgedEvents, dryRunStartedEvents,
|
||||
dryRunReadyEvents, fencedEvents, manualEvents, reconciledEvents int
|
||||
for eventType, target := range map[string]*int{
|
||||
@@ -1617,11 +1682,12 @@ func TestDeviceCancelAcknowledgementFollowsAdminStopRequest(
|
||||
}
|
||||
|
||||
type deviceHTTPFixture struct {
|
||||
db *sql.DB
|
||||
store *repository.Store
|
||||
assets *usecase.AssetService
|
||||
tasks *usecase.TaskService
|
||||
router http.Handler
|
||||
db *sql.DB
|
||||
store *repository.Store
|
||||
assets *usecase.AssetService
|
||||
tasks *usecase.TaskService
|
||||
router http.Handler
|
||||
adminRouter http.Handler
|
||||
|
||||
taskSequence int
|
||||
}
|
||||
@@ -1678,6 +1744,14 @@ func newDeviceHTTPFixture(t *testing.T) *deviceHTTPFixture {
|
||||
if err != nil {
|
||||
t.Fatalf("usecase.NewExecutionResultService() error = %v", err)
|
||||
}
|
||||
authorizations, err := usecase.NewOrderAuthorizationService(
|
||||
store,
|
||||
clock,
|
||||
ids,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("usecase.NewOrderAuthorizationService() error = %v", err)
|
||||
}
|
||||
commands, err := usecase.NewDeviceOrderCommandService(store, clock, ids)
|
||||
if err != nil {
|
||||
t.Fatalf("usecase.NewDeviceOrderCommandService() error = %v", err)
|
||||
@@ -1716,12 +1790,37 @@ func newDeviceHTTPFixture(t *testing.T) *deviceHTTPFixture {
|
||||
if err != nil {
|
||||
t.Fatalf("NewRouter() error = %v", err)
|
||||
}
|
||||
adminRoutes, err := NewAdminRouteRegistrar(
|
||||
AdminServices{
|
||||
Assets: assets,
|
||||
Tasks: tasks,
|
||||
Results: results,
|
||||
Authorizations: authorizations,
|
||||
},
|
||||
emptyAdminWeb{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("NewAdminRouteRegistrar() error = %v", err)
|
||||
}
|
||||
adminRouter, err := NewRouter(RouterDependencies{
|
||||
Database: db,
|
||||
RegisterPublicRoutes: discardRoutes,
|
||||
RegisterAdminRoutes: adminRoutes,
|
||||
RegisterDeviceRoutes: discardRoutes,
|
||||
AdminSessions: authenticator,
|
||||
DeviceAccess: authenticator,
|
||||
LogEvent: discardEvent,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("NewRouter(admin) error = %v", err)
|
||||
}
|
||||
return &deviceHTTPFixture{
|
||||
db: db,
|
||||
store: store,
|
||||
assets: assets,
|
||||
tasks: tasks,
|
||||
router: router,
|
||||
db: db,
|
||||
store: store,
|
||||
assets: assets,
|
||||
tasks: tasks,
|
||||
router: router,
|
||||
adminRouter: adminRouter,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user