paymentsdb: implement InitPayment for sql backend

This commit is contained in:
ziggie committed 2026-02-25 08:02:09 +01:00
1 parent 0c96a2d722
commit 510f6fb532
3 files changed
+133 -1

No files matched your search

+129 -1
View File
@@ -61,7 +61,7 @@ type SQLQueries interface {
Payment DB write operations. Payment DB write operations.
*/ */
InsertPaymentIntent(ctx context.Context, arg sqlc.InsertPaymentIntentParams) (int64, error) InsertPaymentIntent(ctx context.Context, arg sqlc.InsertPaymentIntentParams) (int64, error)
InsertPayment(ctx context.Context, arg sqlc.InsertPaymentParams) error InsertPayment(ctx context.Context, arg sqlc.InsertPaymentParams) (int64, error)
InsertPaymentFirstHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentFirstHopCustomRecordParams) error InsertPaymentFirstHopCustomRecord(ctx context.Context, arg sqlc.InsertPaymentFirstHopCustomRecordParams) error
InsertHtlcAttempt(ctx context.Context, arg sqlc.InsertHtlcAttemptParams) (int64, error) InsertHtlcAttempt(ctx context.Context, arg sqlc.InsertHtlcAttemptParams) (int64, error)
@@ -916,3 +916,131 @@ func (s *SQLStore) DeletePayment(paymentHash lntypes.Hash,
return nil return nil
} }
// InitPayment creates a new payment record in the database with the given
// payment hash and creation info.
//
// Before creating the payment, this method checks if a payment with the same
// hash already exists and validates whether initialization is allowed based on
// the existing payment's status:
// - StatusInitiated: Returns ErrPaymentExists (payment already created,
// HTLCs may be in flight)
// - StatusInFlight: Returns ErrPaymentInFlight (payment currently being
// attempted)
// - StatusSucceeded: Returns ErrAlreadyPaid (payment already succeeded)
// - StatusFailed: Allows retry by deleting the old payment record and
// creating a new one
//
// If no existing payment is found, a new payment record is created with
// StatusInitiated and stored with all associated metadata.
//
// This method is part of the PaymentControl interface, which is embedded in
// the PaymentWriter interface and ultimately the DB interface, representing
// the first step in the payment lifecycle control flow.
func (s *SQLStore) InitPayment(paymentHash lntypes.Hash,
paymentCreationInfo *PaymentCreationInfo) error {
ctx := context.TODO()
// Create the payment in the database.
err := s.db.ExecTx(ctx, sqldb.WriteTxOpt(), func(db SQLQueries) error {
existingPayment, err := db.FetchPayment(ctx, paymentHash[:])
switch {
// A payment with this hash already exists. We need to check its
// status to see if we can re-initialize.
case err == nil:
paymentStatus, err := computePaymentStatusFromDB(
ctx, db, existingPayment,
)
if err != nil {
return fmt.Errorf("failed to compute payment "+
"status: %w", err)
}
// Check if the payment is initializable otherwise
// we'll return early.
if err := paymentStatus.initializable(); err != nil {
return fmt.Errorf("payment is not "+
"initializable: %w", err)
}
// If the initializable check above passes, then the
// existing payment has failed. So we delete it and
// all of its previous artifacts. We rely on
// cascading deletes to clean up the rest.
err = db.DeletePayment(ctx, existingPayment.Payment.ID)
if err != nil {
return fmt.Errorf("failed to delete "+
"payment: %w", err)
}
// An unexpected error occurred while fetching the payment.
case !errors.Is(err, sql.ErrNoRows):
// Some other error occurred
return fmt.Errorf("failed to check existing "+
"payment: %w", err)
// The payment does not yet exist, so we can proceed.
default:
}
// Insert the payment first to get its ID.
paymentID, err := db.InsertPayment(
ctx, sqlc.InsertPaymentParams{
AmountMsat: int64(
paymentCreationInfo.Value,
),
CreatedAt: paymentCreationInfo.
CreationTime.UTC(),
PaymentIdentifier: paymentHash[:],
},
)
if err != nil {
return fmt.Errorf("failed to insert payment: %w", err)
}
// If there's a payment request, insert the payment intent.
if len(paymentCreationInfo.PaymentRequest) > 0 {
_, err = db.InsertPaymentIntent(
ctx, sqlc.InsertPaymentIntentParams{
PaymentID: paymentID,
IntentType: int16(
PaymentIntentTypeBolt11,
),
IntentPayload: paymentCreationInfo.
PaymentRequest,
},
)
if err != nil {
return fmt.Errorf("failed to insert "+
"payment intent: %w", err)
}
}
firstHopCustomRecords := paymentCreationInfo.
FirstHopCustomRecords
for key, value := range firstHopCustomRecords {
err = db.InsertPaymentFirstHopCustomRecord(
ctx,
sqlc.InsertPaymentFirstHopCustomRecordParams{
PaymentID: paymentID,
Key: int64(key),
Value: value,
},
)
if err != nil {
return fmt.Errorf("failed to insert "+
"payment first hop custom "+
"record: %w", err)
}
}
return nil
}, sqldb.NoOpReset)
if err != nil {
return fmt.Errorf("failed to initialize payment: %w", err)
}
return nil
}
+2
View File
@@ -760,6 +760,8 @@ type InsertPaymentParams struct {
} }
// Insert a new payment and return its ID. // Insert a new payment and return its ID.
// When creating a payment we don't have a fail reason because we start the
// payment process.
func (q *Queries) InsertPayment(ctx context.Context, arg InsertPaymentParams) (int64, error) { func (q *Queries) InsertPayment(ctx context.Context, arg InsertPaymentParams) (int64, error) {
row := q.db.QueryRowContext(ctx, insertPayment, arg.AmountMsat, arg.CreatedAt, arg.PaymentIdentifier) row := q.db.QueryRowContext(ctx, insertPayment, arg.AmountMsat, arg.CreatedAt, arg.PaymentIdentifier)
var id int64 var id int64
+2
View File
@@ -163,6 +163,8 @@ type Querier interface {
// to have a newer last_update than the existing node). // to have a newer last_update than the existing node).
InsertNodeMig(ctx context.Context, arg InsertNodeMigParams) (int64, error) InsertNodeMig(ctx context.Context, arg InsertNodeMigParams) (int64, error)
// Insert a new payment and return its ID. // Insert a new payment and return its ID.
// When creating a payment we don't have a fail reason because we start the
// payment process.
InsertPayment(ctx context.Context, arg InsertPaymentParams) (int64, error) InsertPayment(ctx context.Context, arg InsertPaymentParams) (int64, error)
InsertPaymentAttemptFirstHopCustomRecord(ctx context.Context, arg InsertPaymentAttemptFirstHopCustomRecordParams) error InsertPaymentAttemptFirstHopCustomRecord(ctx context.Context, arg InsertPaymentAttemptFirstHopCustomRecordParams) error
InsertPaymentFirstHopCustomRecord(ctx context.Context, arg InsertPaymentFirstHopCustomRecordParams) error InsertPaymentFirstHopCustomRecord(ctx context.Context, arg InsertPaymentFirstHopCustomRecordParams) error