From 917a89a262e6e56414eb2280951023edff37c743 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sun, 25 Jan 2026 12:45:56 +0000 Subject: [PATCH] Autosave: 20260125-124556 --- config/__pycache__/settings.cpython-311.pyc | Bin 6737 -> 6737 bytes config/settings.py | 2 +- core/__pycache__/admin.cpython-311.pyc | Bin 3763 -> 6375 bytes core/__pycache__/models.cpython-311.pyc | Bin 15900 -> 16821 bytes .../whatsapp_utils.cpython-311.pyc | Bin 6958 -> 8270 bytes core/admin.py | 117 ++++++++++++------ ...011_platformprofile_whatsapp_app_secret.py | 18 +++ ...mprofile_whatsapp_access_token_and_more.py | 28 +++++ ...rofile_whatsapp_app_secret.cpython-311.pyc | Bin 0 -> 1012 bytes ...sapp_access_token_and_more.cpython-311.pyc | Bin 0 -> 1544 bytes core/models.py | 19 ++- .../core/platformprofile/test_whatsapp.html | 39 ++++++ core/whatsapp_utils.py | 106 +++++++++++----- 13 files changed, 254 insertions(+), 75 deletions(-) create mode 100644 core/migrations/0011_platformprofile_whatsapp_app_secret.py create mode 100644 core/migrations/0012_alter_platformprofile_whatsapp_access_token_and_more.py create mode 100644 core/migrations/__pycache__/0011_platformprofile_whatsapp_app_secret.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0012_alter_platformprofile_whatsapp_access_token_and_more.cpython-311.pyc create mode 100644 core/templates/admin/core/platformprofile/test_whatsapp.html diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 00afa59248813d0890026288017c5de6f98d7d22..21e051d6a37ac1f7d6591501b07f2fa9e62a7f76 100644 GIT binary patch delta 58 zcmca;a?yl$IWI340}zyOmSs-c$or3#k#jQ-8@Gsr*hOKBE5a7nh3zg0+g%iPxFYOu N!0nQ-*JOPuB>?d35w-vT delta 58 zcmca;a?yl$IWI340}xpLFU_33k@p`fqvK{CHf|9K@r%NiSA;FE3)^23w!bLsctzOp NK-eW=@5%a7N&p#@62Slf diff --git a/config/settings.py b/config/settings.py index ca81335..aacb809 100644 --- a/config/settings.py +++ b/config/settings.py @@ -205,7 +205,7 @@ else: WHATSAPP_API_KEY = os.getenv("WHATSAPP_API_KEY", "") WHATSAPP_PHONE_ID = os.getenv("WHATSAPP_PHONE_ID", "") WHATSAPP_BUSINESS_ACCOUNT_ID = os.getenv("WHATSAPP_BUSINESS_ACCOUNT_ID", "") -WHATSAPP_ENABLED = os.getenv("WHATSAPP_ENABLED", "false").lower() == "true" +WHATSAPP_ENABLED = os.getenv("WHATSAPP_ENABLED", "true").lower() == "true" # Default primary key field type # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 83a696d928dcafa43fa511758c9b5c27108057d9..2653ae8e7cb0ccfd9f26ee9d83d25261d421b1d9 100644 GIT binary patch literal 6375 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 XsLw=wt)o}XLvK{kDXT@@ES~=hd5j!! literal 3763 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 Ak^lez delta 3392 zcmb7GTWlN072TyNK17j}L{TDX+Fn}GV{M5ET~#mBl1VvIok%ooNj54!)(p9l*pOU$ zmUJPdsFc`f`r+0QCTS8RXj2rHlgKF$5G@ikXcM3a3KVGQ!XNJTr~T+hehM22P@v66 z@0}$nSyml%L7tsCbLZ~NnL9K0S6BEqzvsAVx7TybX{92TRg;1-)}if zN9ydnjZW5GaQmT|cmXuylw3%Vb||*d+npXNx$P^q*K=>?%9h>wk1*q2u!xBbq!W8~ zA#@|`0?@2uiwQEGmNO|LBH4=I#uiJSD0!I_mIxcGfDP!0^dQtmL2Ot>z;V;0={*x` zvz0VkGk?fBpVz~%{vCos^p}UoRNV!fL$99>!0{3Q1Gk%gRex7si4d%Whxf6zLujy*;(cGExOFUDSYl!` z7d5eC!~Nh=G_Zkb)`i+?Z;fsIr~t6sz`Rt%9;Drls@S(2ww_V2&`e;FmLDbbF)%yK zg#M4?>xCv^Fh@x^as+_|c@#_kzq&X;f`ALIeg?2gf8zX1S9z31g5J{A##aq4R7f+P zQZn-A7{c%DB2F4~b@5Jm+BL|ZqSsxX0B#u3NWK9tGK+8ufo%vj*ii^%Fa5h~sdGmm zopGYfr!#V}qGgP(wmiWCcQH5to-|9v;899m0K=1&lf%DfOUU}Rhpb3nb04%Z0q1a> zLHZ~6$VnzGPDHazKRF}CW+x{J3tTn>CNf)7V-jMA1;CC62#%K^jF!%~e#q-`PEfz+ zISYPF+(jnn&pdniH2tlodmlUbTuCCsks=jUnMiO>p%f$!#3dC;%#+eS!eTF1)oSw0Y215%@XU;*0YB(iz{sOk7r#Bx9M+-nhJbB=y853kd zW%Jaml9bFOR29O+2u(2MRW@gW_RF?78+8!pe~or@EP!$tcIzDjL*GV+2J$*OG^vF| zp{VYBiaIJzDv`KGTRS@|N9e1a_TKNJ1G8@Ps9?MSb}vIP_W5gpC?0$`4g+1GeQf9A z!1T?H6Vo^Mx3IK8FLgzGSy(#q>EyG;yi~mfFfO&m3b}byjncn$y~zK9E_R<7MqM^T zRkK(0gSE{_lE{;vLp!^&kLo(&^az4fy5Id&U4RW9>q+?69!sV4#jg!CWcJaY8hT4N zf?bA5$TjLxZR?N&Y+xS1o?D)En9>GLi#>DADeqNo4ssaChkTG>?0ztZnQKY;A<@`F z3!#9?4qYH>-U#_0n}(uhn$9Q-i5xkD{7Hn<05B1D-o)u&L-7^&nZ-mwjpXw}B$<>| zRhUtpm2=AjahYTjIf!mSAwogU<`t44OF}|7Dxi@tPn4{Hd591#s_7i`4nov0=RbxI zEqiLd71$`c+V7&-JVKKC2FB^RJ`a7aZ-38bWNptOK1O4N*Wj;0&dHT-4#fGD z@!P!-;U?m=OzF>d&6UrOaaW0R{+N$&`!`#4!`KS4mFY2%-M9VSddjXsy?D; z!`_CiD@RwuUHpgi?QqD0YY-Egx63cm`(a-&42@)S*XuVDaGEixq-4#UFOs|hH^Xnx z?jt&MWZ&MqSnNSytBc1zDOcf_^zxBE+gJl~qL)sOoa9A%VHwBI}da4`3 zZY&jG7grvucwMklfP9Q4=J~;|{o4}GDyd>d9wGk#6Wr|TL8^@Yuw*?N=6hQBzI)um W@Y4Ltnfu6px!Kr5No3TBqx~=K&ioGm diff --git a/core/__pycache__/whatsapp_utils.cpython-311.pyc b/core/__pycache__/whatsapp_utils.cpython-311.pyc index 3a3a5af095d281ef765888bd46670f036a25d295..ecb79838effc5583cd20bec450b4224826d05e58 100644 GIT binary patch delta 3898 zcma)8Yit|G5xy76BY7l6lafe@iq4kgh_N1ym8h~3$&wY(mS1rs#kLe9?3s5GY4g>+ zqZAXzhz62Y@Q+5xHI0Ed1^?7d;UaYr6bO(%0o(*`3lyZm!o&dv3=}SqUu6&!Fx&#| z>`|g9r!Bg-H~XHMot^o1KNJw3r#s12H5&Ee`BjQ?Wr6E zo!0u4k;YbS3X>yMQoBXA&X{-F2H8|nw2186lz(eIkt37ObrDKSy@Ti`(9=1S;@j>i zIJ9q=X4>9wJb8L1e0+LAC$cS3MM=w}gM*@!8Cb;<<$*{lk>%zj zMTsSs<-zIX-j!_IOM)U?5M+t(JslU6r4&k>MyaJ(TnauW2Ty(dFG$)I>U+&Wr*4S~ zvY;pk(O?jS{Z4yG^|za3*jQTktVRVz7Sic3{%eYBZn|4wrKBjY#+0aToqi)CrIlDJ zshi`er&}Sz*po)D# z9tF?M6MyH3f#-jFu6XE`QvYnZf411P_&!^13Kk)sTg=ZqaP;Iu6}KmUtm0_ThpJ|; z_6;3ab6JBATrFi+UzMO7!<9f+IdJe^;LzQ`p;BP79GKj&R{YOzn5$-@skQ8Srb>{G zNwU(>UG5mU*D-##W4zQcQSO-7ux-v5sS=DMSn=~^|KWT7qj&vBOa5bJ|FI481CRfM zL+>3b43#`jmpxAdCwH*o>gXy=6=n-l*I(NRJ$m#|dx3d*t$E{OVeiL#i}w9RX1|P` z@#f*(Qx4*`!!|We-)`-gI!NC>Xo7mA9)2*1fgc~#w|zi%1~mBR6JdNFR^}*(ISM?M z%q9%Kn!^XjYoAy;W(=2=`DHTEM$(eFym6p3j zlWP8u5X~Q94%4Rp#X+=aYo;F@vZ!>91z>~{7DDYq0v5BX30l^VsGL=0MVn~9PUUPO zr&<8H9HLWnU1yfb7@4zcoo0WBRkf*hvFTUt8=%fj_-iT0sa%1>pc=?IRPLfeQY=xibWrn8Uo7g8yR9wf=>R8p4033+)GR-*6V{Pd~G!zZR+n$ON? z{p?@|-&?-{Hb=&c2n-H{vLo8}+4k|?d3>P+`IyWnVzLZ~JHYqO88^u&|CkISeAPZC z2eRHlfZ@TF1A{U|fB)ttwZF0lo?}F~Ds&#MISpM3j%~|)X0dy;P?^*wMj0Zv#UPJ|!{j+VG z2Y{_FA(TSfd&|04+`w2%^_5v)k@Zzv-i^q5w#fP)_Ptc< z3zhp0he}K^KeL{_He48fVE1mWp1spjbk5dtjgj-Jb3-n%?Pa#T$hL1Ys=&QC$Rt*7F9rfQ;HW)SN#=Z{o5 z!s>e9@2<4(!P&RFYBf2WRRR;*;5 zP8PY76;Er?KYwQ!$Y%D;7rFTdzUZwpMPC&17i@WAzE&+REY&LOGxyhk?TM==uK7z` zSDEW7a$OaV?^>|v!PQ6iYqsUFfor7VeI2BDUx)nXGYiGV*Ggy3m(QH9^{iX*>2X)N zjfE21S!O$nY$rB)tmwkkC-Ngm)%_OW3>CQt+~c-wa%j4b_}zFv&@cOJp&|Oq;RB%o z`m0_N>R%0*LPMY4;JPTXP5Xm$(T(ibx!N${G3nM0x&r&Lf6MsB1ZOmSP;X79l-Sbc zupEu06A-y!c)P-bK}4^@tQGBT*B2D(`SeIr7iBh>9oN#kCMQn7;HjV;orDsd!sKO4 zUcux^!O*8WkQ9+(D-sGPg@lA=wQqK{nCI~9dClSJ?ZV+uS0yc6HZ<8>Bcj(}^eye2 z=Wk@sr$@biAtkH{h}SI<0m1tmJ*mPh=4|L;DA-^HJa7(3kVaU%ZWqzET8E)$+dA4t zOTZL{uAgeZY55#PCt808LaW;y=8armfa1M4M(Cv5}wuq%qVDQ z{O|eknFrly3<}+X-`#-zXb4)`KmE^N!Z=q$xNclCfCL|yVJd``Hdy_VK$wql(vR5} z6Dcthm&Q>P`bhZYKLV-JBuQ2XSKjz4gfnk^6~dl3KJCYCBZFP!vsI!_j*({4Qzh!8 TX`F-zsFMjwd)PM3V9EamrN$8% delta 2797 zcma)8duUtN89!I|O1jd$mTWyN`4Ritu3IbfD`^uaY4ea`#mSPmUgMG28)!E=*ODB) zcJ8%Zt5WQZQC!2Q!-ci<0sgD)=qP3RXJsw4rGv3Sk&9Tw1w&wTtPD0m8HD|@?K@YN z9H$-Z`06{4?>oQmalZ2%e}3-0lJ==2XbI%=DaE-w16@(FH(rCWkGxH40 zEon=Joo5+@STRCK)}Nty2etuqVmnX)^FT%H z0NR3`KwX%*hC;$ubjx(;&fSy8djQ}Sr0s#wZm|hjI=F6W2$dLpk~u9~iWbscwCtNi z><`aM2xj)i9+TIcm6+^P14wIaa1nY8kta&5=GkA_cIrR&`z~Xq$i9x=v>f!7L30qL z5__A?^7Vd&SwYXUb7+Mrq9U_k0YBM7$tgj5PSHq8SyuFR_B%moC{L6Hg%AaYq#Th{ zMblE*gsO+xPVX&cby&(Lb6F*p!Xb0B{ug#)WI7}e7ECi*uU`z$UJK96#KJR|Vwaon>}CeHBmWo@eX}&)AOVNX2tx!(J6U zHN-ihOx4|9aSzuJ({iHP*;nbD*s_~TC+HHeyZS3#lUw|Q2fKRM_UxQ@>tb2xE_2;# z0Cc|gOgMl(2{`$^xS2+MxTT45Y-p8KAGG!S3^30D7C-l*OB4Zh!tnMcnI>v#Ab zH|#9I(LwfDiY&3_UTYs?R zDDsOf4F+z#W%^8dsUL@NTao*=48)#mNNb`YwC$pnPh;=HSYW=d=z#6qH-Z5v3U{EV zq1C(t$4bs`LOzH6THC%@(RmQ++iZi|(Oz=ic4AOcAMM|dP@u4(|3c_lTT&tMB`v8) z@mzKxl~{s1Athu@S&>&q&E%g=rc^1F$)}Z!lGS7_mCH)9sxD;|Rl2HZvUEKuYic;3 zmm(BQQB?`W*Cls|{k0>%xh*W5m^ZB%CLoPeEsOGXEi~cvU=V^Y0x&usu)0F2mgS(l- zt0B&4t3yDvVsLZuQd~1ES`LI(^9t#NlljW2pKj?tS?FlGxu+D&&1#aClV%IPCXa>) z3Q&%mk_xOeA{F}dzqa&_o|2|Rt&f(O=yW)8B|1r5pvB-)SqKMBa^lv{y2fYw=&GQ? z$t3^ ziWn)2k*YVa_0*TZzp{Cy+;QW5zA`=uJ$%1ERPT4hxr#Vf7U!zo;MTDRyMoX3YM;=! zh?`>BAO6Vuo9}(l{$YFhyZ$gtclbz!kCgd{>F&l-{qF&^nib*$z1j=0{&YC(n+&1P zEQ6GWoY5invnLNn2iV^YSffLC-)lBQat=B} zz@%xG!DiINLs9s7SGRQUQSMz)Vv22R3@E{p)tb3Mzx^S}(b$zkXWdpeg$K&jII z-E-_bEgSWMK~I~|BQ%3Ys2e8uFA<@;O&k8jKa+v-mR8P?>##=u5mbMzztG#QZ}%SW Z^)ZKPs7Z#08Gj8m$qyObDn&U8^{}', + reverse('admin:test-whatsapp'), + _('Test WhatsApp Configuration') + ) + test_connection_link.short_description = _("Actions") + test_connection_link.allow_tags = True + + readonly_fields = ('test_connection_link',) + + def get_fieldsets(self, request, obj=None): + fieldsets = super().get_fieldsets(request, obj) + # Add the test link to the first fieldset or a new one + if obj: + fieldsets += ((_('Tools'), {'fields': ('test_connection_link',)}),) + return fieldsets + +admin.site.unregister(User) +admin.site.register(User, CustomUserAdmin) +admin.site.register(Parcel, ParcelAdmin) +admin.site.register(Country) +admin.site.register(Governate) +admin.site.register(City) +admin.site.register(PlatformProfile, PlatformProfileAdmin) \ No newline at end of file diff --git a/core/migrations/0011_platformprofile_whatsapp_app_secret.py b/core/migrations/0011_platformprofile_whatsapp_app_secret.py new file mode 100644 index 0000000..55c8fd3 --- /dev/null +++ b/core/migrations/0011_platformprofile_whatsapp_app_secret.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-01-25 12:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0010_platformprofile_whatsapp_access_token_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='platformprofile', + name='whatsapp_app_secret', + field=models.CharField(blank=True, help_text='App Secret or Verify Token for Webhooks.', max_length=255, verbose_name='WhatsApp App Secret (Security Key)'), + ), + ] diff --git a/core/migrations/0012_alter_platformprofile_whatsapp_access_token_and_more.py b/core/migrations/0012_alter_platformprofile_whatsapp_access_token_and_more.py new file mode 100644 index 0000000..074eb60 --- /dev/null +++ b/core/migrations/0012_alter_platformprofile_whatsapp_access_token_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.7 on 2026-01-25 12:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0011_platformprofile_whatsapp_app_secret'), + ] + + operations = [ + migrations.AlterField( + model_name='platformprofile', + name='whatsapp_access_token', + field=models.TextField(blank=True, help_text='Your Wablas API Token.', verbose_name='Wablas API Token'), + ), + migrations.AlterField( + model_name='platformprofile', + name='whatsapp_app_secret', + field=models.CharField(blank=True, help_text='Your Wablas API Secret Key (if required).', max_length=255, verbose_name='Wablas Secret Key'), + ), + migrations.AlterField( + model_name='platformprofile', + name='whatsapp_business_phone_number_id', + field=models.CharField(blank=True, default='https://deu.wablas.com', help_text='The Wablas API domain (e.g., https://deu.wablas.com).', max_length=100, verbose_name='Wablas Domain'), + ), + ] diff --git a/core/migrations/__pycache__/0011_platformprofile_whatsapp_app_secret.cpython-311.pyc b/core/migrations/__pycache__/0011_platformprofile_whatsapp_app_secret.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5bff709aa88fa255a9d8beaa192388fbe360c71c GIT binary patch literal 1012 zcmaJ%NY=yH?ac0Wx9!wv7g$al zJb2{BH$aVi2tNV`4rH#LxRt<(i@qr=s2FEDzj<%od-HztUO$hH+6cz?>U#J{L+FQ6 zhNE&RoX&%=izuQvLNRXOSZin)Y3K!_+DAlnhP(POXb+@sS6V}-#xim%$I9$#(vHKn z@bi!-nKWZgS(F9CNVS(_U_X5iu!|UKV2Tcs3;!L=OJpcQJ; z>V9p{01~h+#=i9@DY&afZJ>2aT5pGZfSeI-#y8V~G9KQ~Lpo|5ABS!E(@4V)Eqmjl_H zHP-BK-pgi=e**Qb={1#A#rzgmhxon-^MUi44P4idm1g88J!y4Vl#-ln=1^sSlSC|O z=N&n+&P0=Ej3j={Tpetx>ZK+PbtWsVkVSOKwPcNy=Y&X`z`JN49!}&4As_mFqq(g1xlZ*JA^8^v=%$QD0OM`lwG#_D>_x(IDhALc+9+hv!CRBk8Q$Z zaxT0LE_zw&2h3AV@UDLo585&JpLSeTcP1SO6Ec*bgscm&eKiXcSub&+YZK=7o5Duc z)F=C}HhFZfesuTo(b)9Pr`de^X9#o_wn1`yz%o|%_8_buRRX# z>xBFxjqy_%DW`W)c}5svN=PE5r9^5=RftNyCQSXEFb$MVZ5*_Z+QGzXX-r=yPUVH% zc2r?SL6>`Zki?lVqJ%-1`Qu1A*G9(a5S3>HNK0X)r81?fv^14~zBvYkrI}nYHs?k$ zgz2opjKjHCAskj;wJ&8c%PiHYEEc=M<_~N8NXP+>^XGr_ymcwhRkpwuS^aQnA5kAn zkk4l7YAus3BTd(Y`Tdw5Bun=b4x(}U_MJO44ZXaR@F?X;CkTP=^t?Rt(v-@PfzKfq zS7)5WwF@ubhb*IcvIX%*f92Pt;PykW9eSC)`r|kDhFov<7iO0nO;pe+-V$aH!j$H) zn~V9!z}ra%H1;Au534-K(h|B{6O~Q?Arng%}GJ=$`#WPSKCN4BcK5fm^hTI4@k-1;5A>?(QUfD@#2e zT-j~z#r<@#?>?lSEcb@)o9Zj-#SFiMLouWIv-A kYKo#Dd5SY2jmy8Gv7#75a#=jJjuzfOksD{P*)8k-2N-UpQUCw| literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index b7f2233..07c5370 100644 --- a/core/models.py +++ b/core/models.py @@ -5,6 +5,7 @@ from django.utils.translation import get_language from django.db.models.signals import post_save from django.dispatch import receiver from django.utils import timezone +from django.core.exceptions import ValidationError import uuid class Country(models.Model): @@ -160,9 +161,21 @@ class PlatformProfile(models.Model): privacy_policy = models.TextField(_('Privacy Policy'), blank=True) terms_conditions = models.TextField(_('Terms and Conditions'), blank=True) - # WhatsApp Configuration - whatsapp_access_token = models.TextField(_('WhatsApp Access Token'), blank=True, help_text=_("Permanent or temporary access token from Meta Business.")) - whatsapp_business_phone_number_id = models.CharField(_('WhatsApp Phone Number ID'), max_length=100, blank=True, help_text=_("The Phone Number ID from WhatsApp API setup.")) + # WhatsApp Configuration (Wablas Gateway) + whatsapp_access_token = models.TextField(_('Wablas API Token'), blank=True, help_text=_("Your Wablas API Token.")) + whatsapp_business_phone_number_id = models.CharField(_('Wablas Domain'), max_length=100, blank=True, default="https://deu.wablas.com", help_text=_("The Wablas API domain (e.g., https://deu.wablas.com).")) + whatsapp_app_secret = models.CharField(_('Wablas Secret Key'), max_length=255, blank=True, help_text=_("Your Wablas API Secret Key (if required).")) + + def save(self, *args, **kwargs): + # Auto-clean whitespace from credentials + if self.whatsapp_access_token: + self.whatsapp_access_token = self.whatsapp_access_token.strip() + if self.whatsapp_business_phone_number_id: + self.whatsapp_business_phone_number_id = self.whatsapp_business_phone_number_id.strip() + if self.whatsapp_app_secret: + self.whatsapp_app_secret = self.whatsapp_app_secret.strip() + + super().save(*args, **kwargs) def __str__(self): return self.name diff --git a/core/templates/admin/core/platformprofile/test_whatsapp.html b/core/templates/admin/core/platformprofile/test_whatsapp.html new file mode 100644 index 0000000..8875851 --- /dev/null +++ b/core/templates/admin/core/platformprofile/test_whatsapp.html @@ -0,0 +1,39 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_urls static admin_modify %} + +{% block extrahead %}{{ block.super }} + +{{ media }} +{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block content %} +
+
+ {% csrf_token %} +
+
+

Test WhatsApp Configuration

+
+ + +
Enter the phone number (with country code) to receive the test message.
+
+
+ +
+ + Go Back +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/whatsapp_utils.py b/core/whatsapp_utils.py index 8c46bec..d398931 100644 --- a/core/whatsapp_utils.py +++ b/core/whatsapp_utils.py @@ -1,5 +1,6 @@ import requests import logging +import json from django.conf import settings from .models import PlatformProfile @@ -7,71 +8,112 @@ logger = logging.getLogger(__name__) def get_whatsapp_credentials(): """ - Retrieves WhatsApp credentials from PlatformProfile (preferred) or settings. - Returns tuple: (api_key, phone_id) + Retrieves Wablas WhatsApp credentials from PlatformProfile. + Returns tuple: (api_token, secret_key, domain, source_info) """ - # Default to settings - api_key = settings.WHATSAPP_API_KEY - phone_id = settings.WHATSAPP_PHONE_ID + # Defaults + api_token = settings.WHATSAPP_API_KEY if hasattr(settings, 'WHATSAPP_API_KEY') else "" + # We repurpose Phone ID as Domain in settings if needed, or default to Wablas DEU + domain = "https://deu.wablas.com" + secret_key = "" # Add this to settings if you want env support, but for now mostly DB + source = "Settings/Env" # Try to fetch from PlatformProfile try: profile = PlatformProfile.objects.first() if profile: + # Check for token override if profile.whatsapp_access_token: - api_key = profile.whatsapp_access_token + api_token = profile.whatsapp_access_token.strip() + source = "Database (PlatformProfile)" + + # Check for secret key override + if profile.whatsapp_app_secret: + secret_key = profile.whatsapp_app_secret.strip() + + # Check for domain override if profile.whatsapp_business_phone_number_id: - phone_id = profile.whatsapp_business_phone_number_id + domain = profile.whatsapp_business_phone_number_id.strip() + # Ensure no trailing slash + if domain.endswith('/'): + domain = domain[:-1] + except Exception as e: logger.warning(f"Failed to fetch PlatformProfile for WhatsApp config: {e}") - return api_key, phone_id + return api_token, secret_key, domain, source def send_whatsapp_message(phone_number, message): """ - Sends a WhatsApp message using the configured gateway. - This implementation assumes Meta WhatsApp Business API (Graph API). + Sends a WhatsApp message using the Wablas gateway. + Returns True if successful, False otherwise. """ - if not settings.WHATSAPP_ENABLED: - logger.info("WhatsApp notifications are disabled by settings.") - return False + success, _ = send_whatsapp_message_detailed(phone_number, message) + return success - api_key, phone_id = get_whatsapp_credentials() +def send_whatsapp_message_detailed(phone_number, message): + """ + Sends a WhatsApp message via Wablas V2 API and returns detailed status. + Returns tuple: (success: bool, response_msg: str) + """ + if not getattr(settings, 'WHATSAPP_ENABLED', True): + msg = "WhatsApp notifications are disabled by settings (WHATSAPP_ENABLED=False)." + logger.info(msg) + return False, msg - if not api_key or not phone_id: - logger.warning("WhatsApp API configuration is missing (checked PlatformProfile and settings).") - return False + api_token, secret_key, domain, source = get_whatsapp_credentials() - # Normalize phone number (ensure it has country code and no +) + if not api_token: + msg = f"WhatsApp API configuration (Token) is missing. (Source: {source})" + logger.warning(msg) + return False, msg + + # Normalize phone number (Wablas expects international format without +, e.g. 628123...) + # Remove all non-digits clean_phone = "".join(filter(str.isdigit, str(phone_number))) - url = f"https://graph.facebook.com/v17.0/{phone_id}/messages" + # Construct Authorization Header + # Wablas V2: Authorization: {$token}.{$secret_key} + # Some Wablas servers just need Token, but docs say Token.Secret + auth_header = api_token + if secret_key: + auth_header = f"{api_token}.{secret_key}" + + # Endpoint V2 + url = f"{domain}/api/v2/send-message" headers = { - "Authorization": f"Bearer {api_key}", + "Authorization": auth_header, "Content-Type": "application/json", } payload = { - "messaging_product": "whatsapp", - "to": clean_phone, - "type": "text", - "text": {"body": message} + "data": [ + { + "phone": clean_phone, + "message": message, + "isGroup": "false", + "flag": "instant" # Priority + } + ] } try: - response = requests.post(url, headers=headers, json=payload, timeout=10) + response = requests.post(url, headers=headers, json=payload, timeout=15) response_data = response.json() - if response.status_code == 200: - logger.info(f"WhatsApp message sent to {clean_phone}") - return True + # Wablas success usually has status: true + if response.status_code == 200 and response_data.get('status') is not False: + logger.info(f"WhatsApp message sent to {clean_phone} via Wablas") + return True, f"Message sent successfully via Wablas. (Source: {source})" else: - logger.error(f"WhatsApp API error: {response.status_code} - {response_data}") - return False + error_msg = f"Wablas API error (Source: {source}): {response.status_code} - {response_data}" + logger.error(error_msg) + return False, error_msg except Exception as e: - logger.error(f"Failed to send WhatsApp message: {str(e)}") - return False + error_msg = f"Failed to send WhatsApp message via Wablas (Source: {source}): {str(e)}" + logger.error(error_msg) + return False, error_msg def notify_shipment_created(parcel): """Notifies the shipper that the shipment request was received."""