From e335d235f6465bcf6869254049d73237f5c6b290 Mon Sep 17 00:00:00 2001 From: Nikunj Ladava Date: Tue, 21 Nov 2017 10:58:32 +0000 Subject: [PATCH] [ADD] payment_alipay: added new Alipay payment acquirer This commit introduces the Alipay payment provider, a popular acquirer in the Chinese market. There are 2 possible ways to use this acquirer: - express checkout mode (only available for merchants located in CN) - standard checkout mode (availabe for foreign merchants) Note that this provider does not support server-to-server payments, tokenization or any other bells and whistles besides fees. There are no specific behaviours related to this acquirer, it behaves like most 'form based' payment acquirers with a form submission, s2s notification from the provider as well as redirect in case the s2s did not reach the server in time. closes odoo/odoo#21855 Signed-off-by: Damien Bouvy (dbo) --- addons/payment/data/payment_acquirer_data.xml | 17 ++ addons/payment/data/payment_icon_data.xml | 10 + addons/payment/static/img/webmoney.png | Bin 0 -> 24974 bytes addons/payment/static/img/western_union.png | Bin 0 -> 22772 bytes addons/payment_alipay/__init__.py | 5 + addons/payment_alipay/__manifest__.py | 15 ++ addons/payment_alipay/controllers/__init__.py | 4 + addons/payment_alipay/controllers/main.py | 63 +++++ .../data/payment_acquirer_data.xml | 18 ++ addons/payment_alipay/models/__init__.py | 4 + addons/payment_alipay/models/payment.py | 228 ++++++++++++++++++ .../static/description/icon.png | Bin 0 -> 8567 bytes addons/payment_alipay/tests/__init__.py | 4 + addons/payment_alipay/tests/test_alipay.py | 195 +++++++++++++++ addons/payment_alipay/views/alipay_views.xml | 18 ++ .../views/payment_alipay_templates.xml | 22 ++ 16 files changed, 603 insertions(+) create mode 100644 addons/payment/static/img/webmoney.png create mode 100644 addons/payment/static/img/western_union.png create mode 100644 addons/payment_alipay/__init__.py create mode 100644 addons/payment_alipay/__manifest__.py create mode 100644 addons/payment_alipay/controllers/__init__.py create mode 100644 addons/payment_alipay/controllers/main.py create mode 100644 addons/payment_alipay/data/payment_acquirer_data.xml create mode 100644 addons/payment_alipay/models/__init__.py create mode 100644 addons/payment_alipay/models/payment.py create mode 100644 addons/payment_alipay/static/description/icon.png create mode 100644 addons/payment_alipay/tests/__init__.py create mode 100644 addons/payment_alipay/tests/test_alipay.py create mode 100644 addons/payment_alipay/views/alipay_views.xml create mode 100644 addons/payment_alipay/views/payment_alipay_templates.xml diff --git a/addons/payment/data/payment_acquirer_data.xml b/addons/payment/data/payment_acquirer_data.xml index be5d404adee..b9040c22757 100644 --- a/addons/payment/data/payment_acquirer_data.xml +++ b/addons/payment/data/payment_acquirer_data.xml @@ -265,4 +265,21 @@ ref("payment.payment_icon_cc_codensa_easy_credit")])]'/> + + Alipay + + + + +

+ Alipay is the most popular online payment platform in China. Chinese consumers can buy online using their Alipay eWallet. +

+
    +
  • Online Payment
  • +
  • Payment Status Tracking
  • +
+
+ + +
diff --git a/addons/payment/data/payment_icon_data.xml b/addons/payment/data/payment_icon_data.xml index ded11d7dee9..a4613d3e3d9 100644 --- a/addons/payment/data/payment_icon_data.xml +++ b/addons/payment/data/payment_icon_data.xml @@ -54,4 +54,14 @@ Codensa Easy Credit + + + Western Union + + + + + WebMoney + + \ No newline at end of file diff --git a/addons/payment/static/img/webmoney.png b/addons/payment/static/img/webmoney.png new file mode 100644 index 0000000000000000000000000000000000000000..2e4e591669cd007d360692ea95308f8b7aee503c GIT binary patch literal 24974 zcmbrl1zc2Jw=h0{NC}8^OG^webPY&@NOyO4!;pfsh_sZ1q@;AGfJk>YNO$*l@Oj?% z)_ec={=e`1bU1U)T6?d(dau1EOhHZ(9hC?b1OlN;ONl9gKyXXIes42`W#U0xcQnp@gIDGwT2C|_EdKq=KZWm#n%L`^L$rC?5`$}l+< zW0P(U}IJY*JFC0Gb&8 z6VAca$>z`GCdMqLHm0_wb}r5UEaacC4i@$<_Rbde|IN_Sw3kyXBR^|V^e7{C?!BDv!$g89~&Dd2QM2Bgq?$(9Rd+& z7v_NQ@Q6V~+1W+d*mxwk{(&oIZ|wRg`+wk?{2MOY|AZ^=WC_@vq0PSoY}7Y_u&!wr!Ti0>aG2m`Bd2IS)Wx8;Z!n*V)gV=2f6 zf$)m6v5Ip+#6^WAI5|Zi+!CB@VjOG|5~AE(Y~lhekEQ_t{$+K*&0ltHVs8vA>fa_a zWi#dCVKZU_oAB`RfFTeQ9S2n49yzj^*kK48kSOBhJPt%Fe~X zBPu2afryB+vx|y|aI&)Vu(AuYO8i%}M|nK*l$HBG@2`BVg2th z*;s|yd3nTG#fAUoq%fBluds-O1P`wWI|nP*e~&4~EyBwN;S?4V`TvV4!OJc2AEd|1 z!!9Br%ncC}7Zw&375R5_{$cuGa$@;69{($W{_zcbQkIVb_WWB&%BGJ06xYi??tstG z_|eTlDUBaZ)YOFX@5h$^LdpL}CHouK-NFpD}u&c1K@v(CLJ7@U6T*-fO%zt71 zrSHy}iAN0~8R*e>SP5voq)gB9Iy- zvgp>wCCA0NFhd|mKTo$t-Bpq!!E1p^tAkD?G_86{JFlwjl(4h-jJrMzxX(FCre9uO zGSi}X>jDH=@#nli!XTNt+N)W#^ZuP=?iTN-^Qvl3X%<}d!VA@*cB~{`i{k-eK9IV9 zvEX&E^c0M%Qbvi7WBA>*z-ZfFI?fyG$faO(2>MqHzSiLKI8R6n< zcU8CqY%R{=D%#rT1^|aZy(q;on`)l%SOzDJ3BBdHPcE+%6&#*l$&}t+WQvBk^{4S) zUI@&CSZ9)Wj;jukkdd z)o6KK)C4MX&IoFthMYGz@#5$xv+UDz)Mu#KF)J%sL1_5C9ER=hWkq_!CZug!Mj-j< zSSv3J;ff4<6TrlWKdGWVFwxT=y=nR^p_y0_6rm|61y14IQIFVczeWUsGH~l|n#~ZR z-X@;~ihr(R#qodQsz3A;6{!8GefDXNQ6k;D5`tL1%_kHht z=ZyiBXUm+iy#U2laCMrr+Yb{Y|xSKsweq-=@)_AO_?d5II4zcYAv>7&X}2%WZ0+``vr|iMlUQ zkv<@w5@ST`nU4XuMXtI`Kg45(xA*pBQBY9QMn^_4Y!(`miuK7Dy_?HBt%KQh9AnUv z1w4-)-@za&W`u8V%CB&ke2E9@o(?cf2r1Vx;g@MkiV`bCgB@#J$AyXc6*#0UyD z?VN&Ofp*=5T7|#Y_b>s#M##A5T}J!2wg}@=QW#;Gj9hOgbOy$Y7k~A7s_KRGS$CfP z8f6knOtPD9RLRYJyy`UB(Q-tn7*AsRHp)w#g0mKo0vH+I(sa_OXeqrxC+_MJ6BDC; z09I*p=Y+bU9KW&Nj@scV;^cJv5EK-|xw0&w6;Q$>8rpbPD8MxpU1F}$4FW0L3=w15 zUL{3;Z&Od8kBihXsut_c2?PSgo-G{(*UT zcsP>T+#KzwO5kNqvwoAvrqm?+4}1`4$%<8S65dXjz2H9Q<6DR?`W2e@buNghT=W7))82@+@SiJ_7clGVOgd4u@vFFyX{%|L%;K0Xw}WA-0UN{$kaMxV)pKm+^lmUN zEb`@luT)YhAyl);}qFp0+Q{Xul zQ$?zh+sXs@8!R*7iz z{H3AcVl;SJMROvK?8xX0H@qQFuaz=dxPy?c6vfI|#-pBPz?R|0YVBA#i}Bwsw>9&$ zfix^)sy5B76-S*4n2NX3qK5MjInl?d$|PPmzy2X?`_3<~dr&{uCy#A&De3JnMp}RN znv5sLteHtDIcN0yT2-4@t`ly5hnBhXndw*%M zO;uF_-u~3W@`TW%B!d{s0@`cw!eIO-Ys9^NZ8}^&Mnml6zH}yM!{F#l6F!O6JW)gd*K^~?j&b$$-#_&R>$p;IDAxPcA>f?gXr4oE8MeBCgK{grp#54 z+uX8z3q=XoQ&ST&wa|jZMD?l*bvO(ehL-$BO)R>+D$LN_3TCrN*oOiqsdyBePl?_uH4UOr6%$qmg8nv1+ zG10yrhB_Rd9T67xz^lEGmt;mdLfrrLeXaL&eMb3IcEu8lXT8Kj26L)&-SX^BHgWev zhhB7qN$#k5gzhaTffl%vyPIEnU7{*ExZbee<+)7^=>h^9e+* zgPbveT6fks@+*mYJfW%>bL!WneiJ47yQlkNzJ@416-SlN7gl4|wTVORaU>$>E^_Oa z;W)fp)ESrz5bT#>iFc$Cop-K zin}^H)IdmwrxlE-(Qv7U@Ka-ZK2+ZCoo>B$v#D0ON9*ro!6}h zPp0Z|N$_Y<96U6FKC9B2!Yu1JlZ4&2AIhDs zgvwQVIKd-cd(OJvk+j{;K}jvHhtAdft{eA)uwjZDwNm{%E#E|;L_^JF%#=$wB*hRn zZNHog!E)v4&c0P|S?a~mxV$8$YNKz>s+|astvN3c$vKDY=~=My>2>1GE2`P6jxZkf z@!gPyVf1<9&c(D8$&nOeuc04^+F~t5&9Jp|rK3@jL&8LqYM zXb+9L*U$;DAh=`MrvyXd8LY{yEiJ=t>p2stiacnN;5*A~gu#?sM$D048lu1GR#!;; z;C_p&n~r+K=`5Rf&9U0X%oUOBc?bJZBk~@>$MPsnaID_&bNj*P%`L;dtt7+e`U*GT zb*m%_Y5jXN7B7+~U3UR9?e1abo9Bu5ICaU^@MQNDw>`dNg|2bCr?W8bEc1gc1CKjM z^t(zUm|y3ab*5ZCIXGn^WW`A2oA*9r(fR)ACzCsPg_KS$`+5Baw6d>m$o|PMxm!Me z(JBx8a%<4=>OdX4EW_vit)JECtGBMrNF}y%#&%XYUU$Vr!6WAK7x<;s?OU%BW%sCl z5wlUe6Fj}oeo|khE~@i3Lk;aa^sHYdVp|_lz$nIlcf82@berbTgCXKz9&6x+0nv&lwF|NDVW)rBQ-WCV)GHxcz{2^#dOAKjr> z=&x;;qjY=Pk6p)}lY<>94`)|lfM>5MS-g2bKaiqN@;sgWXfp0uSDJ`Wa_haTulH$G z(f4Q>q9)+9@gX`GR!=i8jZMpt|!BY=A$5hIa#__RTM~u1f8WFK`qyai7f|vP8sly0Sg)K;I4G3 zJ4vU29)3uv39BZR#`crS>qX1^?PqEX(>xXwnYDk3?yK zPj8g*Y>Qv{A^Y=dbxE8KqEVs>1no9b!q$q#6#2?>4IEQXq_~%fs_6tcEVfpC@=a}C zffI^)X1%ya9-gplEs{ukJlt*BX`QYbYE@pB>0TU3DdEkZ%<%EKa23u}F1pi1?7=Td zuk0E3wx9b3fLayHW>1#u!YX$zbBJa`mO$h%cW;&uaWw95j4X1!MlFpgnBmSz9j5!p zByovYKm~#GSS1#u#-w{#hQn-ucKin|QpfK<@8vyt^Bl3M4ucZ_I?eto$jC#woR`Kl#@AlX(~i zW@&#pk&8-cnJ(K{>UGT%`xHO1YA|fB&Jn={+P70A#xqdI(Gz$Ef|hwWkk0;s6B@`h zu0W3V`Q|wPa=zCo;FGrgWo+}TOBcmW7iZ*wr%#%yV}B1z*7p;xu)E=ohC+Ug{+9B*uOICwc;TUK?h5MRWr;u{8=z~!~Qsv+!LZ!v=of{GHa&zIr^@e%q zu3~buv*`AX-IA;+UMA^)>`I#ok)Shaj>+0q^-sx`s_61#)MC4i-d-{L$Q?`NMF@T_ zGUj>5*@xeHGli<@J<>^KAO~9gVjlaV~_pi&l%3ktjL&gU-6DqNvqaM<_VT#s_oiuBkt)@v*xQh zaOLAC9(>De{!*@0D^FY$bkjB{xmiFnTaaHX)`p(S@`Js2j%}j1yM_E~H@q zYP!B6gWdHusfkB|4af$Ix&zi-n~%-}v2=BmtkP|ib-1spSk5hy6FV(@>|9| zC3UbRox9IpYrO%%xqVsp-!wBIE@G5tT9A{rtdm=G`_vM(d9dya4A1C3b2Do(T`K9S zd8IYqN}|{|!C+op&MlMUGqJtBq1EmE5&o_bw+Tu4emFsoU|1R_3Dk*~eAV=?&XG50;hI|*5;f* z%6L0yY8MEGonI%uECQJ+7F{B39ZQ1A?Pl5!{aaKuqU{5Bm5_2=V^ z7-||UAiQ!}EOM-7|+ z9&08(I`I(BB%9VAW`G0^N4~6s-@({Ljipof4EBK4SsiurZ&WeZlJA_Co;D+8xUFdq z%eI}tzWX{QIxHaLbmSF-lzLWl=3PoC+XpNP-z@vfWI0IfCpacRRFObplks(-0nPyP)5D(&p}BFv@Uyi1EOEylLy&a)$&_;Y6>RWvW<^n%N`pQkwo!tc?TZ!bq) zZqH<4FdKp3p0>{OIPe7mp((&wRw5ru0PvEg01UUeYXuCkkX zjU^6k*V@xpPlO2#4Z5$h9<*g#Do7;A41n?yfQa&$B4CCm`#$Jw7X6Elb`$kHfkj&Vztchh)V35kN0nTLU%f_Hp*yl!B-*Qazlc z_^&6MNYUeOv~N$al1pL?I`l$sd(_HJb!TaUzmP8s=k9%|E5Pk18ISaCbi4t9;GYSE z_oa#`a!(MJ{cJopP^aL78BQKD5@Fj+p~*?G&Ah2DT2}8SWPlA0k`2y}3JZKN*c1d5 ze(7D~Nr!O97QtC-iJ4o)!gy)ezUefLfU0=_x>pYJ@N}FU$Zx%v&6zpT00P7famZ7!Vm~_ z-a#4AuJR?aLxqzuq^1e<4(gpVhIw&B_~FxFshPF2Sos$K*R3;{sS; zpLtM%94yfYK9s7!M;A5Q7^uX4*2EM)&Vwg;4~F&D4tD06%?51mLQSH5 zYqp*}?l-P|%*%BrQq&cgpldc+EVu&XyI}-pxv+99fa##A)`i$Dx!$Xy#Pn)fl;L5t zhO8>;)}dc({3}~IH+=lXQPXd&%mmT>eA4odO{z8Wm}ZQeGgM%1aPvZ5O{$PZDqUg; z0jx&}47Dci$kmUQy`e_IFnCwa&AR1Ryu399U$^RIYapwU~ay?oZmcvb*+6OI<7Yd|c{*E~@UX^>uX-u!Fm{&=P8>|6WxtT+Soe zr$aY4XjOK3Tx(hUPHAi8qHhQ4M?W~^3%*-_1-?Cc?#luA4}Yje(_Yc+F5`utoKBgh z`vYABOGMd3Ha`0Hlkg|R^I8***AS-+TC;!aXc5 zx2Z+dy%n3Tf2oxTJiJ2E1Ks~U5q!=vw3vb?yDJ-+6?nJ`2Lxst!^HxZ`cGdr+-|^1 zYRv^grZncg$%QfCLdnAX<0r%u^W#8;W}?Zb3Mq54c}ld@yL#3k1H0jO`h!Pe?t0$* z_{aUtQH{xKDw-Y7E9^TUf{zP}3X-&i~0q}z-vz0gdHit_oLZ^J3J>HNn0bx0@Ps|!D~b=+;f{pHCY!<7_!3=_@=t!5nd-Ym!F7nN1c)f)oy z++JzO7HsxHHDyynSA+DgWXg{EyGGsk=}4&0deXa#?NWAd21zGJzvXvvV|-=#(hv@8 zjo7%ewva$%D`^yEFCX;dN>8J+%E18M3FVFSWNz|n>Fnw7wP;-cG z-mLvDyiK}lnk!Udq^B?NpgCD|Sw9*2^nBRc+q8zzhjj%-veSVj%*s`8xik! zvj}icnY*hy@U6#>9I`@zWU%rECPS^de$-u|zORa_v2ahUzc+5@6ScWJq}56y4fVaJ z<%12t+R-6zcaEBk`Z!KnUBR1sl3p?Cn0C6YcLnFjme%;J1%G1G2b<5BwcJ{x&+D;M z=^42gejc(C%S?$L{=Vm=j%+zlDa?t1dfj`Ctub5%^?eaU&0&F5%V2k676GS|1HHCMR#GLT|b@;LcOG zgMT!7&Yg?5X2m5oqoumg?U6YRqMp^BL~)fvUt)nLa}U>>x3b9GFk{`~oek1O5vn=aw?H?I5I~JbCntg`?rxvd8R2eZvOKK-UcFyT9@=TXR^B$m1~UMLpgK2^Soz)GJ#NU>edFaE>R~ zKzmFTx!bzhP8IE_hJ!2(rfZJh3%u{=gX>miwjw8kGT3py&0UB-afVESE}$V z)4#H*0c5t9BB5f^yYhs%G-`XF4TWn!uP>A0;Yy}L6%+J?z=OjEZXy9GY`L^FXHDk% zy27h6Jd2yGL=%fW25f!SJ6W$15Q&h-zPYbH8ES-?_4MfB<7zh|5Tx^$e_H69cdYPl z_oLTG5YCMhM(0bY#5$b1Wwe}|P&h&;n4K56N#Ex9y}=aPV1(W_p7t^nn0JQreQY(qWt;@4)%%=1WEE#1O1H z;uag37jQNSj!$#~FOM#&!&_OQtDoV9B2&-fA9I=4w$DBrs+UYq2*xk>DZ8IruZ;Pu zLmKo2xrgXidf%B9B%SY^m~)@Jcjsn&dK5-7byB&}`m~i0$ParJlw?HJTIM~p+*v>I zo{YKb67!gnV^jAYj^-gLG=+jf?03=*Fh|ycv^U>r?(8brd+c-HY+XgUp~|SEFHGd~ z67R;y69X|QUv=#F3)fvaz58y+{D$#KNmM|$y8gCtr!Qh z%N2WAfjY&W&e-Ztbwc+_+_EG}zd|vSc!RQzW0a`b8aDMcnw2N{l-Z6gZlXClZYobS zh-CF@>(s;-5Nb2vSl?!(yB7;dxYc}2NzCUb2<=Q96;c}zFvY>skxM?BvOU5OT0q*~(64u&%SjC<5GWSK$sViM#_h`Ls2RHMsdWPwGQvkf#0~?nIedJx3`Oswe?3Ii}vC$Z6@U6Eol_ z0<}a#P3Ap+p+|Qnifh>8utpiD2{qSH)Hni1Kj3u1*Enw-ICfV^&$spKDL?izUkka> zSnK85H^4ZQ%YRpyEw5bVF3!ILHf>3bZ>5+X6IGSxSi8KcE>r0J`xZ@zuYi-8*QfK_ z?_>~wO66-{SNgFdDgyDeIcevaYPAvm-4Aze6u8M>q&`HWK-o|`ZLoVb!Yp!Mc-77> zlAH)|CI(^bU77VS=D7JB+sq1$HNb?9vd%>AGQ_ecP63he5A`$jC|qhK{L;pY3sSsS zT_-SS)uNCMyX;teW^29R!OjluYNgZx*AI&+Vs1NSA`y0T(Psd_x>&a=?mK^Ibo4*UI3th2y@S$2yy>Q048c*9^ zMS2RX_SJBM6aK4PB({%@HJcg4PhP86;L*_}N4wx^i5BScK5(wYI0x~CGJ zf3}@vUwoYe$>+#*co*6A}5acRLwHgFZZqO7`$-L`HR{p2g6|nJzs|#VoOY2DnvFq(ke6JZ&(O@5P{2 z9wX}KWgE?h&9C5Ji6N-KR=0Tfk&KT%{EA{!)xE@mr{9y3Xb4&+OAXhuluVKDS@N>h1|+#w+2=_o@S&apg=Gr1^Zx-52CQ9TLmt zI?3>uo%y4mT53lnkD;B18mFC&>s=oNE7vD1i;+g;oeswzwquo{>Bv_zL2ZEZK`~e7 zrOBi*lG|60s$<@I_w@oM<@D5zLjJ)ZrR0P1GrC3%R!Ez}S|xNT|LmpT&E(>pDjz;( zpPnS)bP(lraxVv8mI%&c{nmtlFGDjjJc1n(;}~A|P1Glu_Z$DX7^*FvUUmDyuJz)6 zfc%4i-0B9Ml3tFKwWLbupF9{IsGhp0FVd1ZH!?9Zu`zMMYQM@G!G16fqkq~zy4kNwR<9BGVQRh>)&6H@sYdr_gL3Ue zZq$3p?<1Jh`nN--Y}0nF%o5ofzEmCdcPUdkpg&3t!)%1~i+b+IkcAH^iBdu0G|7pE z@^C<|D~u#LRHF;M-rXw9xAX9ypXe!I=bhSCPlK}%aOqVXo_sX5S0P`0px=!Ky6)&X ze8L3X8Ig(|JzJ_H_q5foCiEg7Pk{E4Gk0~gnCDNni6C=7kyDY^Ze-055gxbVBq-*J z$JxyW?2C2+vMj#k%9Ct5vRkWkH_D^1b4P6F#p5|g=pIq@!1{}j#|T?#h_lb_YjmV` zlmY)JV5H$@%lBpN0?|=^lj-1yuW^a8JB)gf?U(|i2m7xdxErxOr;oNmzzbexw=_%! z+J^YhhV5uD8bG+iOL=J{h0&yRD^v_VBWn#z8#rRX)KIw0KJLo_0nv7H_0m&YNgRkG zh9#=rd^E&DFCL8u>xTs5FabYWK*P&&Q}DFJ&iOXuLhIeef$aX*UY|Y`t_2|U1)o}5 zO&VYBmSS@(&&#U507|E1)n~1{XFn4jQwpAN2zOPk#IP`#=kBf@>CL&s{B$|cjF88x zjn_~llqC2%?tLY!{KJTQLoHv$2$ysT5bw;w?6~0Y6G!t#v+Ci4FZdg z%oFS;)3{>6j$Y=wY#X^`Q{M%BNgfTAD(u~TO39<6cmRwnz zVjq=x>DR%@)S)!4(MHU>+1YL-S1~&)KnE79yAD&bfT{ywl4e{|Jk1^J+qLy|niB^4 z>K>U}r?;rZyCF1x7aquqP!c!Xdg&$0O#qtm{d%k)9+kkB!E+d z3xdVC4+2Ecn(e7spz}pbKvUn=s5(P(VBvMcM}0Q$-PEIq8e-D@DDuD%Mq;r4m)XaO&W+kzS&Rg;#J%%BF$ z)r3wL6F!j{A2<)g-cotX4Nv^ei#Pa(}8a7dJZ$_Skiua zMHo;7@Z9yhTuL;?**%cO)SK)xoaJ%hiXhXjO=C90`zfbiMH99ibTj;8R?AY)+`^x` zpLjB!%-cEQxpDEceIn6^<`(GH8~oRbSiz)l-Ds>m$fcf|Kd8SV&7@v239eL52EQ@O5?tUKA@W{(=FUzS!5 z9QWxxY=dn1^6kmBx`Ky@zw#A^GYA>THaFoy9nFaYl%G9XHAUC_Sb*LBaO>AWVM9k0 zS?%%&jMSd;-fJ~mUP)#AgREPTz37QuH!r6x=J~s0sP<$gesE1Jt~xnz4j_<)5ZPEw z$n5fHK;uiq+>5RJ-Qpgno=eS#-P93hE><69cdph3V^44CyuO|3Th51sMYs)JoeJpo z9wCgKQXHuYq8(r4)WPDScp>@j@WwlYWLl=FOvbXFXAR|x zSIfvvM~k8+GTUZ&`gn#-ZH^buEZ$LNvhwS+W`Ax_@JjQOxF;=sl0JblH=qBpNT9DVR ztz-rN(=)AmVDU}Lo`84H3o7IHk5!VTE{=Tc*AaAQ842hj0@ngRQ4EfA1Yn!R;K~b? zT;=n3-N+ABbcpygkFfL(TkV*%7ukBnI{^1RY}fC@Z8Yfy%tu9?YeLn{+l2K7y=%2k zWgp8It)j^2r&yjL_6MXp-#1zTKi8?B_kO+5J+%ag@ird^@)v4JW$ah`0Co%8kHE2jFwj`kmA0|T#UHLcFw>esmtIPy8oS+`1Y&Go!fUjG z{23f@b6C>%MKPaQ0h^$YmDVq4b?&0(M_#O;fq?VMHI&1XTzdD)+%{d~O`-II3T<6o z`S&;OdeO_+`>|u7aY{weP(Cm2Gv|G0;n4aMi3u?8kD)`^grmYiyV>PIR-S#zRl7h?cBGf zLb1)-%-l&nlXgLNr$;c;{?@ zZ8gjD4FAbvLY6Ay@rfm}S@5F-hLwc%Mefif@!&|;9ezP}r5mk=4I#0J`{~D=lzQ$A zzD%iQ6o3@}x`%4V|Mnb_)ez0Ur@BTji0>aSQ2UnfssW`)>?VuPvjKE1%y$^H>&SqXSWih&Xl zmCxj^VG$W6V9Emf*;jyH_`re*l6j+A`BUlS4K9b0nm=bD7>58v^&aU8XdojB?Q($5 zwM3Z@eTgM>;h~qYN=#GA_|nT#ZS!2j5z;o6Y02Yig(nPn;$<}WRy(e;RI{WpKoCoB zHIR|qiWrj#ndSWWI3;lcm)zuDtc~@-zZqoC_^>My9a)%>|2{lIhMvF0io#1Slkim) z&|ib~5|^gx8Vha(+R`IJ^@DpTz|aI9y(o(!o4z~Mc@Dt!He z=ugxtIT6gI*(k|t06cx2BBAOFR-Ir5tOJ-vMx%B7ts(R`%iG77RcT6$T4q;TsK8Mm z(CUl(?`HdpAV9@|7k?3CfS?*oQGm`YLWTi6`||OP9{O~fB(2f_7WyCb{Ko@DcjMjL z;_`GMUg|GeT*!`LK7EX@a$Dq0hL*FL-t_n3rz0X=Dl{E}tR|`ZwtbJ;_@i=0!_cPV z($vKk^k~C@Ea+)8+*pn$^*G8|M-cFn8nm+FD&*xruD+sDjG^=fmY;H7jvh$c)Ab*> zk1?C;l9bqvP4bFiM-1bp@~MNl-FP;!X%i*VJb+)BHXYPnX^SXNH($q_xIA-W;!Ocg zyFU0%JL-j&z3AUY8btsaE`_c+a8|6$c9uD`ZhIj!E-xeByuoGLk~+DtQVjt*2tHg_ zM|P2-`zMRZ-k976BkodWo8k^7jp>M^{UYkItaI5{`jTmjkC*t$uA#&v2hOKVqxoSJ z0z1h2s-mJ}7zW zk0}SL%>)sjLBA3N8s8Do5+}N^oz8d+d^kFH4|x_=ngldui?O46vC%Tm3HPQ;y%Zw< zp#i_dv$m31WwC^Nh=$lIw<{PeM}F^{)pfB7bXLP2HojhpfE*kFZJ6r6h87Vpt=kC3 zkUbf(Ez5=jeMIsBp2J~e&vUNye9Q)Qyv>fu@L2t zsNi#JvAvY{%8t_Xq;Gy@J=pPx(i2{lpExfqd@J6t_%SYfv<_OrfVbg5!;_%kB|8P} z2$`AhUywHpFv_6;zFIu$?9tpAeYDg=SNr0TEIRe+R3}N*g+2>@o{IpQsTl={=QxAO z8o54_`FgSM@yF9Q%pLF#j`}?MJ>T?_Tdg-g-y`~qPP+=|I?e!Dl6I*5Rn(x#3lveF z%wVEYfuVx%%VZRk!^}hp7wXvd*e<|Vv}*yfpan+QZ3UX5!$<9Q<`o=?8glKou?ns3 z+$@Mnc$#E86s$;z8Ti31WuFIf)>nETTzS|wODlg!E;J)M?Z5P|M<%l-wN8Q0wxq|iiJ{EB07vsXY~ zH6H(-o|*PDA9qwX+}@6oSXt@aVBQ$>2JWza1IthwyC!8K8=5I_dLAG59P!3Wd9_}c za-KHNajmgWVtMZ;MY)nDuir95QejrdT`>!Jw)g%qs?0MVzuWmE&slGt$dA;fiX)vm zzXn+F3Q3p@j#Zx4Uly@_>>wl-a_A6?;8;lns7aT?NOi8w7JF=)QCAi8Tl z32?Zg)9s6H?iE=!!;ER}1zMshOWph|{c58GsE2$Zxx3366?Eb=_cUAHI(Qo)qiM%n zlmU17K`vl-d)1JqZbfvBcv@4=lz}lg0ysW15{ecXKEBu_=q*XSyPjNwW>R^Z@^ettR+>xvH&ChlK)Tz%_Pd=N4t5QMs+ zkHSpQX34>u|8}c^@sr)AxAF$^{sDIy?p40#d-A$WUf1x5A)njS;L#WR><64xH6H0y zC=dA7@+g^Vzn*uU9CCI>w#W|@3&I?nVVl>r3|CcW<%WI?U@f0GH>q~Uo27Vd>KUNG zaxL>>llK8C1*M_zJxxd$=?H_}pRaDPj5?F=C8Tkk4RD<?$;SLTMZxdG;1 zT(X0lUjIUfOH$Ubolyspk)PJ9I0(C^huw?gQSZpPv}d;r@d*H@(aL;?z7t(jJ+zV@ zMfkCComfb_m}-N1q_ZelhSG6c1S2xxqv_RyoW~D$cdhaTiM|=B^ip0^XUb?*?U>Ck zliWk^Mw6=bD%Z`d%!1hXWet!D) z^~LT*D;-OC@s8_S%ZtMUU&pCV?LYk>uT4d?ks%dvfu9Y&af7cw`-m7ZpZKQI;yGoO zXW*@m*y6PB*3|1Du@>bMs$*14LQ0!<$ApsR?39V0WrJ;5Yl{E_uS z`!t!}uZl5h+K1blfZfLqA%6k>ih!>Nt20g_9f;^Q0e-WCEDzd>-VIz) z*<0>4xP+-p%fyqZrIBXYXU z?qcckgd6QeQk*uUMsM4|3RCF)f}8l!-pjE4IQ|)hK{dtlk|EM==3(tcG90zsIZ?vW zo6)@MnH&M5*m_}(m1Hxcp9wmxdf!eJTGz>i^qrN?Gxynh>dCn>P&lJd6z85^B+7gG zHDp~n`HzqBubKuH)!HWyc9ycyP-CX{WaA4?%a#g_iJ!N{-88IUfA(8+)q8?|%xM$1 z=-zg>`^$qK%JP-B#`3$Nm64})?_JqdQC8u8Npqyd^lDQ$5dnQyLH!B8Ci5WPOkvE=7VhtPMGA-gw>h|8 z?`6gmH_}OJ8)ux*)X9P0mUcI(OD9ZM3XV0n;y6Bwojab-4k?T zn!9nu4dI2(WbNQrOacO0_415`>-|r=m3CmNtsp zU_W$?$(A!Ua!7nXjd^c2{QgUMo4!p@nX78blJ8=auTaV5;qe@Fuh`dJ1ZYcMIc?1_ zDdC1D_Q5|MW^EJU+BPShhHcD>+<97EEw_g3u#>PAT@P0LTe?wa#(Rqvl= z0{FWSh5Cf6cak4>>VLe#=+Lga=OElm03w`t^+7}5-7}pL_@}QK3%Tfe>US z&q^P|x#6`dcA3X}LkrhiMBO47aa)!^93P+5ybt*bWhPBg8!Is-YhapG?ZEV@VKk9h zwe}koHg3~sIf!!?s(E)q2>)y@y_EY)!d)sD)FkAD*J8%zV|{ztVrL&d`q8r1P(^nRy)`JFKFNXPYvfC4ObVgaV0{O5P}3D ziBBCRdh`gQM~e}iD8XQ~=q*GKq72a@h&qfq%;*Hc1W`v9W%Lqd2&3GSd)NK{taHxZ zYwvgM{XWm{QL-F(;~=13s&zzh$DQX#ew|4zb_(bw^bLi2CEa{Xp|g*yNc+7!JkM;W z$T8C%yZot87@Ip9e0%E9qPg+Bf$ox;_$D%pYLo_+dWjL~v+gi3i%g4~O*HqM@tB}G zHU1fcZ>ITfU>-|D5RUVx^_=@Pj|xn&X!m^F)#<9)wel%DDuD*6|}d3ZcHJ`rY7~(9g!eP zEgOIK!qRc6iF}g}`Ch+D5iJw$X;crUWCOH|dO&g%?pDS3Mwc}8-EXEZ`aLo70keOZ zNTHIwCkIW=52>}J=1%6E&;9#=U)9n}MhD~1U}WBsyWBW}Af;$|xUi2-> z`A-e$!^+k6y1p}E1*;n&IxRM#uAu~?h{*fThy zi_|81OUYSP;AsKY(V-#>l`c)8Eu>pli50Cf>{lIh+eo^8Dm2?#=Q+=p+&{GGbB4b20J(ZKLPEW}+2YJ6XEp2_FL|ZG}c^EyOdU8RD8FrXx`k*;u zY^q@aB4rkTqN1c<6V2`$YqsB>f`>Hn1VC|Snr>~|7GiTqeFKB7aaU6iShU{Cwg=ZX z?f2r{bZ`j{Ld|&=1tB568PzAqi6kOQQZA@4rFHHcgV3UK|Ce5cLF}SuVr-S z_g4R#o3=Yu7;ArKWjdxOKB<4ed%FM5S37O38&5)+rQJ?_Sjf(>bGi!ub>Q8ka_S?< zbln{36zvp(l)ajvhI|_8_JEL~4Iq%*93PQa;xvv{jWg?PgTX4?U6%nWip>H-2l2cx zglOt5ViGrdVmS54J$Y258HZV$<_&mj^lRyCYw~z&C7W0?lWgks_H-@SefkYUTdZhv z0-7FXp7IGGEQLh_OcM34bb8NmHS@bH(JN70h0;O?M6Y)l?4BO9XurOho)G&gBo1&g z`s5vPkBy&a+iu;MQp{$M-X~De>W75UY4E#TC>$DvA3aAeC#AM2%Wr?I<`;AH?cWV(95urS0ak z8sg*numkylo~QZMvQYs)P7{G<=%rf(ML?M6;}KB?@L|J6`&gnHh#4zyNzJFu8CR=ZYA!lgZOr+&&E5C9w-zUo$sx&Q^z(Gx4ZX@r;|}G@ zK=cq%O&L*u`yW6m?I~b??d|!{_mTh^CMz*pJqfoc zCFo+9vo>ZWo51Ff%h?IoWzJYI`zyX$8=Bj8Z;VUXT2~6qPlD=Q*W6>Rjbe(NARRg{7 zx~>CII_+-er>0ObeW<58mb!EEX`82_YO{Z!k@Ow3}j{RwZEn|Zg zp%3k$>YkdfZ50Z(G|ixQ`O93>MtfUD@xgUc znz{pgjQZs}4d=&Isemly&?8{zg04n>Q@M;GItTdMIPjnDNHbS@o0n0-lxW#oC;#;s zrP%b&&((r0pr=+0JZ2VgRq@6Oi}1i;s3xtsr*w?TIC~2Mjmb*_w1Z7V+aWEN+K~Oo zx5!x+&7%HxrJ`s@L0WBJ{+-z^*Y$3cwU*nny|<@+_a+P1PSn%ie`BT8eLgCU25?W) z^KFso&5IjDQZL-tbOu7VJznN$_7|%yEC?(d9hz$1^n5v^2jbd#{TU3^DR_0CDiD(& zFTM(sI9~na19&OU<2wI?Ek(cAAvfG?XB~=B5L&=mU5TmtGI0$tjeoPOUZXY@XfrH| zW|Kzk`|6d;<-KVm)*9E*FS| ze*FPBGI$R-C)Iyr$&FKDnDL2Q%}k)Y0-ZW`={;m+qfUaS5}eTAAX3(vjyt3GTfMn$ z5(cyw949vr$A8SS?F60Jg+y?zuFJ(8;R~(y4}gBLxky{GMTU#?1DR|-^WE*!qE~#} zLsH*y*Rp_>YxM2=kZivfbH{G`^LnUfvc0N$VO72o2U9_R9tWfM(mosS`7!|QZ{frZ z732Yr$octN%Cm1VM{fP=qwi4oKdLODT9Bp&uS@6WKRXp6Czf9Uq8xOy66r#xIkp$) zn|;*T_4FtoA$~P4Sc?@USAHqfo<7~RGxOM>DP5WW2CNDP+#6LM&Yl!zI97Ux2=oL^ zLw`7K<(?1Ug4RKs9d*Fs6;`Kr{5Uv4`t*)5jjrzht$mxF`Lzu?o%q`}=c~hp9C_W5 zy`3?{ij61^uC_Q>dJnY>I$A2upe?e0VLtz}T*B=ZxuHiMx0KHcGLEKbN=9i}{?zNx z&)$cIzf))iZQBGHnBPvUE^Rwg_YL@+KvVlYI`K=h_81eC`K)kOoU$_JZri71o>=X4 z=XPG-lAnR~feCA0B%fcOyxYVPWB`zIp=iTq8FCh3mB?bM=u7y>GG{J_+KOzwMQ4pg zrX;-nWyApyp+NP{UZybqL^zsN5n}r^INkTA0GTm4 zFDYX$c%O|~X;EHLPwv^&_d8u@#d#>W;N`_Wq!ge~r(IvR71CT@YxI&%&hX*!Pe0Z0`79_`%Ji5<+Z!szj!C&*c8fxsb^5 z;5E(H5K7x(eAl{PoOk8b!PZ0l{=PnuGxhaTL`cG`MlqBb^iq$0tPh)+7Vj@c0A55LXt1xI2So~w=1sUase@F8pcQ&U zjddatP~e54iYgWY5)Ubn>-QWQ_l-trwj4CN(fj&Njxj~eUgy6WMz0zR@)~m)X6L*& z3S45PB{2UzQZUU?R@nb?vl++HS_Hdi+OV2odput(RxVuw`cA7Lg^yu`MU3~>HM6?t z)H2)Ec?%vOe-_?8UX5CV#Bs1mH=QSbc+r(=3sNpxTn~gN@pCt!YXSw_zPc^fN-9z( zE=fg3s>Y9sjq=HJ8pa?pGBRMs474`P#+hX;Fbi*^MJx0@+`mI^sD9ui>3eBC$N9Fq zGoHLUX^*s;%^z-!);jt)u|N7UD&4`SS3JiWzgRV!x>+^P?0CIN-SjD0IK29AaX%Ax z60{Z2s946OdxfMZv{1T!n5Fo~zoH3u|MA?o!+ec}QJwb0g7xvq{gY>@UPM zKyeX6l9;qy+o!Xik-W>wZ|m-kz4b}0@Ix-SYW@M(c=klQbv%#O`tXjA1J)%%hWK%b zH_%EMuLERMfpwtIzM9k;Qf(vkMC}qo^L}N!Qt5$h@cBQp@AjJ#Z;J=1LsGP<7(_kI z?Wq;|_iUeguxTS+&;*a8#+YbgGO|3b|nk@Ar;IqhELkUB%dIIkW(+4ao6uSoov(I2d4lx`=MB}_UobxGB z`a2^%t|4m;uQ4=O{-~qOjKAa%1KKT`8z1J>lUL5l`|JaQ!i5OZ_QGis_;;W zHViEiGJKew>6;{<5aBFSPIg-D_GtMnD9PX)D-C6+Xl3yFfiuFJ9&QBE@ zJ+Q!d)I=8<(({VbeH z*0IFG3>7C$A>Yr*7S9$I0Cntfm`smqjE&}<&XA`zEP}?6qp9zgF569TM%&UBpl4{F z)|ulnV|Vx%y-bt7Ke#PCD~;)*@_ZiG1<0Pm^vipKg({>HkCwS7hH-0rf`Vm|^PX*@ zjYza}O79zLXMW2;9yfcrYKbGZy?>c{60Q=E(ek(}t6AGGEyp{h=`=DBnGe8kKK#}& zWIMbWAa0OK1cyE&TvUex5-%zq4BCm8!=rxO2Ly|{k$mw$ zz*olQYT(VhvE^AWHZm=_%Z;+_5pR+2(w*{S_wD-c5xq zLK3L!z$yJqpnK_JfI^&tjK;&USrb{;T^}M-6sdB;ddacEi*JiCPEqscKiY2u~B}dKUX=z7o3MoW^{$>0vDO(`b z=MX_{Y7n3uhrwUB*XChVo`D7-XZIoSq;EATla{>Kyb%+U5sSH6FBSIJ*8#q;&@l=! z9fNfGzwt-l;i@a+-nEB;li*MdTdqnSp=;h;ve%^t#ogVUb6(~qVffF>z$$_0JJ9`` z;6yrO!9wo}llFinOAyr)$WBA0SyD!5+Q5UhoL9#oSMcg;cN>REAU^xWi=O9eeS+v- zZal0Vvo1(mA{lDw|6uL(U)@1oxZW}_1j4Ouyxv}PCNbY_jU>ue6b2Z%H#BCFVt1C7 z?lQ3$Y;AALA}$JXYY!{#UTt^0CzXu|C+WxjP&`dHgv$bng=3=LY#^N>1uk4#}ff!>9-emR*G8l+uR#t}355|B? zo%CR5801fb#fn`92seZW!VB5FI6E@V*xvD}`)g|2;h=OCJzl_dXHP!l;;+6m+8>w? z)t;W72?1;WcvLM6Vqnpm#aMB|4hDP_9p@8SKTN9df%YMpZs14-tE0u7NofIrv~Utq z$IfsvE$){&D2?;K1>;vBB>ho{pBa1nc*?iQ0Q}ZO|6rB`W@DrgL{7%JSwKjF zREf$ioSrRywhkn-Qv$3V9bP>quJ>J)ZSXWwqOo@1g}e0h+MP=8fhbK5K@KUmxhPNU z5kLPv!KgNDP#iX6{iQ9Ka1t%^ZY%8*$fPTy&WrzXA+Gy$;O$I>bzsbs3Tt5h0Xuxz zsP4l=g%$b?p(01nQ+KB$(@fuA486{ssx)gX!bR;08^}7LuPrHm^oSwWrCLb99+oyJ zI|IVm8TgLDHjkz}i|KwW)^r>wO=0UXbt6Ia1Gb^aswDg;WU@pGGDV`!JgCfEOLu@J z2Ao(^IevZ=G{+<7vol5B($W%CTU&rb7P@q@Ww|VA<*cyq!!Z4PYul06eOdnU)rNuJ`19c^KSmEd+x`RZGcmLCjP`UVcpCyS4 z_iN|e(uz=2fy?N>=vrWT!c|>dTq64V`Zj+5{!M3PWfi(72A-?$Kgk`r4Y;O==2XIS z8`lhQyBbk;`ZhK;AwE7QIIRq!B5p3O+}RfYqmj+A5@?k;E7iVISqXqG*n-zAf8)?{|`wGIS&8; literal 0 HcmV?d00001 diff --git a/addons/payment/static/img/western_union.png b/addons/payment/static/img/western_union.png new file mode 100644 index 0000000000000000000000000000000000000000..02969bd562467cb9114c200f346c9f2b14fd0e3a GIT binary patch literal 22772 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

E8e*65M0)FV}>jm?h(GkUaxnz=+WH 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 literal 0 HcmV?d00001 diff --git a/addons/payment_alipay/__init__.py b/addons/payment_alipay/__init__.py new file mode 100644 index 00000000000..7d34c7c054a --- /dev/null +++ b/addons/payment_alipay/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import controllers +from . import models diff --git a/addons/payment_alipay/__manifest__.py b/addons/payment_alipay/__manifest__.py new file mode 100644 index 00000000000..1228c13920d --- /dev/null +++ b/addons/payment_alipay/__manifest__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Alipay Payment Acquirer', + 'category': 'Accounting', + 'summary': 'Payment Acquirer: Alipay Implementation', + 'description': """Alipay Payment Acquirer""", + 'depends': ['payment'], + 'data': [ + 'views/alipay_views.xml', + 'views/payment_alipay_templates.xml', + 'data/payment_acquirer_data.xml', + ], +} diff --git a/addons/payment_alipay/controllers/__init__.py b/addons/payment_alipay/controllers/__init__.py new file mode 100644 index 00000000000..5d4b25db9c0 --- /dev/null +++ b/addons/payment_alipay/controllers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import main diff --git a/addons/payment_alipay/controllers/main.py b/addons/payment_alipay/controllers/main.py new file mode 100644 index 00000000000..3f1b4e1e40c --- /dev/null +++ b/addons/payment_alipay/controllers/main.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging +import pprint +import requests +import werkzeug + +from odoo import http +from odoo.http import request + +_logger = logging.getLogger(__name__) + + +class AlipayController(http.Controller): + _notify_url = '/payment/alipay/notify' + _return_url = '/payment/alipay/return' + + def _alipay_validate_data(self, **post): + resp = post.get('trade_status') + if resp: + if resp in ['TRADE_FINISHED', 'TRADE_SUCCESS']: + _logger.info('Alipay: validated data') + elif resp == 'TRADE_CLOSED': + _logger.warning('Alipay: payment refunded to user and closed the transaction') + else: + _logger.warning('Alipay: unrecognized alipay answer, received %s instead of TRADE_FINISHED/TRADE_SUCCESS and TRADE_CLOSED' % (post['trade_status'])) + if post.get('out_trade_no') and post.get('trade_no'): + post['reference'] = request.env['payment.transaction'].sudo().search([('reference', '=', post['out_trade_no'])]).reference + return request.env['payment.transaction'].sudo().form_feedback(post, 'alipay') + return False + + def _alipay_validate_notification(self, **post): + if post.get('out_trade_no'): + alipay = request.env['payment.transaction'].sudo().search([('reference', '=', post.get('out_trade_no'))]).acquirer_id + else: + alipay = request.env['payment.acquirer'].sudo().search([('provider', '=', 'alipay')]) + val = { + 'service': 'notify_verify', + 'partner': alipay.alipay_merchant_partner_id, + 'notify_id': post['notify_id'] + } + response = requests.post(alipay._get_alipay_urls(alipay.environment), val) + response.raise_for_status() + _logger.info('Validate alipay Notification %s' % response.text) + # After program is executed, the page must print “success” (without quote). If not, Alipay server would keep re-sending notification, until over 24 hour 22 minutes Generally, there are 8 notifications within 25 hours (Frequency: 2m,10m,15m,1h,2h,6h,15h) + if response.text == 'true': + self._alipay_validate_data(**post) + return 'success' + return "" + + @http.route('/payment/alipay/return', type='http', auth="none", methods=['GET', 'POST']) + def alipay_return(self, redirect_url=False, **post): + """ Alipay return """ + _logger.info('Beginning Alipay form_feedback with post data %s', pprint.pformat(post)) + self._alipay_validate_data(**post) + return werkzeug.utils.redirect(redirect_url or "/") + + @http.route('/payment/alipay/notify', type='http', auth='none', methods=['POST'], csrf=False) + def alipay_notify(self, **post): + """ Alipay Notify """ + _logger.info('Beginning Alipay notification form_feedback with post data %s', pprint.pformat(post)) + return self._alipay_validate_notification(**post) diff --git a/addons/payment_alipay/data/payment_acquirer_data.xml b/addons/payment_alipay/data/payment_acquirer_data.xml new file mode 100644 index 00000000000..a608788e5fa --- /dev/null +++ b/addons/payment_alipay/data/payment_acquirer_data.xml @@ -0,0 +1,18 @@ + + + + Alipay + alipay + + + test + +

+ You will be redirected to the Alipay website after clicking on the payment button. +

+
+ dummy + dummy + dummy + + diff --git a/addons/payment_alipay/models/__init__.py b/addons/payment_alipay/models/__init__.py new file mode 100644 index 00000000000..2ec5b9cd2f6 --- /dev/null +++ b/addons/payment_alipay/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import payment diff --git a/addons/payment_alipay/models/payment.py b/addons/payment_alipay/models/payment.py new file mode 100644 index 00000000000..7f133b4d31f --- /dev/null +++ b/addons/payment_alipay/models/payment.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging + +from hashlib import md5 +from werkzeug import urls + +from odoo import api, fields, models, _ +from odoo.tools.float_utils import float_compare +from odoo.addons.payment_alipay.controllers.main import AlipayController +from odoo.addons.payment.models.payment_acquirer import ValidationError + +_logger = logging.getLogger(__name__) + + +class PaymentAcquirer(models.Model): + _inherit = 'payment.acquirer' + + provider = fields.Selection(selection_add=[('alipay', 'Alipay')]) + alipay_payment_method = fields.Selection([ + ('express_checkout', 'Express Checkout (only for Chinese Merchant)'), + ('standard_checkout', 'Cross-border'), + ], string='Account', default='express_checkout', + help=" * Cross-border: For the Overseas seller \n * Express Checkout: For the Chinese Seller") + alipay_merchant_partner_id = fields.Char( + string='Merchant Partner ID', required_if_provider='alipay', groups='base.group_user', + help='The Merchant Partner ID is used to ensure communications coming from Alipay are valid and secured.') + alipay_md5_signature_key = fields.Char( + string='MD5 Signature Key', required_if_provider='alipay', groups='base.group_user', + help="The MD5 private key is the 32-byte string which is composed of English letters and numbers.") + alipay_seller_email = fields.Char(string='Alipay Seller Email', groups='base.group_user') + + def _get_feature_support(self): + res = super(PaymentAcquirer, self)._get_feature_support() + res['fees'].append('alipay') + return res + + @api.model + def _get_alipay_urls(self, environment): + """ Alipay URLS """ + if environment == 'prod': + return 'https://mapi.alipay.com/gateway.do' + return 'https://openapi.alipaydev.com/gateway.do' + + @api.multi + def alipay_compute_fees(self, amount, currency_id, country_id): + """ Compute alipay fees. + + :param float amount: the amount to pay + :param integer country_id: an ID of a res.country, or None. This is + the customer's country, to be compared to + the acquirer company country. + :return float fees: computed fees + """ + fees = 0.0 + if self.fees_active: + country = self.env['res.country'].browse(country_id) + if country and self.company_id.country_id.id == country.id: + percentage = self.fees_dom_var + fixed = self.fees_dom_fixed + else: + percentage = self.fees_int_var + fixed = self.fees_int_fixed + fees = (percentage / 100.0 * amount + fixed) / (1 - percentage / 100.0) + return fees + + @api.multi + def _build_sign(self, val): + # Rearrange parameters in the data set alphabetically + data_to_sign = sorted(val.items()) + # Exclude parameters that should not be signed + data_to_sign = ["{}={}".format(k, v) for k, v in data_to_sign if k not in ['sign', 'sign_type', 'reference']] + # And connect rearranged parameters with & + data_string = '&'.join(data_to_sign) + data_string += self.alipay_md5_signature_key + return md5(data_string.encode('utf-8')).hexdigest() + + @api.multi + def _get_alipay_tx_values(self, values): + base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + + tx = self.env['payment.transaction'].search([('reference', '=', values.get('reference'))], limit=1) + alipay_tx_values = ({ + '_input_charset': 'utf-8', + 'notify_url': urls.url_join(base_url, AlipayController._notify_url), + 'out_trade_no': values.get('reference'), + 'partner': self.alipay_merchant_partner_id, + 'return_url': urls.url_join(base_url, AlipayController._return_url) + '?' + urls.url_encode({'redirect_url': values.get('return_url')}), + 'subject': values.get('reference'), + 'total_fee': values.get('amount') + values.get('fees'), + }) + if self.alipay_payment_method == 'standard_checkout': + alipay_tx_values.update({ + 'service': 'create_forex_trade', + 'product_code': 'NEW_OVERSEAS_SELLER', + 'currency': values.get('currency').name, + }) + else: + alipay_tx_values.update({ + 'service': 'create_direct_pay_by_user', + 'payment_type': 1, + 'seller_email': self.alipay_seller_email, + }) + sign = self._build_sign(alipay_tx_values) + alipay_tx_values.update({ + 'sign_type': 'MD5', + 'sign': sign, + }) + return alipay_tx_values + + @api.multi + def alipay_form_generate_values(self, values): + values.update(self._get_alipay_tx_values(values)) + return values + + @api.multi + def alipay_get_form_action_url(self): + return self._get_alipay_urls(self.environment) + + +class PaymentTransaction(models.Model): + _inherit = 'payment.transaction' + + def _check_alipay_configuration(self, vals): + acquirer_id = int(vals.get('acquirer_id')) + acquirer = self.env['payment.acquirer'].sudo().browse(acquirer_id) + if acquirer and acquirer.provider == 'alipay' and acquirer.alipay_payment_method == 'express_checkout': + currency_id = int(vals.get('currency_id')) + if currency_id: + currency = self.env['res.currency'].sudo().browse(currency_id) + if currency and currency.name != 'CNY': + _logger.info("Only CNY currency is allowed for Alipay Express Checkout") + raise ValidationError(_(""" + Only transactions in Chinese Yuan (CNY) are allowed for Alipay Express Checkout.\n + If you wish to use another currency than CNY for your transactions, switch your + configuration to a Cross-border account on the Alipay payment acquirer in Odoo. + """)) + return True + + @api.model + def write(self, vals): + if vals.get('currency_id') or vals.get('acquirer_id'): + for payment in self: + check_vals = { + 'acquirer_id': vals.get('acquirer_id', payment.acquirer_id.id), + 'currency_id': vals.get('currency_id', payment.currency_id.id) + } + payment._check_alipay_configuration(check_vals) + return super(PaymentTransaction, self).write(vals) + + @api.model + def create(self, vals): + self._check_alipay_configuration(vals) + return super(PaymentTransaction, self).create(vals) + + # -------------------------------------------------- + # FORM RELATED METHODS + # -------------------------------------------------- + + @api.model + def _alipay_form_get_tx_from_data(self, data): + reference, txn_id, sign = data.get('reference'), data.get('trade_no'), data.get('sign') + if not reference or not txn_id: + _logger.info('Alipay: received data with missing reference (%s) or txn_id (%s)' % (reference, txn_id)) + raise ValidationError(_('Alipay: received data with missing reference (%s) or txn_id (%s)') % (reference, txn_id)) + + txs = self.env['payment.transaction'].search([('reference', '=', reference)]) + if not txs or len(txs) > 1: + error_msg = _('Alipay: received data for reference %s') % (reference) + logger_msg = 'Alipay: received data for reference %s' % (reference) + if not txs: + error_msg += _('; no order found') + logger_msg += '; no order found' + else: + error_msg += _('; multiple order found') + logger_msg += '; multiple order found' + _logger.info(logger_msg) + raise ValidationError(error_msg) + + # verify sign + sign_check = txs.acquirer_id._build_sign(data) + if sign != sign_check: + _logger.info('Alipay: invalid sign, received %s, computed %s, for data %s' % (sign, sign_check, data)) + raise ValidationError(_('Alipay: invalid sign, received %s, computed %s, for data %s') % (sign, sign_check, data)) + + return txs + + @api.multi + def _alipay_form_get_invalid_parameters(self, data): + invalid_parameters = [] + + if float_compare(float(data.get('total_fee', '0.0')), (self.amount + self.fees), 2) != 0: + invalid_parameters.append(('total_fee', data.get('total_fee'), '%.2f' % (self.amount + self.fees))) # mc_gross is amount + fees + if self.acquirer_id.alipay_payment_method == 'standard_checkout': + if data.get('currency') != self.currency_id.name: + invalid_parameters.append(('currency', data.get('currency'), self.currency_id.name)) + else: + if data.get('seller_email') != self.acquirer_id.alipay_seller_email: + invalid_parameters.append(('seller_email', data.get('seller_email'), self.acquirer_id.alipay_seller_email)) + return invalid_parameters + + @api.multi + def _alipay_form_validate(self, data): + if self.state in ['done']: + _logger.info('Alipay: trying to validate an already validated tx (ref %s)', self.reference) + return True + + status = data.get('trade_status') + res = { + 'acquirer_reference': data.get('trade_no'), + } + if status in ['TRADE_FINISHED', 'TRADE_SUCCESS']: + _logger.info('Validated Alipay payment for tx %s: set as done' % (self.reference)) + date_validate = fields.Datetime.now() + res.update(state='done', date=date_validate) + self.write(res) + self.execute_callback() + return True + elif status == 'TRADE_CLOSED': + _logger.info('Received notification for Alipay payment %s: set as Canceled' % (self.reference)) + res.update(state='cancel', state_message=data.get('close_reason', '')) + return self.write(res) + else: + error = 'Received unrecognized status for Alipay payment %s: %s, set as error' % (self.reference, status) + _logger.info(error) + res.update(state='error', state_message=error) + return self.write(res) diff --git a/addons/payment_alipay/static/description/icon.png b/addons/payment_alipay/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0aeddb172903549140097fadf62cd0f8f1b790d4 GIT binary patch literal 8567 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 CF)K0v literal 0 HcmV?d00001 diff --git a/addons/payment_alipay/tests/__init__.py b/addons/payment_alipay/tests/__init__.py new file mode 100644 index 00000000000..bf53571ae82 --- /dev/null +++ b/addons/payment_alipay/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_alipay diff --git a/addons/payment_alipay/tests/test_alipay.py b/addons/payment_alipay/tests/test_alipay.py new file mode 100644 index 00000000000..b735861b9c2 --- /dev/null +++ b/addons/payment_alipay/tests/test_alipay.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from werkzeug import urls +from lxml import objectify + +import odoo + +from odoo.tools import mute_logger +from odoo.addons.payment.models.payment_acquirer import ValidationError +from odoo.addons.payment.tests.common import PaymentAcquirerCommon +from odoo.addons.payment_alipay.controllers.main import AlipayController + +@odoo.tests.tagged('post_install', '-at_install', 'external', '-standard') +class AlipayTest(PaymentAcquirerCommon): + + def setUp(self): + super(AlipayTest, self).setUp() + + self.currency_yuan = self.env['res.currency'].search([('name', '=', 'CNY'), + '|', + ('active', '=', True), + ('active', '=', False)], limit=1) + self.alipay = self.env.ref('payment.payment_acquirer_alipay') + + def test_10_alipay_form_render(self): + base_url = self.env['ir.config_parameter'].get_param('web.base.url') + self.assertEqual(self.alipay.environment, 'test', 'test without test environment') + + # ---------------------------------------- + # Test: button direct rendering + # ---------------------------------------- + + # render the button + res = self.alipay.render( + 'test_ref0', 0.01, self.currency_euro.id, + values=self.buyer_values) + + form_values = { + '_input_charset': 'utf-8', + 'notify_url': urls.url_join(base_url, AlipayController._notify_url), + 'out_trade_no': 'SO12345-1', + 'partner': self.alipay.alipay_merchant_partner_id, + 'return_url': urls.url_join(base_url, AlipayController._return_url) + '?' + urls.url_encode({'redirect_url': '/payment/process'}), + 'subject': 'test_ref0', + 'total_fee': '0.01', + } + + if self.alipay.alipay_payment_method == 'standard_checkout': + form_values.update({ + 'service': 'create_forex_trade', + 'currency': 'EUR', + 'product_code': 'NEW_OVERSEAS_SELLER', + }) + else: + form_values.update({ + 'payment_type': '1', + 'seller_email': self.alipay.alipay_seller_email, + 'service': 'create_direct_pay_by_user' + }) + sign = self.alipay._build_sign(form_values) + + form_values.update({'sign': sign, 'sign_type': 'MD5'}) + # check form result + tree = objectify.fromstring(res) + + data_set = tree.xpath("//input[@name='data_set']") + self.assertEqual(len(data_set), 1, 'Alipay: Found %d "data_set" input instead of 1' % len(data_set)) + self.assertEqual(data_set[0].get('data-action-url'), 'https://openapi.alipaydev.com/gateway.do', 'alipay: wrong form POST url') + for form_input in tree.input: + if form_input.get('name') in ['submit', 'data_set', 'sign', 'out_trade_no']: + continue + self.assertEqual(form_input.get('value'), form_values[form_input.get('name')], 'alipay: wrong value for input %s: received %s instead of %s' % (form_input.get('name'), form_input.get('value'), form_values[form_input.get('name')])) + + def test_11_alipay_form_with_fees(self): + self.assertEqual(self.alipay.environment, 'test', 'test without test environment') + + # update acquirer: compute fees + self.alipay.write({ + 'fees_active': True, + 'fees_dom_fixed': 1.0, + 'fees_dom_var': 0.35, + 'fees_int_fixed': 1.5, + 'fees_int_var': 0.50, + }) + + # render the button + res = self.alipay.render( + 'test_ref0', 12.50, self.currency_euro.id, + values=self.buyer_values) + + tree = objectify.fromstring(res) + + data_set = tree.xpath("//input[@name='data_set']") + self.assertEqual(len(data_set), 1, 'alipay: Found %d "data_set" input instead of 1' % len(data_set)) + self.assertEqual(data_set[0].get('data-action-url'), 'https://openapi.alipaydev.com/gateway.do', 'alipay: wrong form POST url') + for form_input in tree.input: + if form_input.get('name') in ['total_fee']: + self.assertEqual(form_input.get('value'), '14.07', 'alipay: wrong computed fees') # total amount = amount + fees + + @mute_logger('odoo.addons.payment_alipay.models.payment', 'ValidationError') + def test_20_alipay_form_management(self): + self.alipay.alipay_payment_method = 'standard_checkout' + self._test_20_alipay_form_management() + self.alipay.alipay_payment_method = 'express_checkout' + self._test_20_alipay_form_management() + + def _test_20_alipay_form_management(self): + self.assertEqual(self.alipay.environment, 'test', 'test without test environment') + + # typical data posted by alipay after client has successfully paid + alipay_post_data = { + 'trade_no': '2017112321001003690200384552', + 'reference': 'test_ref_' + self.alipay.alipay_payment_method, + 'total_fee': 1.95, + 'trade_status': 'TRADE_CLOSED', + } + + if self.alipay.alipay_payment_method == 'express_checkout': + alipay_post_data.update({ + 'seller_email': self.alipay.alipay_seller_email, + }) + else: + alipay_post_data.update({ + 'currency': 'EUR', + }) + + alipay_post_data['sign'] = self.alipay._build_sign(alipay_post_data) + # should raise error about unknown tx + with self.assertRaises(ValidationError): + self.env['payment.transaction'].form_feedback(alipay_post_data, 'alipay') + + if self.alipay.alipay_payment_method == 'express_checkout': + currency = self.currency_yuan + else: + currency = self.currency_euro + + # create tx + tx = self.env['payment.transaction'].create({ + 'amount': 1.95, + 'acquirer_id': self.alipay.id, + 'currency_id': currency.id, + 'reference': 'test_ref_' + self.alipay.alipay_payment_method, + 'partner_name': 'Norbert Buyer', + 'partner_country_id': self.country_france.id + }) + + # validate tx + tx.form_feedback(alipay_post_data, 'alipay') + # check tx + self.assertEqual(tx.state, 'cancel', 'alipay: wrong state after receiving a valid pending notification') + self.assertEqual(tx.acquirer_reference, '2017112321001003690200384552', 'alipay: wrong txn_id after receiving a valid pending notification') + + # update tx + tx.write({'acquirer_reference': False}) + + # update notification from alipay should not go through since it has already been set as 'done' + if self.alipay.alipay_payment_method == 'standard_checkout': + alipay_post_data['trade_status'] = 'TRADE_FINISHED' + else: + alipay_post_data['trade_status'] = 'TRADE_SUCCESS' + alipay_post_data['sign'] = self.alipay._build_sign(alipay_post_data) + # validate tx + tx.form_feedback(alipay_post_data, 'alipay') + # check tx + self.assertEqual(tx.acquirer_reference, '2017112321001003690200384552', 'alipay: notification should not go throught since it has already been validated') + + # this time it should go through since the transaction is not validated yet + tx.write({'state': 'draft', 'acquirer_reference': False}) + tx.form_feedback(alipay_post_data, 'alipay') + self.assertEqual(tx.state, 'done', 'alipay: wrong state after receiving a valid pending notification') + self.assertEqual(tx.acquirer_reference, '2017112321001003690200384552', 'alipay: wrong txn_id after receiving a valid pending notification') + + @mute_logger('odoo.addons.payment_alipay.models.payment', 'ValidationError') + def test_30_alipay_bad_configuration(self): + self.alipay.alipay_payment_method = 'express_checkout' + + # should raise error since `express_checkout` must only be used with CNY currency + with self.assertRaises(ValidationError): + # create tx + tx = self.env['payment.transaction'].create({ + 'acquirer_id': self.alipay.id, + 'amount': 4, + 'currency_id': self.currency_euro.id, + 'reference': 'test_ref_2', + 'partner_country_id': self.country_france.id + }) + + tx = self.env['payment.transaction'].create({ + 'acquirer_id': self.alipay.id, + 'amount': 4, + 'currency_id': self.currency_yuan.id, + 'reference': 'test_ref_2', + 'partner_country_id': self.country_france.id + }) \ No newline at end of file diff --git a/addons/payment_alipay/views/alipay_views.xml b/addons/payment_alipay/views/alipay_views.xml new file mode 100644 index 00000000000..3289f4c775c --- /dev/null +++ b/addons/payment_alipay/views/alipay_views.xml @@ -0,0 +1,18 @@ + + + + payment.acquirer.view.form.inherit.payment.alipay + payment.acquirer + + + + + + + + + + + + + diff --git a/addons/payment_alipay/views/payment_alipay_templates.xml b/addons/payment_alipay/views/payment_alipay_templates.xml new file mode 100644 index 00000000000..0f672861074 --- /dev/null +++ b/addons/payment_alipay/views/payment_alipay_templates.xml @@ -0,0 +1,22 @@ + + + +