Skip to content

Commit 6dd984f

Browse files
authored
Merge pull request #37 from thgO-O/feat/depix-p2p-mode
[Feature] Add DePix P2P mode with dedicated seller commission
2 parents 6ad5471 + 02bda52 commit 6dd984f

15 files changed

Lines changed: 1469 additions & 144 deletions

BTCPayServer.Plugins.Depix.Tests/DepixWebhookServiceTests.cs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,61 @@ public async Task DuplicateWebhookWithMismatchedAmountDoesNotChangeStoredAmount(
184184
Assert.Equal(1234, promptDetails.ValueInCents);
185185
}
186186

187+
[Fact(Timeout = TestUtils.TestTimeout)]
188+
public async Task CreatedP2PInvoiceIsRestrictedToPixPaymentPrompt()
189+
{
190+
var storeId = await CreateStoreAsync();
191+
var handlers = _server.PayTester.GetService<PaymentMethodHandlerDictionary>();
192+
var pixHandler = handlers[PixPmid];
193+
var invoiceRepository = _server.PayTester.InvoiceRepository;
194+
var storeRepository = _server.PayTester.GetService<StoreRepository>();
195+
var store = await storeRepository.FindStore(storeId) ?? throw new InvalidOperationException("Store not found.");
196+
var invoice = invoiceRepository.CreateNewInvoice(storeId);
197+
invoice.Currency = "BRL";
198+
invoice.Price = 12.34m;
199+
invoice.AddRate(new CurrencyPair("BRL", "BRL"), 1m);
200+
invoice.SetPaymentPrompts(new PaymentPromptDictionary([
201+
new PaymentPrompt
202+
{
203+
PaymentMethodId = PixPmid,
204+
Currency = "BRL",
205+
Divisibility = 2,
206+
Destination = "https://example.invalid/pix.png",
207+
Details = JToken.FromObject(new DePixPaymentMethodDetails
208+
{
209+
QrId = "qr-p2p-restricted",
210+
P2PMode = true,
211+
ValueInCents = 1234
212+
}, pixHandler.Serializer)
213+
},
214+
new PaymentPrompt
215+
{
216+
PaymentMethodId = PaymentTypes.CHAIN.GetPaymentMethodId("BTC"),
217+
Currency = "BRL",
218+
Divisibility = 2,
219+
Destination = "bitcoin-address",
220+
Details = new JObject()
221+
}
222+
]));
223+
224+
await invoiceRepository.CreateInvoiceAsync(new InvoiceCreationContext(
225+
store,
226+
store.GetStoreBlob(),
227+
invoice,
228+
new InvoiceLogs(),
229+
handlers,
230+
invoicePaymentMethodFilter: null));
231+
232+
_server.PayTester.GetService<EventAggregator>().Publish(new InvoiceEvent(invoice, InvoiceEvent.Created));
233+
234+
await TestUtils.EventuallyAsync(async () =>
235+
{
236+
var storedInvoice = await invoiceRepository.GetInvoice(invoice.Id);
237+
var prompt = Assert.Single(storedInvoice.GetPaymentPrompts());
238+
Assert.Equal(PixPmid, prompt.PaymentMethodId);
239+
});
240+
}
241+
187242
[Fact(Timeout = TestUtils.TestTimeout)]
188243
public async Task FirstConfirmedWebhookWithMismatchedAmountUsesPromptAmount()
189244
{
@@ -211,6 +266,61 @@ public async Task FirstConfirmedWebhookWithMismatchedAmountUsesPromptAmount()
211266
Assert.Equal(1234, promptDetails.ValueInCents);
212267
}
213268

269+
[Fact(Timeout = TestUtils.TestTimeout)]
270+
public async Task ConfigurePromptReturnsUnavailableWhenP2PCommissionAddressCannotBeGenerated()
271+
{
272+
var storeId = await CreateStoreAsync();
273+
var storeRepository = _server.PayTester.GetService<StoreRepository>();
274+
var handlers = _server.PayTester.GetService<PaymentMethodHandlerDictionary>();
275+
var handler = handlers[PixPmid];
276+
var protector = _server.PayTester.GetService<ISecretProtector>();
277+
var store = await storeRepository.FindStore(storeId) ?? throw new InvalidOperationException("Store not found.");
278+
279+
store.SetPaymentMethodConfig(handler, new PixPaymentMethodConfig
280+
{
281+
EncryptedApiKey = protector.Protect("fixture-api-key"),
282+
WebhookSecretHashHex = BTCPayServer.Plugins.Depix.Services.Utils.ComputeSecretHash(
283+
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"),
284+
IsEnabled = true,
285+
P2PMode = true,
286+
P2PCommissionPercent = "5%"
287+
});
288+
var storeBlob = store.GetStoreBlob();
289+
storeBlob.SetExcluded(PixPmid, false);
290+
store.SetStoreBlob(storeBlob);
291+
await storeRepository.UpdateStore(store);
292+
293+
var invoice = _server.PayTester.InvoiceRepository.CreateNewInvoice(storeId);
294+
invoice.Currency = "BRL";
295+
invoice.Price = 12.34m;
296+
invoice.AddRate(new CurrencyPair("BRL", "BRL"), 1m);
297+
#pragma warning disable CS0618
298+
invoice.Payments = [];
299+
#pragma warning restore CS0618
300+
invoice.UpdateTotals();
301+
invoice.Metadata = new InvoiceMetadata
302+
{
303+
AdditionalData = new Dictionary<string, JToken>
304+
{
305+
["depixAddress"] = "buyer-depix-address"
306+
}
307+
};
308+
var context = new PaymentMethodContext(
309+
store,
310+
store.GetStoreBlob(),
311+
new JObject(),
312+
handler,
313+
invoice,
314+
new InvoiceLogs());
315+
context.Prompt.ParentEntity = invoice;
316+
context.Prompt.PaymentMethodId = PixPmid;
317+
context.Prompt.Currency = "BRL";
318+
context.Prompt.Divisibility = 2;
319+
320+
var ex = await Assert.ThrowsAsync<PaymentMethodUnavailableException>(() => handler.ConfigurePrompt(context));
321+
Assert.Contains("DePix", ex.Message, StringComparison.OrdinalIgnoreCase);
322+
}
323+
214324
private async Task<string> CreateStoreAsync()
215325
{
216326
var account = _server.NewAccount();

0 commit comments

Comments
 (0)