@@ -467,26 +467,7 @@ func (s *InterceptorService) PaymentErrored(ctx context.Context, id AccountID,
467
467
"has already started" )
468
468
}
469
469
470
- account , err := s .store .Account (ctx , id )
471
- if err != nil {
472
- return err
473
- }
474
-
475
- // Check that this payment is actually associated with this account.
476
- _ , ok = account .Payments [hash ]
477
- if ! ok {
478
- return fmt .Errorf ("payment with hash %s is not associated " +
479
- "with this account" , hash )
480
- }
481
-
482
- // Delete the payment and update the persisted account.
483
- delete (account .Payments , hash )
484
-
485
- if err := s .store .UpdateAccount (ctx , account ); err != nil {
486
- return fmt .Errorf ("error updating account: %w" , err )
487
- }
488
-
489
- return nil
470
+ return s .store .DeleteAccountPayment (ctx , id , hash )
490
471
}
491
472
492
473
// AssociatePayment associates a payment (hash) with the given account,
@@ -498,44 +479,24 @@ func (s *InterceptorService) AssociatePayment(ctx context.Context, id AccountID,
498
479
s .Lock ()
499
480
defer s .Unlock ()
500
481
501
- account , err := s .store .Account (ctx , id )
502
- if err != nil {
503
- return err
504
- }
505
-
506
- // Check if this payment is associated with the account already.
507
- _ , ok := account .Payments [paymentHash ]
508
- if ok {
509
- // We do not allow another payment to the same hash if the
510
- // payment is already in-flight or succeeded. This mitigates a
511
- // user being able to launch a second RPC-erring payment with
512
- // the same hash that would remove the payment from being
513
- // tracked. Note that this prevents launching multipart
514
- // payments, but allows retrying a payment if it has failed.
515
- if account .Payments [paymentHash ].Status !=
516
- lnrpc .Payment_FAILED {
517
-
518
- return fmt .Errorf ("payment with hash %s is already in " +
519
- "flight or succeeded (status %v)" , paymentHash ,
520
- account .Payments [paymentHash ].Status )
521
- }
522
-
523
- // Otherwise, we fall through to correctly update the payment
524
- // amount, in case we have a zero-amount invoice that is
525
- // retried.
526
- }
527
-
528
- // Associate the payment with the account and store it.
529
- account .Payments [paymentHash ] = & PaymentEntry {
530
- Status : lnrpc .Payment_UNKNOWN ,
531
- FullAmount : fullAmt ,
532
- }
533
-
534
- if err := s .store .UpdateAccount (ctx , account ); err != nil {
535
- return fmt .Errorf ("error updating account: %w" , err )
536
- }
482
+ // We add the WithErrIfAlreadyPending option to ensure that if the
483
+ // payment is already associated with the account, then we return
484
+ // an error if the payment is already in-flight or succeeded. This
485
+ // prevents a user from being able to launch a second RPC-erring payment
486
+ // with the same hash that would remove the payment from being tracked.
487
+ //
488
+ // NOTE: this prevents launching multipart payments, but allows
489
+ // retrying a payment if it has failed.
490
+ //
491
+ // If the payment is already associated with the account but not in
492
+ // flight, we update the payment amount in case we have a zero-amount
493
+ // invoice that is retried.
494
+ _ , err := s .store .UpsertAccountPayment (
495
+ ctx , id , paymentHash , fullAmt , lnrpc .Payment_UNKNOWN ,
496
+ WithErrIfAlreadyPending (),
497
+ )
537
498
538
- return nil
499
+ return err
539
500
}
540
501
541
502
// invoiceUpdate credits the account an invoice was registered with, in case the
@@ -629,34 +590,30 @@ func (s *InterceptorService) TrackPayment(ctx context.Context, id AccountID,
629
590
return nil
630
591
}
631
592
632
- // Similarly, if we've already processed the payment in the past, there
633
- // is a reference in the account with the given state.
634
- account , err := s .store .Account (ctx , id )
635
- if err != nil {
636
- return fmt .Errorf ("error fetching account: %w" , err )
637
- }
638
-
639
593
// If the account already stored a terminal state, we also don't need to
640
- // track the payment again.
641
- entry , ok := account .Payments [hash ]
642
- if ok && successState (entry .Status ) {
643
- return nil
594
+ // track the payment again. So we add the WithErrIfAlreadySucceeded
595
+ // option to ensure that we return an error if the payment has already
596
+ // succeeded. We can then match on the ErrAlreadySucceeded error and
597
+ // exit early if it is returned.
598
+ opts := []UpsertPaymentOption {
599
+ WithErrIfAlreadySucceeded (),
644
600
}
645
601
646
602
// There is a case where the passed in fullAmt is zero but the pending
647
603
// amount is not. In that case, we should not overwrite the pending
648
604
// amount.
649
605
if fullAmt == 0 {
650
- fullAmt = entry .FullAmount
651
- }
652
-
653
- account .Payments [hash ] = & PaymentEntry {
654
- Status : lnrpc .Payment_UNKNOWN ,
655
- FullAmount : fullAmt ,
606
+ opts = append (opts , WithPendingAmount ())
656
607
}
657
608
658
- if err := s .store .UpdateAccount (ctx , account ); err != nil {
659
- if ! ok {
609
+ known , err := s .store .UpsertAccountPayment (
610
+ ctx , id , hash , fullAmt , lnrpc .Payment_UNKNOWN , opts ... ,
611
+ )
612
+ if err != nil {
613
+ if errors .Is (err , ErrAlreadySucceeded ) {
614
+ return nil
615
+ }
616
+ if ! known {
660
617
// In the rare case that the payment isn't associated
661
618
// with an account yet, and we fail to update the
662
619
// account we will not be tracking the payment, even if
@@ -832,23 +789,14 @@ func (s *InterceptorService) paymentUpdate(ctx context.Context,
832
789
833
790
// The payment went through! We now need to debit the full amount from
834
791
// the account.
835
- account , err := s .store .Account (ctx , pendingPayment .accountID )
836
- if err != nil {
837
- err = s .disableAndErrorfUnsafe ("error fetching account: %w" ,
838
- err )
839
-
840
- return terminalState , err
841
- }
842
-
843
792
fullAmount := status .Value + status .Fee
844
793
845
- // Update the account and store it in the database.
846
- account .CurrentBalance -= int64 (fullAmount )
847
- account .Payments [hash ] = & PaymentEntry {
848
- Status : lnrpc .Payment_SUCCEEDED ,
849
- FullAmount : fullAmount ,
850
- }
851
- if err := s .store .UpdateAccount (ctx , account ); err != nil {
794
+ // Update the persisted account.
795
+ _ , err := s .store .UpsertAccountPayment (
796
+ ctx , pendingPayment .accountID , hash , fullAmount ,
797
+ lnrpc .Payment_SUCCEEDED , WithDebitAccount (),
798
+ )
799
+ if err != nil {
852
800
err = s .disableAndErrorfUnsafe ("error updating account: %w" ,
853
801
err )
854
802
@@ -892,23 +840,36 @@ func (s *InterceptorService) removePayment(ctx context.Context,
892
840
return nil
893
841
}
894
842
895
- account , err := s .store .Account (ctx , pendingPayment .accountID )
896
- if err != nil {
897
- return err
843
+ _ , err := s .store .UpsertAccountPayment (
844
+ ctx , pendingPayment .accountID , hash , 0 , status ,
845
+ // We don't want the payment to be inserted if it isn't already
846
+ // known. So we pass in this option to ensure that the call
847
+ // exits early if the payment is unknown.
848
+ WithErrIfUnknown (),
849
+ // Otherwise, we just want to update the status of the payment
850
+ // and use the existing pending amount.
851
+ WithPendingAmount (),
852
+ )
853
+ if err != nil && ! errors .Is (err , ErrPaymentNotAssociated ) {
854
+ return fmt .Errorf ("error updating account: %w" , err )
898
855
}
899
856
900
857
pendingPayment .cancel ()
901
858
delete (s .pendingPayments , hash )
902
859
903
- // Have we associated the payment with the account already?
904
- _ , ok = account .Payments [hash ]
905
- if ! ok {
906
- return nil
907
- }
860
+ return nil
861
+ }
908
862
909
- // If we did, let's set the status correctly in the DB now.
910
- account .Payments [hash ].Status = status
911
- return s .store .UpdateAccount (ctx , account )
863
+ // hasPayment returns true if the payment is currently being tracked by the
864
+ // service.
865
+ //
866
+ // NOTE: this is currently used only for tests.
867
+ func (s * InterceptorService ) hasPayment (hash lntypes.Hash ) bool {
868
+ s .RLock ()
869
+ defer s .RUnlock ()
870
+
871
+ _ , ok := s .pendingPayments [hash ]
872
+ return ok
912
873
}
913
874
914
875
// successState returns true if a payment was completed successfully.
0 commit comments