diff --git a/.gitignore b/.gitignore
index 8302665..021960c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 18f5085..1023fb1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/payment-adapter-cash/payment-adapter-cash-gateway/pom.xml b/payment-adapter-cash/payment-adapter-cash-gateway/pom.xml
index e35a9c4..4ab9c48 100644
--- a/payment-adapter-cash/payment-adapter-cash-gateway/pom.xml
+++ b/payment-adapter-cash/payment-adapter-cash-gateway/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter-cash
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-cash/payment-adapter-cash-nostr/pom.xml b/payment-adapter-cash/payment-adapter-cash-nostr/pom.xml
index 1876bab..3371524 100644
--- a/payment-adapter-cash/payment-adapter-cash-nostr/pom.xml
+++ b/payment-adapter-cash/payment-adapter-cash-nostr/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter-cash
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-cash/payment-adapter-cash-webhook/pom.xml b/payment-adapter-cash/payment-adapter-cash-webhook/pom.xml
index 4c5ffcb..2016b30 100644
--- a/payment-adapter-cash/payment-adapter-cash-webhook/pom.xml
+++ b/payment-adapter-cash/payment-adapter-cash-webhook/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter-cash
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-cash/pom.xml b/payment-adapter-cash/pom.xml
index e05a744..efd6253 100644
--- a/payment-adapter-cash/pom.xml
+++ b/payment-adapter-cash/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-core/payment-adapter-client/pom.xml b/payment-adapter-core/payment-adapter-client/pom.xml
index 5c25c58..362d059 100644
--- a/payment-adapter-core/payment-adapter-client/pom.xml
+++ b/payment-adapter-core/payment-adapter-client/pom.xml
@@ -4,7 +4,7 @@
xyz.tcheeric
payment-adapter-core
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-core/payment-adapter-common/pom.xml b/payment-adapter-core/payment-adapter-common/pom.xml
index 51a69bc..d0672a7 100644
--- a/payment-adapter-core/payment-adapter-common/pom.xml
+++ b/payment-adapter-core/payment-adapter-common/pom.xml
@@ -4,7 +4,7 @@
xyz.tcheeric
payment-adapter-core
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-core/payment-adapter-common/src/main/java/xyz/tcheeric/payment/adapter/core/common/Gateway.java b/payment-adapter-core/payment-adapter-common/src/main/java/xyz/tcheeric/payment/adapter/core/common/Gateway.java
index 49c1304..e204fd2 100644
--- a/payment-adapter-core/payment-adapter-common/src/main/java/xyz/tcheeric/payment/adapter/core/common/Gateway.java
+++ b/payment-adapter-core/payment-adapter-common/src/main/java/xyz/tcheeric/payment/adapter/core/common/Gateway.java
@@ -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 {
@@ -73,6 +74,26 @@ public interface Gateway {
*/
Integer getPaymentExpiry(String quoteId);
+ /**
+ * Get the creation timestamp of a quote.
+ *
+ *
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
diff --git a/payment-adapter-core/payment-adapter-model/pom.xml b/payment-adapter-core/payment-adapter-model/pom.xml
index 60a55da..c7d9ad2 100644
--- a/payment-adapter-core/payment-adapter-model/pom.xml
+++ b/payment-adapter-core/payment-adapter-model/pom.xml
@@ -5,11 +5,11 @@
xyz.tcheeric
payment-adapter-core
- 0.12.0
+ 0.13.0
../pom.xml
payment-adapter-model
- 0.12.0
+ 0.13.0
payment-adapter-model
Demo project for Spring Boot
diff --git a/payment-adapter-core/payment-adapter-model/src/main/java/xyz/tcheeric/payment/adapter/core/model/entity/GatewayQuote.java b/payment-adapter-core/payment-adapter-model/src/main/java/xyz/tcheeric/payment/adapter/core/model/entity/GatewayQuote.java
index 4e0584a..b91c776 100644
--- a/payment-adapter-core/payment-adapter-model/src/main/java/xyz/tcheeric/payment/adapter/core/model/entity/GatewayQuote.java
+++ b/payment-adapter-core/payment-adapter-model/src/main/java/xyz/tcheeric/payment/adapter/core/model/entity/GatewayQuote.java
@@ -12,6 +12,7 @@
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;
@@ -19,6 +20,7 @@
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)}
@@ -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;
+
+ @PrePersist
+ void onCreate() {
+ if (this.createdAt == null) {
+ this.createdAt = Instant.now();
+ }
+ }
+
/**
* Factory method to create a {@link GatewayQuote} with default state and direction.
*
diff --git a/payment-adapter-core/payment-adapter-model/src/main/resources/db/migration/V6__add_quote_created_at.sql b/payment-adapter-core/payment-adapter-model/src/main/resources/db/migration/V6__add_quote_created_at.sql
new file mode 100644
index 0000000..2aa340b
--- /dev/null
+++ b/payment-adapter-core/payment-adapter-model/src/main/resources/db/migration/V6__add_quote_created_at.sql
@@ -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;
diff --git a/payment-adapter-core/payment-adapter-model/src/test/java/xyz/tcheeric/payment/adapter/core/model/entity/GatewayQuoteTest.java b/payment-adapter-core/payment-adapter-model/src/test/java/xyz/tcheeric/payment/adapter/core/model/entity/GatewayQuoteTest.java
index 83c5a8f..8f9e356 100644
--- a/payment-adapter-core/payment-adapter-model/src/test/java/xyz/tcheeric/payment/adapter/core/model/entity/GatewayQuoteTest.java
+++ b/payment-adapter-core/payment-adapter-model/src/test/java/xyz/tcheeric/payment/adapter/core/model/entity/GatewayQuoteTest.java
@@ -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;
@@ -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);
+ }
}
diff --git a/payment-adapter-core/payment-adapter-rest/pom.xml b/payment-adapter-core/payment-adapter-rest/pom.xml
index f522d26..b689794 100644
--- a/payment-adapter-core/payment-adapter-rest/pom.xml
+++ b/payment-adapter-core/payment-adapter-rest/pom.xml
@@ -5,12 +5,12 @@
xyz.tcheeric
payment-adapter-core
- 0.12.0
+ 0.13.0
../pom.xml
xyz.tcheeric
payment-adapter-rest
- 0.12.0
+ 0.13.0
payment-adapter-rest
A simple JPA application to manage quotes and invoices
diff --git a/payment-adapter-core/pom.xml b/payment-adapter-core/pom.xml
index d65bd04..8413c7b 100644
--- a/payment-adapter-core/pom.xml
+++ b/payment-adapter-core/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-ln/payment-adapter-ln-dummy/pom.xml b/payment-adapter-ln/payment-adapter-ln-dummy/pom.xml
index c0d7e6b..8e7fe8b 100644
--- a/payment-adapter-ln/payment-adapter-ln-dummy/pom.xml
+++ b/payment-adapter-ln/payment-adapter-ln-dummy/pom.xml
@@ -4,7 +4,7 @@
xyz.tcheeric
payment-adapter-ln
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-ln/payment-adapter-ln-phoenixd/pom.xml b/payment-adapter-ln/payment-adapter-ln-phoenixd/pom.xml
index 25af23e..ace7325 100644
--- a/payment-adapter-ln/payment-adapter-ln-phoenixd/pom.xml
+++ b/payment-adapter-ln/payment-adapter-ln-phoenixd/pom.xml
@@ -4,7 +4,7 @@
xyz.tcheeric
payment-adapter-ln
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-ln/payment-adapter-ln-phoenixd/src/main/java/xyz/tcheeric/payment/adapter/ln/phoenixd/PhoenixdGateway.java b/payment-adapter-ln/payment-adapter-ln-phoenixd/src/main/java/xyz/tcheeric/payment/adapter/ln/phoenixd/PhoenixdGateway.java
index a131ffe..87ee2bc 100644
--- a/payment-adapter-ln/payment-adapter-ln-phoenixd/src/main/java/xyz/tcheeric/payment/adapter/ln/phoenixd/PhoenixdGateway.java
+++ b/payment-adapter-ln/payment-adapter-ln-phoenixd/src/main/java/xyz/tcheeric/payment/adapter/ln/phoenixd/PhoenixdGateway.java
@@ -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) {
/*
diff --git a/payment-adapter-ln/payment-adapter-ln-webhook/pom.xml b/payment-adapter-ln/payment-adapter-ln-webhook/pom.xml
index 8ff3dbd..edff665 100644
--- a/payment-adapter-ln/payment-adapter-ln-webhook/pom.xml
+++ b/payment-adapter-ln/payment-adapter-ln-webhook/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter-ln
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-ln/pom.xml b/payment-adapter-ln/pom.xml
index 3cb10c0..0da3aa5 100644
--- a/payment-adapter-ln/pom.xml
+++ b/payment-adapter-ln/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-stripe/payment-adapter-stripe-connect/pom.xml b/payment-adapter-stripe/payment-adapter-stripe-connect/pom.xml
index 9477cb8..b3babc0 100644
--- a/payment-adapter-stripe/payment-adapter-stripe-connect/pom.xml
+++ b/payment-adapter-stripe/payment-adapter-stripe-connect/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter-stripe
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-stripe/payment-adapter-stripe-gateway/pom.xml b/payment-adapter-stripe/payment-adapter-stripe-gateway/pom.xml
index 130e1cf..a4c9490 100644
--- a/payment-adapter-stripe/payment-adapter-stripe-gateway/pom.xml
+++ b/payment-adapter-stripe/payment-adapter-stripe-gateway/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter-stripe
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-stripe/payment-adapter-stripe-webhook/pom.xml b/payment-adapter-stripe/payment-adapter-stripe-webhook/pom.xml
index c7d73fa..bdc722f 100644
--- a/payment-adapter-stripe/payment-adapter-stripe-webhook/pom.xml
+++ b/payment-adapter-stripe/payment-adapter-stripe-webhook/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter-stripe
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-stripe/pom.xml b/payment-adapter-stripe/pom.xml
index b85dd8b..ecd465e 100644
--- a/payment-adapter-stripe/pom.xml
+++ b/payment-adapter-stripe/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-test/payment-adapter-test-e2e/pom.xml b/payment-adapter-test/payment-adapter-test-e2e/pom.xml
index b8d79c4..266f51e 100644
--- a/payment-adapter-test/payment-adapter-test-e2e/pom.xml
+++ b/payment-adapter-test/payment-adapter-test-e2e/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter-test
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-test/payment-adapter-test-integration/pom.xml b/payment-adapter-test/payment-adapter-test-integration/pom.xml
index ce54535..47a5808 100644
--- a/payment-adapter-test/payment-adapter-test-integration/pom.xml
+++ b/payment-adapter-test/payment-adapter-test-integration/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter-test
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-test/pom.xml b/payment-adapter-test/pom.xml
index 4118434..ba5affe 100644
--- a/payment-adapter-test/pom.xml
+++ b/payment-adapter-test/pom.xml
@@ -5,7 +5,7 @@
xyz.tcheeric
payment-adapter
- 0.12.0
+ 0.13.0
../pom.xml
diff --git a/payment-adapter-webhook/pom.xml b/payment-adapter-webhook/pom.xml
index 5a4ba6e..29aee24 100644
--- a/payment-adapter-webhook/pom.xml
+++ b/payment-adapter-webhook/pom.xml
@@ -5,12 +5,12 @@
xyz.tcheeric
payment-adapter
- 0.12.0
+ 0.13.0
xyz.tcheeric
payment-adapter-webhook
- 0.12.0
+ 0.13.0
payment-adapter-webhook
jar
diff --git a/pom.xml b/pom.xml
index aee792c..f1b90e8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
xyz.tcheeric
payment-adapter
- 0.12.0
+ 0.13.0
pom
payment-adapter