diff --git a/cashu-gateway-client/pom.xml b/cashu-gateway-client/pom.xml
index 9ba0075..4bca1b8 100644
--- a/cashu-gateway-client/pom.xml
+++ b/cashu-gateway-client/pom.xml
@@ -4,7 +4,7 @@
xyz.tcheeric
cashu-gateway
- 0.3.0
+ 0.3.1
cashu-gateway-client
diff --git a/cashu-gateway-common/pom.xml b/cashu-gateway-common/pom.xml
index 2c83ffd..1b0f596 100644
--- a/cashu-gateway-common/pom.xml
+++ b/cashu-gateway-common/pom.xml
@@ -4,7 +4,7 @@
xyz.tcheeric
cashu-gateway
- 0.3.0
+ 0.3.1
cashu-gateway-common
diff --git a/cashu-gateway-dummy/pom.xml b/cashu-gateway-dummy/pom.xml
index 59a764b..809196f 100644
--- a/cashu-gateway-dummy/pom.xml
+++ b/cashu-gateway-dummy/pom.xml
@@ -4,7 +4,7 @@
xyz.tcheeric
cashu-gateway
- 0.3.0
+ 0.3.1
cashu-gateway-dummy
diff --git a/cashu-gateway-dummy/src/test/java/xyz/tcheeric/gateway/dummy/DummyGatewayTest.java b/cashu-gateway-dummy/src/test/java/xyz/tcheeric/gateway/dummy/DummyGatewayTest.java
index c018cea..ea81d67 100644
--- a/cashu-gateway-dummy/src/test/java/xyz/tcheeric/gateway/dummy/DummyGatewayTest.java
+++ b/cashu-gateway-dummy/src/test/java/xyz/tcheeric/gateway/dummy/DummyGatewayTest.java
@@ -29,4 +29,45 @@ void testGatewayMethodsReturnNonNull() {
assertNotNull(payment);
assertNotNull(preimage);
}
+
+ /**
+ * Ensures paying with the exact quoteId returned by createMeltQuote produces a preimage
+ * and that the stored preimage for that quoteId matches what pay() returned (consistency).
+ */
+ @Test
+ void testQuoteIdConsistencyOnPay() {
+ DummyGateway gateway = new DummyGateway();
+
+ String invoice = "lnbc1-test-invoice";
+ String quoteId = gateway.createMeltQuote(5, invoice, "consistency");
+
+ String preimage = gateway.pay(quoteId);
+ String storedPreimage = gateway.getPaymentPreimage(quoteId);
+
+ assertNotNull(preimage);
+ assertEquals(preimage, storedPreimage);
+ }
+
+ /**
+ * Verifies that attempting to pay with an unknown/stale quoteId is rejected.
+ */
+ @Test
+ void testPayWithUnknownQuoteIdThrows() {
+ DummyGateway gateway = new DummyGateway();
+ org.junit.jupiter.api.Assertions.assertThrows(IllegalArgumentException.class,
+ () -> gateway.pay("unknown-quote-id"));
+ }
+
+ /**
+ * Confirms that getRequest returns the same invoice that was provided when creating the quote.
+ */
+ @Test
+ void testGetRequestMatchesCreatedInvoice() {
+ DummyGateway gateway = new DummyGateway();
+ String invoice = "lnbc1-sample-invoice";
+ String quoteId = gateway.createMeltQuote(7, invoice, "match-invoice");
+
+ String fetched = gateway.getRequest(quoteId);
+ assertEquals(invoice, fetched);
+ }
}
diff --git a/cashu-gateway-model/pom.xml b/cashu-gateway-model/pom.xml
index ca6cc99..fd371a7 100644
--- a/cashu-gateway-model/pom.xml
+++ b/cashu-gateway-model/pom.xml
@@ -5,10 +5,10 @@
xyz.tcheeric
cashu-gateway
- 0.3.0
+ 0.3.1
cashu-gateway-model
- 0.3.0
+ 0.3.1
cashu-gateway-model
Demo project for Spring Boot
diff --git a/cashu-gateway-phoenixd/pom.xml b/cashu-gateway-phoenixd/pom.xml
index 59baf14..20394e9 100644
--- a/cashu-gateway-phoenixd/pom.xml
+++ b/cashu-gateway-phoenixd/pom.xml
@@ -4,7 +4,7 @@
xyz.tcheeric
cashu-gateway
- 0.3.0
+ 0.3.1
cashu-gateway-phoenixd
diff --git a/cashu-gateway-phoenixd/src/main/java/xyz/tcheeric/gateway/phoenixd/PhoenixdGateway.java b/cashu-gateway-phoenixd/src/main/java/xyz/tcheeric/gateway/phoenixd/PhoenixdGateway.java
index 5b03240..a0ea713 100644
--- a/cashu-gateway-phoenixd/src/main/java/xyz/tcheeric/gateway/phoenixd/PhoenixdGateway.java
+++ b/cashu-gateway-phoenixd/src/main/java/xyz/tcheeric/gateway/phoenixd/PhoenixdGateway.java
@@ -198,6 +198,12 @@ public String pay(String quoteId) {
QuoteClient quoteClient = new QuoteClient();
GatewayQuote quote = quoteClient.getByEntityId(quoteId);
+ if (quote == null) {
+ throw new IllegalStateException("Unknown quoteId: " + quoteId);
+ }
+ if (!quoteId.equals(quote.getQuoteId())) {
+ throw new IllegalStateException("Mismatched quoteId: requested=" + quoteId + ", stored=" + quote.getQuoteId());
+ }
String request = quote.getRequest();
if (request == null) {
diff --git a/cashu-gateway-phoenixd/src/test/java/xyz/tcheeric/gateway/phoenixd/PhoenixdGatewayTest.java b/cashu-gateway-phoenixd/src/test/java/xyz/tcheeric/gateway/phoenixd/PhoenixdGatewayTest.java
index 2713178..758aa4e 100644
--- a/cashu-gateway-phoenixd/src/test/java/xyz/tcheeric/gateway/phoenixd/PhoenixdGatewayTest.java
+++ b/cashu-gateway-phoenixd/src/test/java/xyz/tcheeric/gateway/phoenixd/PhoenixdGatewayTest.java
@@ -134,6 +134,67 @@ public void testPayBoltInvoice() throws Exception {
}
}
+ // ensures the created payment carries the exact quoteId used to initiate payment (no stale/mismatched quoteId)
+ @Test
+ public void testQuoteIdConsistencyOnPay() throws Exception {
+ PayBolt11InvoiceInvoiceResponse payResp = new PayBolt11InvoiceInvoiceResponse();
+ payResp.setPaymentId("pid2");
+ payResp.setPaymentPreimage("pre2");
+ payResp.setPaymentHash("hash2");
+ payResp.setRecipientAmountSat(5);
+ payResp.setRoutingFeeSat(1);
+
+ GatewayQuote[] savedQuote = new GatewayQuote[1];
+ GatewayPayment[] savedPayment = new GatewayPayment[1];
+ when(service.payBolt11Invoice(any())).thenReturn(payResp);
+ try (
+ MockedConstruction quotes = mockConstruction(QuoteClient.class,
+ (mock, context) -> {
+ when(mock.create(any(GatewayQuote.class))).thenAnswer(inv -> {
+ GatewayQuote q = inv.getArgument(0);
+ savedQuote[0] = q;
+ return q;
+ });
+ when(mock.getByEntityId(anyString())).thenAnswer(inv -> savedQuote[0]);
+ });
+ MockedConstruction payments = mockConstruction(PaymentClient.class,
+ (mock, context) -> {
+ when(mock.create(any(GatewayPayment.class))).thenAnswer(inv -> {
+ GatewayPayment p = inv.getArgument(0);
+ savedPayment[0] = p;
+ return p;
+ });
+ })
+ ) {
+ String quoteId = gateway.createMeltQuote(5, "lnbc1consistency", "consistency");
+ gateway.pay(quoteId);
+
+ Assertions.assertNotNull(savedPayment[0]);
+ Assertions.assertEquals(quoteId, savedPayment[0].getQuoteId());
+ }
+ }
+
+ // verifies paying with an unknown/stale quoteId is rejected with a clear error
+ @Test
+ public void testPayWithUnknownQuoteIdThrows() {
+ PayBolt11InvoiceInvoiceResponse payResp = new PayBolt11InvoiceInvoiceResponse();
+ payResp.setPaymentId("pid");
+ payResp.setRecipientAmountSat(1);
+ payResp.setRoutingFeeSat(0);
+ // when(service.payBolt11Invoice(any())).thenReturn(payResp);
+
+ try (
+ MockedConstruction quotes = mockConstruction(QuoteClient.class,
+ (mock, context) -> {
+ // Simulate repository returning null for unknown quoteId
+ when(mock.getByEntityId(anyString())).thenReturn(null);
+ });
+ MockedConstruction ignored = mockConstruction(PaymentClient.class)
+ ) {
+ Assertions.assertThrows(IllegalStateException.class, () -> gateway.pay("stale-or-unknown-quote"));
+ }
+ }
+
// verifies paying a lightning address invoice results in a paid payment record
@Test
public void testPayLnInvoice() throws Exception {
diff --git a/cashu-gateway-rest/pom.xml b/cashu-gateway-rest/pom.xml
index ab46268..25114b3 100644
--- a/cashu-gateway-rest/pom.xml
+++ b/cashu-gateway-rest/pom.xml
@@ -5,11 +5,11 @@
xyz.tcheeric
cashu-gateway
- 0.3.0
+ 0.3.1
xyz.tcheeric
cashu-gateway-rest
- 0.3.0
+ 0.3.1
cashu-gateway-rest
A simple JPA application to manage quotes and invoices
diff --git a/cashu-gateway-webhook/pom.xml b/cashu-gateway-webhook/pom.xml
index b64ea66..75cca40 100644
--- a/cashu-gateway-webhook/pom.xml
+++ b/cashu-gateway-webhook/pom.xml
@@ -5,12 +5,12 @@
xyz.tcheeric
cashu-gateway
- 0.3.0
+ 0.3.1
xyz.tcheeric
cashu-gateway-webhook
- 0.3.0
+ 0.3.1
cashu-gateway-webhook
war
diff --git a/docs/reference/changelog.md b/docs/reference/changelog.md
index 8658f9a..63d8be5 100644
--- a/docs/reference/changelog.md
+++ b/docs/reference/changelog.md
@@ -2,6 +2,11 @@
This document summarizes notable changes to the cashu-gateway project. Versions follow semantic versioning when possible.
+## 0.3.1
+
+- Phoenixd: reject unknown or mismatched quoteId during pay(), ensuring the wallet’s POST /mint/bolt11 quoteId matches the mint’s generated quote from POST /mint/quote/bolt11.
+- Tests: add unit tests for quoteId consistency and unknown/stale IDs in both Phoenixd and Dummy gateways.
+
## 0.3.0
- REST: enable POST create for Quote and Payment via Spring Data REST and expose IDs.
diff --git a/pom.xml b/pom.xml
index e5197c7..5f24a8a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
xyz.tcheeric
cashu-gateway
- 0.3.0
+ 0.3.1
pom
cashu-gateway