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: 1 addition & 1 deletion cashu-gateway-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>cashu-gateway</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
</parent>

<artifactId>cashu-gateway-client</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion cashu-gateway-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>cashu-gateway</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
</parent>

<artifactId>cashu-gateway-common</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion cashu-gateway-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>cashu-gateway</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
</parent>

<artifactId>cashu-gateway-dummy</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
4 changes: 2 additions & 2 deletions cashu-gateway-model/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>cashu-gateway</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
</parent>
<artifactId>cashu-gateway-model</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
<name>cashu-gateway-model</name>
<description>Demo project for Spring Boot</description>
<url/>
Expand Down
2 changes: 1 addition & 1 deletion cashu-gateway-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>cashu-gateway</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
</parent>

<artifactId>cashu-gateway-phoenixd</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<QuoteClient> 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<PaymentClient> 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<QuoteClient> quotes = mockConstruction(QuoteClient.class,
(mock, context) -> {
// Simulate repository returning null for unknown quoteId
when(mock.getByEntityId(anyString())).thenReturn(null);
});
MockedConstruction<PaymentClient> 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 {
Expand Down
4 changes: 2 additions & 2 deletions cashu-gateway-rest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
<parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>cashu-gateway</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
</parent>
<groupId>xyz.tcheeric</groupId>
<artifactId>cashu-gateway-rest</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
<name>cashu-gateway-rest</name>
<description>A simple JPA application to manage quotes and invoices</description>
<url/>
Expand Down
4 changes: 2 additions & 2 deletions cashu-gateway-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>cashu-gateway</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
</parent>

<groupId>xyz.tcheeric</groupId>
<artifactId>cashu-gateway-webhook</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
<name>cashu-gateway-webhook</name>
<packaging>war</packaging>

Expand Down
5 changes: 5 additions & 0 deletions docs/reference/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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>cashu-gateway</artifactId>
<version>0.3.0</version>
<version>0.3.1</version>
<packaging>pom</packaging>

<name>cashu-gateway</name>
Expand Down
Loading