-
Notifications
You must be signed in to change notification settings - Fork 121
v1.0.37 #757
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v1.0.37 #757
Conversation
…ustomer management
…stomer and payment link creation
…ving invoice details and using a generic label
…unction for improved readability
Feat/razorpay integration
…efactor coupon service to use new DTO conversion
…chedulePhase entities and related logic from the codebase
…with SubscriptionLineItem
…to CouponAssociation schema and mutation
…seID, StartDate, and EndDate fields
…and enhance filtering capabilities with CouponAssociationFilter
… associations and update repository methods for improved filtering capabilities
…ject and reduce bloat
…fy method to utilize new filtering capabilities
… handling and validation in subscriptions
…nCouponRequest to ensure end date is not before start date
…xes for subscription management
…enhance SubscriptionPhase with subscription relationship
…st struct for improved readability
…d in CreateSubscriptionRequest, recommending SubscriptionCoupons as the preferred alternative
…d update coupon association methods to handle new field
…uponFilter to ensure proper filtering
… repository, service, and webhook integration
…lidate creation, retrieval, updating, and deletion of subscription phases
…haseRequest and add validation for SubscriptionPhaseCreateRequest
… Razorpay integration
…ce data conversion
feat(razorpay): implement invoice synchronization service and enhance…
feat(wallet): establish top-up via tx(pending) -> inv(payment) -> tx(completed) workflow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (2)
internal/integration/razorpay/customer.go (1)
221-235: Mapping creation failures leave system in inconsistent state.When
entityIntegrationMappingRepo.Createfails (lines 221-228), the error is logged but the method still returns the Razorpay customer ID as if everything succeeded. This creates a situation where:
- The customer exists in Razorpay
- No local mapping exists
- Future calls to
EnsureCustomerSyncedToRazorpaywill attempt to create another Razorpay customer for the same FlexPrice customerConsider returning both the Razorpay ID and the error so callers can handle the inconsistent state:
err = s.entityIntegrationMappingRepo.Create(ctx, mapping) if err != nil { s.logger.Errorw("failed to store Razorpay customer mapping", "error", err, "customer_id", flexpriceCustomer.ID, "razorpay_customer_id", razorpayCustomerID) - // Don't fail the entire operation if mapping storage fails - // The customer was created successfully in Razorpay + return razorpayCustomerID, ierr.WithError(err). + WithHint("Customer created in Razorpay but failed to store local mapping"). + WithReportableDetails(map[string]interface{}{ + "customer_id": flexpriceCustomer.ID, + "razorpay_customer_id": razorpayCustomerID, + }). + Mark(ierr.ErrDatabase) } else { s.logger.Infow("stored Razorpay customer mapping", "customer_id", flexpriceCustomer.ID, "razorpay_customer_id", razorpayCustomerID) }internal/integration/razorpay/client.go (1)
279-294: Webhook signature verification vulnerable to timing attacks.Line 285 uses string equality (
==) to compare HMAC signatures, which is vulnerable to timing attacks. This security issue was previously identified and should usehmac.Equalwith constant-time comparison.As detailed in the previous review, apply this diff:
mac := hmac.New(sha256.New, []byte(secretForVerification)) mac.Write(payload) - expectedSignature := hex.EncodeToString(mac.Sum(nil)) + expectedMAC := mac.Sum(nil) - if expectedSignature != signature { + providedMAC, err := hex.DecodeString(signature) + if err != nil { + c.logger.Errorw("invalid webhook signature encoding", + "error", err, + "payload_length", len(payload), + "using_webhook_secret", config.WebhookSecret != "") + return ierr.NewError("webhook signature verification failed"). + WithHint("Invalid webhook signature"). + Mark(ierr.ErrValidation) + } + + if !hmac.Equal(expectedMAC, providedMAC) { c.logger.Errorw("webhook signature mismatch", - "expected_signature_length", len(expectedSignature), - "received_signature_length", len(signature), + "expected_signature_length", len(expectedMAC), + "received_signature_length", len(providedMAC), "payload_length", len(payload), "using_webhook_secret", config.WebhookSecret != "") return ierr.NewError("webhook signature verification failed"). WithHint("Invalid webhook signature"). Mark(ierr.ErrValidation) }
🧹 Nitpick comments (1)
internal/integration/razorpay/client.go (1)
244-246: Address or remove TODO comment.The TODO comment at lines 244-246 suggests converting the payment link response to a struct. If this is intended work, create an issue to track it. Otherwise, remove the comment.
Consider opening an issue to track this refactoring work or implement it now if it's a quick change.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (8)
internal/integration/factory.go(6 hunks)internal/integration/razorpay/client.go(1 hunks)internal/integration/razorpay/customer.go(1 hunks)internal/integration/razorpay/dto.go(1 hunks)internal/integration/razorpay/invoice.go(1 hunks)internal/integration/razorpay/payment.go(1 hunks)internal/integration/razorpay/webhook/handler.go(1 hunks)internal/integration/razorpay/webhook/types.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- internal/integration/factory.go
- internal/integration/razorpay/dto.go
🧰 Additional context used
🧬 Code graph analysis (5)
internal/integration/razorpay/webhook/handler.go (3)
internal/integration/razorpay/client.go (1)
RazorpayClient(18-29)internal/types/payment.go (2)
PaymentStatusSucceeded(15-15)PaymentStatusFailed(17-17)internal/api/dto/payment.go (1)
UpdatePaymentRequest(33-42)
internal/integration/razorpay/invoice.go (10)
internal/integration/razorpay/client.go (1)
RazorpayClient(18-29)internal/logger/logger.go (1)
Logger(13-15)internal/integration/razorpay/dto.go (4)
RazorpayInvoiceSyncRequest(96-98)RazorpayInvoiceSyncResponse(101-110)RazorpayLineItem(113-119)DefaultItemName(10-10)internal/errors/errors.go (4)
ErrNotFound(13-13)ErrDatabase(20-20)ErrInternal(22-22)ErrValidation(16-16)internal/errors/builder.go (1)
WithError(26-28)internal/types/invoice.go (2)
InvoiceLineItemEntityTypePlan(14-14)InvoiceLineItemEntityTypeAddon(15-15)internal/types/uuid.go (2)
GenerateUUIDWithPrefix(19-24)UUID_PREFIX_ENTITY_INTEGRATION_MAPPING(98-98)internal/types/entityintegrationmapping.go (2)
IntegrationEntityTypeInvoice(14-14)EntityIntegrationMappingFilter(43-59)internal/types/secret.go (1)
SecretProviderRazorpay(35-35)internal/types/filter.go (2)
QueryFilter(61-68)NewDefaultQueryFilter(71-79)
internal/integration/razorpay/customer.go (9)
internal/integration/razorpay/client.go (1)
RazorpayClient(18-29)internal/logger/logger.go (1)
Logger(13-15)internal/errors/builder.go (1)
WithError(26-28)internal/errors/errors.go (3)
ErrNotFound(13-13)ErrAlreadyExists(14-14)ErrSystem(21-21)internal/types/entityintegrationmapping.go (2)
EntityIntegrationMappingFilter(43-59)IntegrationEntityTypeCustomer(12-12)internal/types/secret.go (1)
SecretProviderRazorpay(35-35)internal/api/dto/customer.go (1)
UpdateCustomerRequest(64-97)internal/types/context.go (1)
GetEnvironmentID(54-59)internal/types/uuid.go (2)
GenerateUUIDWithPrefix(19-24)UUID_PREFIX_ENTITY_INTEGRATION_MAPPING(98-98)
internal/integration/razorpay/client.go (7)
internal/integration/razorpay/dto.go (1)
RazorpayConfig(17-21)internal/domain/connection/model.go (1)
Connection(10-19)internal/security/encryption.go (1)
EncryptionService(18-27)internal/logger/logger.go (1)
Logger(13-15)internal/types/secret.go (1)
SecretProviderRazorpay(35-35)internal/errors/errors.go (4)
ErrNotFound(13-13)ErrValidation(16-16)ErrInternal(22-22)ErrDatabase(20-20)internal/errors/builder.go (1)
WithError(26-28)
internal/integration/razorpay/payment.go (10)
internal/integration/razorpay/client.go (1)
RazorpayClient(18-29)internal/integration/razorpay/customer.go (2)
RazorpayCustomerService(17-21)CustomerService(24-29)internal/integration/razorpay/invoice.go (1)
InvoiceSyncService(19-25)internal/logger/logger.go (1)
Logger(13-15)internal/integration/razorpay/dto.go (4)
CreatePaymentLinkRequest(71-81)RazorpayPaymentLinkResponse(84-93)DefaultInvoiceLabel(13-13)DefaultItemName(10-10)internal/errors/errors.go (3)
ErrNotFound(13-13)ErrValidation(16-16)ErrSystem(21-21)internal/types/payment.go (5)
PaymentStatusSucceeded(15-15)PaymentStatusOverpaid(16-16)PaymentStatusPending(13-13)PaymentDestinationTypeInvoice(86-86)PaymentMethodTypeCard(52-52)internal/errors/builder.go (1)
WithError(26-28)internal/types/payment_gateway.go (1)
PaymentGatewayTypeRazorpay(12-12)internal/api/dto/payment.go (2)
CreatePaymentRequest(16-30)UpdatePaymentRequest(33-42)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: deploy
🔇 Additional comments (6)
internal/integration/razorpay/webhook/handler.go (2)
59-81: LGTM - Robust webhook event routing with proper error tolerance.The webhook handler correctly:
- Routes events by type with a clear switch statement
- Returns
nilfor unhandled event types (prevents unnecessary retries)- Logs unhandled events for visibility without failing
This error-tolerant pattern ensures Razorpay webhooks always receive 200 OK responses, preventing retry storms.
84-212: Comprehensive payment capture handling with proper safeguards.The
handlePaymentCapturedimplementation includes several good practices:
- Graceful handling of external payments (lines 96-115)
- Idempotency check for already-processed payments (lines 138-145)
- Correct amount conversion from paise to standard units using decimal arithmetic (line 152)
- Non-fatal invoice reconciliation with detailed logging (lines 196-209)
internal/integration/razorpay/webhook/types.go (1)
82-105: Excellent handling of Razorpay's inconsistent notes format.The
FlexibleNotescustom unmarshaler gracefully handles both object ({}) and array ([]) formats from Razorpay's webhook payloads. This defensive programming prevents webhook processing failures when Razorpay sends empty notes as an array instead of an object.The implementation:
- Attempts object unmarshaling first (expected format)
- Falls back to array unmarshaling (empty notes case)
- Returns clear error if neither format matches
internal/integration/razorpay/payment.go (3)
307-333: Multiple unsafe type assertions could panic on malformed Razorpay responses.Lines 307-333 perform several type assertions without proper error checking:
- Line 307:
paymentLinkID, ok := razorpayPaymentLink["id"].(string)- checked ✓- Line 316:
paymentLinkURL, ok := razorpayPaymentLink["short_url"].(string)- checked ✓- Line 326:
status, ok := razorpayPaymentLink["status"].(string)- checked ✓- Line 335:
createdAtFloat, ok := razorpayPaymentLink["created_at"].(float64)- checked ✓Actually, upon closer inspection, these type assertions ARE properly checked with the
okidiom and have fallback values. The code is safe.Good defensive programming with fallback values for optional fields.
509-520: Excellent idempotency check prevents duplicate payment records.The
PaymentExistsByGatewayPaymentIDcheck (lines 509-520) prevents duplicate payment records when webhooks are redelivered or processed multiple times. This is a critical safeguard for maintaining data integrity in the payment processing flow.
592-636: Two-step payment creation is the correct design pattern—no changes needed.Your review comment correctly identified the pattern, but verification confirms it's intentional and sound. The
ProcessPaymentflag explicitly controls whether immediate processing occurs: when set tofalse(line 116 ininternal/service/payment.go), the payment processor is skipped entirely. The subsequentUpdatePaymentcall correctly marks the payment as succeeded—the proper way to record externally-processed payments from Razorpay or Stripe.The DTO separation (distinct
CreatePaymentRequestandUpdatePaymentRequest) follows standard API design principles for immutability at boundaries. This pattern is consistently used across both Razorpay and Stripe integrations, with proper idempotency keys and error handling to prevent inconsistent state.While
CreatePaymentRequestcould theoretically accept an initial status field, the current design has clear merits: it avoids polluting the creation DTO with fields that don't apply universally, and it enforces logical separation between record initialization and state transitions.
| // EnsureCustomerSyncedToRazorpay ensures a customer is synced to Razorpay | ||
| func (s *CustomerService) EnsureCustomerSyncedToRazorpay(ctx context.Context, customerID string, customerService interfaces.CustomerService) (*customer.Customer, error) { | ||
| // Get FlexPrice customer | ||
| customerResp, err := customerService.GetCustomer(ctx, customerID) | ||
| if err != nil { | ||
| return nil, ierr.WithError(err). | ||
| WithHint("Failed to get customer"). | ||
| WithReportableDetails(map[string]interface{}{ | ||
| "customer_id": customerID, | ||
| }). | ||
| Mark(ierr.ErrNotFound) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect error marking - GetCustomer failures aren't always "not found".
Line 56 marks all errors from GetCustomer as ErrNotFound, but the underlying error could be a database connection failure, permission issue, or other system error. This misclassification could mislead callers and monitoring systems.
Apply this diff to preserve the original error type:
customerResp, err := customerService.GetCustomer(ctx, customerID)
if err != nil {
- return nil, ierr.WithError(err).
- WithHint("Failed to get customer").
- WithReportableDetails(map[string]interface{}{
- "customer_id": customerID,
- }).
- Mark(ierr.ErrNotFound)
+ return nil, ierr.WithError(err).
+ WithHint("Failed to get customer").
+ WithReportableDetails(map[string]interface{}{
+ "customer_id": customerID,
+ })
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // EnsureCustomerSyncedToRazorpay ensures a customer is synced to Razorpay | |
| func (s *CustomerService) EnsureCustomerSyncedToRazorpay(ctx context.Context, customerID string, customerService interfaces.CustomerService) (*customer.Customer, error) { | |
| // Get FlexPrice customer | |
| customerResp, err := customerService.GetCustomer(ctx, customerID) | |
| if err != nil { | |
| return nil, ierr.WithError(err). | |
| WithHint("Failed to get customer"). | |
| WithReportableDetails(map[string]interface{}{ | |
| "customer_id": customerID, | |
| }). | |
| Mark(ierr.ErrNotFound) | |
| } | |
| // EnsureCustomerSyncedToRazorpay ensures a customer is synced to Razorpay | |
| func (s *CustomerService) EnsureCustomerSyncedToRazorpay(ctx context.Context, customerID string, customerService interfaces.CustomerService) (*customer.Customer, error) { | |
| // Get FlexPrice customer | |
| customerResp, err := customerService.GetCustomer(ctx, customerID) | |
| if err != nil { | |
| return nil, ierr.WithError(err). | |
| WithHint("Failed to get customer"). | |
| WithReportableDetails(map[string]interface{}{ | |
| "customer_id": customerID, | |
| }) | |
| } |
🤖 Prompt for AI Agents
internal/integration/razorpay/customer.go lines 46-57: the current code
unconditionally marks any error from customerService.GetCustomer as
ierr.ErrNotFound which misclassifies non-NotFound failures; change the return to
preserve the original error type by only marking as ErrNotFound when the
underlying error actually indicates “not found” (e.g., check using the package's
helper like ierr.IsNotFound(err) or compare error value) and otherwise return
the wrapped error without Mark, so database/permission/system errors are not
misreported as ErrNotFound.
| razorpayInvoiceID := razorpayInvoice["id"].(string) | ||
| s.logger.Infow("successfully created invoice in Razorpay", | ||
| "invoice_id", req.InvoiceID, | ||
| "razorpay_invoice_id", razorpayInvoiceID) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsafe type assertion could panic on unexpected Razorpay response.
Line 112 performs a type assertion without checking if the value exists or is the correct type. If Razorpay returns an unexpected response format (e.g., missing id field or non-string type), this will panic and crash the service.
Apply this diff to add safe extraction:
- razorpayInvoiceID := razorpayInvoice["id"].(string)
+ razorpayInvoiceID, ok := razorpayInvoice["id"].(string)
+ if !ok || razorpayInvoiceID == "" {
+ return nil, ierr.NewError("razorpay invoice id missing in response").
+ WithHint("Check Razorpay CreateInvoice response payload").
+ Mark(ierr.ErrSystem)
+ }
s.logger.Infow("successfully created invoice in Razorpay",
"invoice_id", req.InvoiceID,
"razorpay_invoice_id", razorpayInvoiceID)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| razorpayInvoiceID := razorpayInvoice["id"].(string) | |
| s.logger.Infow("successfully created invoice in Razorpay", | |
| "invoice_id", req.InvoiceID, | |
| "razorpay_invoice_id", razorpayInvoiceID) | |
| razorpayInvoiceID, ok := razorpayInvoice["id"].(string) | |
| if !ok || razorpayInvoiceID == "" { | |
| return nil, ierr.NewError("razorpay invoice id missing in response"). | |
| WithHint("Check Razorpay CreateInvoice response payload"). | |
| Mark(ierr.ErrSystem) | |
| } | |
| s.logger.Infow("successfully created invoice in Razorpay", | |
| "invoice_id", req.InvoiceID, | |
| "razorpay_invoice_id", razorpayInvoiceID) |
🤖 Prompt for AI Agents
In internal/integration/razorpay/invoice.go around lines 112 to 116, the code
currently performs an unchecked type assertion on razorpayInvoice["id"] which
can panic if the key is missing or not a string; replace the direct assertion
with a safe extraction: first look up the value, verify it is present, assert
its type using the comma-ok idiom (string, ok), and if not present or not a
string log an error (including the raw response) and return an appropriate error
instead of proceeding; then use the safely extracted razorpayInvoiceID in the
success log.
…ount directly and removing unnecessary quantity calculations
refactor(razorpay): streamline line item processing by using total am…
fix(alertslog): fix sorting logic for alert logs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
internal/integration/razorpay/invoice.go (1)
112-115: Guard against missing Razorpay invoice IDs.The direct type assertion on Razorpay’s response will panic whenever
idis absent or not a string (e.g., API change, transient failure returning an error payload). That turns a recoverable integration error into a crash. Please extract the value defensively and return a structured error when it’s missing.- razorpayInvoiceID := razorpayInvoice["id"].(string) + rawInvoiceID, ok := razorpayInvoice["id"] + razorpayInvoiceID, ok := rawInvoiceID.(string) + if !ok || strings.TrimSpace(razorpayInvoiceID) == "" { + return nil, ierr.NewError("razorpay invoice id missing in response"). + WithHint("Check Razorpay CreateInvoice response payload"). + Mark(ierr.ErrInternal) + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
internal/api/dto/wallet.go(2 hunks)internal/api/v1/wallet.go(2 hunks)internal/domain/wallet/repository.go(1 hunks)internal/idempotency/generator.go(1 hunks)internal/integration/razorpay/invoice.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/api/v1/wallet.go
🧰 Additional context used
🧬 Code graph analysis (3)
internal/api/dto/wallet.go (3)
ent/schema/wallettransaction.go (5)
WalletTransaction(14-16)WalletTransaction(19-24)WalletTransaction(27-117)WalletTransaction(120-122)WalletTransaction(125-140)ent/wallettransaction.go (2)
WalletTransaction(18-69)WalletTransaction(72-91)ent/wallet.go (2)
Wallet(19-72)Wallet(75-96)
internal/domain/wallet/repository.go (2)
internal/domain/wallet/transaction.go (1)
Transaction(12-32)internal/types/wallet.go (1)
WalletTransactionFilter(159-172)
internal/integration/razorpay/invoice.go (11)
internal/integration/razorpay/client.go (1)
RazorpayClient(18-29)internal/integration/razorpay/customer.go (1)
CustomerService(24-29)internal/logger/logger.go (1)
Logger(13-15)internal/integration/razorpay/dto.go (4)
RazorpayInvoiceSyncRequest(96-98)RazorpayInvoiceSyncResponse(101-110)RazorpayLineItem(113-119)DefaultItemName(10-10)internal/errors/errors.go (4)
ErrNotFound(13-13)ErrDatabase(20-20)ErrInternal(22-22)ErrValidation(16-16)internal/errors/builder.go (1)
WithError(26-28)internal/types/invoice.go (2)
InvoiceLineItemEntityTypePlan(14-14)InvoiceLineItemEntityTypeAddon(15-15)internal/types/uuid.go (2)
GenerateUUIDWithPrefix(19-24)UUID_PREFIX_ENTITY_INTEGRATION_MAPPING(98-98)internal/types/entityintegrationmapping.go (2)
IntegrationEntityTypeInvoice(14-14)EntityIntegrationMappingFilter(43-59)internal/types/secret.go (1)
SecretProviderRazorpay(35-35)internal/types/filter.go (2)
QueryFilter(61-68)NewDefaultQueryFilter(71-79)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: deploy
🔇 Additional comments (4)
internal/idempotency/generator.go (1)
29-31: LGTM! TheScopeWalletTopUpconstant is properly defined and actively integrated into the wallet top-up implementation atinternal/service/wallet.go:322, where it's correctly used with the idempotency generator. Tests confirm the functionality works end-to-end.internal/api/dto/wallet.go (1)
443-451: LGTM! Well-structured response type.The
TopUpWalletResponsestruct provides a clear and comprehensive response format, separating transaction details, optional invoice reference, and resulting wallet state. The inline documentation effectively explains when each field is populated.internal/domain/wallet/repository.go (2)
28-28: No issues found. The implementation correctly protects immutable fields.The production implementation updates only
TxStatus,CreditBalanceBefore,CreditBalanceAfter,CreditsAvailable,UpdatedBy, andUpdatedAt—never touchingAmount,WalletID, orType. The WHERE clause further restricts updates to only published transactions.The two methods serve complementary purposes:
UpdateTransactionStatushandles status-only changes, whileUpdateTransactionhandles atomic updates of status and balance fields together. This design ensures consistency—balance fields stay synchronized with transaction status in a single operation. The existing code comments already document this distinction.
23-23: Verification complete — all concerns addressed.The implementation is properly supported by the database schema:
- GetTransactionByIdempotencyKey and UpdateTransaction methods are implemented at
internal/repository/ent/wallet.go:480and654respectively- Index exists on
idempotency_key(idx_tenant_environment_idempotency_key) with proper scoping to tenant, environment, and published status- Unique constraint is active on the index (scoped appropriately to prevent duplicates within the same tenant/environment for published transactions)
The approach correctly prevents duplicate transactions in the Razorpay integration flow.
| Priority *int `json:"priority,omitempty"` | ||
| // idempotency_key is a unique key for the transaction | ||
| IdempotencyKey *string `json:"idempotency_key" binding:"required"` | ||
| IdempotencyKey *string `json:"idempotency_key,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Requiring idempotency_key for purchased credit operations is critical to prevent duplicate charges.
Making idempotency_key optional for wallet top-ups is risky, especially for PURCHASED_CREDIT_INVOICED and PURCHASED_CREDIT_DIRECT transaction reasons. Without idempotency protection, retried requests due to network failures or timeouts could result in duplicate transactions and double-charging customers.
Note that ManualBalanceDebitRequest (Line 503) still requires idempotency_key, showing an inconsistency in approach.
Consider making idempotency_key conditionally required based on transaction_reason:
func (r *TopUpWalletRequest) Validate() error {
+ // Require idempotency key for purchased credit operations
+ purchaseReasons := []types.TransactionReason{
+ types.TransactionReasonPurchasedCreditInvoiced,
+ types.TransactionReasonPurchasedCreditDirect,
+ }
+ if lo.Contains(purchaseReasons, r.TransactionReason) && (r.IdempotencyKey == nil || *r.IdempotencyKey == "") {
+ return ierr.NewError("idempotency_key is required for purchased credit operations").
+ WithHint("Idempotency key prevents duplicate charges during retries").
+ Mark(ierr.ErrValidation)
+ }
+
if r.CreditsToAdd.LessThanOrEqual(decimal.Zero) {
return ierr.NewError("credits_to_add must be greater than 0").Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In internal/api/dto/wallet.go at line 390, the IdempotencyKey field is optional
but must be required for purchased-credit operations; make idempotency_key
required for transaction reasons PURCHASED_CREDIT_INVOICED and
PURCHASED_CREDIT_DIRECT by adding conditional validation: keep the pointer field
but add validation logic (where the DTO is bound/validated) that checks
transaction_reason and returns a validation error if idempotency_key is
nil/empty for those two reasons; ensure this behavior matches
ManualBalanceDebitRequest and update any JSON tags, validation annotations, and
unit/integration tests and API docs accordingly.
feat(invoice): move preview api from raw events to feature_usage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
internal/domain/events/repository.go (2)
184-190: Consider adding validation tags to the new fields.The embedded
UsageParamsstruct has validation tags likevalidate:"required"on several fields (e.g.,EventName,PropertyName,AggregationType). Consider whether the new fields inFeatureUsageParamsshould also have validation constraints.For example, if these fields are required for the
GetUsageForMaxMetersWithBucketsoperation, add appropriate validation tags:type FeatureUsageParams struct { *UsageParams - FeatureID string `json:"feature_id"` - PriceID string `json:"price_id"` - MeterID string `json:"meter_id"` - SubLineItemID string `json:"sub_line_item_id"` + FeatureID string `json:"feature_id" validate:"required"` + PriceID string `json:"price_id" validate:"required"` + MeterID string `json:"meter_id" validate:"required"` + SubLineItemID string `json:"sub_line_item_id" validate:"required"` }
185-185: Be mindful of nil pointer dereference with embedded pointer.The struct embeds
*UsageParamsas a pointer. Ensure that code usingFeatureUsageParamschecks for nil before accessing embedded fields to avoid potential nil pointer dereferences. This is particularly important in the implementation ofGetUsageForMaxMetersWithBuckets.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
internal/domain/events/feature_usage.go(1 hunks)internal/domain/events/models.go(1 hunks)internal/domain/events/repository.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
internal/domain/events/feature_usage.go (1)
internal/domain/events/repository.go (2)
FeatureUsageParams(184-190)AggregationResult(150-158)
internal/domain/events/repository.go (1)
ent/entitlement/where.go (1)
FeatureID(113-115)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: deploy
🔇 Additional comments (2)
internal/domain/events/models.go (1)
88-88: Verify the clickhouse repository implementation populates PriceID for UsageByFeatureResult.The inmemory store already populates PriceID correctly (internal/testutil/inmemory_feature_usage_store.go line 107), but the primary production repository at internal/repository/clickhouse/feature_usage.go needs verification to ensure GetFeatureUsageBySubscription properly maps the PriceID field when constructing UsageByFeatureResult objects from clickhouse query results.
internal/domain/events/feature_usage.go (1)
32-33: All implementations of FeatureUsageRepository include the new method.Verification confirms both concrete implementations have been properly updated:
InMemoryFeatureUsageStore(internal/testutil/inmemory_feature_usage_store.go:131) — has all 8 interface methodsFeatureUsageRepositoryin ClickHouse (internal/repository/clickhouse/feature_usage.go:1570) — has all 8 interface methodsThe breaking interface change is complete and correctly implemented across all implementations.
feat(invoice): consider max features also for entitlement
Important
Add Razorpay integration for payment processing and webhook handling, including client, services, and API updates.
internal/integration/razorpay.internal/integration/factory.goto include Razorpay inGetIntegrationByProvider()andGetAvailableProviders().internal/api/router.goandinternal/api/v1/webhook.go.RazorpayConnectionMetadatatointernal/types/connection.go.internal/domain/connection/model.goto support Razorpay metadata.internal/service/payment_processor.goto handle Razorpay payment link creation.go.modandgo.sumdependencies.This description was created by
for 806aefa. You can customize this summary. It will automatically update as commits are pushed.
Summary by CodeRabbit
New Features
Breaking Changes
Documentation