o
    3hRT                     @   sZ  d dl mZ d dlmZmZ d dlmZmZmZ d dl	Z	d dl
m
Z
mZ d dlmZmZ edeZdd	gZd
dgZdd Zdd Zdd Zejddgdedd Zd dlZd dlmZ d dlmZ dZdedB fddZdeeB e B dB defdd Z!d1d"e fd#d$Z"d d%l#m$Z$ d2d'e%de&fd(d)Z'd d*l#m(Z( d+d, Z)d-d. Z*d3de&fd/d0Z+dS )4    )db)BillingSync
Subscriber)requestjsonify	BlueprintN)datetimetimezone)DecimalInvalidOperationcallbackz192.168.0.134z203.0.113.0ztrustedpartner.comz	myapi.comc                 C      | t v S )z$Check if the IP is in the whitelist.)ALLOWED_IPS)ip r   F/home/www/bk.finsightngr.online/FinSight/app/routes/callback_routes.pyis_ip_whitelisted      r   c                 C   r   )z(Check if the domain is in the whitelist.)ALLOWED_DOMAINS)domainr   r   r   is_domain_whitelisted   r   r   c                    s    fdd}|S )zIDecorator function to check if the request's IP or domain is whitelisted.c                     sf   t j}t j}td| d|  t|st|r  | i |S td| d|  tddidfS )NzReceived request from IP: z
, Domain: zUnauthorized request from IP: errorzUnauthorized IP/domaini  )	r   remote_addrhostlogginginfor   r   warningr   )argskwargsZ	client_ipZclient_domainfuncr   r   wrapper   s   z whitelist_check.<locals>.wrapperr   )r    r!   r   r   r   whitelist_check   s   r"   z
/data_syncPOST)methodsc                  C   s  zt jdd} | d}| d}| d}| d}| d}| d}| d	}| d
}| d}	| d}
t||||||||	|
g	sOtddidfW S zt|d}|r^t|dnd }W n tys   tddidf Y W S w t|||||||||	|
d
}t	j
| t	j
  tjj|d }|r||_||_|r|n|j|_||_||_td| d nt|||||d|d}t	j
| td| d t	j
  td|  tddidfW S  ty
 } ztdt|  tddidfW  Y d }~S d }~ww ) NT)forceZTransactionIDmsisdnZ	ProductIDZAmountZ	TimestampZ
ExpiryDateZ
UpdateTypeZNetworkZChannelZ	Autorenewr   zMissing required fieldsi  z%Y-%m-%dT%H:%M:%SzInvalid date format
transaction_idr&   
product_idamount	timestampexpiry_dateupdate_typenetworkchannel	autorenewphonezSubscriber z	 updated.)r2   servicePack
pricePoint	starttimeendtimesubscribeStatusr.   zNew subscriber z added.z%Successfully processed billing sync: messagez"Billing synchronization successful   zError processing data sync: zFailed to process data sync  )r   get_jsongetallr   r   strptime
ValueErrorr   r   sessionaddcommitr   query	filter_byfirstr3   r4   r6   r.   r5   r   r   	Exceptionr   str)datar(   r&   r)   r*   r+   r,   r-   r.   r/   r0   Zbilling_entry
subscriberZnew_subscriberer   r   r   	data_synck   sz   










	
rK   )current_app)SQLAlchemyErrorz8https://fin.finsightngr.online:30701/subscription/activesc                 C   sX   | sd S z|  drt| ddtjjd dW S t| W S  ty+   Y d S w )NZz+00:00)tzinfo)endswithr   fromisoformatreplace
astimezoner	   utcrF   )rN   r   r   r   _parse_iso_dt   s   
$rV   valreturnc              
   C   sZ   | d u rdS zt t| }t|| kr| dW S |dW S  tttfy,   Y dS w )N0f)r
   rG   format	normalizer   r?   	TypeError)rW   dr   r   r   _to_decimal_str   s   &r_   d   limitc                    s  |pt j}d}d } }}d}	 ztjt| |ddd}|  W n  tjyA } ztj	d| d|  W Y d}~qd}~ww |
 pGi }	|	d	g }
|	d
}|	dd}|	d}|
D ]Q}|d7 }|d}|d}|d}|d}t|dpd}t|d}|rt|ntd}t|d}|d}|d}t|d}t|d}t|d}d|rtjj|d du rtjj||dtj  t|pd|||||||d||d }|r||d< |r||d< du r8t|p| d!| ||||p|pt ||||dd"
|p d_|_|_|r.|_| |d7 }q`d  fd#d$}|d|d  |d|d  |d|d  |d|d  |d%|d%  |d&|d&  |d'|d'  |d|d  |d(|d(  |d|d  |d|d  d|v r|d|d  d|v r|d|d   r|d7 }q`|d7 }q`z|  W n ty } ztj d) |!  W Y d}~qd}~ww |sݐq|du r|| 7 }n|}qtd
t" v r|nd||||t # d* d+}tj$d,|  |S )-ze
    Pull all active subs from provider and upsert into BillingSync.
    Returns a summary dict.
    r   T)ra   offset   )paramstimeoutz%[BillingSync] Fetch failed at offset=z: NrH   totalhas_moreFnext_offset   r&   operatorsubid	cptransidr)    r*   rY   
expirydater/   actionlast_seen_at
created_at
updated_at)rl   )r&   r)   unknown)rl   r&   r)   r*   r,   r-   r.   r/   r0   rk   rp   -r'   c                    s*   t | }||krt| | d d S d S )NT)getattrsetattr)attrnew_valZold_valchangedrowr   r   set_if_diffT  s
   
z1sync_active_subscribers_once.<locals>.set_if_diffr,   r-   r.   r0   z)[BillingSync] Commit failed; rolling backrO   )Ztotal_reported
total_seeninsertedupdated	unchangedfinished_atz[BillingSync] Sync summary: )%r   r@   requestsr<   PROVIDER_URLZraise_for_statusZRequestExceptionrL   loggerr   jsonrG   r_   r
   rV   r   rC   rD   rE   order_byrr   descdictr   utcnowrl   rk   rp   rq   rA   rB   rM   	exceptionrollbacklocals	isoformatr   )ra   r@   rb   r~   r   r   r}   resprJ   payloadrH   rf   rg   rh   itemr&   r.   rk   rl   r)   
amount_strZ
amount_decr,   r/   ro   rp   rq   rr   Zdesiredr|   summaryr   ry   r   sync_active_subscribers_once   s   


















  r   )textThard_truncatec                 C   s  t j}ddd}zc| rP|jj }|dv r(tjd t j	t
d d|d< q`|d	v r?tjd
 t j	t
d d|d< q`tjd t jtjdd ntjd t jtjdd t j  d|d< |W S  ty   tjd t j  | Y S w )z
    Remove all rows from BillingSync.
    hard_truncate=True tries TRUNCATE (faster). Fallback to DELETE ALL.
    Returns a summary dict.
    deleteF)modeok)
postgresqlZpostgresz)[BillingSync:EXPUNGE] TRUNCATE (Postgres)z6TRUNCATE TABLE billing_syncs RESTART IDENTITY CASCADE;Ztruncate_postgresr   )mysqlmariadbz&[BillingSync:EXPUNGE] TRUNCATE (MySQL)zTRUNCATE TABLE billing_syncs;Ztruncate_mysqlz=[BillingSync:EXPUNGE] Unknown dialect, falling back to DELETE)synchronize_sessionz [BillingSync:EXPUNGE] DELETE ALLTr   z[BillingSync:EXPUNGE] DB error)r   enginedialectnamelowerrL   r   r   r@   executer   rC   r   r   rB   rM   r   r   )r   r   r   r   r   r   r   expunge_billing_sync  s2   




r   )r   c                   C   s   t  S N)r   r   r   r   r   r   _utcnow_naive  r   r   c                 C   s.   z| d ur
t | W S dW S  ty   Y dS w )N        )floatrF   )rW   r   r   r   	_to_float  s
   r   c                 C   s.  | pt j} t }t }d } }}d}tjtjdktj|ktj	
 ttjttj}|dD ]}|d7 }|j	}	|	sAq5|	|v rFq5||	 |	}
|j}|jpW|jpW|}t|jp]d}t|j}d}|j}tjj|
d }|du rt|
d||||||d}| | |d7 }q5d	}|jdu r|r||_d}|j|kr||_d}|jpd|kr||_d}|jpd
|kr||_d}|jdurd|_d}|jpd|pdkr||_d}|r|d7 }q5|d7 }q5z|   W n ty   t j!"d | #  Y nw t$|t%||||t&' ( d d}t j!)d|  |S )a  
    Upsert Subscriber rows from BillingSync rows where expiry_date > now.
    - Only keep the latest BillingSync row per MSISDN (by updated_at desc).
    - Insert if Subscriber(phone=msisdn) does not exist.
    - Update if exists (only change fields we own: endtime, servicePack, pricePoint,
      subscribeStatus, network, starttime (initial if missing)).
    - Do not touch: email, news_link, has_received_message.
    r   Nr:   ri   rm   Tr1   )r2   emailr5   r6   r3   r4   r7   r.   Fr   z%[SubSync] commit failed; rolling backrO   )scannedZunique_msisdnr~   r   r   r   z[SubSync] summary: )*r   r@   r   setr   rC   filterr,   r   r&   ascr   rr   r+   	yield_perrA   rq   rG   r)   r   r*   r.   r   rD   rE   r5   r6   r3   r4   r7   rB   rM   rL   r   r   r   r   lenr   r   r   r   )r@   nowZprocessed_msisdnr~   r   r   r   qr{   r&   r2   r6   r5   r3   r4   r7   r.   subrz   r   r   r   r    sync_billing_to_subscribers_once  s   
	






r   )r`   N)Tr   ),appr   
app.modelsr   r   flaskr   r   r   r   r   r	   decimalr
   r   __name__callback_bpr   r   r   r   r"   routerK   r   rL   Zsqlalchemy.excrM   r   rG   rV   r   intr_   r   
sqlalchemyr   boolr   r   r   r   r   r   r   r   r   r   <module>   s:    
P\ *+