mirror of
https://github.com/bitwarden/server.git
synced 2025-05-23 12:31:06 -05:00
payment service support for iap
This commit is contained in:
parent
eee4dd9877
commit
ff5a0ff0ce
@ -19,7 +19,7 @@ namespace Bit.Core.Services
|
|||||||
Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false);
|
Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false);
|
||||||
Task ReinstateSubscriptionAsync(ISubscriber subscriber);
|
Task ReinstateSubscriptionAsync(ISubscriber subscriber);
|
||||||
Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType,
|
Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType,
|
||||||
string paymentToken);
|
string paymentToken, bool allowInAppPurchases = false);
|
||||||
Task<bool> CreditAccountAsync(ISubscriber subscriber, decimal creditAmount);
|
Task<bool> CreditAccountAsync(ISubscriber subscriber, decimal creditAmount);
|
||||||
Task<BillingInfo> GetBillingAsync(ISubscriber subscriber);
|
Task<BillingInfo> GetBillingAsync(ISubscriber subscriber);
|
||||||
Task<SubscriptionInfo> GetSubscriptionAsync(ISubscriber subscriber);
|
Task<SubscriptionInfo> GetSubscriptionAsync(ISubscriber subscriber);
|
||||||
|
@ -9,21 +9,28 @@ using Bit.Core.Models.Business;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Bit.Billing.Models;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
public class StripePaymentService : IPaymentService
|
public class StripePaymentService : IPaymentService
|
||||||
{
|
{
|
||||||
private const string PremiumPlanId = "premium-annually";
|
private const string PremiumPlanId = "premium-annually";
|
||||||
|
private const string PremiumPlanAppleIapId = "premium-annually-appleiap";
|
||||||
|
private const decimal PremiumPlanAppleIapPrice = 14.99M;
|
||||||
private const string StoragePlanId = "storage-gb-annually";
|
private const string StoragePlanId = "storage-gb-annually";
|
||||||
|
|
||||||
private readonly ITransactionRepository _transactionRepository;
|
private readonly ITransactionRepository _transactionRepository;
|
||||||
|
private readonly IUserRepository _userRepository;
|
||||||
|
private readonly IAppleIapService _appleIapService;
|
||||||
private readonly ILogger<StripePaymentService> _logger;
|
private readonly ILogger<StripePaymentService> _logger;
|
||||||
private readonly Braintree.BraintreeGateway _btGateway;
|
private readonly Braintree.BraintreeGateway _btGateway;
|
||||||
|
|
||||||
public StripePaymentService(
|
public StripePaymentService(
|
||||||
ITransactionRepository transactionRepository,
|
ITransactionRepository transactionRepository,
|
||||||
|
IUserRepository userRepository,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
|
IAppleIapService appleIapService,
|
||||||
ILogger<StripePaymentService> logger)
|
ILogger<StripePaymentService> logger)
|
||||||
{
|
{
|
||||||
_btGateway = new Braintree.BraintreeGateway
|
_btGateway = new Braintree.BraintreeGateway
|
||||||
@ -35,6 +42,8 @@ namespace Bit.Core.Services
|
|||||||
PrivateKey = globalSettings.Braintree.PrivateKey
|
PrivateKey = globalSettings.Braintree.PrivateKey
|
||||||
};
|
};
|
||||||
_transactionRepository = transactionRepository;
|
_transactionRepository = transactionRepository;
|
||||||
|
_userRepository = userRepository;
|
||||||
|
_appleIapService = appleIapService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,9 +338,14 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
throw new BadRequestException("Your account does not have any credit available.");
|
throw new BadRequestException("Your account does not have any credit available.");
|
||||||
}
|
}
|
||||||
if(paymentMethodType == PaymentMethodType.BankAccount)
|
if(paymentMethodType == PaymentMethodType.BankAccount || paymentMethodType == PaymentMethodType.GoogleInApp)
|
||||||
{
|
{
|
||||||
throw new GatewayException("Bank account payment method is not supported at this time.");
|
throw new GatewayException("Payment method is not supported at this time.");
|
||||||
|
}
|
||||||
|
if((paymentMethodType == PaymentMethodType.GoogleInApp ||
|
||||||
|
paymentMethodType == PaymentMethodType.AppleInApp) && additionalStorageGb > 0)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You cannot add storage with this payment method.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var customerService = new CustomerService();
|
var customerService = new CustomerService();
|
||||||
@ -361,7 +375,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken);
|
await UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken, true);
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
@ -397,6 +411,18 @@ namespace Bit.Core.Services
|
|||||||
braintreeCustomer = customerResult.Target;
|
braintreeCustomer = customerResult.Target;
|
||||||
stripeCustomerMetadata.Add("btCustomerId", braintreeCustomer.Id);
|
stripeCustomerMetadata.Add("btCustomerId", braintreeCustomer.Id);
|
||||||
}
|
}
|
||||||
|
else if(paymentMethodType == PaymentMethodType.AppleInApp)
|
||||||
|
{
|
||||||
|
var verifiedReceiptStatus = await _appleIapService.GetVerifiedReceiptStatusAsync(paymentToken);
|
||||||
|
if(verifiedReceiptStatus == null)
|
||||||
|
{
|
||||||
|
throw new GatewayException("Cannot verify apple in-app purchase.");
|
||||||
|
}
|
||||||
|
var receiptOriginalTransactionId = verifiedReceiptStatus.GetOriginalTransactionId();
|
||||||
|
await VerifyAppleReceiptNotInUseAsync(receiptOriginalTransactionId, user);
|
||||||
|
await _appleIapService.SaveReceiptAsync(verifiedReceiptStatus, user.Id);
|
||||||
|
stripeCustomerMetadata.Add("appleReceipt", receiptOriginalTransactionId);
|
||||||
|
}
|
||||||
else if(!stripePaymentMethod)
|
else if(!stripePaymentMethod)
|
||||||
{
|
{
|
||||||
throw new GatewayException("Payment method is not supported at this time.");
|
throw new GatewayException("Payment method is not supported at this time.");
|
||||||
@ -434,7 +460,7 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
subCreateOptions.Items.Add(new SubscriptionItemOption
|
subCreateOptions.Items.Add(new SubscriptionItemOption
|
||||||
{
|
{
|
||||||
PlanId = PremiumPlanId,
|
PlanId = paymentMethodType == PaymentMethodType.AppleInApp ? PremiumPlanAppleIapId : PremiumPlanId,
|
||||||
Quantity = 1,
|
Quantity = 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -473,6 +499,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
var addedCreditToStripeCustomer = false;
|
var addedCreditToStripeCustomer = false;
|
||||||
Braintree.Transaction braintreeTransaction = null;
|
Braintree.Transaction braintreeTransaction = null;
|
||||||
|
Transaction appleTransaction = null;
|
||||||
var invoiceService = new InvoiceService();
|
var invoiceService = new InvoiceService();
|
||||||
var customerService = new CustomerService();
|
var customerService = new CustomerService();
|
||||||
|
|
||||||
@ -490,9 +517,39 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
if(previewInvoice.AmountDue > 0)
|
if(previewInvoice.AmountDue > 0)
|
||||||
{
|
{
|
||||||
|
var appleReceiptOrigTransactionId = customer.Metadata != null &&
|
||||||
|
customer.Metadata.ContainsKey("appleReceipt") ? customer.Metadata["appleReceipt"] : null;
|
||||||
var braintreeCustomerId = customer.Metadata != null &&
|
var braintreeCustomerId = customer.Metadata != null &&
|
||||||
customer.Metadata.ContainsKey("btCustomerId") ? customer.Metadata["btCustomerId"] : null;
|
customer.Metadata.ContainsKey("btCustomerId") ? customer.Metadata["btCustomerId"] : null;
|
||||||
if(!string.IsNullOrWhiteSpace(braintreeCustomerId))
|
if(!string.IsNullOrWhiteSpace(appleReceiptOrigTransactionId))
|
||||||
|
{
|
||||||
|
if(!subcriber.IsUser())
|
||||||
|
{
|
||||||
|
throw new GatewayException("In-app purchase is only allowed for users.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var appleReceipt = await _appleIapService.GetReceiptAsync(
|
||||||
|
appleReceiptOrigTransactionId);
|
||||||
|
var verifiedAppleReceipt = await _appleIapService.GetVerifiedReceiptStatusAsync(
|
||||||
|
appleReceipt.Item1);
|
||||||
|
if(verifiedAppleReceipt == null)
|
||||||
|
{
|
||||||
|
throw new GatewayException("Failed to get Apple IAP receipt data.");
|
||||||
|
}
|
||||||
|
subInvoiceMetadata.Add("appleReceipt", verifiedAppleReceipt.GetOriginalTransactionId());
|
||||||
|
var lastTransactionId = verifiedAppleReceipt.GetLastTransactionId();
|
||||||
|
subInvoiceMetadata.Add("appleReceiptTransactionId", lastTransactionId);
|
||||||
|
var existingTransaction = await _transactionRepository.GetByGatewayIdAsync(
|
||||||
|
GatewayType.AppStore, lastTransactionId);
|
||||||
|
if(existingTransaction == null)
|
||||||
|
{
|
||||||
|
appleTransaction = verifiedAppleReceipt.BuildTransactionFromLastTransaction(
|
||||||
|
PremiumPlanAppleIapPrice, subcriber.Id);
|
||||||
|
appleTransaction.Type = TransactionType.Charge;
|
||||||
|
await _transactionRepository.CreateAsync(appleTransaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(!string.IsNullOrWhiteSpace(braintreeCustomerId))
|
||||||
{
|
{
|
||||||
var btInvoiceAmount = (previewInvoice.AmountDue / 100M);
|
var btInvoiceAmount = (previewInvoice.AmountDue / 100M);
|
||||||
var transactionResult = await _btGateway.Transaction.SaleAsync(
|
var transactionResult = await _btGateway.Transaction.SaleAsync(
|
||||||
@ -523,17 +580,17 @@ namespace Bit.Core.Services
|
|||||||
subInvoiceMetadata.Add("btTransactionId", braintreeTransaction.Id);
|
subInvoiceMetadata.Add("btTransactionId", braintreeTransaction.Id);
|
||||||
subInvoiceMetadata.Add("btPayPalTransactionId",
|
subInvoiceMetadata.Add("btPayPalTransactionId",
|
||||||
braintreeTransaction.PayPalDetails.AuthorizationId);
|
braintreeTransaction.PayPalDetails.AuthorizationId);
|
||||||
|
|
||||||
await customerService.UpdateAsync(customer.Id, new CustomerUpdateOptions
|
|
||||||
{
|
|
||||||
Balance = customer.Balance - previewInvoice.AmountDue
|
|
||||||
});
|
|
||||||
addedCreditToStripeCustomer = true;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new GatewayException("No payment was able to be collected.");
|
throw new GatewayException("No payment was able to be collected.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await customerService.UpdateAsync(customer.Id, new CustomerUpdateOptions
|
||||||
|
{
|
||||||
|
Balance = customer.Balance - previewInvoice.AmountDue
|
||||||
|
});
|
||||||
|
addedCreditToStripeCustomer = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(paymentMethodType == PaymentMethodType.Credit)
|
else if(paymentMethodType == PaymentMethodType.Credit)
|
||||||
@ -607,6 +664,10 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
await _btGateway.Customer.DeleteAsync(braintreeCustomer.Id);
|
await _btGateway.Customer.DeleteAsync(braintreeCustomer.Id);
|
||||||
}
|
}
|
||||||
|
if(appleTransaction != null)
|
||||||
|
{
|
||||||
|
await _transactionRepository.DeleteAsync(appleTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
if(e is StripeException strEx &&
|
if(e is StripeException strEx &&
|
||||||
(strEx.StripeError?.Message?.Contains("cannot be used because it is not verified") ?? false))
|
(strEx.StripeError?.Message?.Contains("cannot be used because it is not verified") ?? false))
|
||||||
@ -701,7 +762,10 @@ namespace Bit.Core.Services
|
|||||||
paymentIntentClientSecret = result.Item2;
|
paymentIntentClientSecret = result.Item2;
|
||||||
}
|
}
|
||||||
|
|
||||||
await subUpdateAction(!invoicedNow);
|
if(subUpdateAction != null)
|
||||||
|
{
|
||||||
|
await subUpdateAction(!invoicedNow);
|
||||||
|
}
|
||||||
return paymentIntentClientSecret;
|
return paymentIntentClientSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -767,6 +831,18 @@ namespace Bit.Core.Services
|
|||||||
public async Task<Tuple<bool, string>> PreviewUpcomingInvoiceAndPayAsync(ISubscriber subscriber, string planId,
|
public async Task<Tuple<bool, string>> PreviewUpcomingInvoiceAndPayAsync(ISubscriber subscriber, string planId,
|
||||||
List<InvoiceSubscriptionItemOptions> subItemOptions, int prorateThreshold = 500)
|
List<InvoiceSubscriptionItemOptions> subItemOptions, int prorateThreshold = 500)
|
||||||
{
|
{
|
||||||
|
var customerService = new CustomerService();
|
||||||
|
var customerOptions = new CustomerGetOptions();
|
||||||
|
customerOptions.AddExpand("default_source");
|
||||||
|
customerOptions.AddExpand("invoice_settings.default_payment_method");
|
||||||
|
var customer = await customerService.GetAsync(subscriber.GatewayCustomerId, customerOptions);
|
||||||
|
var usingInAppPaymentMethod = customer.Metadata.ContainsKey("appleReceipt");
|
||||||
|
if(usingInAppPaymentMethod)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Cannot perform this action with in-app purchase payment method. " +
|
||||||
|
"Contact support.");
|
||||||
|
}
|
||||||
|
|
||||||
var invoiceService = new InvoiceService();
|
var invoiceService = new InvoiceService();
|
||||||
var invoiceItemService = new InvoiceItemService();
|
var invoiceItemService = new InvoiceItemService();
|
||||||
string paymentIntentClientSecret = null;
|
string paymentIntentClientSecret = null;
|
||||||
@ -793,12 +869,6 @@ namespace Bit.Core.Services
|
|||||||
// Owes more than prorateThreshold on next invoice.
|
// Owes more than prorateThreshold on next invoice.
|
||||||
// Invoice them and pay now instead of waiting until next billing cycle.
|
// Invoice them and pay now instead of waiting until next billing cycle.
|
||||||
|
|
||||||
var customerService = new CustomerService();
|
|
||||||
var customerOptions = new CustomerGetOptions();
|
|
||||||
customerOptions.AddExpand("default_source");
|
|
||||||
customerOptions.AddExpand("invoice_settings.default_payment_method");
|
|
||||||
var customer = await customerService.GetAsync(subscriber.GatewayCustomerId, customerOptions);
|
|
||||||
|
|
||||||
string cardPaymentMethodId = null;
|
string cardPaymentMethodId = null;
|
||||||
var invoiceAmountDue = upcomingPreview.StartingBalance + invoiceAmount;
|
var invoiceAmountDue = upcomingPreview.StartingBalance + invoiceAmount;
|
||||||
if(invoiceAmountDue > 0 && !customer.Metadata.ContainsKey("btCustomerId"))
|
if(invoiceAmountDue > 0 && !customer.Metadata.ContainsKey("btCustomerId"))
|
||||||
@ -984,6 +1054,16 @@ namespace Bit.Core.Services
|
|||||||
throw new GatewayException("No subscription.");
|
throw new GatewayException("No subscription.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
|
||||||
|
{
|
||||||
|
var customerService = new CustomerService();
|
||||||
|
var customer = await customerService.GetAsync(subscriber.GatewayCustomerId);
|
||||||
|
if(customer.Metadata.ContainsKey("appleReceipt"))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You are required to manage your subscription from the app store.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var subscriptionService = new SubscriptionService();
|
var subscriptionService = new SubscriptionService();
|
||||||
var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId);
|
var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId);
|
||||||
if(sub == null)
|
if(sub == null)
|
||||||
@ -1052,7 +1132,7 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType,
|
public async Task<bool> UpdatePaymentMethodAsync(ISubscriber subscriber, PaymentMethodType paymentMethodType,
|
||||||
string paymentToken)
|
string paymentToken, bool allowInAppPurchases = false)
|
||||||
{
|
{
|
||||||
if(subscriber == null)
|
if(subscriber == null)
|
||||||
{
|
{
|
||||||
@ -1066,12 +1146,15 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
var createdCustomer = false;
|
var createdCustomer = false;
|
||||||
|
AppleReceiptStatus appleReceiptStatus = null;
|
||||||
Braintree.Customer braintreeCustomer = null;
|
Braintree.Customer braintreeCustomer = null;
|
||||||
string stipeCustomerSourceToken = null;
|
string stipeCustomerSourceToken = null;
|
||||||
string stipeCustomerPaymentMethodId = null;
|
string stipeCustomerPaymentMethodId = null;
|
||||||
var stripeCustomerMetadata = new Dictionary<string, string>();
|
var stripeCustomerMetadata = new Dictionary<string, string>();
|
||||||
var stripePaymentMethod = paymentMethodType == PaymentMethodType.Card ||
|
var stripePaymentMethod = paymentMethodType == PaymentMethodType.Card ||
|
||||||
paymentMethodType == PaymentMethodType.BankAccount;
|
paymentMethodType == PaymentMethodType.BankAccount;
|
||||||
|
var inAppPurchase = paymentMethodType == PaymentMethodType.AppleInApp ||
|
||||||
|
paymentMethodType == PaymentMethodType.GoogleInApp;
|
||||||
|
|
||||||
var cardService = new CardService();
|
var cardService = new CardService();
|
||||||
var bankSerice = new BankAccountService();
|
var bankSerice = new BankAccountService();
|
||||||
@ -1079,6 +1162,16 @@ namespace Bit.Core.Services
|
|||||||
var paymentMethodService = new PaymentMethodService();
|
var paymentMethodService = new PaymentMethodService();
|
||||||
Customer customer = null;
|
Customer customer = null;
|
||||||
|
|
||||||
|
if(!allowInAppPurchases && inAppPurchase)
|
||||||
|
{
|
||||||
|
throw new GatewayException("In-app purchase payment method is not allowed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!subscriber.IsUser() && inAppPurchase)
|
||||||
|
{
|
||||||
|
throw new GatewayException("In-app purchase payment method is only allowed for users.");
|
||||||
|
}
|
||||||
|
|
||||||
if(!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
|
if(!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
|
||||||
{
|
{
|
||||||
customer = await customerService.GetAsync(subscriber.GatewayCustomerId);
|
customer = await customerService.GetAsync(subscriber.GatewayCustomerId);
|
||||||
@ -1088,6 +1181,16 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(inAppPurchase && customer != null && customer.Balance != 0)
|
||||||
|
{
|
||||||
|
throw new GatewayException("Customer balance cannot exist when using in-app purchases.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!inAppPurchase && customer != null && stripeCustomerMetadata.ContainsKey("appleReceipt"))
|
||||||
|
{
|
||||||
|
throw new GatewayException("Cannot change from in-app payment method. Contact support.");
|
||||||
|
}
|
||||||
|
|
||||||
var hadBtCustomer = stripeCustomerMetadata.ContainsKey("btCustomerId");
|
var hadBtCustomer = stripeCustomerMetadata.ContainsKey("btCustomerId");
|
||||||
if(stripePaymentMethod)
|
if(stripePaymentMethod)
|
||||||
{
|
{
|
||||||
@ -1156,6 +1259,15 @@ namespace Bit.Core.Services
|
|||||||
braintreeCustomer = customerResult.Target;
|
braintreeCustomer = customerResult.Target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(paymentMethodType == PaymentMethodType.AppleInApp)
|
||||||
|
{
|
||||||
|
appleReceiptStatus = await _appleIapService.GetVerifiedReceiptStatusAsync(paymentToken);
|
||||||
|
if(appleReceiptStatus == null)
|
||||||
|
{
|
||||||
|
throw new GatewayException("Cannot verify apple in-app purchase.");
|
||||||
|
}
|
||||||
|
await VerifyAppleReceiptNotInUseAsync(appleReceiptStatus.GetOriginalTransactionId(), subscriber);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new GatewayException("Payment method is not supported at this time.");
|
throw new GatewayException("Payment method is not supported at this time.");
|
||||||
@ -1175,6 +1287,12 @@ namespace Bit.Core.Services
|
|||||||
stripeCustomerMetadata.Add("btCustomerId", braintreeCustomer.Id);
|
stripeCustomerMetadata.Add("btCustomerId", braintreeCustomer.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(appleReceiptStatus != null)
|
||||||
|
{
|
||||||
|
stripeCustomerMetadata.Add("appleReceipt", appleReceiptStatus.GetOriginalTransactionId());
|
||||||
|
await _appleIapService.SaveReceiptAsync(appleReceiptStatus, subscriber.Id);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if(customer == null)
|
if(customer == null)
|
||||||
@ -1328,7 +1446,14 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
billingInfo.Balance = customer.Balance / 100M;
|
billingInfo.Balance = customer.Balance / 100M;
|
||||||
|
|
||||||
if(customer.Metadata?.ContainsKey("btCustomerId") ?? false)
|
if(customer.Metadata?.ContainsKey("appleReceipt") ?? false)
|
||||||
|
{
|
||||||
|
billingInfo.PaymentSource = new BillingInfo.BillingSource
|
||||||
|
{
|
||||||
|
Type = PaymentMethodType.AppleInApp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if(customer.Metadata?.ContainsKey("btCustomerId") ?? false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -1377,11 +1502,19 @@ namespace Bit.Core.Services
|
|||||||
public async Task<SubscriptionInfo> GetSubscriptionAsync(ISubscriber subscriber)
|
public async Task<SubscriptionInfo> GetSubscriptionAsync(ISubscriber subscriber)
|
||||||
{
|
{
|
||||||
var subscriptionInfo = new SubscriptionInfo();
|
var subscriptionInfo = new SubscriptionInfo();
|
||||||
var subscriptionService = new SubscriptionService();
|
|
||||||
var invoiceService = new InvoiceService();
|
if(subscriber.IsUser() && !string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
|
||||||
|
{
|
||||||
|
var customerService = new CustomerService();
|
||||||
|
var customer = await customerService.GetAsync(subscriber.GatewayCustomerId);
|
||||||
|
subscriptionInfo.UsingInAppPurchase = customer.Metadata.ContainsKey("appleReceipt");
|
||||||
|
}
|
||||||
|
|
||||||
if(!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
|
if(!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
|
||||||
{
|
{
|
||||||
|
var subscriptionService = new SubscriptionService();
|
||||||
|
var invoiceService = new InvoiceService();
|
||||||
|
|
||||||
var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId);
|
var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId);
|
||||||
if(sub != null)
|
if(sub != null)
|
||||||
{
|
{
|
||||||
@ -1414,5 +1547,18 @@ namespace Bit.Core.Services
|
|||||||
new PaymentMethodListOptions { CustomerId = customerId, Type = "card" });
|
new PaymentMethodListOptions { CustomerId = customerId, Type = "card" });
|
||||||
return cardPaymentMethods.OrderByDescending(m => m.Created).FirstOrDefault();
|
return cardPaymentMethods.OrderByDescending(m => m.Created).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task VerifyAppleReceiptNotInUseAsync(string receiptOriginalTransactionId, ISubscriber subscriber)
|
||||||
|
{
|
||||||
|
var existingReceipt = await _appleIapService.GetReceiptAsync(receiptOriginalTransactionId);
|
||||||
|
if(existingReceipt != null && existingReceipt.Item2.HasValue && existingReceipt.Item2 != subscriber.Id)
|
||||||
|
{
|
||||||
|
var existingUser = await _userRepository.GetByIdAsync(existingReceipt.Item2.Value);
|
||||||
|
if(existingUser != null)
|
||||||
|
{
|
||||||
|
throw new GatewayException("Apple receipt already in use.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user