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.
*/
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
InsertHtlcAttempt(ctx context.Context, arg sqlc.InsertHtlcAttemptParams) (int64, error)
@@ -916,3 +916,131 @@ func (s *SQLStore) DeletePayment(paymentHash lntypes.Hash,
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.
// 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) {
row := q.db.QueryRowContext(ctx, insertPayment, arg.AmountMsat, arg.CreatedAt, arg.PaymentIdentifier)
var id int64
+2
View File
@@ -163,6 +163,8 @@ type Querier interface {
// to have a newer last_update than the existing node).
InsertNodeMig(ctx context.Context, arg InsertNodeMigParams) (int64, error)
// 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)
InsertPaymentAttemptFirstHopCustomRecord(ctx context.Context, arg InsertPaymentAttemptFirstHopCustomRecordParams) error
InsertPaymentFirstHopCustomRecord(ctx context.Context, arg InsertPaymentFirstHopCustomRecordParams) error