1
0
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:
Kyle Spearrin 2019-02-22 08:49:11 -05:00
parent c0e9d95538
commit 4c84eeca5b
4 changed files with 152 additions and 11 deletions

View File

@ -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);
}
}
}

View File

@ -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();

View 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; }
}
}
}

View File

@ -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,
}