Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ build/
/payment-adapter-stripe/payment-adapter-stripe-gateway/logs/
/payment-adapter-stripe/payment-adapter-stripe-webhook/logs/
/payment-adapter-stripe/payment-adapter-stripe-gateway/logs/

/payment-adapter-stripe/payment-adapter-stripe-connect/logs/
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.13.0] - 2026-06-05

### Added
- `Gateway.getCreatedAt(quoteId)` default port returning `Instant` (default: `null`). Lets cashu-mint's `MintTask` enforce strict NUT-04 quote expiry by computing `createdAt + getPaymentExpiry()` — required by spec 041 REQ-MINT-3 (client-side voucher minting).
- `GatewayQuote.createdAt` JPA column (`@Column(name = "created_at") Instant`) auto-populated by a `@PrePersist` hook. Nullable for backward compatibility — pre-existing rows decode cleanly and the mint falls through to permissive behaviour for those.
- `PhoenixdGateway.getCreatedAt(String)` override — looks up the JPA row and returns `createdAt`.

### Changed
- None of the additions are breaking. The new interface default method preserves binary compatibility for existing `Gateway` implementations; the new JPA column is additive.

## [0.12.0] - 2026-03-22

### Added
Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-cash/payment-adapter-cash-gateway/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-cash</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-cash/payment-adapter-cash-nostr/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-cash</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-cash/payment-adapter-cash-webhook/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-cash</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-cash/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-core/payment-adapter-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-core</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-core/payment-adapter-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-core</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import xyz.tcheeric.cashu.common.nut18.PaymentMethod;
import xyz.tcheeric.cashu.entities.annotation.Supports;

import java.time.Instant;
import java.util.Arrays;

public interface Gateway {
Expand Down Expand Up @@ -73,6 +74,26 @@ public interface Gateway {
*/
Integer getPaymentExpiry(String quoteId);

/**
* Get the creation timestamp of a quote.
*
* <p>Spec 041 (cashu-mint REQ-MINT-3): the mint enforces strict quote
* expiry by computing {@code createdAt + getPaymentExpiry(quoteId)}
* and rejecting requests past that instant. The default implementation
* returns {@code null} so legacy gateway impls that haven't been
* updated continue to compile; callers MUST treat null as "creation
* time unknown — skip enforcement" and fall through to the existing
* permissive behaviour.
*
* @param quoteId the quote identifier
* @return the {@link Instant} when the quote was created, or
* {@code null} when the gateway does not track creation
* time for this quote
*/
default Instant getCreatedAt(String quoteId) {
return null;
}

/**
* Get the fee reserve of a payment
* @param request
Expand Down
4 changes: 2 additions & 2 deletions payment-adapter-core/payment-adapter-model/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-core</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>payment-adapter-model</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<name>payment-adapter-model</name>
<description>Demo project for Spring Boot</description>
<url/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.PrePersist;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
import xyz.tcheeric.payment.adapter.core.model.entity.enums.Direction;
import xyz.tcheeric.payment.adapter.core.model.entity.enums.State;

import java.io.Serial;
import java.time.Instant;
/**
* JPA entity representing a quote handled by the gateway for minting or melting
* operations. The {@link #create(String, String, Integer, String, String, Integer, String)}
Expand Down Expand Up @@ -57,6 +59,25 @@ public class GatewayQuote implements GatewayEntity {
@Enumerated(EnumType.STRING)
private Direction direction;

/**
* Spec 041 (cashu-mint REQ-MINT-3) — when the row was created. Lets the
* mint compute the absolute expiry instant from this + {@link #expiry}
* (which is a TTL in seconds) and enforce {@code quote_expired} strictly.
* Auto-populated by {@link #onCreate()} via JPA's {@code @PrePersist} so
* existing factory callers don't need to set it. Nullable for backward
* compatibility — rows persisted before this column was added decode
* cleanly with null and the mint skips enforcement in that case.
*/
@Column(name = "created_at")
private Instant createdAt;
Comment thread
tcheeric marked this conversation as resolved.

@PrePersist
void onCreate() {
Comment thread
tcheeric marked this conversation as resolved.
if (this.createdAt == null) {
this.createdAt = Instant.now();
}
Comment thread
tcheeric marked this conversation as resolved.
}

/**
* Factory method to create a {@link GatewayQuote} with default state and direction.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Spec 041 REQ-MINT-3 — strict NUT-04 quote expiry enforcement.
--
-- Adds the `created_at` column to the existing `quote` table so the cashu
-- mint's MintTask can compute the absolute quote-expiry instant as
-- `created_at + expiry` and reject mint requests past that instant.
--
-- Nullable on purpose:
-- * Pre-existing rows persisted before this column was added retain NULL,
-- and Gateway.getCreatedAt(quoteId) returns null for them. The mint
-- treats null as "skip enforcement" and falls through to existing
-- permissive behaviour. See:
-- - payment-adapter-core/payment-adapter-common/src/main/java/
-- xyz/tcheeric/payment/adapter/core/common/Gateway.java
-- - GatewayQuote.@PrePersist onCreate() — populates createdAt for
-- every NEW row.
--
-- Idempotent via IF NOT EXISTS so re-runs against an already-migrated
-- database (e.g. an environment where Hibernate hbm2ddl previously added
-- the column) are safe.
ALTER TABLE quote ADD COLUMN IF NOT EXISTS created_at TIMESTAMP;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package xyz.tcheeric.payment.adapter.core.model.entity;

import java.time.Instant;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import xyz.tcheeric.payment.adapter.core.model.entity.enums.Direction;
Expand All @@ -15,4 +16,35 @@ void createInitializesDefaults() {
assertThat(quote.getQuoteId()).isEqualTo("qid");
assertThat(quote.getInvoiceId()).isEqualTo("iid");
}

@Test
void prePersistPopulatesCreatedAtWhenNull() {
// Spec 041 REQ-MINT-3 — the mint reads Gateway.getCreatedAt to compute
// strict quote expiry. The @PrePersist hook MUST populate createdAt
// whenever JPA persists a NEW row.
GatewayQuote quote = GatewayQuote.create("qid", "iid", 60, "desc", "req", 100, "sat");
assertThat(quote.getCreatedAt()).isNull();

Instant before = Instant.now();
quote.onCreate();
Instant after = Instant.now();

assertThat(quote.getCreatedAt())
.isNotNull()
.isBetween(before, after);
}

@Test
void prePersistDoesNotOverwriteAnExplicitlySetCreatedAt() {
// Backward compatibility — if a caller pre-populates createdAt
// (e.g. during a backfill of legacy rows), the @PrePersist hook
// MUST NOT overwrite it.
GatewayQuote quote = GatewayQuote.create("qid", "iid", 60, "desc", "req", 100, "sat");
Instant explicit = Instant.parse("2025-01-15T10:30:00Z");
quote.setCreatedAt(explicit);

quote.onCreate();

assertThat(quote.getCreatedAt()).isEqualTo(explicit);
}
}
4 changes: 2 additions & 2 deletions payment-adapter-core/payment-adapter-rest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-core</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-rest</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<name>payment-adapter-rest</name>
<description>A simple JPA application to manage quotes and invoices</description>
<url/>
Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-ln/payment-adapter-ln-dummy/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-ln</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-ln/payment-adapter-ln-phoenixd/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-ln</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,13 @@ public Integer getPaymentExpiry(String quoteId) {
return quote.getExpiry();
}

@Override
public java.time.Instant getCreatedAt(String quoteId) {
QuoteClient client = new QuoteClient();
GatewayQuote quote = client.getByEntityId(quoteId);
return quote != null ? quote.getCreatedAt() : null;
}

@Override
public Integer getFeeReserve(String quoteId) {
/*
Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-ln/payment-adapter-ln-webhook/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-ln</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-ln/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-stripe</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-stripe</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-stripe</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-stripe/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-test/payment-adapter-test-e2e/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-test</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-test</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion payment-adapter-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions payment-adapter-webhook/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
</parent>

<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter-webhook</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<name>payment-adapter-webhook</name>
<packaging>jar</packaging>

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>xyz.tcheeric</groupId>
<artifactId>payment-adapter</artifactId>
<version>0.12.0</version>
<version>0.13.0</version>
<packaging>pom</packaging>

<name>payment-adapter</name>
Expand Down
Loading