1
1
using Bit . Billing . Constants ;
2
+ using Bit . Billing . Services ;
2
3
using Bit . Core . Context ;
3
4
using Bit . Core . Entities ;
4
5
using Bit . Core . Enums ;
@@ -44,12 +45,13 @@ public class StripeController : Controller
44
45
private readonly IAppleIapService _appleIapService ;
45
46
private readonly IMailService _mailService ;
46
47
private readonly ILogger < StripeController > _logger ;
47
- private readonly Braintree . BraintreeGateway _btGateway ;
48
+ private readonly BraintreeGateway _btGateway ;
48
49
private readonly IReferenceEventService _referenceEventService ;
49
50
private readonly ITaxRateRepository _taxRateRepository ;
50
51
private readonly IUserRepository _userRepository ;
51
52
private readonly ICurrentContext _currentContext ;
52
53
private readonly GlobalSettings _globalSettings ;
54
+ private readonly IStripeEventService _stripeEventService ;
53
55
54
56
public StripeController (
55
57
GlobalSettings globalSettings ,
@@ -67,7 +69,8 @@ public StripeController(
67
69
ILogger < StripeController > logger ,
68
70
ITaxRateRepository taxRateRepository ,
69
71
IUserRepository userRepository ,
70
- ICurrentContext currentContext )
72
+ ICurrentContext currentContext ,
73
+ IStripeEventService stripeEventService )
71
74
{
72
75
_billingSettings = billingSettings ? . Value ;
73
76
_hostingEnvironment = hostingEnvironment ;
@@ -83,7 +86,7 @@ public StripeController(
83
86
_taxRateRepository = taxRateRepository ;
84
87
_userRepository = userRepository ;
85
88
_logger = logger ;
86
- _btGateway = new Braintree . BraintreeGateway
89
+ _btGateway = new BraintreeGateway
87
90
{
88
91
Environment = globalSettings . Braintree . Production ?
89
92
Braintree . Environment . PRODUCTION : Braintree . Environment . SANDBOX ,
@@ -93,6 +96,7 @@ public StripeController(
93
96
} ;
94
97
_currentContext = currentContext ;
95
98
_globalSettings = globalSettings ;
99
+ _stripeEventService = stripeEventService ;
96
100
}
97
101
98
102
[ HttpPost ( "webhook" ) ]
@@ -103,7 +107,7 @@ public async Task<IActionResult> PostWebhook([FromQuery] string key)
103
107
return new BadRequestResult ( ) ;
104
108
}
105
109
106
- Stripe . Event parsedEvent ;
110
+ Event parsedEvent ;
107
111
using ( var sr = new StreamReader ( HttpContext . Request . Body ) )
108
112
{
109
113
var json = await sr . ReadToEndAsync ( ) ;
@@ -125,7 +129,7 @@ public async Task<IActionResult> PostWebhook([FromQuery] string key)
125
129
}
126
130
127
131
// If the customer and server cloud regions don't match, early return 200 to avoid unnecessary errors
128
- if ( ! await ValidateCloudRegionAsync ( parsedEvent ) )
132
+ if ( ! await _stripeEventService . ValidateCloudRegion ( parsedEvent ) )
129
133
{
130
134
return new OkResult ( ) ;
131
135
}
@@ -135,7 +139,7 @@ public async Task<IActionResult> PostWebhook([FromQuery] string key)
135
139
136
140
if ( subDeleted || subUpdated )
137
141
{
138
- var subscription = await GetSubscriptionAsync ( parsedEvent , true ) ;
142
+ var subscription = await _stripeEventService . GetSubscription ( parsedEvent , true ) ;
139
143
var ids = GetIdsFromMetaData ( subscription . Metadata ) ;
140
144
var organizationId = ids . Item1 ?? Guid . Empty ;
141
145
var userId = ids . Item2 ?? Guid . Empty ;
@@ -204,7 +208,7 @@ await _userService.UpdatePremiumExpirationAsync(userId,
204
208
}
205
209
else if ( parsedEvent . Type . Equals ( HandledStripeWebhook . UpcomingInvoice ) )
206
210
{
207
- var invoice = await GetInvoiceAsync ( parsedEvent ) ;
211
+ var invoice = await _stripeEventService . GetInvoice ( parsedEvent ) ;
208
212
var subscriptionService = new SubscriptionService ( ) ;
209
213
var subscription = await subscriptionService . GetAsync ( invoice . SubscriptionId ) ;
210
214
if ( subscription == null )
@@ -250,7 +254,7 @@ await _mailService.SendInvoiceUpcomingAsync(email, invoice.AmountDue / 100M,
250
254
}
251
255
else if ( parsedEvent . Type . Equals ( HandledStripeWebhook . ChargeSucceeded ) )
252
256
{
253
- var charge = await GetChargeAsync ( parsedEvent ) ;
257
+ var charge = await _stripeEventService . GetCharge ( parsedEvent ) ;
254
258
var chargeTransaction = await _transactionRepository . GetByGatewayIdAsync (
255
259
GatewayType . Stripe , charge . Id ) ;
256
260
if ( chargeTransaction != null )
@@ -377,7 +381,7 @@ await _mailService.SendInvoiceUpcomingAsync(email, invoice.AmountDue / 100M,
377
381
}
378
382
else if ( parsedEvent . Type . Equals ( HandledStripeWebhook . ChargeRefunded ) )
379
383
{
380
- var charge = await GetChargeAsync ( parsedEvent ) ;
384
+ var charge = await _stripeEventService . GetCharge ( parsedEvent ) ;
381
385
var chargeTransaction = await _transactionRepository . GetByGatewayIdAsync (
382
386
GatewayType . Stripe , charge . Id ) ;
383
387
if ( chargeTransaction == null )
@@ -427,7 +431,7 @@ await _transactionRepository.CreateAsync(new Transaction
427
431
}
428
432
else if ( parsedEvent . Type . Equals ( HandledStripeWebhook . PaymentSucceeded ) )
429
433
{
430
- var invoice = await GetInvoiceAsync ( parsedEvent , true ) ;
434
+ var invoice = await _stripeEventService . GetInvoice ( parsedEvent , true ) ;
431
435
if ( invoice . Paid && invoice . BillingReason == "subscription_create" )
432
436
{
433
437
var subscriptionService = new SubscriptionService ( ) ;
@@ -479,125 +483,53 @@ await _referenceEventService.RaiseEventAsync(
479
483
}
480
484
else if ( parsedEvent . Type . Equals ( HandledStripeWebhook . PaymentFailed ) )
481
485
{
482
- await HandlePaymentFailed ( await GetInvoiceAsync ( parsedEvent , true ) ) ;
486
+ await HandlePaymentFailed ( await _stripeEventService . GetInvoice ( parsedEvent , true ) ) ;
483
487
}
484
488
else if ( parsedEvent . Type . Equals ( HandledStripeWebhook . InvoiceCreated ) )
485
489
{
486
- var invoice = await GetInvoiceAsync ( parsedEvent , true ) ;
490
+ var invoice = await _stripeEventService . GetInvoice ( parsedEvent , true ) ;
487
491
if ( ! invoice . Paid && UnpaidAutoChargeInvoiceForSubscriptionCycle ( invoice ) )
488
492
{
489
493
await AttemptToPayInvoiceAsync ( invoice ) ;
490
494
}
491
495
}
492
496
else if ( parsedEvent . Type . Equals ( HandledStripeWebhook . PaymentMethodAttached ) )
493
497
{
494
- var paymentMethod = await GetPaymentMethodAsync ( parsedEvent ) ;
498
+ var paymentMethod = await _stripeEventService . GetPaymentMethod ( parsedEvent ) ;
495
499
await HandlePaymentMethodAttachedAsync ( paymentMethod ) ;
496
500
}
497
- else
498
- {
499
- _logger . LogWarning ( "Unsupported event received. " + parsedEvent . Type ) ;
500
- }
501
-
502
- return new OkResult ( ) ;
503
- }
504
-
505
- /// <summary>
506
- /// Ensures that the customer associated with the parsed event's data is in the correct region for this server.
507
- /// We use the customer instead of the subscription given that all subscriptions have customers, but not all
508
- /// customers have subscriptions
509
- /// </summary>
510
- /// <param name="parsedEvent"></param>
511
- /// <returns>true if the customer's region and the server's region match, otherwise false</returns>
512
- /// <exception cref="Exception"></exception>
513
- private async Task < bool > ValidateCloudRegionAsync ( Event parsedEvent )
514
- {
515
- var serverRegion = _globalSettings . BaseServiceUri . CloudRegion ;
516
- var eventType = parsedEvent . Type ;
517
- var expandOptions = new List < string > { "customer" } ;
518
-
519
- try
501
+ else if ( parsedEvent . Type . Equals ( HandledStripeWebhook . CustomerUpdated ) )
520
502
{
521
- Dictionary < string , string > customerMetadata ;
522
- switch ( eventType )
523
- {
524
- case HandledStripeWebhook . SubscriptionDeleted :
525
- case HandledStripeWebhook . SubscriptionUpdated :
526
- customerMetadata = ( await GetSubscriptionAsync ( parsedEvent , true , expandOptions ) ) ? . Customer
527
- ? . Metadata ;
528
- break ;
529
- case HandledStripeWebhook . ChargeSucceeded :
530
- case HandledStripeWebhook . ChargeRefunded :
531
- customerMetadata = ( await GetChargeAsync ( parsedEvent , true , expandOptions ) ) ? . Customer ? . Metadata ;
532
- break ;
533
- case HandledStripeWebhook . UpcomingInvoice :
534
- customerMetadata = ( await GetInvoiceAsync ( parsedEvent ) ) ? . Customer ? . Metadata ;
535
- break ;
536
- case HandledStripeWebhook . PaymentSucceeded :
537
- case HandledStripeWebhook . PaymentFailed :
538
- case HandledStripeWebhook . InvoiceCreated :
539
- customerMetadata = ( await GetInvoiceAsync ( parsedEvent , true , expandOptions ) ) ? . Customer ? . Metadata ;
540
- break ;
541
- case HandledStripeWebhook . PaymentMethodAttached :
542
- customerMetadata = ( await GetPaymentMethodAsync ( parsedEvent , true , expandOptions ) )
543
- ? . Customer
544
- ? . Metadata ;
545
- break ;
546
- default :
547
- customerMetadata = null ;
548
- break ;
549
- }
503
+ var customer =
504
+ await _stripeEventService . GetCustomer ( parsedEvent , true , new List < string > { "subscriptions" } ) ;
550
505
551
- if ( customerMetadata is null )
506
+ if ( customer . Subscriptions == null || ! customer . Subscriptions . Any ( ) )
552
507
{
553
- return false ;
508
+ return new OkResult ( ) ;
554
509
}
555
510
556
- var customerRegion = GetCustomerRegionFromMetadata ( customerMetadata ) ;
511
+ var subscription = customer . Subscriptions . First ( ) ;
557
512
558
- return customerRegion == serverRegion ;
559
- }
560
- catch ( Exception e )
561
- {
562
- _logger . LogError ( e , "Encountered unexpected error while validating cloud region" ) ;
563
- throw ;
564
- }
565
- }
513
+ var ( organizationId , _) = GetIdsFromMetaData ( subscription . Metadata ) ;
566
514
567
- /// <summary>
568
- /// Gets the customer's region from the metadata.
569
- /// </summary>
570
- /// <param name="customerMetadata">The metadata of the customer.</param>
571
- /// <returns>The region of the customer. If the region is not specified, it returns "US", if metadata is null,
572
- /// it returns null. It is case insensitive.</returns>
573
- private static string GetCustomerRegionFromMetadata ( IDictionary < string , string > customerMetadata )
574
- {
575
- const string defaultRegion = "US" ;
515
+ if ( ! organizationId . HasValue )
516
+ {
517
+ return new OkResult ( ) ;
518
+ }
576
519
577
- if ( customerMetadata is null )
578
- {
579
- return null ;
580
- }
520
+ var organization = await _organizationRepository . GetByIdAsync ( organizationId . Value ) ;
521
+ organization . BillingEmail = customer . Email ;
522
+ await _organizationRepository . ReplaceAsync ( organization ) ;
581
523
582
- if ( customerMetadata . TryGetValue ( "region" , out var value ) )
583
- {
584
- return value ;
524
+ await _referenceEventService . RaiseEventAsync (
525
+ new ReferenceEvent ( ReferenceEventType . OrganizationEditedInStripe , organization , _currentContext ) ) ;
585
526
}
586
-
587
- var miscasedRegionKey = customerMetadata . Keys
588
- . FirstOrDefault ( key =>
589
- key . Equals ( "region" , StringComparison . OrdinalIgnoreCase ) ) ;
590
-
591
- if ( miscasedRegionKey is null )
527
+ else
592
528
{
593
- return defaultRegion ;
529
+ _logger . LogWarning ( "Unsupported event received. " + parsedEvent . Type ) ;
594
530
}
595
531
596
- _ = customerMetadata . TryGetValue ( miscasedRegionKey , out var regionValue ) ;
597
-
598
- return ! string . IsNullOrWhiteSpace ( regionValue )
599
- ? regionValue
600
- : defaultRegion ;
532
+ return new OkResult ( ) ;
601
533
}
602
534
603
535
private async Task HandlePaymentMethodAttachedAsync ( PaymentMethod paymentMethod )
@@ -975,109 +907,6 @@ private bool UnpaidAutoChargeInvoiceForSubscriptionCycle(Invoice invoice)
975
907
invoice . BillingReason == "subscription_cycle" && invoice . SubscriptionId != null ;
976
908
}
977
909
978
- private async Task < Charge > GetChargeAsync ( Event parsedEvent , bool fresh = false , List < string > expandOptions = null )
979
- {
980
- if ( ! ( parsedEvent . Data . Object is Charge eventCharge ) )
981
- {
982
- throw new Exception ( "Charge is null (from parsed event). " + parsedEvent . Id ) ;
983
- }
984
- if ( ! fresh )
985
- {
986
- return eventCharge ;
987
- }
988
- var chargeService = new ChargeService ( ) ;
989
- var chargeGetOptions = new ChargeGetOptions { Expand = expandOptions } ;
990
- var charge = await chargeService . GetAsync ( eventCharge . Id , chargeGetOptions ) ;
991
- if ( charge == null )
992
- {
993
- throw new Exception ( "Charge is null. " + eventCharge . Id ) ;
994
- }
995
- return charge ;
996
- }
997
-
998
- private async Task < Invoice > GetInvoiceAsync ( Stripe . Event parsedEvent , bool fresh = false , List < string > expandOptions = null )
999
- {
1000
- if ( ! ( parsedEvent . Data . Object is Invoice eventInvoice ) )
1001
- {
1002
- throw new Exception ( "Invoice is null (from parsed event). " + parsedEvent . Id ) ;
1003
- }
1004
- if ( ! fresh )
1005
- {
1006
- return eventInvoice ;
1007
- }
1008
- var invoiceService = new InvoiceService ( ) ;
1009
- var invoiceGetOptions = new InvoiceGetOptions { Expand = expandOptions } ;
1010
- var invoice = await invoiceService . GetAsync ( eventInvoice . Id , invoiceGetOptions ) ;
1011
- if ( invoice == null )
1012
- {
1013
- throw new Exception ( "Invoice is null. " + eventInvoice . Id ) ;
1014
- }
1015
- return invoice ;
1016
- }
1017
-
1018
- private async Task < Subscription > GetSubscriptionAsync ( Stripe . Event parsedEvent , bool fresh = false ,
1019
- List < string > expandOptions = null )
1020
- {
1021
- if ( parsedEvent . Data . Object is not Subscription eventSubscription )
1022
- {
1023
- throw new Exception ( "Subscription is null (from parsed event). " + parsedEvent . Id ) ;
1024
- }
1025
- if ( ! fresh )
1026
- {
1027
- return eventSubscription ;
1028
- }
1029
- var subscriptionService = new SubscriptionService ( ) ;
1030
- var subscriptionGetOptions = new SubscriptionGetOptions { Expand = expandOptions } ;
1031
- var subscription = await subscriptionService . GetAsync ( eventSubscription . Id , subscriptionGetOptions ) ;
1032
- if ( subscription == null )
1033
- {
1034
- throw new Exception ( "Subscription is null. " + eventSubscription . Id ) ;
1035
- }
1036
- return subscription ;
1037
- }
1038
-
1039
- private async Task < Customer > GetCustomerAsync ( string customerId )
1040
- {
1041
- if ( string . IsNullOrWhiteSpace ( customerId ) )
1042
- {
1043
- throw new Exception ( "Customer ID cannot be empty when attempting to get a customer from Stripe" ) ;
1044
- }
1045
-
1046
- var customerService = new CustomerService ( ) ;
1047
- var customer = await customerService . GetAsync ( customerId ) ;
1048
- if ( customer == null )
1049
- {
1050
- throw new Exception ( $ "Customer is null. { customerId } ") ;
1051
- }
1052
-
1053
- return customer ;
1054
- }
1055
-
1056
- private async Task < PaymentMethod > GetPaymentMethodAsync ( Event parsedEvent , bool fresh = false ,
1057
- List < string > expandOptions = null )
1058
- {
1059
- if ( parsedEvent . Data . Object is not PaymentMethod eventPaymentMethod )
1060
- {
1061
- throw new Exception ( "Invoice is null (from parsed event). " + parsedEvent . Id ) ;
1062
- }
1063
-
1064
- if ( ! fresh )
1065
- {
1066
- return eventPaymentMethod ;
1067
- }
1068
-
1069
- var paymentMethodService = new PaymentMethodService ( ) ;
1070
- var paymentMethodGetOptions = new PaymentMethodGetOptions { Expand = expandOptions } ;
1071
- var paymentMethod = await paymentMethodService . GetAsync ( eventPaymentMethod . Id , paymentMethodGetOptions ) ;
1072
-
1073
- if ( paymentMethod == null )
1074
- {
1075
- throw new Exception ( $ "Payment method is null. { eventPaymentMethod . Id } ") ;
1076
- }
1077
-
1078
- return paymentMethod ;
1079
- }
1080
-
1081
910
private async Task < Subscription > VerifyCorrectTaxRateForCharge ( Invoice invoice , Subscription subscription )
1082
911
{
1083
912
if ( ! string . IsNullOrWhiteSpace ( invoice ? . CustomerAddress ? . Country ) && ! string . IsNullOrWhiteSpace ( invoice ? . CustomerAddress ? . PostalCode ) )
0 commit comments