feat(payments): payment-event dispatcher + the provider-return UX
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 22s
CI / ui (pull_request) Successful in 1m9s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m45s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 22s
CI / ui (pull_request) Successful in 1m9s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m45s
Deliver payment_events to connected clients as an in-app wallet-refresh push: a background dispatcher drains undispatched events and publishes a KindNotification "payment" signal, marking each delivered; the client bumps a wallet-refresh counter the open Wallet screen watches, re-fetching in place. A return-focus refetch is the fallback. The Robokassa Success/Fail return now serves a self-closing page (the payment opens in a separate window) so the customer drops back into the live app instead of a cold start. Integration test for the event drain/mark queue.
This commit is contained in:
@@ -101,6 +101,45 @@ func TestPaymentsFundAmountMismatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPaymentsEventDispatchDrain verifies the payment_events dispatcher queue: a recorded event is
|
||||
// returned as undispatched until marked, then drops out (so the dispatcher delivers it once).
|
||||
func TestPaymentsEventDispatchDrain(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
svc := newPaymentsService()
|
||||
acc := uuid.New()
|
||||
if err := svc.RecordPaymentEvent(ctx, acc, nil, "succeeded", []byte(`{"chips":10,"source":"direct"}`)); err != nil {
|
||||
t.Fatalf("record event: %v", err)
|
||||
}
|
||||
|
||||
// testDB is shared, so filter the queue to our account.
|
||||
find := func() *payments.PaymentEvent {
|
||||
evs, err := svc.UndispatchedEvents(ctx, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("undispatched: %v", err)
|
||||
}
|
||||
for i := range evs {
|
||||
if evs[i].AccountID == acc {
|
||||
return &evs[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
mine := find()
|
||||
if mine == nil {
|
||||
t.Fatal("recorded event not in the undispatched queue")
|
||||
}
|
||||
if mine.Type != "succeeded" {
|
||||
t.Errorf("event type = %s, want succeeded", mine.Type)
|
||||
}
|
||||
if err := svc.MarkEventDispatched(ctx, mine.EventID); err != nil {
|
||||
t.Fatalf("mark dispatched: %v", err)
|
||||
}
|
||||
if find() != nil {
|
||||
t.Error("event still undispatched after MarkEventDispatched")
|
||||
}
|
||||
}
|
||||
|
||||
// TestPaymentsExpiredOrderStillCredits verifies an expired pending order is still honoured by a
|
||||
// later valid callback (§9/D23: expiry is cosmetic, the money is real).
|
||||
func TestPaymentsExpiredOrderStillCredits(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user