
    Bi                       d dl mZmZmZmZmZ d dlmZmZ d dl	m
Z
mZ d dlmZmZmZmZmZmZmZ d dlmZmZ d dlmZmZmZmZ d dlmZ d dlZd dlZd dlZd dl Z d dl!Z!d dl"Z"d dl#Z#d dl$Z$d dl%Z%d d	l&m'Z' d d
l(m)Z)  ede*          Z+ e!j,        e*          Z-dede.defdZ/dede0fdZ1defdZ2de.fdZ3d Z4d Z5 e4            Z6 e5            Z7e-8                    de%j9        :                    dd           de6 de7            e%j9        :                    d          ddd d!d"Z;e+<                    d#d$g%          d&             Z=e+<                    d'd(g%          d)             Z>d* Z?e+<                    d+d$g%          d,             Z@e+<                    d-d(g%          d.             ZAe+<                    d/d(g%          d0             ZBd1 ZCdeDfd2ZEd[deDfd3ZFdeDfd4ZGd[deDfd5ZHe+<                    d6d$g%          d7             ZId8 ZJd[d9ed:eDdz  fd;ZKe+<                    d<d(g%          d=             ZLd> ZMd? ZNd@e.fdAZOe+<                    dBd(g%          dC             ZPe+<                    dDd$g%          dE             ZQe+<                    dDdFg%          dG             ZRe+<                    dHd(g%          dI             ZSe+<                    dJd$g%          dK             ZTdeUfdLZVdM ZWdNe0de.fdOZXdeDfdPZYde.fdQZZdeDfdRZ[dSe0dz  deUfdTZ\dU Z]dV Z^e+<                    dW          dX             Z_e+`                    dY          dZ             ZadS )\    )	Blueprintrequestjsonifysessioncurrent_app)login_requiredcurrent_user)dbUser)ProductSubscriptionPaymentSubscriptionStatusPaymentStatusPaymentMethodUserProfilePaymentHistoryr   )datetime	timedeltatimezonedate)
monthrangeN)env)resolve_user_refsubscription	base_date	start_dayreturnc                     | j         dk    r| j        dz   d}}n| j        | j         dz   }}t          ||          d         }t          ||          }t	          |||          S )uO   base_date 기준으로 +1개월, 시작일의 '일(day)' 유지. 말일 보정.      )monthyearr   minr   )r   r   ymlast_daykeep_days         P/var/www/html/web/mlink/mlink_AI_Server/mlink-backend/src/routes/subscription.py_add_one_month_same_dayr+      sl     "~!11~y21 !Q"H9h''H1h    
start_datebilling_cyclec                 N   |dk    rdnd}| j         1|                     t          t          d                              n| }|                    t          t          d                              }|j        }|                                }t          ||          }d}|||fS )u   구독 생성 시 크레딧 관련 필드 계산
    Returns:
        tuple: (prepaid_cycles_remaining, next_credit_date, last_credit_date)
    yearlyr!   r   N	   )hourstzinfo)r4   replacer   r   
astimezonedayr   r+   )	r-   r.   prepaid_cycles_remainingstart_date_kststart_date_localr   start_date_onlynext_credit_datelast_credit_dates	            r*   %_calculate_subscription_credit_fieldsr>   "   s     &3h%>%>rrA Q[PaPiZ''x	8J8J8J/K/K'LLLoyN%00)!:L:L:L1M1MNN $I&++--O.	JJ #%57GGGr,   c                 v    d| _         d| _        d| _        t                              d| j         d           dS )u[  구독 취소 시 크레딧 관련 필드 초기화
    프로시저 sp_renew_subscription_credits_one이 더 이상 실행되지 않도록 필드 초기화
    - next_credit_date를 NULL로 설정하여 프로시저가 실행되지 않도록 함
    - last_credit_date를 NULL로 초기화
    - prepaid_cycles_remaining을 0으로 초기화
    Nr      구독 uI   의 크레딧 관련 필드 초기화 완료 (프로시저 실행 방지))r<   r=   r8   loggerinfoid)r   s    r*   !_reset_subscription_credit_fieldsrD   6   sB     %)L!$(L!,-L)
KKt,/tttuuuuur,   subscription_idc           	         	 ddl m} t          j                             |d          d| i          }t          j                                         t                              d|             ddiS # t          $ rg}t          j        	                                 t          
                    d|  d	t          |                      d
t          |          dcY d}~S d}~ww xY w)u   프로시저 sp_renew_subscription_credits_one 호출 (선택사항)
    필요시 구독 크레딧 갱신을 위해 프로시저를 직접 호출할 수 있습니다.
    r   )textz8CALL sp_renew_subscription_credits_one(:subscription_id)rE   uN   프로시저 sp_renew_subscription_credits_one 호출 완료: subscription_id=successTu,   프로시저 호출 실패: subscription_id=z, error=FrH   errorN)
sqlalchemyrG   r
   r   executecommitrA   rB   	ExceptionrollbackrJ   str)rE   rG   resultes       r*   )call_renew_subscription_credits_procedurerS   B   s   3########DKLL0
 
 	
vetvvwww4   3 3 3

eOee]`ab]c]ceefff 3q66222222223s   A/A2 2
C#<ACC#C#c                  h    t           j                            d          } | r| S t          j        rdS dS )u&   프론트엔드 URL 반환 (환경별)	FRONT_URLhttps://mlink.sellmall.co.krzhttp://localhost:3001osenvirongetr   is_production)	front_urls    r*   get_front_urlr]   U   s:    
{++I 
 .--""r,   c                  h    t           j                            d          } | r| S t          j        rdS dS )u    백엔드 URL 반환 (환경별)BACK_URLrV   zhttp://localhost:8011rW   )back_urls    r*   get_back_urlra   _   s9    z~~j))H 
 .--""r,   u   환경 설정: ENVIRONMENT=ENVIRONMENTdevelopmentz, FRONT_URL=z, BACK_URL=TOSS_API_KEYz#https://pay.toss.im/api/v2/paymentsz"https://pay.toss.im/api/v2/refundsz!https://pay.toss.im/api/v2/statusz"https://pay.toss.im/api/v2/execute)api_keybase_url
refund_url
status_urlexecute_urlz/api/subscription/productsGET)methodsc                  t   	 t           j                            dd          } | dk    r4t          j                            dd                                          }nl| dk    r4t          j                            dd                                          }n2t          j                            d                                          }d t          fd|D                       d	fS # t          $ rD}t          
                    d
           t          dt          |          i          dfcY d}~S d}~ww xY w)u   상품 목록 조회r.   monthlyT)	is_activer.   r0   )rn   c                     | sg S 	 t          j        |           }t          |t                    r|ng S # t           j        t
          f$ r,}t                              d| d|             g cY d}~S d}~ww xY w)u:   features JSON 파싱 (파싱 실패 시 빈 배열 반환)u   features JSON 파싱 실패: u   , 원본 데이터: N)jsonloads
isinstancelistJSONDecodeError	TypeErrorrA   warning)features_stringparsedrR   s      r*   safe_parse_featuresz)get_products.<locals>.safe_parse_features   s    " 	O44!+FD!9!9AvvrA()4   gqggVegghhh						s   ,3 A5	!A0*A50A5c                     g | ]>}|j         |j        |j        |j        |j        |j        |j         |j                  d ?S )rC   namedescriptionpricecurrencyr.   
trial_daysfeaturesr{   ).0pry   s     r*   
<listcomp>z get_products.<locals>.<listcomp>   se     	 	 	  $F=W
_,++AJ77	
 	
 	 	 	r,      zget_products failedrJ     N)r   argsrZ   r   query	filter_byallr   rN   rA   	exceptionrP   )r.   productsrR   ry   s      @r*   get_productsr   z   sc   "/(()DD I%%}..Y.WW[[]]HHh&&}..X.VVZZ\\HH}...>>BBDDH		 		 		  	 	 	 	 	 	 	 	 	  #	# 		#  / / /.///Q())3......./s   C%C) )
D739D2,D72D7z/api/subscription/subscribePOSTc                     	 t          j        d          } | st          ddi          dfS t          j        dd          pi }|                    d          }|st          ddi          d	fS g d
}|D ]@}||v r:t
                              d|  d| d           t          dd| di          dfc S At          j        	                    |          }t          j                    }|j        pddk    r'|t          |j                  z   }|}t          j        }	n.d}|t          |j        dk    rdnd          z   }t          j        }	t%          ||j                  \  }
}}t'          | ||	|||dd|
||          }t(          j                             |           t(          j                                          t          |j        |j        j        |j                                        d          dfS # t8          $ rb}t(          j                                          t
                              d           t          dt?          |          i          dfcY d}~S d}~ww xY w)u   구독 생성 (결제 없이 선 생성 가능) — auto_renew 비활성화
    보안: prepaid_cycles_remaining, next_credit_date, last_credit_date는 시스템 내부에서만 설정 가능
    user_idrJ      로그인이 필요합니다.  Tforcesilent
product_idu   상품 ID가 필요합니다.  )r8   r<   r=      보안 경고: 사용자 u   가 보호된 필드 u    설정 시도u   필드 u&   는 직접 설정할 수 없습니다.  r   daysNrm      m  F)r   r   statusr-   end_datetrial_end_date
auto_renewnext_billing_dater8   r<   r=   )rE   r   r      zcreate_subscription failedr   ) r   rZ   r   r   get_jsonrA   rv   r   r   
get_or_404r   nowr   r   r   TRIALr.   ACTIVEr>   r   r
   addrM   rC   r   valuer   	isoformatrN   rO   r   rP   )r   datar   protected_fieldsfieldproductr-   r   r   r   r8   r<   r=   r   rR   s                  r*   create_subscriptionr      s   
;/+i(( 	LG%DEFFKKd4888>BXXl++
 	LG%DEFFKK `__% 	h 	hE}}n7nnY^nnnooo)`5)`)`)`abbdggggg  -**:66\^^
#!q((')9K*L*L*LLN%H'-FF!N!I9NR[9[9[22ad$e$e$eeH'.F Hm-H
 H
D "24D $!!)"%=--
 
 
 	
|$$$

+")/$-7799
 
   	 	  / / /

5666Q())3......./s2   (G3 AG3 -AG3 3D?G3 3
I=AIIIc                    	 |dk    rt           j        | _        d| _        d| _        | j        }|j        t          j        k    rt          j        |_        	 ddl	m
}  ||j        |j                  }|d         r,t                              d|j         d|d                     n0t                              d	|                    d
                      n# t"          $ r5}t                              dt'          |                      Y d}~nd}~ww xY wn|dk    rrt           j        | _        d| _        d| _        | j        }	 ddlm} ddl	m} t1          |j                  }	|	r|	j        |j        k    r ||j        dt'          |j                  d          }|                    d          r3t                              d|                    dd           d           n0t                              d|                    d
                      nA# t"          $ r4}t                              dt'          |                      Y d}~nd}~ww xY wt          j        |_        t7          j                    |_        t=          |           n=|dk    r7t           j        | _        d| _        d| _        | j        }t          j         |_        t7          j                    | _!        tD          j#        $                                 dS # t"          $ rH}tD          j#        %                                 tM          dt'          |                      Y d}~dS d}~ww xY w)u   결제 상태 변경 처리rH   SUCCESSu   결제 완료r   )renew_subscription_creditsu(   구독 크레딧 추가 완료: user_id=z, added=addedu    구독 크레딧 추가 실패: rJ   u-   구독 크레딧 추가 중 오류 (무시): N	cancelled	CANCELLEDu   결제 취소SubscriptionTypeconsume_all_remaining_credits6   구독 취소로 인한 남은 크레딧 사용 완료subscription_cancellationr   r}   reference_idreference_typeu?   [handle_payment_status_change] 남은 크레딧 사용 완료: consumed
    크레딧u?   [handle_payment_status_change] 남은 크레딧 사용 실패: uG   [handle_payment_status_change] 크레딧 사용 처리 실패(무시): failedFAILEDu   결제 실패u$   결제 상태 변경 처리 실패: )'r   	COMPLETEDr   pg_response_codepg_response_messager   r   r   r   src.utils.credit_managerr   r   rC   rA   rB   rv   rZ   rN   rJ   rP   r   src.models.userr   r   r   subscription_typeCREDITr   r   cancelled_atrD   r   EXPIRED
updated_atr
   r   rM   rO   print)
paymentr   webhook_datar   r   credit_resultrR   r   r   users
             r*   handle_payment_status_changer      s(   D?Y*4GN'0G$*9G' #/L"&8&>>>&8&?#[SSSSSS$>$>|?SUaUd$e$eM$Y/ h  %F|Oc  %F  %Fmz  |C  nD  %F  %F  G  G  G  G'f-J[J[\cJdJd'f'fggg  [ [ [LL!YQTUVQWQW!Y!YZZZZZZZZ[ ? {""*4GN'2G$*9G' #/Ls<<<<<<RRRRRR'(<== 
GD26F6MMM$A$A , 4$\%(%9%9'B	% % %M %((33 G  %Sfsfwfw  yC  EF  gG  gG  %S  %S  %S  T  T  T  T  (Fiviziz  |C  jD  jD  (F  (F  G  G  G s s sqilmnioioqqrrrrrrrrs #5">L(0L%-l;;;;x*1GN'/G$*9G' #/L"4"<L%\^^

 ? ? ?

=SVV==>>>>>>>>>?so   AL
 B C L
 
D"*DL
 D2L
 
CH L
 
I%*IL
 IB/L
 

M=MMz!/api/subscription/my-subscriptionc                     	 t          j        d          } | st          ddi          dfS t          j                            | t          j                                      t          j	        
                                                                          }|st          ddi          dfS |j        | k    r9t                              d	|  d
|j         d           t          ddi          dfS t          d|j        |j        j        |j        r0|j                            d                                          dz   nd|j        r0|j                            d                                          dz   nd|j        r0|j                            d                                          dz   nd|j        |j        r0|j                            d                                          dz   nd|j        r|j        j        nd|j        |j        |j        |j        |j        r|j                                        nd|j        r|j                                        nd|j         j        |j         j!        |j         j"        |j         j        ddi          dfS # tF          $ rD}t          $                    d           t          dtK          |          i          dfcY d}~S d}~ww xY w)uX   내 구독 정보 조회 - 보안: 사용자는 자신의 구독 정보만 조회 가능r   rJ   r   r   r   r   r   Nr   r      가 다른 사용자의 구독 u   에 접근 시도   권한이 없습니다.r   r3   Z)rC   r|   r~   r.   )rC   r   r-   r   r   r   r   payment_method
payment_idamountr.   r8   r<   r=   r   zget_my_subscription failedr   )&r   rZ   r   r   r   r   r   r   order_by
created_atdescfirstr   rA   rv   rC   r   r   r-   r5   r   r   r   r   r   r   r   r   r.   r8   r<   r=   r   r|   r~   rN   r   rP   r   r   rR   s      r*   get_my_subscriptionr   /  sQ   ,/+i(( 	LG%DEFFKK#)33GL^Le3ffoo#((**
 

%'' 	  	8ND122C77 7**NN  Bw  B  B_k_n  B  B  B  C  C  CG%>?@@#EE"o&-3`l`w  Bl5==T=JJTTVVY\\\  ~B\h\q{L1999FFPPRRUXXXw{ht  iD  #N,"="E"ET"E"R"R"\"\"^"^ad"d"d  JN*5nz  oM  &W\%C%K%KSW%K%X%X%b%b%d%dgj%j%j  SWGSGb"l,"="C"Chl*5&-!-!;,8,QQ]Qn$xL$A$K$K$M$M$MtxQ]Qn$xL$A$K$K$M$M$Mtx&.1(05)17%1%9%G	  
  . / 	2  / / /5666Q())3......./s1   (J B J ,AJ 0F&J 
K%!9K K% K%z'/api/subscription/fix-next-billing-datec                  F   	 t          j        d          } | st          ddi          dfS t          j                            | t          j                                                  }|st          ddi          dfS |j	        | k    r9t                              d|  d	|j         d
           t          ddi          dfS |j        |_        t          j                                          t          d|j        |j                                        |j                                        d          dfS # t$          $ rb}t          j                                          t                              d           t          dt+          |          i          dfcY d}~S d}~ww xY w)ua   기존 구독의 next_billing_date 수정 (임시 수정용) - 보안: 사용자 소유권 검증r   rJ   r   r   r   u)   활성 구독을 찾을 수 없습니다.  r   r   u    수정 시도r   r   T)rH   rE   r   r   r   zfix_next_billing_date failedr   N)r   rZ   r   r   r   r   r   r   r   r   rA   rv   rC   r   r   r
   rM   r   rN   rO   r   rP   r   s      r*   fix_next_billing_dater   c  s    /+i(( 	LG%DEFFKK#)33%, 4 
 
 %'' 	
  	XG%PQRRTWWW 7**NN~w~~_k_n~~~G%>?@@#EE *6)>&

+!-!?!I!I!K!K$-7799	
 
  
  	  / / /

7888Q())3......./s2   (D4 AD4 =AD4 A2D4 4
F >AFF F z/api/subscription/cancelc                  z   	 t          j        d          } | st          ddi          dfS t          j                            | t          j                                                  }|st          ddi          dfS |j	        | k    r9t                              d|  d	|j         d
           t          ddi          dfS g }t                              d|j         d|j                    ddd}d}	 ddlm} |j                            |j        t"          j                                      |j                                                                                  }|r3t                              d|j         d|j         d|j                    n#t                              d|j         d           nA# t0          $ r4}t                              dt5          |                      Y d}~nd}~ww xY w|j        t6          j        k    r|r	 ddlm}m} |j                            |j        |j                                      |j                                                                                  }|s%t                              d           ddddd}	d}
nt=          |          }	|	d         }t                              d |	            d}g }|D ]c}t?          |j        pd          }|dk    r ddl }ddl!}d! |j"                    j#        dd"          }t?          |d#z            }||z
  }tH          j%                            d$          |j        |||d|dd%d&	} |j&        tN          d'         d(d)i|d*+          }|j(        d,k    rY||z  }|j)        |_*        d-| |_+        tX          j         -                                 |.                    |j        ||d.           t                              d/|j         d0|j(         d1|j/                    t          dd2|j        |j/        d3          d4fc S d}
|	d         dk    r$ta          |j	        ||d         |	d         5          }
	 dd6l1m2}m3} dd7l4m5} tm          |j	                  }|r|j7        |j8        k    r ||j	        d8t5          |j                  d9:          }|                    d;          r3t                              d<|                    d=d           d>           n0t                              d?|                    d                      nA# t0          $ r4}t                              d@t5          |                      Y d}~nd}~ww xY wt          j9        |_*        d|_:        tw          j<                    |_=        tw          j<                    |_>        t          |           tw          j<                    |_@        tX          j         -                                 	 ddAl1m2} tm          |j	                  }|r%dB|_A        tX          j         -                                 nA# t0          $ r4}t                              dCt5          |                      Y d}~nd}~ww xY wt          dDdEdF|rt          dG |D                       nddH|	                    dd          |
pi                     dI          |
pi                     dJ          |
pi                     dK          |	                    dL          |	                    dM          |	                    dN          dOdPdQ          d,fS # t0          $ rD}t          C                    dR           t          dt5          |          i          dSfcY d}~S d}~ww xY wt          dDdT|dU          d,fS # t0          $ rb}tX          j         D                                 t          C                    dV           t          dt5          |          i          dSfcY d}~S d}~ww xY w)WuK   구독 취소 — 환불 처리 포함 - 보안: 사용자 소유권 검증r   rJ   r   r   r      활성 구독이 없습니다.r   r   r       취소 시도r   r   '   구독 정보 확인 - payment_method: , subscription_id: Fu   환불 처리 로직 미실행rI   Nr   r   rE   r   "   최근 결제 발견: payment_key=	, amount=, created_at=r@   )   의 완료된 결제를 찾을 수 없음!   최근 결제 조회 중 오류: r   uA   완료된 결제 없음 — 사용분 정산만 0원으로 처리
total_paid
total_days	used_daysusage_charger   [CancelChain] usage_meta=refund_   皙?rd   u   반품 취소(전체 환불)	apiKeypayTokenrefundNor   amountTaxableamountTaxFree	amountVatamountServiceFeereasonrg   Content-Typeapplication/jsonr   headersrp   timeoutr      환불 완료 - )r   	refund_nor   u#   [CancelChain] 환불 실패: ph_id=z, resp= u   일부 환불 실패(중단).rH   rJ   failed_payment_idrawr   r   r   latest_paymentr   r   r   r   r   r   r   rH   u.   [CancelChain] 남은 크레딧 사용 완료: r   r   u.   [CancelChain] 남은 크레딧 사용 실패: u6   [CancelChain] 크레딧 사용 처리 실패(무시): )r   	role_freeu   권한 회수 실패(무시): Tu[   구독이 취소되었고, 전체 환불 후 사용분 정산 결제를 준비했습니다.
full_chainc              3   B   K   | ]}t          |j        pd           V  dS r   Nintr   r   phs     r*   	<genexpr>z&cancel_subscription.<locals>.<genexpr>3  s0      +Y+YBC	Q,?,?+Y+Y+Y+Y+Y+Yr,   )modetotal_refundorderIdpayUrl
simulationr   r   r   r   r   r   r   r  r  r  meta)rH   messagerefundusage_settlementu+   [CancelChain] 전체 환불 + 정산 실패r   u0   구독이 성공적으로 취소되었습니다.rH   r"  r#  u%   기간 연장형 구독 취소 실패)Er   rZ   r   r   r   r   r   r   r   r   rA   rv   rC   rB   r   src.models.paymentr   r   r   r   r   r   payment_keyr   rN   rJ   rP   r   TOSS_PAYMENTSr   _calc_usage_charge_for_chainr  uuidrequestsuuid4hexrX   rY   postTOSS_CONFIGstatus_codeREFUNDEDr   r}   r
   rM   appendrG   !_prepare_usage_settlement_paymentr   r   r   r   r   r   r   r   r   r   r   r   r   r   rD   r   rolesumr   rO   )r   r   refund_resultsrefund_resultr  r   rR   PHStatuscompleted_list
usage_metausage_paymentr   refund_totalrefund_detailsr  refund_amountr*  r+  r	  amount_taxable
amount_vatrefund_datarr   r   r   r   r   us                                r*   cancel_subscriptionrD    s
   F/+i(( 	LG%DEFFKK#)33%, 4 
 
 %'' 	
  	MG%EFGGLL 7**NN~w~~_k_n~~~G%>?@@#EE l>Ynzn}  	A  	A  	A 5
 
 	G999999+1;; ,$. <   h~0557788 
  e  fA[  f  fftf{  f  f  KY  Kd  f  f  g  g  g  gccccddd 	G 	G 	GLLESVVEEFFFFFFFF	G &-*EEE.EJ7XXXXXXXX #1"6#,9\_U]Ug9#h#h#+8N,E,J,J,L,L#M#M#&355 
 & AKK cddd01QRde!f!fJ$(MM ">n!M!MJ#-n#=LKK HJ H HIII $%L%'N, *$ *$(+BIN(;(;(A--$--------$Ejdjll.>ss.C$E$E	),]S-@)A)A%2^%C
 ')jnn^&D&D(*(1&3-;-.)301&D
' 
' *HM+l*C3ACU2V/:BH H H =C//(M9L(0(9BI-K	-K-KBNJ--///*11.0e-6*73 3     #LL)uru)u)u]^]j)u)umnms)u)uvvv#*+0)H57U'(v	, , $ $
 !$$$ $ $ $ %)M!.1A55(I$0$8)5+9!+<)3N)C	) ) )fFFFFFFFFVVVVVV+L,@AAD 
z 6:J:Q Q Q(E(E$0$8(`),\_)=)=+F	) ) ) ),,Y77 z"KK  )FYfYjYjkuwxYyYy  )F  )F  )F  G  G  G  G"NN+x\i\m\mnu\v\v+x+xyyy  f f fNN#d\_`a\b\b#d#deeeeeeeef '9&B#15.(0%,4LNN)1,???*2,..'
!!###N444444()=>>A ,!,
))+++  N N NNN#LCFF#L#LMMMMMMMMN #| ,]k(r+Y+Y.+Y+Y+Y(Y(Y(Yqr 
 #-.."C"C$1$7R#<#<Y#G#G#0#6B";";H"E"E'4':&?&?&M&M*4..*F*F*4..*F*F)3)D)D! !
) 
)     $ % (  7 7 7  !NOOOQ011366666667 I#
 
   	 	  / / /

@AAAQ())3......./s   (_ A_ =A_ 3_ 5CF; :_ ;
G9*G4/_ 4G99_ H/]( 2]( 9CU ]( 
V
*V ]( V

B]( AY ]( 
Z'*Z]( ZC]( (
^629^1+^6,_ 1^66_ 
`:A`5/`:5`:c                 D   	 | j         r| j        sdS t          j                    }| j         }| j        }||k     r2t                              d| j         d| j         d           | j        S ||k    r%t                              d| j         d           dS ||z
  j        }||z
  j        }|dk    rdS | j        }t                              d| j         d           t                              d| j         d           t                              d| d	|            t                              d
| d| d           t                              d| d           |S # t          $ r5}t          
                    dt          |                      Y d}~dS d}~ww xY w)u?   기간 연장형 모델 - 특정 결제의 환불 금액 계산r   u   결제 u)    - 기간 미시작으로 전액 환불: u   원u!    - 기간 만료로 환불 불가u2    환불 계산 (토스페이먼츠 전체 환불):u     - 결제 금액: u     - 구독 기간: z ~ u     - 남은 일수: u
   일 / 총 u   일u     - 환불 금액: u   원 (전체 금액 환불)u   환불 금액 계산 실패: N)subscription_start_datesubscription_end_dater   r   rA   rB   rC   r   r   rN   rJ   rP   )payment_historyr   r-   r   remaining_daysr   r>  rR   s           r*   #calculate_refund_amount_for_paymentrJ  T  s   (6 	o>c 	1lnn$<
"8 KKz/"4zz_n_uzzz{{{")) (??KKW/"4WWWXXX1 #S..+1
??1 (.do0dddeeeE/*@EEEFFFC*CCCCDDDS.SSJSSSTTTS-SSSTTT   =SVV==>>>qqqqqs0   E  AE  +)E  E  2B-E   
F**FFc           	      b   t          | j        pd          }t          | j        pd          p| j        dk    rdnd}| j        p| j        pt          j                    }t          j                    }t          dt          ||                                |                                z
  j        dz                       }t          t          | dd          pd          }||z   |dk    r||z  ndz  }t          dt          t          ||z
                                }||t          |          |dS )	ug  
    최근 결제 1건을 기준으로 '전체 환불 후 사용분 재결제'에 필요한 금액을 계산.
    - 전체 환불: latest_payment.amount
    - 사용분 청구: (amount + carryover_credit_amount) * (경과일수 / 총일수) - carryover_credit_amount
      (carryover 크레딧은 먼저 소진된 가치이므로 사용분에서 차감)
    r   rm   r   r   r"   carryover_credit_amountg      ?)r   elapsed_daysrL  r   )floatr   r  subscription_daysr.   rF  r   r   r   maxr%   r   r   getattrround)	r  r   r   start_dttodayrM  	co_amount
used_valuer   s	            r*   _calc_usage_chargerW    s8    >(-A..F^5:;;ynFbfoFoFouxJ 5d9RdV^VbVdVdHLNNEq#j5::<<(--//+I*ORS*STTUUL gn.GKKPqQQI 9$jSTnn
)B)BZ]^J q#eJ$:;;<<==L !$#&y>>$	  r,   c           
          ddl m }m}m} | sdddddS  |j                    |p}|                                }ddlm fdfd| D             }|sdddddS fdd }t          fd	|D                       }|                                }	d}
d}d}d
}|D ]}t          t          |dd          pd          }|
|z  }
 |          } ||          }||z  }t          |dd          }|s| ||          z   }|                                }|                                }t          |	|          }t          ||          }||z
  j
        dz   }|dk     rd}|dk    r||z  n|}||t          dt          ||                    z  z  }|t          dt          ||                    z  }t          t          |                    }t          |
          t          |          t          |          t          |          |dS )u  
    월 기준 구독자용 사용분 정산 금액 계산
    체인 전체 환불 모드 — 구간별 일일단가 반영.
    - 구간별 per-day = (금액 + 이월금액?) / 구간일수
      * 기본은 금액/일수. carryover_credit_amount가 '그 구간의 추가 일수 가치'로 포함된 경우
        (subscription_days에 carryover가 더해진 구조라면) per-day를 (amount + carryover_credit_amount)/subscription_days 로도 선택 가능.
        아래 기본값은 금액/일수이며, 주석의 옵션을 사용하려면 해당 줄을 교체하세요.
    - overlap_days: [구간시작, 구간끝] ∩ [체인시작, as_of] 의 일 수(일 단위, 오늘 포함)
    r   )r   r   r   r   r   c                 d   t          | dd           j        k    }t          t          | dd          pd          }t          t          | dd          pd                                          }t          t          | dd          pd          }|                    d          p	|dk    pd|v }|o| S )	Nr   order_id r.   product_namemlink_usage_onetime   정산)rQ  r   rP   lower
startswith)r  	status_okoidcycpnameis_settlementr8  s         r*   _is_effectivez;_calc_usage_charge_for_chain_monthly.<locals>._is_effective  s    R400H4FF	'"j"--344'"or228b99??AAGB339r::~66a#:Jax[`O`...r,   c                 *    g | ]} |          |S  rj  )r   r  rh  s     r*   r   z8_calc_usage_charge_for_chain_monthly.<locals>.<listcomp>  s(    
5
5
5"==#4#4
52
5
5
5r,   c                 $    | j         p| j        pS N)rF  r   )r  r   s    r*   	_start_ofz7_calc_usage_charge_for_chain_monthly.<locals>._start_of  s    *BbmBsCr,   c                     t          t          | dd          pd          }|dk    r:t          t          | dd                                                    }|dk    rdnd}|S )NrO  r   r.   rm   r   r   )r  rQ  rP   ra  )r  dbcs      r*   _days_ofz6_calc_usage_charge_for_chain_monthly.<locals>._days_of  se    /338q9966WR)<<==CCEEBIoo3Ar,   c              3   .   K   | ]} |          V  d S rl  rj  )r   r  rm  s     r*   r  z7_calc_usage_charge_for_chain_monthly.<locals>.<genexpr>  s+      5522555555r,           r   rG  Nr   r"   r   r   r   r   chain_start)r   r   r   r   r&  r   r%   rN  rQ  rP  r   r  rR  )ph_listas_ofr   r   r   
as_of_dateeffrq  chain_start_dtchain_start_dater   r   used_days_totalrV  r  r   rS  r   end_dtstart_dend_dleftrightoverlap_daysper_dayr   r8  rh  rm  r   s                             @@@@r*   $_calc_usage_charge_for_chain_monthlyr    s    3222222222 UqqRSTTT (,..CLSEJ =<<<<</ / / / / 6
5
5
5
5
5
5C UqqRSTTTD D D D D   555555555N%**,,JJOJ !; !;wr8Q//4155f
 9R==x||d
4d;; 	5		t 4 4 44F--//++-- $g..J&&*Q.!L $(!88&4-- 	gAs<'>'> ? ???
3q#lD"9"9:::uZ(())L *oo*oo))L))%  r,   c                    ddl m} |sdddddS |j                            |                                           }|sdddddS t
          j                            | t          j                                                  }|r|j        s.t          d |D                       }t          |          dddddS |j        }|j        }|j                                        }d	|v rd
}	nd|v rd}	n	d|v rd}	nd
}	|	dk    r
|j        |	z  nd}
|	|z
  }t          t          ||
z                      }t          d |D                       }t          |          dd|ddS )u   
    크레딧 기준 구독자용 사용분 정산 금액 계산
    - credit_balances의 남은 credit을 기준으로 사용한 크레딧 계산
    - usage_charge = (monthly_credits - 남은 크레딧) × 크레딧당 단가
    r   )CreditBalancer   r   r   c              3   V   K   | ]$}t          t          |d d          pd          V  %dS r   r   NrN  rQ  r  s     r*   r  z6_calc_usage_charge_for_chain_credit.<locals>.<genexpr>  s;      PP"wr8Q77<1==PPPPPPr,   Nrt  basicil  plusi<  proi8J  c              3   V   K   | ]$}t          t          |d d          pd          V  %dS r  r  r  s     r*   r  z6_calc_usage_charge_for_chain_credit.<locals>.<genexpr>@  s;      LLbU72x338q99LLLLLLr,   )src.models.creditr  r   r   r   r   r   r   r   r5  r  subscription_creditr|   ra  r~   rR  )r   rv  r  credit_balancer   r   remaining_creditr   r]  monthly_creditsprice_per_creditused_creditsr   s                r*   #_calc_usage_charge_for_chain_creditr    s    0///// UqqRSTTT #(2272CCIIKKN UqqRSTTT  %//!( 0   egg 
  	
|3 	
PPPPPPP
j//
 
 	
 &9 "G<%%''L ,	<			,		 ;JA:M:Mw}66ST #%55Lu\,<<==>>L LLGLLLLLJ *oo$  r,   c                    ddl m} | sdddddS t          | d         dd          }|st          | |          S t	          |          }|st          | |          S |j        |j        k    rt          ||           S t          | |          S )u  
    체인 전체 환불 모드 — 구독 타입에 따라 분기 처리
    - user.subscription_type == 'MONTHLY': 월 기준 계산 (_calc_usage_charge_for_chain_monthly)
    - user.subscription_type == 'CREDIT': 크레딧 기준 계산 (_calc_usage_charge_for_chain_credit)
    r   r   r   r   N)r   r   rQ  r  r   r   r   r  )rv  rw  r   r   r   s        r*   r)  r)  K  s     100000 UqqRSTTT gaj)T22G D3GUCCC G$$D D3GUCCC !1!88827GDDD 4GUCCCr,   z!/api/subscription/payment-historyc                     	 t          j        d          } | st          ddi          dfS t          j                            |                               t          j        t          j	        k              
                    t          j                                                                                  }g }|D ]+}|                                }|                    |           ,t                               d|  dt%          |           d           t          d	|d
          dfS # t&          $ rG}t                               dt+          |                      t          ddi          dfcY d}~S d}~ww xY w)u!   사용자의 결제 이력 조회r   rJ   r   r   r  u
   사용자 u   의 결제 이력 조회: u   건T)rH   paymentsr   u   결제 이력 조회 실패: u+   결제 이력 조회에 실패했습니다.r   N)r   rZ   r   r   r   r   filterr   r   PENDINGr   r   r   r   to_dictr2  rA   rB   lenrN   rJ   rP   )r   r  payments_datar   payment_dictrR   s         r*   get_payment_historyr  k  s   V+i(( 	LG%DEFFKK "'11'1BBII!]%::
 

(>,1133
4
4SSUU 	
  	/ 	/G"??,,L  ....[[[CDVDV[[[\\\%
 
    	
  V V V=SVV==>>>!NOPPRUUUUUUUUVs#   (D) C=D) )
E:3<E5/E:5E:c                    ddl m} ddlm}m} ddl}ddl}ddl}t          j	                    }g }d}	|j
                            | j        |j                                      |j                                                                                  }
|
r| j        |k    r	 ddlm}m} ddlm} t1          | j                  }|r|j        |j        k    r || j        dt9          | j                  d	
          }|                    d          r3t<                              d|                    dd           d           n0t<                               d|                    d                      nA# tB          $ r4}t<                               dt9          |                      Y d}~nd}~ww xY w|j"        | _#        d| _$        t          j	                    | _%        tM          |            t          j	                    | _'        tP          j)        *                                 dg dS |
D ]}|j+        r|j,        s|j,        |k    r|j-        }d |j.                    j/        dd          }ta          |dz            }||z
  }|j1                            d          |j2        |||d|ddd	}	  |j3        th          d         ddi|d          }|j5        dk    r|j6        |_#        d| |_7        tP          j)        *                                 |j8        rX| j        ts          |j8                   z
  | _        t          j	                    | _'        tP          j)        *                                 |	|z  }	|:                    |j        ||d!dd"           | j        |k    r nmn%|:                    |j        ||d#|j;        d"           # tB          $ r8}|:                    |j        ||d#t9          |          d"           Y d}~d}~ww xY w| j        |k    r	 dd$lm} ddlm} t1          | j                  }|r|j        |j        k    r || j        dt9          | j                  d	
          }|                    d          r3t<                              d|                    dd           d           n0t<                               d|                    d                      nA# tB          $ r4}t<                               dt9          |                      Y d}~nd}~ww xY w|j"        | _#        d| _$        t          j	                    | _%        tM          |            tP          j)        *                                 	 t1          | j                  }|r%d%|_<        tP          j)        *                                 nA# tB          $ r4}t<          =                    d&t9          |                      Y d}~nd}~ww xY w|	|dS )'u  
    현재 활성 구독의 '지금 이후 구간'에 해당하는 결제들을 최근 순서로 전액 환불하고,
    각 환불마다 subscription.end_date에서 subscription_days를 차감(회수)한다.
    end_date <= now가 되면 구독을 CANCELLED 처리한다.

    반환:
      {
        "total_refund": int,
        "items": [
          {"payment_history_id": int, "amount": int, "refund_no": str, "success": bool, "error": str|None}
        ]
      }
    r   r   r   r   Nr   r  r   r   r   r   rH   uH   [_refund_all_future_periods_and_cancel] 남은 크레딧 사용 완료: r   r   uH   [_refund_all_future_periods_and_cancel] 남은 크레딧 사용 실패: rJ   uP   [_refund_all_future_periods_and_cancel] 크레딧 사용 처리 실패(무시): r  itemsr   r   r   rd   u   반품 취소 환불r   rg   r  r  r   r  r   u   플랜 변경 환불 완료 - r   Tpayment_history_idr   r	  rH   rJ   Fr   r  u   권한 회수 실패: )>r&  r   src.models.subscriptionr   r   r*  r+  rX   r   r   r   r   rC   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   rP   rZ   rA   rB   rv   rN   r   r   r   r   rD   r   r
   r   rM   rF  rG  r   r,  r-  r  rY   r'  r.  r/  r0  r1  r}   rO  r   r2  rG   r4  rJ   )r   r   r   r   r*  r+  rX   r   detailsr  	historiesr   r   r   r   r   rR   r  r   r	  taxablevatrA  rB  s                           r*   %_refund_all_future_periods_and_cancelr    s    211111IIIIIIII
,..CGL $..$& /   h~(--//00   0-44	x>>>>>>>>NNNNNN#L$899D 
L.2B2III = =(0 X!$\_!5!5#>	! ! ! !$$Y// LKK  !Xkxk|k|  ~H  JK  lL  lL  !X  !X  !X  Y  Y  Y  YNN  $Kn{nn  AH  oI  oI  $K  $K  L  L  L 	x 	x 	xNNvnqrsntntvvwwwwwwww	x 1:)-&$,LNN!),777"*,..

 !B/// 8G 8G) 	1I 	#s** 5jdjll.ss355	 fsl##w jnn^44!$ !,

 

	GL)');< 	  A }##)2	!M)!M!M
!!### ' (,8,AISUSgDhDhDh,hL).6lnnL+J%%'''&bev\erv  BF   G   G  H  H  H  (C//E 0 bev\erw  CD  CI   J   J  K  K  K 	G 	G 	GNN"%6Xans  B  CD  E  E  F  F  G  G  G  G  G  G  G  G	G ##	x888888NNNNNN#L$899D 
L.2B2III = =(0 X!$\_!5!5#>	! ! ! !$$Y// LKK  !Xkxk|k|  ~H  JK  lL  lL  !X  !X  !X  Y  Y  Y  YNN  $Kn{nn  AH  oI  oI  $K  $K  L  L  L 	x 	x 	xNNvnqrsntntvvwwwwwwww	x 1:)-&$,LNN!),777

	<#L$899D $'	
!!### 	< 	< 	<LL:#a&&::;;;;;;;;	< )7;;;si   #CE6 6
F4 *F//F43C/O$&O
P-P		PCS/ /
T-9*T((T-	;W 
X*W>>Xr   	carryoverc                    ddl }ddl}ddlm} t          |j                  }|j        }|j        }d|  d |j                    j        dd          d| d| dt           |j	                    
                                           
}	t          |dz            }
||
z
  }|	|d|
||t          d         d	d
dt           d|	 dt           d|	 d} |j        t          d         ddi|d          }|j        dk    r|                                }|                    d          }t%          | d||	|t&          j        t*          j        |j        ||||pi                     dd          |pi                     dd           |j	                              }t0          j                            |           t0          j                                         ||	|||                    dd          ddS d|	 |	||dd	dS )u   
    새 상품 결제를 위한 Toss 결제 세션 생성 유틸.
    - PaymentHistory(PENDING) 임시 레코드 생성
    - payUrl/checkoutPage 반환
    r   N)r   mlink__   r   re   T*https://pay.toss.im/payfront/demo/callbackV2&/api/v2/payments/toss/success?orderno=&status=PAY_COMPLETEX   /payment/fail?code=PAY_PROCESS_CANCELED&message=결제가 취소되었습니다&orderId=orderNor   r   r   r   productDescr   autoExecuteresultCallbackcallbackVersionretUrlretCancelUrlrf   r  r  
   r  r   r   credit_amountcredit_days)r   rE   r'  r[  r   r   r   r   r]  product_pricer.   rL  carryover_credit_daysr   checkoutPager\  F)
paymentKeyr  r   	orderNamer  r  sim_payment_key_)r*  r+  r   r  r~   r|   r.   r,  r-  r   	timestampr/  r_   rU   r.  r0  rp   rZ   r   r   r  r   r(  rC   r
   r   r   rM   )r   r   r  r*  r+  r   r   r]  r.   r[  r?  r@  payment_datarB  rQ   	pay_tokentemp_phs                    r*   !_prepare_toss_payment_for_productr  &  s    !!!!!!F<L)M tss*$*,,"22A2"6ssss-ssRUVbV^VbVdVdVnVnVpVpRqRqssH #&&N.(J '#i(FccXccc$  I  I  G  I  I L 	J!34		 	 	A 	}JJz**	 ! ! ((6z% '%._"$9$9/1$M$M#,?"7"7q"I"I#x|~~
 
 
" 	
w

 $%jj44
 
 	
 433!  r,   z/api/subscription/change-planc                     	 t          j        d          } | st          ddi          dfS t          j        dd          pi }|                    d          }|                    d          pd	                                }|st          dd
i          dfS t          j                            |          }t          j        
                    | t          j                                                  }|rD|j        | k    r9t                              d|  d|j         d           t          ddi          dfS |s.t%          | |d          }t          ddg dddd|d          dfS |dk    r<t'          |          }ddd}t%          | |d          }t          d|||d          dfS t)          |          }t+          ||          }t%          | ||          }t          d|||d          dfS # t,          $ rb}	t                              d           t0          j                                          t          dt5          |	          i          dfcY d}	~	S d}	~	ww xY w)uu   
    req: { "new_product_id": int, "strategy": "carryover"|"refund_all" }
    보안: 사용자 소유권 검증
    r   rJ   r   r   Tr   new_product_idstrategyr  u"   new_product_id가 필요합니다.r   r   r   r   u    변경 시도r   r   N)r  r   r  r  r  )rH   r#  r  r   r   
refund_allzchange_plan failedr   )r   rZ   r   r   r   ra  r   r   r   r   r   r   r   r   r   rA   rv   rC   r  r  _refund_pure_future_periods*_compute_partial_credit_for_current_periodrN   r   r
   rO   rP   )
r   r   r  r  r   r   payment_prepr7  r  rR   s
             r*   change_planr  |  s   E/+i(( 	LG%DEFFKKd4888>B"233HHZ((7K>>@@ 	QG%IJKKSPP-**>:: $)33$6$= 4 
 

%'' 	
  	FL0G;;NN~w~~_k_n~~~G%>?@@#EE  	<WgY]^^^L+,r::/0CC'	   
   |##A,OOM*+A>>I<WgY]^^^L'&'	   
   4LAA ?|WUU	 9'U^___#"#	
 
  
  	  / / /-...

Q())3......./s?   (G1 A*G1 B!G1 8/G1 (AG1 *AG1 1
I;AIIIc                 .   ddl m} ddlm} ddlm} t          j                                                    }	 |j	        
                    |j        | j        k    |j        |j        k    |j        |                                k    |j        |                                k                                  |j                                                                                  }|r|j        r|j        dk    rdddS t-          |j                  }t/          d|j                                        |z
  j                  }|dk    rdddS t-          |j        ||z  z            }	t5          |dd          dk    rd	nd
}
|j        |
z  }t-          |	|z            }t8          j                            d| d| d|j         d|	 d| 
           |	|d}t8          j                            d|            |S # t>          $ r}t8          j                             dtC          |                      t8          j                             d| r| j        nd            t8          j                             d|r|j        nd            dddcY d}~S d}~ww xY w)u   
    현재(오늘 포함) 활성 구간 1건의 남은 가치를 새 상품 기준 '일수'로 환산
    - 현재 코드베이스 정책: 토스 부분환불 불가 → 금액은 환불하지 않고 '일수'로 업사이클
    r   r   rY  )funcr  r.   rm   r0   r   r   z'[ChangePlan] carryover compute: remain=/r   z -> credit_amount=z, credit_days=z@[ChangePlan] _compute_partial_credit_for_current_period return: z@[ChangePlan] _compute_partial_credit_for_current_period failed: z[ChangePlan] subscription_id: Nonez[ChangePlan] new_product_id: N)"r&  r   r  r   rK   r  r   r   r   r   r  rE   rC   r   r   rF  rG  r   r   r   r   rO  r  rP  r   r   rQ  r~   r   rA   rB   rN   rJ   rP   )r   new_productr   r   r  rT  curr   rI  r  new_cycle_daysprice_per_day_newr  rQ   rR   s                  r*   r  r    s   
 211111555555LNN!!E%6"))*lo=!]%<<2dhhjj@0DHHJJ>	
 

 (>,1133
4
4UUWW 	  	:#/ 	:33HA3M3M%&q999.//
Q!:!?!?!A!AE!I OPPQ%&q999 CJ.:*EFGG ")oy!Q!QU]!]!]df'->-*;;<<_n _ _z _ _j_ _4A_ _Q\_ _	
 	
 	

 $1MM kci k klll 6 6 6  !ldghidjdj!l!lmmm  !oUaBm,//gm!o!oppp  !lS^Ajdj!l!lmmm!"1555555556s-   CG6 =AG6 B*G6 6
J B	J	JJc                    ddl m} ddlm}m} t          j                    }g }d}|j                            | j	        |j
                                      |j                                                                                  }|D ]}|j        r|j        s|j        |k    r|j        }	dt%          j                    j        dd          }
t+          |	dz            }|	|z
  }t,          j                            d	          |j        |
|	|d|dd
d	}	 t5          j        t8          d         ddi|d          }|j        dk    r|j        |_        d|
 |_         tB          j"        #                                 |j$        rX| j%        tM          |j$                  z
  | _%        t          j                    | _'        tB          j"        #                                 ||	z  }|(                    |j	        |	|
ddd           n%|(                    |j	        |	|
d|j)        d           # tT          $ r8}|(                    |j	        |	|
dtW          |          d           Y d}~d}~ww xY w||dS )u   
    '지금 이후'에 완전히 위치하는 결제 기간(아직 시작도 안 한 구간)만 전액 환불.
    현재 진행 중인 구간(now ∈ [start, end])은 환불하지 않음.
    r   r   r  r   r   Nr   r   rd   u#   플랜 변경(미래 구간 환불)r   rg   r  r  r   r  r   u   미래구간 환불 완료 - r   Tr  Fr  ),r&  r   r  r   r   r   r   r   r   rC   r   r   r   r   r   rF  rG  r   r*  r,  r-  r  rX   rY   rZ   r'  r+  r.  r/  r0  r1  r   r}   r
   r   rM   rO  r   r   r   r2  rG   rN   rP   )r   r   r   r   r   r  r  r  r  r   r	  r  r  rA  rB  rR   s                   r*   r  r    s   
 211111IIIIIIII
,..CGL$..$& /   h~(--//00 
  -\ -\) 	1I 	%++YF9$*,,"23B3"799I&3,''G7"C *..88N% !(!" $%?
 
K\M+l";+9;M*N'2B@ @ @ =C'' - 6BI%PY%P%PBNJ%%''' + ,0<0E	WYWkHlHlHl0l-2:,../
))+++ F*LNN"%61:tVZ$\ $\ ] ] ] ] NN"%61:uWXW]$_ $_ ` ` ` \ \ \bev-65SVWXSYSY [  [ \ \ \ \ \ \ \ \\O ,V )7;;;s   DH((
I*2-I%%I*r   c                 X   |dk    rdS ddl }ddl}d|  d |j                    j        dd          d| dt	          t          j                                                               }d}dd	i}t	          |d
z            }	||	z
  }
||d|	|
|t          d         dddt           d| dt           d| d}	  |j        t          d         ||d          }t          j                            d|j         d|j                    d}d}d}|j        dk    re|                                }|                    d          }|                    dd          }t          j                            d| d|            n8t          j                            d|j         d |j                    d!| }d}d}dd"lm}m}  || |r|j        nd||||j        d#t3          |d$d          ||d%dddt          j                    &          }t4          j                            |           t4          j                                         |||||d'S # t<          $ rX}t          j                            d(tA          |                      t4          j        !                                 Y d}~dS d}~ww xY w))u  
    사용분 정산 결제를 위해 토스 '결제 준비'를 생성.
    - product_name: '구독 사용분 정산'
    - billing_cycle: 'onetime'로 표기하되, DB에는 monthly처럼 저장해도 무방(실제 갱신 없음)
    - PaymentHistory: PENDING 생성
    r   Nr^  r  r  	_onetime_u   반품 정산r  r  r   re   Tr  r  r  r  u_   /payment/fail?code=PAY_PROCESS_CANCELED&message=정산 결제가 취소되었습니다&orderId=r  rf   r  r  z [UsageSettle] toss prepare resp r
  r\  Fr   r   r  u5   [UsageSettle] 토스 결제 준비 성공: pay_token=z, checkout=u+   [UsageSettle] 토스 결제 준비 실패: z - 
sim_usage_r   toss_paymentsr   r_  r   rE   r'  r[  r   r   r   r   r]  r  r.   rO  rF  rG  r   )r  r  r   r  r  z[UsageSettle] prepare failed: )"r*  r+  r,  r-  r  r   r   r  r/  r_   rU   r.  r   rA   rB   r0  rG   rp   rZ   rv   r&  r   r   rC   r  rQ  r
   r   r   rM   rN   rJ   rP   rO   )r   r   r  r   r*  r+  r[  r]  r  r  r  payloadrespr  checkoutr  r   r   r8  r  rR   s                        r*   r3  r3  ?  sa    qtwgww

(8!(<ww|wwVYZbZfZhZhZrZrZtZtVuVuwwH"L12G ,$%%G

 C  #i(FccXccc$  P  P  FN  P  P G5x}[4gG]_``` a4CS a aVZV_ a abbb	
s""99;;D,,Ixx33H##$|\e$|$|rz$|$|}}}} &&'uUYUe'u'ujnjs'u'uvvv/X//IHJ 	QPPPPPPP^/;ELOO!#*~|TBB%&#"$("&|~~!
 
 
$ 	
r

  #"$
 
 	
      !J#a&&!J!JKKK

ttttts   6FI 
J)AJ$$J)z%/api/subscription/cancel-orchestratedc                  
   	 t          j        d          } | st          ddd          dfS t          j                            | t          j                                                  }|st          ddd          dfS |j	        | k    r:t                              d	|  d
|j         d           t          ddd          dfS t                              d|j         d|j                    d}	 t          j                            |j        t           j                                      t          j                                                                                  }|r3t                              d|j         d|j         d|j                    n#t                              d|j         d           nA# t.          $ r4}t                              dt3          |                      Y d}~nd}~ww xY w|j        t4          j        k    r|r	 t          j                            |j        t           j                                      t          j                                                                                  }|s4t                              d           t          dddddddd          dfS t;          |          }t=          |                    dd                    }t                              d |            |dk    r[t          ddddd|                    d!d          |                    d"d          |                    d#d          d$d%d&d          dfS t?          |j	        ||d         |'          }|rV|                    d(          rA|                    d)          r,|                    d)d*                                           d*k    rt          dd+|pi d,          d-fS t          d||d(         |d)         |                    d.d          |                    d!          |                    d"          |                    d#          d$d%d/d          dfS # t.          $ rE}t          !                    d0           t          dt3          |          d          d1fcY d}~S d}~ww xY wt          dd2d          d-fS # t.          $ rc}tD          j         #                                 t          !                    d3           t          dt3          |          d          d1fcY d}~S d}~ww xY w)4u   
    "정산 먼저 → 전체 환불" 모드 시작점.
    1) 사용분 정산 금액 계산
    2) 정산 결제 준비 + payUrl 반환
    보안: 사용자 소유권 검증
    r   Fr   rI   r   r   r   r   r   r   r   r   r   r   r   Nr   r   r   r   r@   r   r   u>   완료된 결제 없음 — 사용분 정산 0원으로 처리Tr   )r   r  r  r  uN   정산할 금액이 없어 바로 취소/환불을 진행할 수 있습니다.)rH   r$  r"  r   r   r   r   r   r   r  r   uO   정산 금액이 0원입니다. 바로 전체 환불/취소를 진행하세요.r  r  r  r\  u"   정산 결제 링크 생성 실패)rH   rJ   detailr   r  ub   정산 결제부터 진행해주세요. 결제 완료 후 전체 환불/취소가 이어집니다.u)   [CancelChain] 정산 링크 생성 실패r   uc   오케스트레이션 취소를 진행할 수 없습니다. (PG 또는 결제 이력 확인 필요)u<   기간 연장형 구독 취소(오케스트레이션) 실패)$r   rZ   r   r   r   r   r   r   r   r   rA   rv   rC   rB   r   r   r8  r   r   r   r   r'  r   rN   rJ   rP   r   r(  r   r)  r  r3  stripr   r
   rO   )r   r   r  rR   r9  r:  r   r;  s           r*    cancel_subscription_orchestratedr    s   yA+i(( 	^u7VWWXXZ]]]#)33$6$= 4 
 

%'' 	  	_u7WXXYY[^^^ 7**NN~w~~_k_n~~~u7PQQRRTWWWl>Ynzn}  	A  	A  	A 
	G,2(yQYQcydd'x(A(F(F(H(HII$uww   e  fA[  f  fftf{  f  f  KY  Kd  f  f  g  g  g  gccccddd 	G 	G 	GLLESVVEEFFFFFFFF	G &-*EEE.ELI"0"6#,9\_U]Ug9#h#h#+8N,E,J,J,L,L#M#M#&355 
 & KK `aaa"#'&''+&**/	- - $t	$ 	$ 	 	 	 	 :.II
":>>.!#D#DEED
DDEEE1$$"#'&''+&**/.8nn\1.M.M.8nn\1.M.M-7^^K-K-K% %
- 
- $u$ $    " !B(0!-#1!#4!-	! ! ! % M,=,=i,H,H P]PaPabjPkPk o|  pA  pA  BJ  LN  pO  pO  pU  pU  pW  pW  []  p]  p]"#(!E"/"52$ $   	  #".#0#;"/"9&3&7&7e&L&L*4..*F*F*4..*F*F)3)D)D! !
) 
)  D          I I I  !LMMM53q66BBCCSHHHHHHHI
 z
 
    	
  A A A

WXXX53q66::;;S@@@@@@@As   )S AS ?AS ,S 1C	F; :S ;
G9*G4/S 4G99S B&Q2 =B/Q2 -BQ2 :A7Q2 2
S<:R<6S7S <SS 
U"AU :U Uz/api/user/profilec            	         	 t          j        d          } | st          ddi          dfS t          j                            |                                           }|st          ddddddd          dfS t          |j        pd|j        pd|j	        pd|j
        pd|j        pd|j        pdd          dfS # t          $ r*}t          dt          |          i          d	fcY d
}~S d
}~ww xY w)u   사용자 프로필 조회r   rJ   r   r   r  r\  )company_namebusiness_numberphoneaddress
departmentpositionr   r   N)r   rZ   r   r   r   r   r   r  r  r  r  r  r  rN   rP   )r   profilerR   s      r*   get_user_profiler     sS   /+i(( 	LG%DEFFKK#--g->>DDFF 	 "#%       #06B&6<"](b,"!,2(.B
 
    	  / / /Q())3......./s*   (B9 AB9 7AB9 9
C-C("C-(C-PUTc                     	 t          j        d          } | st          ddi          dfS t          j                    }t
          j                            |                                           }|s/t          |           }t          j         
                    |           |                    dd          |_        |                    dd          |_        |                    d	d          |_        |                    d
d          |_        |                    dd          |_        |                    dd          |_        t#          j        t&          j                  |_        t          j                                          t          ddi          dfS # t.          $ rH}t          j                                          t          dt3          |          i          dfcY d}~S d}~ww xY w)u    사용자 프로필 업데이트r   rJ   r   r   r  r  r\  r  r  r  r  r  r"  u)   프로필이 업데이트되었습니다.r   r   N)r   rZ   r   r   r   r   r   r   r   r
   r   r  r  r  r  r  r  r   r   r   utcr   rM   rN   rO   rP   )r   r   r  rR   s       r*   update_user_profiler  @  s   /+i(( 	LG%DEFFKK!!#--g->>DDFF 	$!'222GJNN7####xx;;"&((+<b"A"A"--((9b11!XXlB7788J33%\(,77

	#NOPPRUUU / / /

Q())3......./s#   (F E+F 
G)!=G$G)$G)z//api/subscription/finish-settlement-then-refundc                     	 t          j        d          } | st          ddd          dfS t          j        dd          pi }|                    d          t
          j                            | t          j	        	          
                                }|st          dd
d          dfS |j        | k    r:t                              d|  d|j         d           t          ddd          dfS r\t          j                            t           j        |           
                                }|st          ddd          dfS |j        }dTd} ||          \  }}|r9t&          j                            d|            t          dd| d          dfS |                    d          p|                    d          }|dk    rt          dd| dd          dfS t           j        |_        t/          j                    |_        t4          j                                          t&          j                            d            nt&          j                            d            t          j                            |j        t           j        !                              t          j                                                                                   }	d"t          d#tB          fd$fd%|	D             }	|	s2t&          j                            d&           	 d'd(l"m#}
 d'd)l$m%} tM          |j                  }|r|j'        |
j(        k    r ||j        d*tS          |j                  d+,          }|                    d-          r8t&          j                            d.|                    d/d'           d0           n5t&          j                            d1|                    d2                      nF# tT          $ r9}t&          j                            d3tS          |                      Y d}~nd}~ww xY wt          j+        |_        d|_,        t/          j                    |_-        t/          j                    |_.        t_          |           t/          j                    |_        t4          j                                          	 tM          |j                  }|r%d4|_0        t4          j                                          n# tT          $ r Y nw xY wt          dd'g d5d6          d7fS d'}g }|	D ]z} |          r0t&          j                            d8|j         d9|j1                    >te          |j3        pd'          }|d'k    r[d:ti          j5                    j6        dd;          }te          |d<z            }||z
  }tn          d=         |j        |||d'|d'd>d?	}	 tq          j9        tn          d@         dAdBi|dCD          }|j:        d7k    rt           j;        |_        dE| |_<        t4          j                                          |j=        rxt}          |j?        |j-        t          te          |j=                  F          z
            |_-        t/          j                    |_        t4          j                                          ||z  }|A                    |j        ||ddG           nYt&          j                            dH|j         dI|j:         dI|jB                    t          ddJ|j        |jB        dK          dfc S # tT          $ rU}t&          j        C                    dL           t          ddMtS          |           |j        dN          dOfcY d}~c S d}~ww xY w	 d'd(l"m#}
 d'd)l$m%} tM          |j                  }|r|j'        |
j(        k    r ||j        d*tS          |j                  d+,          }|                    d-          r8t&          j                            d.|                    d/d'           d0           n5t&          j                            d1|                    d2                      nF# tT          $ r9}t&          j                            d3tS          |                      Y d}~nd}~ww xY wt          j+        |_        d|_,        t/          j                    |_-        t/          j                    |_.        t_          |           t/          j                    |_        t4          j                                          	 tM          |j                  }|r%d4|_0        t4          j                                          nF# tT          $ r9}t&          j                            dPtS          |                      Y d}~nd}~ww xY wt          ddQ||d5dR          d7fS # tT          $ rh}t4          j         D                                 t&          j        C                    dS           t          dtS          |          d          dOfcY d}~S d}~ww xY w)Uu  
    1) 프론트에서 정산 결제(usage_settlement)를 완료한 뒤 호출되는 마무리 단계
    2) (있다면) 정산 결제의 Toss 상태를 확인해 PENDING -> COMPLETED 로 확정
    3) 체인에 속한 COMPLETED 결제들을 최신→과거 순으로 '전액 환불'
    4) 구독을 CANCELLED 처리, 권한 회수
    응답: { success, refund: { total_refund, items: [...] } }
    보안: 사용자 소유권 검증
    r   Fr   rI   r   Tr   r  r   r   r   r   r   u    처리 시도r   r   )r[  r   r   un   정산 결제 내역(PENDING)을 찾을 수 없습니다. 이미 처리되었거나 잘못된 요청입니다.r   Nc                 2   	 | s|sdS t          j        t          d         ddit          d         | |dd          }|j        d	k    r|                                d fS d d
|j         d|j         fS # t          $ r}d t          |          fcY d }~S d }~ww xY w)N)Nu6   payToken 또는 orderNo 중 하나는 필요합니다.rh   r  r  re   )r   r   r  r  r  r   u   status 조회 실패: r
  )r+  r.  r/  r0  rp   rG   rN   rP   )r  order_nor  rR   s       r*   _fetch_toss_statusz9finish_settlement_then_refund.<locals>._fetch_toss_status  s    ($ ^X ^]]#=#L1!/1C D(3I(>*3)13 3 !#  D '3..#yy{{D00!X$:J!X!XTY!X!XXX  ( ( (Q<''''''(s)   A1 AA1 A1 1
B;BBB)r  r  z"[FinishSettle] toss status error: u$   정산 결제 상태 확인 실패:   	payStatusr   PAY_COMPLETEu8   정산 결제가 완료되지 않았습니다. (상태: )z-[FinishSettle] settlement COMPLETED: orderId=zG[FinishSettle] settlement is zero-amount; skipping payment verificationr   r  r   c                     | j         pd}| j        pd}|                    d          p"| j        pd                                dk    pd|v S )Nr\  r^  r_  r`  )r[  r]  rb  r.   ra  )r  rd  r|   s      r*   _is_usage_settlementz;finish_settlement_then_refund.<locals>._is_usage_settlement  s^    ;$"CO)rD ~.. $$*1133y@$t#r,   c                 D    g | ]} |          sr|j         k    |S rj  r[  )r   r  r  settlement_order_ids     r*   r   z1finish_settlement_then_refund.<locals>.<listcomp>  sP     
 
 
''++
 )
 .0[<O-O-O -O-O-Or,   u:   [FinishSettle] 환불할 COMPLETED 결제가 없습니다.r   r   r   r   r   r   rH   u/   [FinishSettle] 남은 크레딧 사용 완료: r   r   u/   [FinishSettle] 남은 크레딧 사용 실패: rJ   u7   [FinishSettle] 크레딧 사용 처리 실패(무시): r  r  )rH   r#  r   z,[FinishSettle] skip usage settlement PH: id=z, order=r   r   r   re   u#   반품 취소(체인 전체 환불)r   rg   r  r  r   r  r  r   )r  r   r	  rH   u$   [FinishSettle] 환불 실패: ph_id=r
  u.   일부 환불 실패로 중단되었습니다.r  u   [FinishSettle] 환불 예외u   환불 처리 중 오류: )rH   rJ   r  r   u-   [FinishSettle] 역할 회수 실패(무시): uR   정산 결제 확인 후 전체 환불 및 구독 해지가 완료되었습니다.r%  z[FinishSettle] unexpected error)NN)Er   rZ   r   r   r   r   r   r   r   r   r   r   rA   rv   rC   r   r8  r  r'  r   rJ   r   r   r   r   r   r
   rM   rB   r   r   r   r   boolr   r   r   r   r   r   r   rP   rN   r   r   r   r   rD   r4  r[  r  r   r*  r,  r-  r/  r+  r.  r0  r1  r}   rO  rP  r-   r   r2  rG   r   rO   )r   r   r   	settle_phr  r  status_dataerr
pay_statusr9  r   r   r   r   rR   rC  r  r  r  r   r	  r  r  refund_payloadrB  r  r  s                            @@r*   finish_settlement_then_refundr  `  sv   AA+i(( 	^u7VWWXXZ]]]d4888>B"hhy11 $)33$6$= 4 
 

%'' 	  	_u7WXXYY[^^^ 7**NN~w~~_k_n~~~u7PQQRRTWWW  7	o&,66,' 7   egg	   $ N       
 "-I( ( ( ($  21IPcdddK o"(()Sc)S)STTT5;gbe;g;ghhiiknnn$55R9R9RJ^++$eXbeee         (1I#+<>>I J##$iTg$i$ijjjj ##$mnnn ).$9\_XM_9``#8N$=$B$B$D$DEE355 	
	^ 	 	 	 	 	
 
 
 
 
'
 
 
  %	_##$`aaao<<<<<<RRRRRR'(<== 
CD26F6MMM$A$A , 4$\%(%9%9'B	% % %M %((33 C#*//  1Obobsbst~  AB  cC  cC  1O  1O  1O  P  P  P  P#*22  4Berevevw~ee  4B  4B  C  C  C o o o"**+mehijekek+m+mnnnnnnnno #5">L-1L*$,LNNL!(0L%-l;;;&.lnnL#J$\%9:: ((AFJ%%'''   tTV7W7WXXYY[^^^  ?	 ?	B##B'' "''(sWYW\(s(sfhfq(s(sttta((F{{9$*,,"23B3"799I&3,''G7"C%i0N% !(!" $%?
 
N'M-+-?@'	   =C'' ( 1BI%C	%C%CBNJ%%''' + ,03(3(1I3rG[C\C\4]4]4]]1 1- 3;,../
))+++ F*LLL/8T"K "K L L L L  &,,-tTVTY-t-t\]\i-t-tlmlr-t-tuuu"#(!Q-/U v	$ $  
    	L    ",,-KLLL$B#a&&BB)+      	        	k888888NNNNNN#L$899D 
.2B2III = =(0 X!$\_!5!5#>	! ! ! !$$Y// &++  -K^k^o^opz|}^~^~  -K  -K  -K  L  L  L  L&../}anararsza{a{/}/}~~~ 	k 	k 	k&&'iadefagag'i'ijjjjjjjj	k 1:)-& ($,LNN!),777"*,..

	a !566A $$
!!### 	a 	a 	a&&'_WZ[\W]W]'_'_````````	a k , 
 
    	  A A A

$$%FGGG53q66::;;S@@@@@@@As,  )h A?h .Ah 3Ah 
Ah  Ah (D=h &CP  h 
Q/P?:h ?QBh ;T h 
Th Th 8B7h 0E#]h h 
^7"A^2*^7+h 2^77h ;Cb h 
c /ch cBh *;f& %h &
g)0/g$h $g))h 
i6Ai1+i61i6z!/api/subscription/cancel-estimatec            
         	 t          j        d          } | st          ddd          dfS t          j                            dt
                    }t          t          j                            d                    }t          j        	                    | t          j        	                                          }|rE|j        | k    r:t                              d
|  d|j         d           t          ddd          dfS |s't          ddddddiddddi|rg nddd          dfS t#          |          }t%          |          }t'          d |D                       }t)          |          }d}|rct*          j                            |          }	|	rBt-          ||	          }|                    |t          |	j        pd          |	j        d           dt          |          t          |                    dd                    ddt          |          i|dt          |                    dd                    t          |                    dd                    t          |                    dd                    |                    d          r'|                    d                                          nddd d}
|rt7          |          |
d!         d"<   t          |
          dfS # t8          $ rE}t                              d#           t          dt=          |          d          d$fcY d}~S d}~ww xY w)%u
  
    여러 시나리오의 예상 금액을 한 번에 제공.
    쿼리:
      - new_product_id (옵션): 캐리오버(플랜 변경) 시뮬레이션용
      - detailed=1/true   : 결제건별 브레이크다운 포함
    보안: 사용자 소유권 검증
    r   Fr   rI   r   r  )typedetailedr   r   r   u    조회 시도r   r   Tr   )r<  settlement_amountr<  N)settle_then_refundrefund_future_onlyr  ru  )	scenariosr!  	breakdown)rH   estimater   c              3   B   K   | ]}t          |j        pd           V  dS r  r  r  s     r*   r  z*estimate_cancel_amounts.<locals>.<genexpr>  s0       H HRY^!!4!4 H H H H H Hr,   )r  new_product_pricenew_product_cycler   r   r   r   )r   r   r   ru  )r  r!  r  r  zcancel-estimate failedr   )r   rZ   r   r   r   r  
_is_truthyr   r   r   r   r   r   r   rA   rv   rC   _get_chain_completed_phr)  r5  _estimate_refund_future_onlyr   &_estimate_carryover_for_current_periodupdater~   r.   r   _build_chain_breakdownrN   r   rP   )r   r  r  subchain_phr:  refund_total_chainr  r  r  r  rR   s               r*   estimate_cancel_amountsr!  n  s   TA+i(( 	^u7VWWXXZ]]] ))*:)EEgl..z::;; **$6$= + 
 

%'' 	
  	X3;'))NNuwuu_b_euuuvvvu7PQQRRTWWW 	 @AWX.Y.Y/=q.A%)" "
 +D1'/!9T       +3// 2(;;
  H Hx H H HHH :(CC 	 	!-++N;;K B3TT	  &4),[->-C!)D)D)4)B" "     ),,>(?(?-0PQ1R1R-S-S+ +
 ',>(?(?+ "+	 	 #&jnn\1&E&E"F"F"%jnn\1&E&E"F"F!$Z^^K%C%C!D!D'1~~m'D'D%OJNN=$A$A$K$K$M$M$MJN  
 
.  	P/Eh/O/OGJ,w$$ A A A122253q66::;;S@@@@@@@As0   )K3 CK3 (K3 +GK3 3
M=:L=7M=Mc                     | j         pd}| j        pd                                }| j        pd}|                    d          p	|dk    pd|v S )uB   정산/일회성 결제 식별(체인 환불 대상에서 제외).r\  r^  r_  r`  )r[  r.   ra  r]  rb  )r  rd  re  r|   s       r*   _is_settlement_phr#    sY    ;"C!r
(
(
*
*CO!rD>>.))SSI-=S(dBRSr,   c                     t           j                            | j        t          j                                      t           j                                                  	                                }d |D             S )u;   체인 COMPLETED 결제(정산건 제외) 최신순 목록.r   c                 0    g | ]}t          |          |S rj  )r#  r  s     r*   r   z+_get_chain_completed_ph.<locals>.<listcomp>  s&    >>>2(9"(=(=>B>>>r,   )
r   r   r   rC   r8  r   r   r   r   r   )r   rv  s     r*   r  r    s^    #	,/(BT	UU27799::  ?>>>>>r,   rp  c                 >    | pd                                 dk    rdndS )Nr\  r0   r   r   )ra  )rp  s    r*   _days_for_cycler'    s&    8""$$0033b8r,   c                    | sddddddS d}t          j                    }d}d}d}d}| D ]j}t          |j        pd          }||z  }|j        p|j        }	|j        }
|	s|}	|
s1t          t          |dd                    }|	t          |          z   }
|rt          ||	          n|	}t          d|
                                |	                                z
  j                  }||z  }d}|                                |	                                k    rmt          |t          dt          |                                |
                                          |	                                z
  j        dz                       }||z  }||z  }|||z  z  }lt          t          |                    }t          |          t          |          t          |          t          |          |d	S )
u@  
    '구간별 일일단가'로 사용가치 합산.
    - 각 결제별 interval = [subscription_start_date, subscription_end_date]
      (없으면 created_at 기준 + 30/365 fallback)
    - interval∩[chain_start..today]의 일수만 사용
    - usage_value += (amount / interval_days) * used_days_in_interval
    r   N)r   r   ru  r   r   rs  r.   rm   r   r"   rt  )r   r   rN  r   rF  r   rG  r'  rQ  r   r%   rP  r   r   r  rR  )rv  ru  rT  r   r   used_days_accumusage_valuer  amtrS  r}  bc_daysinterval_daysused_in_intervalr  r   s                   r*   _calc_usage_charge_for_chain_v2r/    s     14a1 1 	1 KLNNEJJOK 2 2BIN##c
->) 	H 	8%gb/9&M&MNNG	w 7 7 77F 5@Mc+x000X A ?EFFm#
 ::<<8==??**"=#&q3uzz||V[[]]+K+Khmmoo+]*cfg*g#h#h j  j 	++%w!111u[))**L *oo*oo))L))"  r,   c                     | sdS t          j                    }d}| D ]1}|j        p|j        }|r||k    r|t	          |j        pd          z  }2t	          |          S )uX   
    완전한 미래 구간(시작일 > now)만 환불했을 때의 총 환불액.
    r   )r   r   rF  r   r  r   )rv  r   totalr  rS  s        r*   r  r  #  st      q
,..CE ) )-> 	)3Sa(((Eu::r,   c                 t   t          j                                                    }t          j                            t          j        | j        k    t          j        t          j
        k    t          j        t          j        |t           j                                                  k    t          j        t          j        |t           j                                                  k                                  t          j                                                                                  }|r|j        r|j        dk    rdddS t+          |j                  }t-          d|j                                        |z
  j                  }|dk    rdddS t+          t1          t3          |j                  ||z  z                      }t7          t9          |dd                    }|j        pdt-          d|          z  }|dk    rt+          ||z            nd}	t+          |          t+          |	          dS )u   
    현재 진행중인 구간 남은 가치를 새 상품 기준으로 '일수'로 환산(추정치).
    (실환불 없이 계산만)
    r   r  r.   rm   r"   )r   r   r   r   r   r  rE   rC   r   r8  r   rF  combiner%   timerG  r   r   r   r   rO  r  rP  r   rR  rN  r   r'  rQ  r~   )
r   r  rT  r  r   rI  r  r  r  r  s
             r*   r  r  2  s   
 LNN!!EF-@$(::59I%QYQ]QbQbQdQd9e9ee3x7Gx|O`O`ObOb7c7cc	  H^.335566EGG   6c+ 6s/D/I/I!"1555S*++JS6;;==EKLLN!"1555eCJ//>J3NOPPQQM$W[/9%U%UVVN$*/a3q.3I3II<MPQ<Q<Q#m&77888WXK //K@P@PQQQr,   sc                 H    t          |                                           dv S )N)1trueyesr&   )rP   ra  )r5  s    r*   r  r  S  s    q66<<>>666r,   c                 \   | j         p| j        }|st          j                    }| j        }|s@t          | dd          pd                                }|t          |dk    rdnd          z   }t          d|	                                |	                                z
  j
                  }|||fS )u@   결제 1건의 [start, end], 총일수 계산 (fallback 포함).r.   r\  r0   r   r   r   r"   )rF  r   r   r   rG  rQ  ra  r   rP  r   r   )r  rS  r}  rp  r-  s        r*   _interval_boundsr;  W  s    ):R]H "<>>%F Jb/2..4";;==I"..33bIIIIFKKMMHMMOO;ABBMV]**r,   c                    g }t          j                                                    }| D ]}t          |          \  }}}t	          |j        pd          }||z  }d}	||                                k    r[t          |t          dt          ||                                          |                                z
  j        dz                       }	||	z  }
|                                |k    }|rt          |          nd}|
                    |j        |j        |j        |j        pd                                t          |          |                                |                                t          |          t	          t#          |d                    t          |	          t          t#          |
                    t%          |          t          |          d           |S )u   
    결제건별 상세 브레이크다운 생성:
    - 결제 금액, 주기, interval, 일일단가, interval 내 사용일수, 사용가치
    - 완전미래구간 여부 및 환불가능액(미래구간 환불 시나리오 참고용)
    r   r"   r\     )r  r[  r]  r.   r   r-   r   r-  r  used_days_in_intervalr*  fully_futurerefundable_future_amount)r   r   r   r;  rN  r   r%   rP  r   r  r2  rC   r[  r]  r.   ra  r   rR  r  )rv  r  rT  r  rS  r}  r-  r   r  r>  r*  r?  r@  s                r*   r  r  d  s    ELNN!!E    *:2*>*>'&-ry~A&&=( !"HMMOO##$'AE6;;==11HMMOOCIAMNN% %!
  55}}.2>#E3v;;;A "$%O .4";;==&kk",,..((** //U7A..//%()>%?%?u[1122 ..(+,D(E(E
 
 	 	 	 	  Lr,   z/api/payments/statusc                     t           j                            d          } | st          dd          dfS t          j                            |                               t          j        	                                          
                                }|st          dd          d	fS t          d
|j        j                                                  S )Nr  FzorderId requiredrI   r   r  	NOT_FOUND)rH   r   r   T)r   r   rZ   r   r   r   r   r   r   r   r   r   r   ra  )r[  r  s     r*   payment_statusrC    s    |	**H Eu,>???DD			'	'	'	:	:	C	CND]DbDbDdDd	e	e	k	k	m	mB ?u[9993>>4	(=(=(?(?@@@@r,   z/api/subscription/early-renewc                     t          j        d          } | st          ddd          dfS t          j                            | d                                          }|st          dd          d	fS |j        | k    r:t          	                    d
|  d|j
         d           t          ddd          dfS t          j                            |j
        d                              t          j                                                                                  }|r|j        st          dd          d	fS t          j                            t          j        |j
        k    t          j        |j        k    t          j                            d                                                    }|rt          dd          dfS |j        p|j        j        pd}|dk    rdnd}|j        t-          d          z   }|t-          |dz
            z   }|dk    r|j        j        n|j        j        }d|j
         dt3          t5          j                               }	t3          t7          |dz                      }
||
z
  }|	|d|
||j        j        t:          d         d d!d"t<           d#|	 d$t>           d%|	 d&}tA          j!        t:          d'         d(d)i|d*+          }|j"        d,k    rE	 |#                                }n# tH          $ r d-|j%        i}Y nw xY wt          dd.|           d/fS |#                                }|                    d0          }|                    d1d2          }t          | |j
        ||	|tL          j'        tP          j)        |j*        |j        j        |||||tW          j,                    3          }tZ          j         .                    |           tZ          j         /                                 t          d ||	||j        j        |d4|0                                |0                                |d56          d,fS )7u2   조기 갱신 - 보안: 사용자 소유권 검증r   Fr   rI   r   r   r   r   r   r   r   u    갱신 시도r   r   r   r   u5   마지막 결제 내역이 올바르지 않습니다.)r  r   u/   이미 다음 구간 결제가 존재합니다.i  rm   r   r   r"   r   r0   mlink_renew_r  r   r   re   Tr  r  r  r  r  r  rf   r  r  r  r  r   r"  u   Toss 준비 실패: r  r   r  r\  r  )r  r  r   r  r  )startend
cycle_days)rH   r   period)1r   rZ   r   r   r   r   r   r   rA   rv   rC   r   r   rG  r   r  rE   rF  r   in_r.   r   r   
price_yearr~   r  r4  rR  r|   r/  r_   rU   r+  r.  r0  rp   rN   rG   r   r  r   r(  r   r   r   r
   r   rM   r   )r   r  last_phfuture_existscyclerH  
next_startnext_endr   r[  r?  r@  r  rB  r   rQ   r  checkout_urlr  s                      r*   early_renewrR    s    k)$$G Z53RSSTTVYYY


&
&wx
&
H
H
N
N
P
PC Su,LMMMsRR {gq7qq[^[aqqqrrr53LMMNNPSSS #	#&	EE=BBDDEE   j'7 ju,cdddfiii $)f^;SVC+CgFcc+2667NOOQ Q egg	 
  du,]^^^`cccG!:GiEi''SJ.1B1B1BBJiZ\::::H',hS[##CK<MF9cf99s49;;'7'799H v|,,--N.(J '{'i(FccXccc$  I  I  G  I  I L 	J!34		 	 	A 	}	'6688DD 	' 	' 	'qv&DDD	'u,I4,I,IJJJCOOVVXXF

:&&I::nb11L $$2>[%$ *&<>>  G" JNN7J#)"
 
  ))++%%''$
 
    s   *K? ?LLrl  )bflaskr   r   r   r   r   flask_loginr   r	   r   r
   r   r  r   r   r   r   r   r   r   r&  r   r8  r   r   r   r   calendarr   rp   base64hashlibhmacloggingr4  r*  r+  rX   src.config.env_loaderr   src.utils.user_refr   __name__subscription_bp	getLoggerrA   r  r+   rP   r>   rD   rS   r]   ra   rU   r_   rB   rY   rZ   r/  router   r   r   r   r   rD  rJ  dictrW  r  r  r)  r  r  r  r  r  r  r3  r  r  r  r  r!  r  r#  r  r'  r/  r  r  r  r;  r  rC  r.  rR  rj  r,   r*   <module>ra     s   C C C C C C C C C C C C C C 4 4 4 4 4 4 4 4 $ $ $ $ $ $ $ $ B  B  B  B  B  B  B  B  B  B  B  B  B  B  B  B  B  B H H H H H H H H 8 8 8 8 8 8 8 8 8 8 8 8                         % % % % % % / / / / / /)NH55		8	$	$ t            Hh Hs H H H H(
vL 
v 
v 
v 
v3s 3 3 3 3&# # ## # # MOO	<>>   E"*..*V*V  E  Edm  E  E  {C  E  E  F  F  F z~~n--5657  3eWEE$/ $/ FE$/R 4vhGG?/ ?/ HG?/DF? F? F?P :UGLL./ ./ ML./f @6(SS"/ "/ TS"/H 1F8DDH/ H/ EDH/V* * *^$    >` ` ` ` ` `FCT C C C CLD D D D D D@ :UGLLV V MLV>Y< Y< Y<vT T TDSWK T T T Tl 6IIJ/ J/ JIJ/Z/6 /6 /6b@< @< @<HX[^ X X X Xv >QQ@A @A RQ@AJ *UG<</ / =</> *UG<</ / =</> HSYRZ[[JA JA \[JAZ :UGLL\A \A ML\A~TT T T T T? ? ?9 9 9 9 9 9= = = = =@S    R R R R RB7#* 7 7 7 7 7
+ 
+ 
++ + +Z -..A A /.A 566q q 76q q qr,   