1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-15 10:08:14 -05:00

change payment API

This commit is contained in:
Kyle Spearrin 2017-04-08 16:41:40 -04:00
parent 6467cafde3
commit 18d2715c71
6 changed files with 87 additions and 8 deletions

View File

@ -115,6 +115,19 @@ namespace Bit.Api.Controllers
return new OrganizationResponseModel(organization);
}
[HttpPut("{id}/payment")]
[HttpPost("{id}/payment")]
public async Task PutPayment(string id, [FromBody]OrganizationPaymentRequestModel model)
{
var orgIdGuid = new Guid(id);
if(!_currentContext.OrganizationOwner(orgIdGuid))
{
throw new NotFoundException();
}
await _organizationService.ReplacePaymentMethodAsync(orgIdGuid, model.PaymentToken);
}
[HttpDelete("{id}")]
[HttpPost("{id}/delete")]
public async Task Delete(string id)

View File

@ -19,7 +19,7 @@ namespace Bit.Core.Models.Api
public PlanType PlanType { get; set; }
[Required]
public string Key { get; set; }
public string CardToken { get; set; }
public string PaymentToken { get; set; }
[Range(0, double.MaxValue)]
public short AdditionalUsers { get; set; }
public bool Monthly { get; set; }
@ -32,7 +32,7 @@ namespace Bit.Core.Models.Api
OwnerKey = Key,
Name = Name,
Plan = PlanType,
PaymentToken = CardToken,
PaymentToken = PaymentToken,
AdditionalUsers = AdditionalUsers,
BillingEmail = BillingEmail,
BusinessName = BusinessName,
@ -42,9 +42,9 @@ namespace Bit.Core.Models.Api
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(PlanType != PlanType.Free && string.IsNullOrWhiteSpace(CardToken))
if(PlanType != PlanType.Free && string.IsNullOrWhiteSpace(PaymentToken))
{
yield return new ValidationResult("Card required.", new string[] { nameof(CardToken) });
yield return new ValidationResult("Payment required.", new string[] { nameof(PaymentToken) });
}
}
}

View File

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
namespace Bit.Core.Models.Api
{
public class OrganizationPaymentRequestModel
{
[Required]
public string PaymentToken { get; set; }
}
}

View File

@ -58,7 +58,8 @@ namespace Bit.Core.Models.Api
switch(source.Type)
{
case SourceType.Card:
Description = $"{source.Card.Brand}, *{source.Card.Last4}";
Description = $"{source.Card.Brand}, *{source.Card.Last4}, " +
$"{source.Card.ExpirationMonth}/{source.Card.ExpirationYear}";
CardBrand = source.Card.Brand;
break;
case SourceType.BankAccount:

View File

@ -9,6 +9,7 @@ namespace Bit.Core.Services
public interface IOrganizationService
{
Task<OrganizationBilling> GetBillingAsync(Organization organization);
Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken);
Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup organizationSignup);
Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid invitingUserId, string email,
Enums.OrganizationUserType type, IEnumerable<SubvaultUser> subvaults);

View File

@ -51,7 +51,19 @@ namespace Bit.Core.Services
var customer = await customerService.GetAsync(organization.StripeCustomerId);
if(customer != null)
{
orgBilling.PaymentSource = customer.DefaultSource;
if(!string.IsNullOrWhiteSpace(customer.DefaultSourceId) && customer.Sources?.Data != null)
{
if(customer.DefaultSourceId.StartsWith("card_"))
{
orgBilling.PaymentSource =
customer.Sources.Data.FirstOrDefault(s => s.Card?.Id == customer.DefaultSourceId);
}
else if(customer.DefaultSourceId.StartsWith("ba_"))
{
orgBilling.PaymentSource =
customer.Sources.Data.FirstOrDefault(s => s.BankAccount?.Id == customer.DefaultSourceId);
}
}
var charges = await chargeService.ListAsync(new StripeChargeListOptions
{
@ -74,6 +86,47 @@ namespace Bit.Core.Services
return orgBilling;
}
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken)
{
var organization = await _organizationRepository.GetByIdAsync(organizationId);
if(organization == null)
{
throw new NotFoundException();
}
var cardService = new StripeCardService();
var customerService = new StripeCustomerService();
StripeCustomer customer = null;
if(!string.IsNullOrWhiteSpace(organization.StripeCustomerId))
{
customer = await customerService.GetAsync(organization.StripeCustomerId);
}
if(customer == null)
{
customer = await customerService.CreateAsync(new StripeCustomerCreateOptions
{
Description = organization.BusinessName,
Email = organization.BillingEmail,
SourceToken = paymentToken
});
organization.StripeCustomerId = customer.Id;
await _organizationRepository.ReplaceAsync(organization);
}
await cardService.CreateAsync(customer.Id, new StripeCardCreateOptions
{
SourceToken = paymentToken
});
if(!string.IsNullOrWhiteSpace(customer.DefaultSourceId))
{
await cardService.DeleteAsync(customer.Id, customer.DefaultSourceId);
}
}
public async Task<Tuple<Organization, OrganizationUser>> SignUpAsync(OrganizationSignup signup)
{
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == signup.Plan && !p.Disabled);
@ -87,7 +140,8 @@ namespace Bit.Core.Services
StripeCustomer customer = null;
StripeSubscription subscription = null;
if(signup.AdditionalUsers > plan.MaxAdditionalUsers.GetValueOrDefault(0))
if(plan.CanBuyAdditionalUsers && plan.MaxAdditionalUsers.HasValue &&
signup.AdditionalUsers > plan.MaxAdditionalUsers.Value)
{
throw new BadRequestException($"Selected plan allows a maximum of " +
$"{plan.MaxAdditionalUsers.GetValueOrDefault(0)} additional users.");
@ -143,7 +197,7 @@ namespace Bit.Core.Services
PlanType = plan.Type,
MaxUsers = (short)(plan.BaseUsers + (plan.CanBuyAdditionalUsers ? signup.AdditionalUsers : 0)),
MaxSubvaults = plan.MaxSubvaults,
Plan = plan.ToString(),
Plan = plan.Name,
StripeCustomerId = customer?.Id,
StripeSubscriptionId = subscription?.Id,
CreationDate = DateTime.UtcNow,