mirror of
https://github.com/bitwarden/server.git
synced 2025-02-02 23:41:21 +01:00
bitpay ipn for processing credits
This commit is contained in:
parent
c0e9d95538
commit
4c84eeca5b
@ -1,11 +1,16 @@
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Billing.Models;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Transactions;
|
||||
|
||||
namespace Bit.Billing.Controllers
|
||||
{
|
||||
@ -39,30 +44,137 @@ namespace Bit.Billing.Controllers
|
||||
}
|
||||
|
||||
[HttpPost("ipn")]
|
||||
public async Task<IActionResult> PostIpn([FromQuery] string key)
|
||||
public async Task<IActionResult> PostIpn([FromBody] BitPayEventModel model, [FromQuery] string key)
|
||||
{
|
||||
if(key != _billingSettings.BitPayWebhookKey)
|
||||
{
|
||||
return new BadRequestResult();
|
||||
}
|
||||
|
||||
if(HttpContext?.Request == null)
|
||||
if(model == null || string.IsNullOrWhiteSpace(model.Data?.Id) ||
|
||||
string.IsNullOrWhiteSpace(model.Event?.Name))
|
||||
{
|
||||
return new BadRequestResult();
|
||||
}
|
||||
|
||||
string body = null;
|
||||
using(var reader = new StreamReader(HttpContext.Request.Body, Encoding.UTF8))
|
||||
if(model.Event.Name != "invoice_confirmed")
|
||||
{
|
||||
body = await reader.ReadToEndAsync();
|
||||
// Only processing confirmed invoice events for now.
|
||||
return new OkResult();
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(body))
|
||||
var invoice = await _bitPayClient.GetInvoiceAsync(model.Data.Id);
|
||||
if(invoice == null || invoice.Status != "confirmed")
|
||||
{
|
||||
// Request forged...?
|
||||
return new BadRequestResult();
|
||||
}
|
||||
|
||||
if(invoice.Currency != "USD")
|
||||
{
|
||||
// Only process USD payments
|
||||
return new OkResult();
|
||||
}
|
||||
|
||||
var ids = GetIdsFromPosData(invoice);
|
||||
if(!ids.Item1.HasValue && !ids.Item2.HasValue)
|
||||
{
|
||||
return new OkResult();
|
||||
}
|
||||
|
||||
var isAccountCredit = IsAccountCredit(invoice);
|
||||
if(!isAccountCredit)
|
||||
{
|
||||
// Only processing credits
|
||||
return new OkResult();
|
||||
}
|
||||
|
||||
var transaction = await _transactionRepository.GetByGatewayIdAsync(GatewayType.BitPay, invoice.Id);
|
||||
if(transaction != null)
|
||||
{
|
||||
return new OkResult();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var tx = new Core.Models.Table.Transaction
|
||||
{
|
||||
Amount = invoice.Price,
|
||||
CreationDate = invoice.CurrentTime.Date,
|
||||
OrganizationId = ids.Item1,
|
||||
UserId = ids.Item2,
|
||||
Type = TransactionType.Charge,
|
||||
Gateway = GatewayType.BitPay,
|
||||
GatewayId = invoice.Id,
|
||||
PaymentMethodType = PaymentMethodType.BitPay,
|
||||
Details = invoice.Id
|
||||
};
|
||||
await _transactionRepository.CreateAsync(tx);
|
||||
|
||||
if(isAccountCredit)
|
||||
{
|
||||
if(tx.OrganizationId.HasValue)
|
||||
{
|
||||
var org = await _organizationRepository.GetByIdAsync(tx.OrganizationId.Value);
|
||||
if(org != null)
|
||||
{
|
||||
if(await _paymentService.CreditAccountAsync(org, tx.Amount))
|
||||
{
|
||||
await _organizationRepository.ReplaceAsync(org);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var user = await _userRepository.GetByIdAsync(tx.UserId.Value);
|
||||
if(user != null)
|
||||
{
|
||||
if(await _paymentService.CreditAccountAsync(user, tx.Amount))
|
||||
{
|
||||
await _userRepository.ReplaceAsync(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Send email about credit added?
|
||||
}
|
||||
}
|
||||
// Catch foreign key violations because user/org could have been deleted.
|
||||
catch(SqlException e) when(e.Number == 547) { }
|
||||
|
||||
return new OkResult();
|
||||
}
|
||||
|
||||
private bool IsAccountCredit(NBitpayClient.Invoice invoice)
|
||||
{
|
||||
return invoice != null && invoice.PosData != null && invoice.PosData.Contains("accountCredit:1");
|
||||
}
|
||||
|
||||
public Tuple<Guid?, Guid?> GetIdsFromPosData(NBitpayClient.Invoice invoice)
|
||||
{
|
||||
Guid? orgId = null;
|
||||
Guid? userId = null;
|
||||
|
||||
if(invoice != null && !string.IsNullOrWhiteSpace(invoice.PosData) && invoice.PosData.Contains(":"))
|
||||
{
|
||||
var mainParts = invoice.PosData.Split(',');
|
||||
foreach(var mainPart in mainParts)
|
||||
{
|
||||
var parts = mainPart.Split(':');
|
||||
if(parts.Length > 1 && Guid.TryParse(parts[1], out var id))
|
||||
{
|
||||
if(parts[0] == "userId")
|
||||
{
|
||||
userId = id;
|
||||
}
|
||||
else if(parts[0] == "organizationId")
|
||||
{
|
||||
orgId = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Tuple<Guid?, Guid?>(orgId, userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -206,7 +206,8 @@ namespace Bit.Billing.Controllers
|
||||
|
||||
if(ipnTransaction.McCurrency != "USD")
|
||||
{
|
||||
return new BadRequestResult();
|
||||
// Only process USD payments
|
||||
return new OkResult();
|
||||
}
|
||||
|
||||
var ids = ipnTransaction.GetIdsFromCustom();
|
||||
|
28
src/Billing/Models/BitPayEventModel.cs
Normal file
28
src/Billing/Models/BitPayEventModel.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace Bit.Billing.Models
|
||||
{
|
||||
public class BitPayEventModel
|
||||
{
|
||||
public EventModel Event { get; set; }
|
||||
public InvoiceDataModel Data { get; set; }
|
||||
|
||||
public class EventModel
|
||||
{
|
||||
public int Code { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class InvoiceDataModel
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public string PosData { get; set; }
|
||||
public bool ExceptionStatus { get; set; }
|
||||
public long CurrentTime { get; set; }
|
||||
public long AmountPaid { get; set; }
|
||||
public string TransactionCurrency { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -12,8 +12,8 @@ namespace Bit.Core.Enums
|
||||
AppStore = 2,
|
||||
[Display(Name = "Google Play Store")]
|
||||
PlayStore = 3,
|
||||
[Display(Name = "Coinbase")]
|
||||
Coinbase = 4,
|
||||
[Display(Name = "BitPay")]
|
||||
BitPay = 4,
|
||||
[Display(Name = "PayPal")]
|
||||
PayPal = 5,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user