feat(payments): direct-rail order + intake handlers, config and reaper
Wire the Robokassa direct rail into the backend transport. POST /api/v1/user/wallet/order (walletGate + a D36 confirmed-email gate for the direct rail) opens a pending order and returns the signed Robokassa payment URL. The internal, gateway-only /payments/robokassa/result endpoint verifies the Result signature, credits the matched order exactly once via Fund (honoured even if expired), records a succeeded payment event, and answers Robokassa's "OK<InvId>". Add the Robokassa env config, an account HasConfirmedEmail check (D36), the payment_events writer, and a periodic pending-order reaper. The routes register only when a Robokassa merchant login is configured.
This commit is contained in:
@@ -271,6 +271,27 @@ func (s *Store) fund(ctx context.Context, orderID uuid.UUID, provider, providerP
|
||||
return outcome, nil
|
||||
}
|
||||
|
||||
// insertPaymentEvent appends an undispatched lifecycle event (succeeded/failed/refunded) for the
|
||||
// dispatcher to deliver. orderID and payload (a jsonb detail blob) are optional.
|
||||
func (s *Store) insertPaymentEvent(ctx context.Context, accountID uuid.UUID, orderID *uuid.UUID, eventType string, payload []byte, now time.Time) error {
|
||||
id, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
return fmt.Errorf("payments: event id: %w", err)
|
||||
}
|
||||
var pl any = postgres.NULL
|
||||
if payload != nil {
|
||||
pl = string(payload)
|
||||
}
|
||||
stmt := table.PaymentEvents.INSERT(
|
||||
table.PaymentEvents.EventID, table.PaymentEvents.AccountID, table.PaymentEvents.OrderID,
|
||||
table.PaymentEvents.Type, table.PaymentEvents.Payload, table.PaymentEvents.CreatedAt,
|
||||
).VALUES(id, accountID, uuidOrNull(orderID), eventType, pl, now)
|
||||
if _, err := stmt.ExecContext(ctx, s.db); err != nil {
|
||||
return fmt.Errorf("payments: insert %s event: %w", eventType, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// orderTTL reads the configured pending-order lifetime in whole seconds.
|
||||
func (s *Store) orderTTL(ctx context.Context) (int, error) {
|
||||
var cfg model.Config
|
||||
|
||||
Reference in New Issue
Block a user