From 7f6644700d23a24580d64a86d3adab0d99590653 Mon Sep 17 00:00:00 2001 From: phuongntt Date: Wed, 20 Mar 2024 12:20:26 +0700 Subject: [PATCH 1/2] chore: Add admin create refund payment api --- .../payment_pwd_permission.py | 2 +- .../api/v1_0/payments/serializers.py | 14 ++++++ locker_server/api/v1_0/payments/views.py | 15 +++++- locker_server/api/v1_0/urls.py | 1 + .../core/services/payment_service.py | 12 +++++ .../shared/constants/transactions.py | 46 +++++++++---------- 6 files changed, 65 insertions(+), 25 deletions(-) diff --git a/locker_server/api/permissions/locker_permissions/payment_pwd_permission.py b/locker_server/api/permissions/locker_permissions/payment_pwd_permission.py index aaa4cea..12192ba 100644 --- a/locker_server/api/permissions/locker_permissions/payment_pwd_permission.py +++ b/locker_server/api/permissions/locker_permissions/payment_pwd_permission.py @@ -8,7 +8,7 @@ def has_permission(self, request, view): "upgrade_trial_enterprise_by_code", "calc_lifetime_public", "calc_subscription_public"]: return True elif view.action in ["list", "set_invoice_status", "user_invoices", "admin_upgrade_plan", "statistic_income", - "statistic_amount"]: + "statistic_amount", "create_refund"]: return self.is_admin(request) return self.is_auth(request) diff --git a/locker_server/api/v1_0/payments/serializers.py b/locker_server/api/v1_0/payments/serializers.py index 9cdeb6d..a27bf86 100644 --- a/locker_server/api/v1_0/payments/serializers.py +++ b/locker_server/api/v1_0/payments/serializers.py @@ -153,3 +153,17 @@ def to_internal_value(self, data): class CancelPlanSerializer(serializers.Serializer): immediately = serializers.BooleanField(default=False) + +class AdminCreateRefundPaymentSerializer(serializers.Serializer): + user_id = serializers.IntegerField() + total_price = serializers.FloatField() + plan = serializers.ChoiceField(choices=LIST_PLAN_TYPE) + payment_method = serializers.ChoiceField(choices=LIST_PAYMENT_METHOD) + currency = serializers.ChoiceField(choices=LIST_CURRENCY, default=CURRENCY_USD) + status = serializers.ChoiceField(choices=LIST_PAYMENT_STATUS, default=PAYMENT_STATUS_PAID, required=False) + + enterprise_id = serializers.CharField(required=False, max_length=128, allow_blank=True, allow_null=True) + description = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True) + metadata = serializers.CharField(required=False, max_length=512, allow_null=True, allow_blank=True) + saas_market = serializers.ChoiceField(choices=LIST_SAAS_MARKET, required=False) + bank_id = serializers.IntegerField(required=False, default=0) diff --git a/locker_server/api/v1_0/payments/views.py b/locker_server/api/v1_0/payments/views.py index 1b2c902..64e9577 100644 --- a/locker_server/api/v1_0/payments/views.py +++ b/locker_server/api/v1_0/payments/views.py @@ -23,7 +23,7 @@ from .serializers import CalcSerializer, ListInvoiceSerializer, AdminUpgradePlanSerializer, UpgradeTrialSerializer, \ UpgradeThreePromoSerializer, UpgradeLifetimeSerializer, UpgradeLifetimePublicSerializer, \ UpgradeEducationPublicSerializer, CancelPlanSerializer, UpgradePlanSerializer, DetailInvoiceSerializer, \ - CalcLifetimePublicSerializer, UpgradeSubscriptionPublicSerializer + CalcLifetimePublicSerializer, UpgradeSubscriptionPublicSerializer, AdminCreateRefundPaymentSerializer class PaymentPwdViewSet(APIBaseViewSet): @@ -60,6 +60,8 @@ def get_serializer_class(self): self.serializer_class = UpgradeEducationPublicSerializer elif self.action == "cancel_plan": self.serializer_class = CancelPlanSerializer + elif self.action == "create_refund": + self.serializer_class = AdminCreateRefundPaymentSerializer return super().get_serializer_class() def get_invoice(self): @@ -599,3 +601,14 @@ def statistic_amount(self, request, *args, **kwargs): }) return Response(status=status.HTTP_200_OK, data=statistic_result) + + @action(methods=["post"], detail=False) + def create_refund(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + validated_data = serializer.validated_data + try: + refund_payment = self.payment_service.create_refund_payment(**validated_data) + except UserDoesNotExistException: + raise ValidationError(detail={"user_id": ["The user does not exist"]}) + return Response(status=status.HTTP_200_OK, data={"id": refund_payment.id}) diff --git a/locker_server/api/v1_0/urls.py b/locker_server/api/v1_0/urls.py index 0d9fa68..0ed0f30 100644 --- a/locker_server/api/v1_0/urls.py +++ b/locker_server/api/v1_0/urls.py @@ -249,6 +249,7 @@ # ----------------------------------- Admin --------------------------------- # urlpatterns += [ url(r'^admin/payments/invoices$', views.PaymentPwdViewSet.as_view({'get': 'list'})), + url(r'^admin/payments/refund$', views.PaymentPwdViewSet.as_view({'post': 'create_refund'})), url(r'^admin/payments/statistic/income$', views.PaymentPwdViewSet.as_view({'get': 'statistic_income'})), url(r'^admin/payments/statistic/amount$', views.PaymentPwdViewSet.as_view({'get': 'statistic_amount'})), diff --git a/locker_server/core/services/payment_service.py b/locker_server/core/services/payment_service.py index 54f698a..4bb7789 100644 --- a/locker_server/core/services/payment_service.py +++ b/locker_server/core/services/payment_service.py @@ -772,3 +772,15 @@ def statistic_amount(self, **filters) -> Dict: def statistic(self, **filters) -> List[Dict]: return self.payment_repository.statistic_by_type(**filters) + + def create_refund_payment(self, **refund_payment_data) -> Optional[Payment]: + user_id = refund_payment_data.get("user_id") + user = self.user_repository.get_user_by_id(user_id=user_id) + if not user: + raise UserDoesNotExistException + refund_payment_data.update({ + "transaction_type": TRANSACTION_TYPE_REFUND, + "description": refund_payment_data.get("description", "Refund payment") + }) + refund_payment = self.payment_repository.create_payment(**refund_payment_data) + return refund_payment diff --git a/locker_server/shared/constants/transactions.py b/locker_server/shared/constants/transactions.py index c84bd5c..5705f5f 100644 --- a/locker_server/shared/constants/transactions.py +++ b/locker_server/shared/constants/transactions.py @@ -1,25 +1,22 @@ -TRIAL_PERSONAL_PLAN = 14 * 86400 # 14 days +TRIAL_PERSONAL_PLAN = 14 * 86400 # 14 days TRIAL_PERSONAL_DURATION_TEXT = "14 days" -TRIAL_PROMOTION = 6 * 30 * 86400 # 6 months +TRIAL_PROMOTION = 6 * 30 * 86400 # 6 months TRIAL_PROMOTION_DURATION_TEXT = "6 months" -TRIAL_BETA_EXPIRED = 1654016400 # 1/6/2022 00:00:00 -TRIAL_TEAM_PLAN = 14 * 86400 # 14 days +TRIAL_BETA_EXPIRED = 1654016400 # 1/6/2022 00:00:00 +TRIAL_TEAM_PLAN = 14 * 86400 # 14 days TRIAL_TEAM_MEMBERS = 10 -REFERRAL_EXTRA_TIME = 86400 * 30 # 30 days +REFERRAL_EXTRA_TIME = 86400 * 30 # 30 days REFERRAL_LIMIT = 3 - # -------------------- Max attempts ------------------------------ # MAX_ATTEMPTS = 3 - # -------------------- UTM Source Promotion ------------------------ # UTM_SOURCE_PROMOTIONS = "plans-promotion" LIST_UTM_SOURCE_PROMOTIONS = [UTM_SOURCE_PROMOTIONS] - # ------------------- Currency ------------------------------------- # CURRENCY_VND = "VND" CURRENCY_USD = "USD" @@ -31,7 +28,6 @@ TRANSACTION_TYPE_PAYMENT = "Payment" TRANSACTION_TYPE_REFUND: str = "Refund" - # ------------------- Payment method -------------------------------- # PAYMENT_METHOD_CARD = "card" PAYMENT_METHOD_BANKING = "banking" @@ -40,15 +36,17 @@ LIST_PAYMENT_METHOD = [PAYMENT_METHOD_CARD] - # ------------------- Payment status -------------------------------- # -PAYMENT_STATUS_FAILED = "failed" # Failed -PAYMENT_STATUS_PAID = "paid" # Successful -PAYMENT_STATUS_PENDING = "pending" # Contact pending -PAYMENT_STATUS_PROCESSING = "processing" # CyStack banking processing -PAYMENT_STATUS_PAST_DUE = "past_due" # Subscription failed - - +PAYMENT_STATUS_FAILED = "failed" # Failed +PAYMENT_STATUS_PAID = "paid" # Successful +PAYMENT_STATUS_PENDING = "pending" # Contact pending +PAYMENT_STATUS_PROCESSING = "processing" # CyStack banking processing +PAYMENT_STATUS_PAST_DUE = "past_due" # Subscription failed + +LIST_PAYMENT_STATUS = [ + PAYMENT_STATUS_FAILED, PAYMENT_STATUS_PAID, PAYMENT_STATUS_PENDING, + PAYMENT_STATUS_PROCESSING, PAYMENT_STATUS_PAST_DUE +] # ------------------- Duration types -------------------------------- # DURATION_MONTHLY = "monthly" DURATION_HALF_YEARLY = "half_yearly" @@ -56,17 +54,14 @@ LIST_DURATION = [DURATION_MONTHLY, DURATION_YEARLY] - # ------------------- Promo code types ------------------------------ # PROMO_AMOUNT = "amount_off" PROMO_PERCENTAGE = "percentage_off" - # ------------------- Promo code prefix ---------------------- # MISSION_REWARD_PROMO_PREFIX = "LKMR-" EDUCATION_PROMO_PREFIX = "EDU-" - # ------------------- Plan type constants --------------------------- # PLAN_TYPE_PM_FREE = "pm_free" PLAN_TYPE_PM_PREMIUM = "pm_premium" @@ -77,11 +72,16 @@ PLAN_TYPE_PM_LIFETIME = "pm_lifetime_premium" PLAN_TYPE_PM_LIFETIME_FAMILY = "pm_lifetime_family" LIST_LIFETIME_PLAN = [PLAN_TYPE_PM_LIFETIME, PLAN_TYPE_PM_LIFETIME_FAMILY] - - +LIST_PLAN_TYPE = [PLAN_TYPE_PM_FREE, PLAN_TYPE_PM_PREMIUM, PLAN_TYPE_PM_FAMILY, PLAN_TYPE_PM_ENTERPRISE, + PLAN_TYPE_PM_LIFETIME, PLAN_TYPE_PM_LIFETIME_FAMILY] FAMILY_MAX_MEMBER = 6 - # ------------- Banking code ----------------------------- # BANKING_ID_PWD_MANAGER = "LK" BANKING_ID_WEB_SECURITY = "CW" + +# ------------ Saas market ------------------------------- # +SAAS_MARKET_STACK_COMMERCE = "StackCommerce" +SAAS_MARKET_PITCH_GROUND = "PitchGround" +SAAS_MARKET_DEAL_FUEL = "DealFuel" +LIST_SAAS_MARKET = [SAAS_MARKET_STACK_COMMERCE, SAAS_MARKET_DEAL_FUEL, SAAS_MARKET_PITCH_GROUND] From 8b6d8b7e639e21a7a4c9358b39c3ec84a2ba6d44 Mon Sep 17 00:00:00 2001 From: phuongntt Date: Wed, 20 Mar 2024 12:31:16 +0700 Subject: [PATCH 2/2] chore: Fix create affiliate submission --- .../api_orm/repositories/affiliate_submission_repository.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/locker_server/api_orm/repositories/affiliate_submission_repository.py b/locker_server/api_orm/repositories/affiliate_submission_repository.py index 40c08b8..a50debe 100644 --- a/locker_server/api_orm/repositories/affiliate_submission_repository.py +++ b/locker_server/api_orm/repositories/affiliate_submission_repository.py @@ -4,6 +4,7 @@ from locker_server.api_orm.models.wrapper import get_affiliate_submission_model from locker_server.core.entities.form_submission.affiliate_submission import AffiliateSubmission from locker_server.core.repositories.affiliate_submission_repository import AffiliateSubmissionRepository +from locker_server.shared.utils.app import now AffiliateSubmissionORM = get_affiliate_submission_model() ModelParser = get_model_parser() @@ -35,6 +36,9 @@ def get_affiliate_submission_by_id(self, affiliate_submission_id: str) -> Option # ------------------------ Create AffiliateSubmission resource --------------------- # def create_affiliate_submission(self, affiliate_submission_create_data) -> Optional[AffiliateSubmission]: + affiliate_submission_create_data.update({ + "created_time": now() + }) affiliate_submission_orm = AffiliateSubmissionORM.objects.create(**affiliate_submission_create_data) return ModelParser.form_submission_parser().parse_affiliate_submission( affiliate_submission_orm=affiliate_submission_orm