Exemple #1
0
        public async Task ReinstateSubscriptionAsync(ISubscriber subscriber)
        {
            if (subscriber == null)
            {
                throw new ArgumentNullException(nameof(subscriber));
            }

            if (string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
            {
                throw new GatewayException("No subscription.");
            }

            var subscriptionService = new SubscriptionService();
            var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId);

            if (sub == null)
            {
                throw new GatewayException("Subscription was not found.");
            }

            if ((sub.Status != "active" && sub.Status != "trialing") || !sub.CanceledAt.HasValue)
            {
                throw new GatewayException("Subscription is not marked for cancellation.");
            }

            var updatedSub = await subscriptionService.UpdateAsync(sub.Id,
                                                                   new SubscriptionUpdateOptions { CancelAtPeriodEnd = false });

            if (updatedSub.CanceledAt.HasValue)
            {
                throw new GatewayException("Unable to reinstate subscription.");
            }
        }
Exemple #2
0
        public async Task <SubscriptionInfo> GetSubscriptionAsync(ISubscriber subscriber)
        {
            var subscriptionInfo    = new SubscriptionInfo();
            var subscriptionService = new SubscriptionService();
            var invoiceService      = new InvoiceService();

            if (!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
            {
                var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId);

                if (sub != null)
                {
                    subscriptionInfo.Subscription = new SubscriptionInfo.BillingSubscription(sub);
                }

                if (!sub.CanceledAt.HasValue && !string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
                {
                    try
                    {
                        var upcomingInvoice = await invoiceService.UpcomingAsync(
                            new UpcomingInvoiceOptions { CustomerId = subscriber.GatewayCustomerId });

                        if (upcomingInvoice != null)
                        {
                            subscriptionInfo.UpcomingInvoice =
                                new SubscriptionInfo.BillingUpcomingInvoice(upcomingInvoice);
                        }
                    }
                    catch (StripeException) { }
                }
            }

            return(subscriptionInfo);
        }
Exemple #3
0
        // cancel subscription
        public async Task <IdResponseModel> CancelSubscription(string subscriptionId, int userId)
        {
            var user = await EnsureCustomerCreatedAsync(userId);

            SetStripeApiKey();

            var service = new SubscriptionService();

            Subscription subscription = null;

            try
            {
                subscription = await service.GetAsync(subscriptionId);

                if (subscription.CustomerId != user.Profile.StripeCustomerId)
                {
                    throw new CustomException(HttpStatusCode.BadRequest, "subscriptionId", "Invalid subscription id");
                }

                subscription = await service.CancelAsync(subscriptionId, null);
            }
            catch (StripeException ex)
            {
                throw new CustomException(HttpStatusCode.BadRequest, ex.StripeError?.Parameter, ex.StripeError?.Message);
            }

            return(new IdResponseModel
            {
                Id = subscription?.Id
            });
        }
        private async Task UpdateSubscription(string subscriptionId, string newPlanId, bool isProrateCharged = false)
        {
            var subscriptionService = new SubscriptionService();
            var subscription        = await subscriptionService.GetAsync(subscriptionId);

            await subscriptionService.UpdateAsync(subscriptionId, new SubscriptionUpdateOptions
            {
                CancelAtPeriodEnd = false,
                Items             = new List <SubscriptionItemOptions> {
                    new SubscriptionItemOptions {
                        Id   = subscription.Items.Data[0].Id,
                        Plan = newPlanId
                    }
                },
                Prorate = !isProrateCharged
            });

            var lastRecurringPayment = await _subscriptionPaymentRepository.GetByGatewayAndPaymentIdAsync(SubscriptionPaymentGatewayType.Stripe, subscriptionId);

            var payment = await _subscriptionPaymentRepository.GetLastPaymentOrDefaultAsync(
                tenantId : lastRecurringPayment.TenantId,
                SubscriptionPaymentGatewayType.Stripe,
                isRecurring : true);

            payment.IsRecurring = false;
        }
        public override async Task <ApiResult> RefundPaymentAsync(PaymentProviderContext <StripeCheckoutSettings> ctx)
        {
            try
            {
                // We can only refund a captured charge, so make sure we have one
                // otherwise there is nothing we can do
                var chargeId = ctx.Order.Properties["stripeChargeId"];
                if (string.IsNullOrWhiteSpace(chargeId))
                {
                    return(null);
                }

                var secretKey = ctx.Settings.TestMode ? ctx.Settings.TestSecretKey : ctx.Settings.LiveSecretKey;

                ConfigureStripe(secretKey);

                var refundService       = new RefundService();
                var refundCreateOptions = new RefundCreateOptions()
                {
                    Charge = chargeId
                };

                var refund = refundService.Create(refundCreateOptions);
                var charge = refund.Charge ?? await new ChargeService().GetAsync(refund.ChargeId);

                // If we have a subscription then we'll cancel it as refunding an ctx.Order
                // should effecitvely undo any purchase
                if (!string.IsNullOrWhiteSpace(ctx.Order.Properties["stripeSubscriptionId"]))
                {
                    var subscriptionService = new SubscriptionService();
                    var subscription        = await subscriptionService.GetAsync(ctx.Order.Properties["stripeSubscriptionId"]);

                    if (subscription != null)
                    {
                        subscriptionService.Cancel(ctx.Order.Properties["stripeSubscriptionId"], new SubscriptionCancelOptions
                        {
                            InvoiceNow = false,
                            Prorate    = false
                        });
                    }
                }

                return(new ApiResult()
                {
                    TransactionInfo = new TransactionInfoUpdate()
                    {
                        TransactionId = GetTransactionId(charge),
                        PaymentStatus = GetPaymentStatus(charge)
                    }
                });
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Stripe - RefundPayment");
            }

            return(ApiResult.Empty);
        }
        public async Task <Subscription> GetSubscriptionAsync(string subscriptionId)
        {
            var options = new SubscriptionGetOptions
            {
            };

            var subscription = await _subscriptionService.GetAsync(subscriptionId, options);

            return(subscription);
        }
        public async Task <PaymentGatewayResult <IPaymentSubscription> > UpdateSubscriptionAsync(string planId, ChargeType type,
                                                                                                 string subscriptionId, string?coupon)
        {
            try
            {
                var subscription = await _subscriptionService.GetAsync(subscriptionId);

                if (subscription == null)
                {
                    throw new StripeException("Subscription Not Found")
                          {
                              HttpStatusCode = HttpStatusCode.NotFound
                          }
                }
                ;
                var opt = new SubscriptionUpdateOptions
                {
                    CancelAtPeriodEnd = false,
                    Items             = new List <SubscriptionItemUpdateOption> {
                        new SubscriptionItemUpdateOption {
                            Id     = subscription.Items.Data.FirstOrDefault()?.Id,
                            PlanId = planId
                        },
                    },
                    CouponId         = coupon,
                    Prorate          = true,
                    ProrationDate    = DateTime.UtcNow,
                    CollectionMethod = _chargeTypes[(int)type],
                };
                if (type == ChargeType.Manual)
                {
                    opt.DaysUntilDue = _appSettings.Stripe.InvoiceDueDays;
                }

                subscription = await _subscriptionService.UpdateAsync(subscription.Id, opt);

                return(PaymentGatewayResult <IPaymentSubscription> .Success(_mapper.Map <IPaymentSubscription>(subscription)));
            }
            catch (StripeException e)
            {
                return(PaymentGatewayResult <IPaymentSubscription> .Failed(e));
            }
        }
Exemple #8
0
        public async Task CancelSubscriptionAsync(ISubscriber subscriber, bool endOfPeriod = false)
        {
            if (subscriber == null)
            {
                throw new ArgumentNullException(nameof(subscriber));
            }

            if (string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
            {
                throw new GatewayException("No subscription.");
            }

            var subscriptionService = new SubscriptionService();
            var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId);

            if (sub == null)
            {
                throw new GatewayException("Subscription was not found.");
            }

            if (sub.CanceledAt.HasValue || sub.Status == "canceled" || sub.Status == "unpaid")
            {
                // Already canceled
                return;
            }

            try
            {
                var canceledSub = endOfPeriod ?
                                  await subscriptionService.UpdateAsync(sub.Id,
                                                                        new SubscriptionUpdateOptions { CancelAtPeriodEnd = true }) :
                                  await subscriptionService.CancelAsync(sub.Id, new SubscriptionCancelOptions());

                if (!canceledSub.CanceledAt.HasValue)
                {
                    throw new GatewayException("Unable to cancel subscription.");
                }
            }
            catch (StripeException e)
            {
                if (e.Message != $"No such subscription: {subscriber.GatewaySubscriptionId}")
                {
                    throw e;
                }
            }
        }
Exemple #9
0
 private async Task<Subscription> GetSubscriptionAsync(Stripe.Event parsedEvent, bool fresh = false)
 {
     if(!(parsedEvent.Data.Object is Subscription eventSubscription))
     {
         throw new Exception("Subscription is null (from parsed event). " + parsedEvent.Id);
     }
     if(!fresh)
     {
         return eventSubscription;
     }
     var subscriptionService = new SubscriptionService();
     var subscription = await subscriptionService.GetAsync(eventSubscription.Id);
     if(subscription == null)
     {
         throw new Exception("Subscription is null. " + eventSubscription.Id);
     }
     return subscription;
 }
        public async Task CancelSubscription(string subscriptionId, ClaimsPrincipal userFor, CancellationToken token)
        {
            var user = await userManager.GetUserAsync(userFor).ConfigureAwait(false);

            Subscription subscription = await subscriptionService.GetAsync(subscriptionId, cancellationToken : token).ConfigureAwait(false);

            Customer customer = await customerService.GetAsync(subscription.CustomerId, cancellationToken : token).ConfigureAwait(false);

            if (!customer.Email.Equals(user.Email, StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException($"User {user.Email} doesn't match subscription e-mail {subscription.Customer.Email}");
            }

            subscription = await subscriptionService.CancelAsync(subscriptionId, new SubscriptionCancelOptions(), cancellationToken : token).ConfigureAwait(false);

            context.DonationEventLog.Add(new DonationEventLog(userId: user.Id, type: DonationEventType.Internal, eventData: subscription.ToJson()));
            await context.SaveChangesAsync(token).ConfigureAwait(false);
        }
Exemple #11
0
        private async Task HandlePaymentFailed(Invoice invoice)
        {
            if (!invoice.Paid && invoice.AttemptCount > 1 && UnpaidAutoChargeInvoiceForSubscriptionCycle(invoice))
            {
                var subscriptionService = new SubscriptionService();
                var subscription        = await subscriptionService.GetAsync(invoice.SubscriptionId);

                // attempt count 4 = 11 days after initial failure
                if (invoice.AttemptCount > 3 && subscription.Items.Any(i => i.Price.Id == PremiumPlanId || i.Price.Id == PremiumPlanIdAppStore))
                {
                    await CancelSubscription(invoice.SubscriptionId);
                    await VoidOpenInvoices(invoice.SubscriptionId);
                }
                else
                {
                    await AttemptToPayInvoiceAsync(invoice);
                }
            }
        }
Exemple #12
0
        private async Task <bool> AttemptToPayInvoiceWithBraintreeAsync(Invoice invoice)
        {
            var customerService = new CustomerService();
            var customer        = await customerService.GetAsync(invoice.CustomerId);

            if (!customer?.Metadata?.ContainsKey("btCustomerId") ?? true)
            {
                return(false);
            }

            var subscriptionService = new SubscriptionService();
            var subscription        = await subscriptionService.GetAsync(invoice.SubscriptionId);

            var ids = GetIdsFromMetaData(subscription?.Metadata);

            if (!ids.Item1.HasValue && !ids.Item2.HasValue)
            {
                return(false);
            }

            var btObjIdField    = ids.Item1.HasValue ? "organization_id" : "user_id";
            var btObjId         = ids.Item1 ?? ids.Item2.Value;
            var btInvoiceAmount = (invoice.AmountDue / 100M);

            var transactionResult = await _btGateway.Transaction.SaleAsync(
                new Braintree.TransactionRequest
            {
                Amount     = btInvoiceAmount,
                CustomerId = customer.Metadata["btCustomerId"],
                Options    = new Braintree.TransactionOptionsRequest
                {
                    SubmitForSettlement = true,
                    PayPal = new Braintree.TransactionOptionsPayPalRequest
                    {
                        CustomField = $"{btObjIdField}:{btObjId}"
                    }
                },
                CustomFields = new Dictionary <string, string>
                {
                    [btObjIdField] = btObjId.ToString()
                }
            });

            if (!transactionResult.IsSuccess())
            {
                // TODO: Send payment failure email?
                return(false);
            }

            try
            {
                var invoiceService = new InvoiceService();
                await invoiceService.UpdateAsync(invoice.Id, new InvoiceUpdateOptions
                {
                    Metadata = new Dictionary <string, string>
                    {
                        ["btTransactionId"]       = transactionResult.Target.Id,
                        ["btPayPalTransactionId"] =
                            transactionResult.Target.PayPalDetails?.AuthorizationId
                    }
                });

                await invoiceService.PayAsync(invoice.Id, new InvoicePayOptions { PaidOutOfBand = true });
            }
            catch (Exception e)
            {
                await _btGateway.Transaction.RefundAsync(transactionResult.Target.Id);

                throw e;
            }

            return(true);
        }
        public async Task UpdateSubscription(string subscriptionId, string newPlanId, decimal newAmount, string interval)
        {
            var subscriptionService = new SubscriptionService();
            var subscription        = await subscriptionService.GetAsync(subscriptionId);

            if (!await DoesPlanExistAsync(newPlanId))
            {
                await CreatePlanAsync(newPlanId, newAmount, interval, ProductName);
            }

            var oldPlanId = subscription.Items.Data[0].Plan.Id;

            await subscriptionService.UpdateAsync(subscriptionId, new SubscriptionUpdateOptions
            {
                CancelAtPeriodEnd = false,
                Items             = new List <SubscriptionItemUpdateOption> {
                    new SubscriptionItemUpdateOption {
                        Id     = subscription.Items.Data[0].Id,
                        PlanId = newPlanId
                    }
                }
            });

            var invoiceService = new InvoiceService();
            var invoice        = await invoiceService.CreateAsync(new InvoiceCreateOptions
            {
                SubscriptionId = subscription.Id,
                CustomerId     = subscription.CustomerId
            });

            if (!invoice.Paid)
            {
                invoice = await invoiceService.PayAsync(invoice.Id, null);

                if (!invoice.Paid)
                {
                    await subscriptionService.UpdateAsync(subscriptionId, new SubscriptionUpdateOptions
                    {
                        CancelAtPeriodEnd = false,
                        Items             = new List <SubscriptionItemUpdateOption> {
                            new SubscriptionItemUpdateOption {
                                Id     = subscription.Items.Data[0].Id,
                                PlanId = oldPlanId
                            }
                        }
                    });

                    throw new UserFriendlyException(L("PaymentCouldNotCompleted"));
                }
            }

            var lastRecurringPayment = await _subscriptionPaymentRepository.GetByGatewayAndPaymentIdAsync(SubscriptionPaymentGatewayType.Stripe, subscriptionId);

            var payment = await _subscriptionPaymentRepository.GetLastPaymentOrDefaultAsync(
                tenantId : lastRecurringPayment.TenantId,
                SubscriptionPaymentGatewayType.Stripe,
                isRecurring : true);

            payment.Amount            = ConvertFromStripePrice(invoice.Total);
            payment.IsRecurring       = false;
            payment.ExternalPaymentId = invoice.ChargeId;
            payment.SetAsPaid();
        }
        public async Task <IActionResult> PostWebhook([FromQuery] string key)
        {
            if (key != _billingSettings.StripeWebhookKey)
            {
                return(new BadRequestResult());
            }

            Stripe.Event parsedEvent;
            using (var sr = new StreamReader(HttpContext.Request.Body))
            {
                var json = await sr.ReadToEndAsync();

                parsedEvent = EventUtility.ConstructEvent(json, Request.Headers["Stripe-Signature"],
                                                          _billingSettings.StripeWebhookSecret);
            }

            if (string.IsNullOrWhiteSpace(parsedEvent?.Id))
            {
                _logger.LogWarning("No event id.");
                return(new BadRequestResult());
            }

            if (_hostingEnvironment.IsProduction() && !parsedEvent.Livemode)
            {
                _logger.LogWarning("Getting test events in production.");
                return(new BadRequestResult());
            }

            var subDeleted = parsedEvent.Type.Equals("customer.subscription.deleted");
            var subUpdated = parsedEvent.Type.Equals("customer.subscription.updated");

            if (subDeleted || subUpdated)
            {
                var subscription = await GetSubscriptionAsync(parsedEvent, true);

                var ids = GetIdsFromMetaData(subscription.Metadata);

                var subCanceled          = subDeleted && subscription.Status == "canceled";
                var subUnpaid            = subUpdated && subscription.Status == "unpaid";
                var subIncompleteExpired = subUpdated && subscription.Status == "incomplete_expired";

                if (subCanceled || subUnpaid || subIncompleteExpired)
                {
                    // org
                    if (ids.Item1.HasValue)
                    {
                        await _organizationService.DisableAsync(ids.Item1.Value, subscription.CurrentPeriodEnd);
                    }
                    // user
                    else if (ids.Item2.HasValue)
                    {
                        await _userService.DisablePremiumAsync(ids.Item2.Value, subscription.CurrentPeriodEnd);
                    }
                }

                if (subUpdated)
                {
                    // org
                    if (ids.Item1.HasValue)
                    {
                        await _organizationService.UpdateExpirationDateAsync(ids.Item1.Value,
                                                                             subscription.CurrentPeriodEnd);
                    }
                    // user
                    else if (ids.Item2.HasValue)
                    {
                        await _userService.UpdatePremiumExpirationAsync(ids.Item2.Value,
                                                                        subscription.CurrentPeriodEnd);
                    }
                }
            }
            else if (parsedEvent.Type.Equals("invoice.upcoming"))
            {
                var invoice = await GetInvoiceAsync(parsedEvent);

                var subscriptionService = new SubscriptionService();
                var subscription        = await subscriptionService.GetAsync(invoice.SubscriptionId);

                if (subscription == null)
                {
                    throw new Exception("Invoice subscription is null. " + invoice.Id);
                }

                subscription = await VerifyCorrectTaxRateForCharge(invoice, subscription);

                string email = null;
                var    ids   = GetIdsFromMetaData(subscription.Metadata);
                // org
                if (ids.Item1.HasValue)
                {
                    var org = await _organizationRepository.GetByIdAsync(ids.Item1.Value);

                    if (org != null && OrgPlanForInvoiceNotifications(org))
                    {
                        email = org.BillingEmail;
                    }
                }
                // user
                else if (ids.Item2.HasValue)
                {
                    var user = await _userService.GetUserByIdAsync(ids.Item2.Value);

                    if (user.Premium)
                    {
                        email = user.Email;
                    }
                }

                if (!string.IsNullOrWhiteSpace(email) && invoice.NextPaymentAttempt.HasValue)
                {
                    var items = invoice.Lines.Select(i => i.Description).ToList();
                    await _mailService.SendInvoiceUpcomingAsync(email, invoice.AmountDue / 100M,
                                                                invoice.NextPaymentAttempt.Value, items, true);
                }
            }
            else if (parsedEvent.Type.Equals("charge.succeeded"))
            {
                var charge = await GetChargeAsync(parsedEvent);

                var chargeTransaction = await _transactionRepository.GetByGatewayIdAsync(
                    GatewayType.Stripe, charge.Id);

                if (chargeTransaction != null)
                {
                    _logger.LogWarning("Charge success already processed. " + charge.Id);
                    return(new OkResult());
                }

                Tuple <Guid?, Guid?> ids          = null;
                Subscription         subscription = null;
                var subscriptionService           = new SubscriptionService();

                if (charge.InvoiceId != null)
                {
                    var invoiceService = new InvoiceService();
                    var invoice        = await invoiceService.GetAsync(charge.InvoiceId);

                    if (invoice?.SubscriptionId != null)
                    {
                        subscription = await subscriptionService.GetAsync(invoice.SubscriptionId);

                        ids = GetIdsFromMetaData(subscription?.Metadata);
                    }
                }

                if (subscription == null || ids == null || (ids.Item1.HasValue && ids.Item2.HasValue))
                {
                    var subscriptions = await subscriptionService.ListAsync(new SubscriptionListOptions
                    {
                        Customer = charge.CustomerId
                    });

                    foreach (var sub in subscriptions)
                    {
                        if (sub.Status != "canceled" && sub.Status != "incomplete_expired")
                        {
                            ids = GetIdsFromMetaData(sub.Metadata);
                            if (ids.Item1.HasValue || ids.Item2.HasValue)
                            {
                                subscription = sub;
                                break;
                            }
                        }
                    }
                }

                if (!ids.Item1.HasValue && !ids.Item2.HasValue)
                {
                    _logger.LogWarning("Charge success has no subscriber ids. " + charge.Id);
                    return(new BadRequestResult());
                }

                var tx = new Transaction
                {
                    Amount         = charge.Amount / 100M,
                    CreationDate   = charge.Created,
                    OrganizationId = ids.Item1,
                    UserId         = ids.Item2,
                    Type           = TransactionType.Charge,
                    Gateway        = GatewayType.Stripe,
                    GatewayId      = charge.Id
                };

                if (charge.Source != null && charge.Source is Card card)
                {
                    tx.PaymentMethodType = PaymentMethodType.Card;
                    tx.Details           = $"{card.Brand}, *{card.Last4}";
                }
                else if (charge.Source != null && charge.Source is BankAccount bankAccount)
                {
                    tx.PaymentMethodType = PaymentMethodType.BankAccount;
                    tx.Details           = $"{bankAccount.BankName}, *{bankAccount.Last4}";
                }
                else if (charge.Source != null && charge.Source is Source source)
                {
                    if (source.Card != null)
                    {
                        tx.PaymentMethodType = PaymentMethodType.Card;
                        tx.Details           = $"{source.Card.Brand}, *{source.Card.Last4}";
                    }
                    else if (source.AchDebit != null)
                    {
                        tx.PaymentMethodType = PaymentMethodType.BankAccount;
                        tx.Details           = $"{source.AchDebit.BankName}, *{source.AchDebit.Last4}";
                    }
                    else if (source.AchCreditTransfer != null)
                    {
                        tx.PaymentMethodType = PaymentMethodType.BankAccount;
                        tx.Details           = $"ACH => {source.AchCreditTransfer.BankName}, " +
                                               $"{source.AchCreditTransfer.AccountNumber}";
                    }
                }
                else if (charge.PaymentMethodDetails != null)
                {
                    if (charge.PaymentMethodDetails.Card != null)
                    {
                        tx.PaymentMethodType = PaymentMethodType.Card;
                        tx.Details           = $"{charge.PaymentMethodDetails.Card.Brand?.ToUpperInvariant()}, " +
                                               $"*{charge.PaymentMethodDetails.Card.Last4}";
                    }
                    else if (charge.PaymentMethodDetails.AchDebit != null)
                    {
                        tx.PaymentMethodType = PaymentMethodType.BankAccount;
                        tx.Details           = $"{charge.PaymentMethodDetails.AchDebit.BankName}, " +
                                               $"*{charge.PaymentMethodDetails.AchDebit.Last4}";
                    }
                    else if (charge.PaymentMethodDetails.AchCreditTransfer != null)
                    {
                        tx.PaymentMethodType = PaymentMethodType.BankAccount;
                        tx.Details           = $"ACH => {charge.PaymentMethodDetails.AchCreditTransfer.BankName}, " +
                                               $"{charge.PaymentMethodDetails.AchCreditTransfer.AccountNumber}";
                    }
                }

                if (!tx.PaymentMethodType.HasValue)
                {
                    _logger.LogWarning("Charge success from unsupported source/method. " + charge.Id);
                    return(new OkResult());
                }

                try
                {
                    await _transactionRepository.CreateAsync(tx);
                }
                // Catch foreign key violations because user/org could have been deleted.
                catch (SqlException e) when(e.Number == 547)
                {
                }
            }
            else if (parsedEvent.Type.Equals("charge.refunded"))
            {
                var charge = await GetChargeAsync(parsedEvent);

                var chargeTransaction = await _transactionRepository.GetByGatewayIdAsync(
                    GatewayType.Stripe, charge.Id);

                if (chargeTransaction == null)
                {
                    throw new Exception("Cannot find refunded charge. " + charge.Id);
                }

                var amountRefunded = charge.AmountRefunded / 100M;

                if (!chargeTransaction.Refunded.GetValueOrDefault() &&
                    chargeTransaction.RefundedAmount.GetValueOrDefault() < amountRefunded)
                {
                    chargeTransaction.RefundedAmount = amountRefunded;
                    if (charge.Refunded)
                    {
                        chargeTransaction.Refunded = true;
                    }
                    await _transactionRepository.ReplaceAsync(chargeTransaction);

                    foreach (var refund in charge.Refunds)
                    {
                        var refundTransaction = await _transactionRepository.GetByGatewayIdAsync(
                            GatewayType.Stripe, refund.Id);

                        if (refundTransaction != null)
                        {
                            continue;
                        }

                        await _transactionRepository.CreateAsync(new Transaction
                        {
                            Amount            = refund.Amount / 100M,
                            CreationDate      = refund.Created,
                            OrganizationId    = chargeTransaction.OrganizationId,
                            UserId            = chargeTransaction.UserId,
                            Type              = TransactionType.Refund,
                            Gateway           = GatewayType.Stripe,
                            GatewayId         = refund.Id,
                            PaymentMethodType = chargeTransaction.PaymentMethodType,
                            Details           = chargeTransaction.Details
                        });
                    }
                }
                else
                {
                    _logger.LogWarning("Charge refund amount doesn't seem correct. " + charge.Id);
                }
            }
            else if (parsedEvent.Type.Equals("invoice.payment_succeeded"))
            {
                var invoice = await GetInvoiceAsync(parsedEvent, true);

                if (invoice.Paid && invoice.BillingReason == "subscription_create")
                {
                    var subscriptionService = new SubscriptionService();
                    var subscription        = await subscriptionService.GetAsync(invoice.SubscriptionId);

                    if (subscription?.Status == "active")
                    {
                        if (DateTime.UtcNow - invoice.Created < TimeSpan.FromMinutes(1))
                        {
                            await Task.Delay(5000);
                        }

                        var ids = GetIdsFromMetaData(subscription.Metadata);
                        // org
                        if (ids.Item1.HasValue)
                        {
                            if (subscription.Items.Any(i => StaticStore.Plans.Any(p => p.StripePlanId == i.Plan.Id)))
                            {
                                await _organizationService.EnableAsync(ids.Item1.Value, subscription.CurrentPeriodEnd);
                            }
                        }
                        // user
                        else if (ids.Item2.HasValue)
                        {
                            if (subscription.Items.Any(i => i.Plan.Id == "premium-annually"))
                            {
                                await _userService.EnablePremiumAsync(ids.Item2.Value, subscription.CurrentPeriodEnd);
                            }
                        }
                        if (ids.Item1.HasValue || ids.Item2.HasValue)
                        {
                            await _referenceEventService.RaiseEventAsync(
                                new ReferenceEvent(ReferenceEventType.Rebilled, null)
                            {
                                Id     = ids.Item1 ?? ids.Item2 ?? default,
                                Source = ids.Item1.HasValue
                                        ? ReferenceEventSource.Organization
                                        : ReferenceEventSource.User,
                            });
Exemple #15
0
        private async Task <bool> AttemptToPayInvoiceWithBraintreeAsync(Invoice invoice, Customer customer)
        {
            if (!customer?.Metadata?.ContainsKey("btCustomerId") ?? true)
            {
                return(false);
            }

            var subscriptionService = new SubscriptionService();
            var subscription        = await subscriptionService.GetAsync(invoice.SubscriptionId);

            var ids = GetIdsFromMetaData(subscription?.Metadata);

            if (!ids.Item1.HasValue && !ids.Item2.HasValue)
            {
                return(false);
            }

            var orgTransaction  = ids.Item1.HasValue;
            var btObjIdField    = orgTransaction ? "organization_id" : "user_id";
            var btObjId         = ids.Item1 ?? ids.Item2.Value;
            var btInvoiceAmount = (invoice.AmountDue / 100M);

            var existingTransactions = orgTransaction ?
                                       await _transactionRepository.GetManyByOrganizationIdAsync(ids.Item1.Value) :
                                       await _transactionRepository.GetManyByUserIdAsync(ids.Item2.Value);

            var duplicateTimeSpan = TimeSpan.FromHours(24);
            var now = DateTime.UtcNow;
            var duplicateTransaction = existingTransactions?
                                       .FirstOrDefault(t => (now - t.CreationDate) < duplicateTimeSpan);

            if (duplicateTransaction != null)
            {
                _logger.LogWarning("There is already a recent PayPal transaction ({0}). " +
                                   "Do not charge again to prevent possible duplicate.", duplicateTransaction.GatewayId);
                return(false);
            }

            var transactionResult = await _btGateway.Transaction.SaleAsync(
                new Braintree.TransactionRequest
            {
                Amount     = btInvoiceAmount,
                CustomerId = customer.Metadata["btCustomerId"],
                Options    = new Braintree.TransactionOptionsRequest
                {
                    SubmitForSettlement = true,
                    PayPal = new Braintree.TransactionOptionsPayPalRequest
                    {
                        CustomField = $"{btObjIdField}:{btObjId}"
                    }
                },
                CustomFields = new Dictionary <string, string>
                {
                    [btObjIdField] = btObjId.ToString()
                }
            });

            if (!transactionResult.IsSuccess())
            {
                if (invoice.AttemptCount < 4)
                {
                    await _mailService.SendPaymentFailedAsync(customer.Email, btInvoiceAmount, true);
                }
                return(false);
            }

            var invoiceService = new InvoiceService();

            try
            {
                await invoiceService.UpdateAsync(invoice.Id, new InvoiceUpdateOptions
                {
                    Metadata = new Dictionary <string, string>
                    {
                        ["btTransactionId"]       = transactionResult.Target.Id,
                        ["btPayPalTransactionId"] =
                            transactionResult.Target.PayPalDetails?.AuthorizationId
                    }
                });

                await invoiceService.PayAsync(invoice.Id, new InvoicePayOptions { PaidOutOfBand = true });
            }
            catch (Exception e)
            {
                await _btGateway.Transaction.RefundAsync(transactionResult.Target.Id);

                if (e.Message.Contains("Invoice is already paid"))
                {
                    await invoiceService.UpdateAsync(invoice.Id, new InvoiceUpdateOptions
                    {
                        Metadata = invoice.Metadata
                    });
                }
                else
                {
                    throw;
                }
            }

            return(true);
        }
Exemple #16
0
        private async Task <bool> AttemptToPayInvoiceWithAppleReceiptAsync(Invoice invoice, Customer customer)
        {
            if (!customer?.Metadata?.ContainsKey("appleReceipt") ?? true)
            {
                return(false);
            }

            var originalAppleReceiptTransactionId = customer.Metadata["appleReceipt"];
            var appleReceiptRecord = await _appleIapService.GetReceiptAsync(originalAppleReceiptTransactionId);

            if (string.IsNullOrWhiteSpace(appleReceiptRecord?.Item1) || !appleReceiptRecord.Item2.HasValue)
            {
                return(false);
            }

            var subscriptionService = new SubscriptionService();
            var subscription        = await subscriptionService.GetAsync(invoice.SubscriptionId);

            var ids = GetIdsFromMetaData(subscription?.Metadata);

            if (!ids.Item2.HasValue)
            {
                // Apple receipt is only for user subscriptions
                return(false);
            }

            if (appleReceiptRecord.Item2.Value != ids.Item2.Value)
            {
                _logger.LogError("User Ids for Apple Receipt and subscription do not match: {0} != {1}.",
                                 appleReceiptRecord.Item2.Value, ids.Item2.Value);
                return(false);
            }

            var appleReceiptStatus = await _appleIapService.GetVerifiedReceiptStatusAsync(appleReceiptRecord.Item1);

            if (appleReceiptStatus == null)
            {
                // TODO: cancel sub if receipt is cancelled?
                return(false);
            }

            var receiptExpiration = appleReceiptStatus.GetLastExpiresDate().GetValueOrDefault(DateTime.MinValue);
            var invoiceDue        = invoice.DueDate.GetValueOrDefault(DateTime.MinValue);

            if (receiptExpiration <= invoiceDue)
            {
                _logger.LogWarning("Apple receipt expiration is before invoice due date. {0} <= {1}",
                                   receiptExpiration, invoiceDue);
                return(false);
            }

            var receiptLastTransactionId = appleReceiptStatus.GetLastTransactionId();
            var existingTransaction      = await _transactionRepository.GetByGatewayIdAsync(
                GatewayType.AppStore, receiptLastTransactionId);

            if (existingTransaction != null)
            {
                _logger.LogWarning("There is already an existing transaction for this Apple receipt.",
                                   receiptLastTransactionId);
                return(false);
            }

            var appleTransaction = appleReceiptStatus.BuildTransactionFromLastTransaction(
                PremiumPlanAppleIapPrice, ids.Item2.Value);

            appleTransaction.Type = TransactionType.Charge;

            var invoiceService = new InvoiceService();

            try
            {
                await invoiceService.UpdateAsync(invoice.Id, new InvoiceUpdateOptions
                {
                    Metadata = new Dictionary <string, string>
                    {
                        ["appleReceipt"] = appleReceiptStatus.GetOriginalTransactionId(),
                        ["appleReceiptTransactionId"] = receiptLastTransactionId
                    }
                });

                await _transactionRepository.CreateAsync(appleTransaction);

                await invoiceService.PayAsync(invoice.Id, new InvoicePayOptions { PaidOutOfBand = true });
            }
            catch (Exception e)
            {
                if (e.Message.Contains("Invoice is already paid"))
                {
                    await invoiceService.UpdateAsync(invoice.Id, new InvoiceUpdateOptions
                    {
                        Metadata = invoice.Metadata
                    });
                }
                else
                {
                    throw;
                }
            }

            return(true);
        }
Exemple #17
0
        public async Task <IActionResult> PostWebhook([FromQuery] string key)
        {
            if (key != _billingSettings.StripeWebhookKey)
            {
                return(new BadRequestResult());
            }

            Stripe.Event parsedEvent;
            using (var sr = new StreamReader(HttpContext.Request.Body))
            {
                var json = await sr.ReadToEndAsync();

                parsedEvent = EventUtility.ConstructEvent(json, Request.Headers["Stripe-Signature"],
                                                          _billingSettings.StripeWebhookSecret);
            }

            if (string.IsNullOrWhiteSpace(parsedEvent?.Id))
            {
                return(new BadRequestResult());
            }

            if (_hostingEnvironment.IsProduction() && !parsedEvent.Livemode)
            {
                return(new BadRequestResult());
            }

            var subDeleted = parsedEvent.Type.Equals("customer.subscription.deleted");
            var subUpdated = parsedEvent.Type.Equals("customer.subscription.updated");

            if (subDeleted || subUpdated)
            {
                if (!(parsedEvent.Data.Object is Subscription subscription))
                {
                    throw new Exception("Subscription is null.");
                }

                var ids = GetIdsFromMetaData(subscription.Metadata);

                var subCanceled = subDeleted && subscription.Status == "canceled";
                var subUnpaid   = subUpdated && subscription.Status == "unpaid";

                if (subCanceled || subUnpaid)
                {
                    // org
                    if (ids.Item1.HasValue)
                    {
                        await _organizationService.DisableAsync(ids.Item1.Value, subscription.CurrentPeriodEnd);
                    }
                    // user
                    else if (ids.Item2.HasValue)
                    {
                        await _userService.DisablePremiumAsync(ids.Item2.Value, subscription.CurrentPeriodEnd);
                    }
                }

                if (subUpdated)
                {
                    // org
                    if (ids.Item1.HasValue)
                    {
                        await _organizationService.UpdateExpirationDateAsync(ids.Item1.Value,
                                                                             subscription.CurrentPeriodEnd);
                    }
                    // user
                    else if (ids.Item2.HasValue)
                    {
                        await _userService.UpdatePremiumExpirationAsync(ids.Item2.Value,
                                                                        subscription.CurrentPeriodEnd);
                    }
                }
            }
            else if (parsedEvent.Type.Equals("invoice.upcoming"))
            {
                if (!(parsedEvent.Data.Object is Invoice invoice))
                {
                    throw new Exception("Invoice is null.");
                }

                var subscriptionService = new SubscriptionService();
                var subscription        = await subscriptionService.GetAsync(invoice.SubscriptionId);

                if (subscription == null)
                {
                    throw new Exception("Invoice subscription is null.");
                }

                string email = null;
                var    ids   = GetIdsFromMetaData(subscription.Metadata);
                // org
                if (ids.Item1.HasValue)
                {
                    var org = await _organizationRepository.GetByIdAsync(ids.Item1.Value);

                    if (org != null && OrgPlanForInvoiceNotifications(org))
                    {
                        email = org.BillingEmail;
                    }
                }
                // user
                else if (ids.Item2.HasValue)
                {
                    var user = await _userService.GetUserByIdAsync(ids.Item2.Value);

                    if (user.Premium)
                    {
                        email = user.Email;
                    }
                }

                if (!string.IsNullOrWhiteSpace(email) && invoice.NextPaymentAttempt.HasValue)
                {
                    var items = invoice.Lines.Select(i => i.Description).ToList();
                    await _mailService.SendInvoiceUpcomingAsync(email, invoice.AmountDue / 100M,
                                                                invoice.NextPaymentAttempt.Value, items, ids.Item1.HasValue);
                }
            }
            else if (parsedEvent.Type.Equals("charge.succeeded"))
            {
                if (!(parsedEvent.Data.Object is Charge charge))
                {
                    throw new Exception("Charge is null.");
                }

                if (charge.InvoiceId == null)
                {
                    return(new OkResult());
                }

                var chargeTransaction = await _transactionRepository.GetByGatewayIdAsync(
                    GatewayType.Stripe, charge.Id);

                if (chargeTransaction == null)
                {
                    var invoiceService = new InvoiceService();
                    var invoice        = await invoiceService.GetAsync(charge.InvoiceId);

                    if (invoice == null)
                    {
                        return(new OkResult());
                    }

                    var subscriptionService = new SubscriptionService();
                    var subscription        = await subscriptionService.GetAsync(invoice.SubscriptionId);

                    if (subscription == null)
                    {
                        return(new OkResult());
                    }

                    var ids = GetIdsFromMetaData(subscription.Metadata);
                    if (ids.Item1.HasValue || ids.Item2.HasValue)
                    {
                        var tx = new Transaction
                        {
                            Amount         = charge.Amount / 100M,
                            CreationDate   = charge.Created,
                            OrganizationId = ids.Item1,
                            UserId         = ids.Item2,
                            Type           = TransactionType.Charge,
                            Gateway        = GatewayType.Stripe,
                            GatewayId      = charge.Id
                        };

                        if (charge.Source is Card card)
                        {
                            tx.PaymentMethodType = PaymentMethodType.Card;
                            tx.Details           = $"{card.Brand}, *{card.Last4}";
                        }
                        else if (charge.Source is BankAccount bankAccount)
                        {
                            tx.PaymentMethodType = PaymentMethodType.BankAccount;
                            tx.Details           = $"{bankAccount.BankName}, *{bankAccount.Last4}";
                        }
                        else
                        {
                            return(new OkResult());
                        }

                        try
                        {
                            await _transactionRepository.CreateAsync(tx);
                        }
                        // Catch foreign key violations because user/org could have been deleted.
                        catch (SqlException e) when(e.Number == 547)
                        {
                        }
                    }
                }
            }
            else if (parsedEvent.Type.Equals("charge.refunded"))
            {
                if (!(parsedEvent.Data.Object is Charge charge))
                {
                    throw new Exception("Charge is null.");
                }

                var chargeTransaction = await _transactionRepository.GetByGatewayIdAsync(
                    GatewayType.Stripe, charge.Id);

                if (chargeTransaction == null)
                {
                    throw new Exception("Cannot find refunded charge.");
                }

                var amountRefunded = charge.AmountRefunded / 100M;

                if (!chargeTransaction.Refunded.GetValueOrDefault() &&
                    chargeTransaction.RefundedAmount.GetValueOrDefault() < amountRefunded)
                {
                    chargeTransaction.RefundedAmount = amountRefunded;
                    if (charge.Refunded)
                    {
                        chargeTransaction.Refunded = true;
                    }
                    await _transactionRepository.ReplaceAsync(chargeTransaction);

                    foreach (var refund in charge.Refunds)
                    {
                        var refundTransaction = await _transactionRepository.GetByGatewayIdAsync(
                            GatewayType.Stripe, refund.Id);

                        if (refundTransaction != null)
                        {
                            continue;
                        }

                        await _transactionRepository.CreateAsync(new Transaction
                        {
                            Amount            = refund.Amount / 100M,
                            CreationDate      = refund.Created,
                            OrganizationId    = chargeTransaction.OrganizationId,
                            UserId            = chargeTransaction.UserId,
                            Type              = TransactionType.Refund,
                            Gateway           = GatewayType.Stripe,
                            GatewayId         = refund.Id,
                            PaymentMethodType = chargeTransaction.PaymentMethodType,
                            Details           = chargeTransaction.Details
                        });
                    }
                }
            }
            else if (parsedEvent.Type.Equals("invoice.payment_failed"))
            {
                if (!(parsedEvent.Data.Object is Invoice invoice))
                {
                    throw new Exception("Invoice is null.");
                }

                if (invoice.AttemptCount > 1 && UnpaidAutoChargeInvoiceForSubscriptionCycle(invoice))
                {
                    await AttemptToPayInvoiceWithBraintreeAsync(invoice);
                }
            }
            else if (parsedEvent.Type.Equals("invoice.created"))
            {
                if (!(parsedEvent.Data.Object is Invoice invoice))
                {
                    throw new Exception("Invoice is null.");
                }

                if (UnpaidAutoChargeInvoiceForSubscriptionCycle(invoice))
                {
                    await AttemptToPayInvoiceWithBraintreeAsync(invoice);
                }
            }

            return(new OkResult());
        }
Exemple #18
0
        public async Task <BillingInfo> GetBillingAsync(ISubscriber subscriber)
        {
            var billingInfo = new BillingInfo();

            ICollection <Transaction> transactions = null;

            if (subscriber is User)
            {
                transactions = await _transactionRepository.GetManyByUserIdAsync(subscriber.Id);
            }
            else if (subscriber is Organization)
            {
                transactions = await _transactionRepository.GetManyByOrganizationIdAsync(subscriber.Id);
            }
            if (transactions != null)
            {
                billingInfo.Transactions = transactions?.OrderByDescending(i => i.CreationDate)
                                           .Select(t => new BillingInfo.BillingTransaction(t));
            }

            var customerService     = new CustomerService();
            var subscriptionService = new SubscriptionService();
            var chargeService       = new ChargeService();
            var invoiceService      = new InvoiceService();

            if (!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
            {
                var customer = await customerService.GetAsync(subscriber.GatewayCustomerId);

                if (customer != null)
                {
                    billingInfo.CreditAmount = customer.AccountBalance / 100M;

                    if (customer.Metadata?.ContainsKey("btCustomerId") ?? false)
                    {
                        try
                        {
                            var braintreeCustomer = await _btGateway.Customer.FindAsync(
                                customer.Metadata["btCustomerId"]);

                            if (braintreeCustomer?.DefaultPaymentMethod != null)
                            {
                                billingInfo.PaymentSource = new BillingInfo.BillingSource(
                                    braintreeCustomer.DefaultPaymentMethod);
                            }
                        }
                        catch (Braintree.Exceptions.NotFoundException) { }
                    }
                    else if (!string.IsNullOrWhiteSpace(customer.DefaultSourceId) && customer.Sources?.Data != null)
                    {
                        if (customer.DefaultSourceId.StartsWith("card_") || customer.DefaultSourceId.StartsWith("ba_"))
                        {
                            var source = customer.Sources.Data.FirstOrDefault(s =>
                                                                              (s is Card || s is BankAccount) && s.Id == customer.DefaultSourceId);
                            if (source != null)
                            {
                                billingInfo.PaymentSource = new BillingInfo.BillingSource(source);
                            }
                        }
                    }

                    var charges = await chargeService.ListAsync(new ChargeListOptions
                    {
                        CustomerId = customer.Id,
                        Limit      = 20
                    });

                    billingInfo.Charges = charges?.Data?.OrderByDescending(c => c.Created)
                                          .Select(c => new BillingInfo.BillingCharge(c));

                    var invoices = await invoiceService.ListAsync(new InvoiceListOptions
                    {
                        CustomerId = customer.Id,
                        Limit      = 20
                    });

                    billingInfo.Invoices = invoices?.Data?.OrderByDescending(i => i.Date)
                                           .Select(i => new BillingInfo.BillingInvoice(i));
                }
            }

            if (!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))
            {
                var sub = await subscriptionService.GetAsync(subscriber.GatewaySubscriptionId);

                if (sub != null)
                {
                    billingInfo.Subscription = new BillingInfo.BillingSubscription(sub);
                }

                if (!sub.CanceledAt.HasValue && !string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
                {
                    try
                    {
                        var upcomingInvoice = await invoiceService.UpcomingAsync(
                            new UpcomingInvoiceOptions { CustomerId = subscriber.GatewayCustomerId });

                        if (upcomingInvoice != null)
                        {
                            billingInfo.UpcomingInvoice = new BillingInfo.BillingInvoiceInfo(upcomingInvoice);
                        }
                    }
                    catch (StripeException) { }
                }
            }

            return(billingInfo);
        }
Exemple #19
0
        public async Task AdjustStorageAsync(IStorableSubscriber storableSubscriber, int additionalStorage,
                                             string storagePlanId)
        {
            var subscriptionItemService = new SubscriptionItemService();
            var subscriptionService     = new SubscriptionService();
            var sub = await subscriptionService.GetAsync(storableSubscriber.GatewaySubscriptionId);

            if (sub == null)
            {
                throw new GatewayException("Subscription not found.");
            }

            Func <bool, Task <SubscriptionItem> > subUpdateAction = null;
            var storageItem    = sub.Items?.FirstOrDefault(i => i.Plan.Id == storagePlanId);
            var subItemOptions = sub.Items.Where(i => i.Plan.Id != storagePlanId)
                                 .Select(i => new InvoiceSubscriptionItemOptions
            {
                Id       = i.Id,
                PlanId   = i.Plan.Id,
                Quantity = i.Quantity,
            }).ToList();

            if (additionalStorage > 0 && storageItem == null)
            {
                subItemOptions.Add(new InvoiceSubscriptionItemOptions
                {
                    PlanId   = storagePlanId,
                    Quantity = additionalStorage,
                });
                subUpdateAction = (prorate) => subscriptionItemService.CreateAsync(
                    new SubscriptionItemCreateOptions
                {
                    PlanId         = storagePlanId,
                    Quantity       = additionalStorage,
                    SubscriptionId = sub.Id,
                    Prorate        = prorate
                });
            }
            else if (additionalStorage > 0 && storageItem != null)
            {
                subItemOptions.Add(new InvoiceSubscriptionItemOptions
                {
                    Id       = storageItem.Id,
                    PlanId   = storagePlanId,
                    Quantity = additionalStorage,
                });
                subUpdateAction = (prorate) => subscriptionItemService.UpdateAsync(storageItem.Id,
                                                                                   new SubscriptionItemUpdateOptions
                {
                    PlanId   = storagePlanId,
                    Quantity = additionalStorage,
                    Prorate  = prorate
                });
            }
            else if (additionalStorage == 0 && storageItem != null)
            {
                subItemOptions.Add(new InvoiceSubscriptionItemOptions
                {
                    Id      = storageItem.Id,
                    Deleted = true
                });
                subUpdateAction = (prorate) => subscriptionItemService.DeleteAsync(storageItem.Id);
            }

            var invoicedNow = false;

            if (additionalStorage > 0)
            {
                invoicedNow = await PreviewUpcomingInvoiceAndPayAsync(
                    storableSubscriber, storagePlanId, subItemOptions, 400);
            }

            await subUpdateAction(!invoicedNow);
        }
    public async Task <ActionResult <Subscription> > RetrieveSubscriptionAsync([FromBody] SubscriptionRetrieveRequest request)
    {
        var subscriptionService = new SubscriptionService(this.client);

        return(await subscriptionService.GetAsync(request.SubscriptionId));
    }
        public override async Task <CallbackResult> ProcessCallbackAsync(PaymentProviderContext <StripeCheckoutSettings> ctx)
        {
            // The ProcessCallback method is only intendid to be called via a Stripe Webhook and so
            // it's job is to process the webhook event and finalize / update the ctx.Order accordingly

            try
            {
                var secretKey            = ctx.Settings.TestMode ? ctx.Settings.TestSecretKey : ctx.Settings.LiveSecretKey;
                var webhookSigningSecret = ctx.Settings.TestMode ? ctx.Settings.TestWebhookSigningSecret : ctx.Settings.LiveWebhookSigningSecret;

                ConfigureStripe(secretKey);

                var stripeEvent = await GetWebhookStripeEventAsync(ctx, webhookSigningSecret);

                if (stripeEvent != null && stripeEvent.Type == Events.CheckoutSessionCompleted)
                {
                    if (stripeEvent.Data?.Object?.Instance is Session stripeSession)
                    {
                        if (stripeSession.Mode == "payment")
                        {
                            var paymentIntentService = new PaymentIntentService();
                            var paymentIntent        = await paymentIntentService.GetAsync(stripeSession.PaymentIntentId, new PaymentIntentGetOptions
                            {
                                Expand = new List <string>(new[] {
                                    "review"
                                })
                            });

                            return(CallbackResult.Ok(new TransactionInfo
                            {
                                TransactionId = GetTransactionId(paymentIntent),
                                AmountAuthorized = AmountFromMinorUnits(paymentIntent.Amount),
                                PaymentStatus = GetPaymentStatus(paymentIntent)
                            },
                                                     new Dictionary <string, string>
                            {
                                { "stripeSessionId", stripeSession.Id },
                                { "stripeCustomerId", stripeSession.CustomerId },
                                { "stripePaymentIntentId", stripeSession.PaymentIntentId },
                                { "stripeSubscriptionId", stripeSession.SubscriptionId },
                                { "stripeChargeId", GetTransactionId(paymentIntent) },
                                { "stripeCardCountry", paymentIntent.Charges?.Data?.FirstOrDefault()?.PaymentMethodDetails?.Card?.Country }
                            }));
                        }
                        else if (stripeSession.Mode == "subscription")
                        {
                            var subscriptionService = new SubscriptionService();
                            var subscription        = await subscriptionService.GetAsync(stripeSession.SubscriptionId, new SubscriptionGetOptions {
                                Expand = new List <string>(new[] {
                                    "latest_invoice",
                                    "latest_invoice.charge",
                                    "latest_invoice.charge.review",
                                    "latest_invoice.payment_intent",
                                    "latest_invoice.payment_intent.review"
                                })
                            });

                            var invoice = subscription.LatestInvoice;

                            return(CallbackResult.Ok(new TransactionInfo
                            {
                                TransactionId = GetTransactionId(invoice),
                                AmountAuthorized = AmountFromMinorUnits(invoice.PaymentIntent.Amount),
                                PaymentStatus = GetPaymentStatus(invoice)
                            },
                                                     new Dictionary <string, string>
                            {
                                { "stripeSessionId", stripeSession.Id },
                                { "stripeCustomerId", stripeSession.CustomerId },
                                { "stripePaymentIntentId", invoice.PaymentIntentId },
                                { "stripeSubscriptionId", stripeSession.SubscriptionId },
                                { "stripeChargeId", invoice.ChargeId },
                                { "stripeCardCountry", invoice.Charge?.PaymentMethodDetails?.Card?.Country }
                            }));
                        }
                    }
                    else if (stripeEvent != null && stripeEvent.Type == Events.ReviewClosed)
                    {
                        if (stripeEvent.Data?.Object?.Instance is Review stripeReview && !string.IsNullOrWhiteSpace(stripeReview.PaymentIntentId))
                        {
                            var paymentIntentService = new PaymentIntentService();
                            var paymentIntent        = paymentIntentService.Get(stripeReview.PaymentIntentId, new PaymentIntentGetOptions
                            {
                                Expand = new List <string>(new[] {
                                    "review"
                                })
                            });

                            return(CallbackResult.Ok(new TransactionInfo
                            {
                                TransactionId = GetTransactionId(paymentIntent),
                                AmountAuthorized = AmountFromMinorUnits(paymentIntent.Amount),
                                PaymentStatus = GetPaymentStatus(paymentIntent)
                            }));
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Stripe - ProcessCallback");
            }

            return(CallbackResult.BadRequest());
        }