PaymentCycle.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. from Ansjer.config import PAYPAL_CRD,SERVER_DOMAIN,SERVER_DOMAIN_SSL,PAYPAL_WEB_HOOK_ID
  2. from Model.models import PayCycleConfigModel,Order_Model, Store_Meal, UID_Bucket, PromotionRuleModel, Unused_Uid_Meal,Device_Info, CouponModel, Order_Model
  3. from Service.CommonService import CommonService
  4. from django.http import JsonResponse, HttpResponseRedirect, HttpResponse
  5. import requests
  6. import time
  7. import sys
  8. from Object.TokenObject import TokenObject
  9. from Object.UidTokenObject import UidTokenObject
  10. from Object.ResponseObject import ResponseObject
  11. import paypalrestsdk
  12. from paypalrestsdk import BillingAgreement
  13. from django.views.generic.base import View
  14. from django.db import transaction
  15. from Controller import CloudStorage
  16. from django.db.models import Q, F, Count
  17. from paypalrestsdk.notifications import WebhookEvent
  18. import logging
  19. import json
  20. from paypalrestsdk import BillingPlan
  21. #周期扣款相关
  22. class Paypal:
  23. #检查是否有重复订阅
  24. def checkSubscriptions(userID,uid,rank):
  25. hasOrder = Order_Model.objects.filter(UID=uid,rank=rank)
  26. hasOrder = hasOrder.filter(~Q(agreement_id='')).values('agreement_id','orderID').order_by('-addTime')[0:1]
  27. if not hasOrder.exists():
  28. return True
  29. paypalrestsdk.configure(PAYPAL_CRD)
  30. billing_agreement = paypalrestsdk.BillingAgreement.find(hasOrder[0]['agreement_id'])
  31. if billing_agreement.state == 'Active':
  32. return False
  33. return True
  34. def subscriptions(store_info,lang,orderID,price):
  35. cycle_config = PayCycleConfigModel.objects.filter(id=store_info['cycle_config_id']).values()
  36. if not cycle_config:
  37. return False
  38. cal_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  39. if lang != 'cn':
  40. cal_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  41. return_url = "{SERVER_DOMAIN_SSL}payCycle/paypalCycleReturn?lang={lang}". \
  42. format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL, lang=lang)
  43. # call_sub_url = "http://binbin.uicp.vip/cloudstorage/dopaypalcallback?orderID={orderID}".format(
  44. # SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL, orderID=orderID)
  45. # exit(price)
  46. BillingPlan = {
  47. "description": orderID,
  48. "merchant_preferences": {
  49. "auto_bill_amount": "YES",
  50. "cancel_url": cal_url, # 取消协议url
  51. "initial_fail_amount_action": "CANCEL",
  52. "max_fail_attempts": "1", # 允许的最大失败付款尝试次数
  53. "return_url": return_url, # 客户批准协议的url
  54. # "notify_url": "http://www.notify.com", #通知客户协议已创建的 URL。只读并保留供将来使用。
  55. "setup_fee": {
  56. "currency": store_info['currency'],
  57. "value": price,
  58. }
  59. },
  60. "name": store_info['lang__content'],
  61. "payment_definitions": [
  62. {
  63. "amount": {
  64. "currency": store_info['currency'],
  65. "value": store_info['price']
  66. },
  67. # "charge_models": [
  68. # {
  69. # "amount": {
  70. # "currency": "USD",
  71. # "value": "20"
  72. # },
  73. # "type": "TAX" #税金
  74. # }
  75. # ],
  76. "cycles": cycle_config[0]['cycles'],
  77. "frequency": cycle_config[0]['frequency'],
  78. "frequency_interval": cycle_config[0]['frequencyInterval'],
  79. "name": store_info['lang__title'],
  80. "type": "REGULAR"
  81. },
  82. ],
  83. "type": "INFINITE",
  84. }
  85. paypalrestsdk.configure(PAYPAL_CRD)
  86. billing_plan = paypalrestsdk.BillingPlan(BillingPlan)
  87. if billing_plan.create():
  88. billing_plan.activate() # 激活
  89. plan_id = billing_plan.id
  90. else:
  91. print(billing_plan.error)
  92. return False
  93. now_time = int(time.time())
  94. if cycle_config[0]['frequency'] == "DAY":
  95. start_date_timestamp = now_time + 86400 - 3600 # 下次扣款为明天,提前1个小时扣款
  96. start_date_str = CommonService.timestamp_to_str(start_date_timestamp, "%Y-%m-%dT%H:%M:%SZ")
  97. elif cycle_config[0]['frequency'] == "MONTH":
  98. start_date_timestamp = CommonService.calcMonthLater(1, now_time) - (5 * 86400) #下次扣款为下个月提前5天扣款
  99. start_date_str = CommonService.timestamp_to_str(start_date_timestamp, "%Y-%m-%dT%H:%M:%SZ")
  100. #订阅
  101. billingAgreement = {
  102. "name": store_info['lang__content'],
  103. "description": orderID,
  104. "start_date": start_date_str,
  105. "plan": {
  106. "id": plan_id
  107. },
  108. "payer": {
  109. "payment_method": "paypal"
  110. },
  111. }
  112. billing_agreement = paypalrestsdk.BillingAgreement(billingAgreement)
  113. # print(billing_agreement.create())
  114. if billing_agreement.create():
  115. for link in billing_agreement.links:
  116. if link.rel == "approval_url":
  117. return {"plan_id": plan_id, "url": link.href}
  118. else:
  119. print(billing_agreement.error)
  120. return False
  121. class PaypalCycleNotify(View):
  122. def get(self, request, *args, **kwargs):
  123. request.encoding = 'utf-8'
  124. operation = kwargs.get('operation')
  125. return self.validation(request.GET, request, operation)
  126. def post(self, request, *args, **kwargs):
  127. request.encoding = 'utf-8'
  128. operation = kwargs.get('operation')
  129. return self.validation(request.POST, request, operation)
  130. def validation(self, request_dict, request, operation):
  131. response = ResponseObject()
  132. if operation is None:
  133. return response.json(444, 'error path')
  134. elif operation == 'paypalCycleReturn': # paypal成功订阅回调
  135. return self.do_paypal_cycle_return(request_dict, response)
  136. elif operation == 'paypalCycleNotify': # paypal 周期付款回调
  137. return self.do_paypal_webhook_notify(request_dict,request, response)
  138. elif operation == 'test': # paypal 周期付款回调
  139. return self.do_test(request_dict,request, response)
  140. def do_paypal_cycle_return(self, request_dict, response):
  141. lang = request_dict.get('lang', 'en')
  142. token = request_dict.get('token',None)
  143. logger = logging.getLogger('info')
  144. logger.info('---------进入paypay自动续费异步回调')
  145. logger.info(request_dict)
  146. paypalrestsdk.configure(PAYPAL_CRD)
  147. billing_agreement = paypalrestsdk.BillingAgreement()
  148. billing_agreement_response = billing_agreement.execute(token)
  149. if billing_agreement_response.error:
  150. print(billing_agreement_response.error)
  151. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  152. if lang != 'cn':
  153. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  154. return HttpResponseRedirect(red_url)
  155. orderID = billing_agreement_response.description
  156. agreement_id = billing_agreement_response.id
  157. promotion_rule_id = ''
  158. order_qs = Order_Model.objects.filter(orderID=orderID, status=0)
  159. order_list = order_qs.values("UID", "channel", "commodity_code", "rank", "isSelectDiscounts",
  160. "userID__userID",
  161. "userID__username", 'coupon_id')
  162. try:
  163. if not orderID:
  164. print("not orderID")
  165. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  166. if lang != 'cn':
  167. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  168. return HttpResponseRedirect(red_url)
  169. userid = order_list[0]['userID__userID']
  170. username = order_list[0]['userID__username']
  171. UID = order_list[0]['UID']
  172. channel = order_list[0]['channel']
  173. rank = order_list[0]['rank']
  174. smqs = Store_Meal.objects.filter(id=rank). \
  175. values("day", "bucket_id", "bucket__storeDay", "expire")
  176. bucketId = smqs[0]['bucket_id']
  177. if not smqs.exists():
  178. print("not smqs")
  179. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  180. if lang != 'cn':
  181. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  182. return HttpResponseRedirect(red_url)
  183. # ##
  184. ubqs = UID_Bucket.objects.filter(uid=UID).values("id", "bucket_id", "bucket__storeDay", "bucket__region",
  185. "endTime", "use_status")
  186. expire = smqs[0]['expire']
  187. if order_list[0]['isSelectDiscounts'] == 1:
  188. expire = smqs[0]['expire'] * 2
  189. # 是否有促销
  190. nowTime = int(time.time())
  191. promotion = PromotionRuleModel.objects.filter(status=1, startTime__lte=nowTime,
  192. endTime__gte=nowTime).values('id','ruleConfig')
  193. if promotion.exists():
  194. promotion_rule_id = promotion[0]['id']
  195. expire = expire * 2
  196. with transaction.atomic():
  197. if ubqs.exists():
  198. ubq = ubqs[0]
  199. if ubq['use_status'] == 1 and ubq['bucket_id'] == bucketId: #套餐使用中并且相同套餐叠加过期时间
  200. endTime = CommonService.calcMonthLater(expire, ubq['endTime'])
  201. UID_Bucket.objects.filter(id=ubq['id']).update \
  202. (uid=UID, channel=channel, bucket_id=bucketId,
  203. endTime=endTime, updateTime=nowTime)
  204. else: #已过期或者不相同的套餐加入未使用的关联套餐表
  205. has_unused = Unused_Uid_Meal.objects.filter(uid=UID, bucket_id=bucketId).values("id")
  206. nums = 2 if order_list[0]['isSelectDiscounts'] == 1 else 1
  207. if promotion.exists():
  208. nums = nums + 1
  209. if has_unused.exists():
  210. Unused_Uid_Meal.objects.filter(id=has_unused[0]['id']).update(num=F('num') + nums)
  211. else:
  212. Unused_Uid_Meal.objects.create(uid=UID,channel=channel,addTime=nowTime,num=nums,
  213. expire=smqs[0]['expire'],bucket_id=bucketId)
  214. UID_Bucket.objects.filter(id=ubq['id']).update(has_unused=1)
  215. uid_bucket_id = ubq['id']
  216. else:
  217. endTime = CommonService.calcMonthLater(expire)
  218. ub_cqs = UID_Bucket.objects.create \
  219. (uid=UID, channel=channel, bucket_id=bucketId, endTime=endTime, addTime=nowTime,
  220. updateTime=nowTime,use_status=1)
  221. uid_bucket_id = ub_cqs.id
  222. dvq = Device_Info.objects.filter(UID=UID, vodPrimaryUserID='', vodPrimaryMaster='')
  223. if dvq.exists():
  224. dvq_set_update_dict = {
  225. 'vodPrimaryUserID': userid,
  226. 'vodPrimaryMaster': username
  227. }
  228. dvq.update(**dvq_set_update_dict)
  229. # uid_main_exist = UIDMainUser.objects.filter(UID=UID)
  230. # if not uid_main_exist.exists():
  231. # uid_main_dict = {
  232. # 'UID': UID,
  233. # 'user_id': userid
  234. # }
  235. # UIDMainUser.objects.create(**uid_main_dict)
  236. # 核销coupon
  237. if order_list[0]['coupon_id']:
  238. CouponModel.objects.filter(id=order_list[0]['coupon_id']).update(use_status=2)
  239. order_qs.update(status=1, updTime=nowTime, uid_bucket_id=uid_bucket_id,
  240. promotion_rule_id=promotion_rule_id,agreement_id=agreement_id)
  241. datetime = time.strftime("%Y-%m-%d", time.localtime())
  242. sys_msg_text_list = ['温馨提示:尊敬的客户,您的' + UID + '设备在' + datetime + '已成功订阅云存套餐',
  243. 'Dear customer,you already subscribed the cloud storage package successfully for device ' + UID + ' on ' + time.strftime(
  244. "%b %dth,%Y", time.localtime())]
  245. CloudStorage.CloudStorageView.do_vod_msg_Notice(self, UID, channel, userid, lang, sys_msg_text_list, 'SMS_219738485')
  246. # return response.json(0)
  247. red_url = "{SERVER_DOMAIN_SSL}web/paid2/success.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  248. if lang != 'cn':
  249. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_success.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  250. return HttpResponseRedirect(red_url)
  251. except Exception as e:
  252. print(repr(e))
  253. if order_qs:
  254. order_qs.update(status=10, promotion_rule_id=promotion_rule_id)
  255. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  256. if lang != 'cn':
  257. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  258. return HttpResponseRedirect(red_url)
  259. def do_paypal_webhook_notify(self, request_dict, request, response):
  260. logger = logging.getLogger('info')
  261. json_agreement_str = request.body.decode("utf-8")
  262. json_obj = json.loads(json_agreement_str)
  263. header = request.META
  264. paypal_body = json_obj.get('resource')
  265. billing_agreement_id = paypal_body.get('billing_agreement_id')
  266. amount = paypal_body.get('amount')
  267. if not billing_agreement_id:
  268. return HttpResponse('success')
  269. transmission_id = header.get('HTTP_PAYPAL_TRANSMISSION_ID',None)
  270. transmission_time = header.get('HTTP_PAYPAL_TRANSMISSION_TIME',None)
  271. cert_url = header.get('HTTP_PAYPAL_CERT_URL',None)
  272. transmission_sig = header.get('HTTP_PAYPAL_TRANSMISSION_SIG',None)
  273. auth_algo = header.get('HTTP_PAYPAL_AUTH_ALGO',None)
  274. resource_type = json_obj.get('resource_type')
  275. # self.get_plan_desc('P-4CG284532S612303METMEINY')
  276. if resource_type == 'sale' and paypal_body.get('state') == 'completed':
  277. paypalrestsdk.configure(PAYPAL_CRD)
  278. response = paypalrestsdk.WebhookEvent.verify(
  279. transmission_id, transmission_time, PAYPAL_WEB_HOOK_ID, json_agreement_str, cert_url, transmission_sig, auth_algo)
  280. logger.info('-----------------------verify')
  281. logger.info(response)
  282. if response:
  283. try:
  284. agreement_id = paypal_body.get('billing_agreement_id')
  285. billing_agreement = paypalrestsdk.BillingAgreement.find(agreement_id)
  286. # 订阅续费订单(如果完成周期数`不是0, 则说明是续费订单,)
  287. if billing_agreement.agreement_details.cycles_completed == '0':
  288. return HttpResponse('success')
  289. oldOrderID = billing_agreement.description
  290. order_qs = Order_Model.objects.filter(orderID=oldOrderID, status=1)
  291. if not order_qs:
  292. return HttpResponse('fail')
  293. order_list = order_qs.values("UID", "channel", "commodity_code", "rank", "isSelectDiscounts",
  294. "userID__userID","uid_bucket_id",
  295. "userID__username",'plan_id','addTime','desc','payType','currency','commodity_type','updTime')
  296. nowTime = int(time.time())
  297. if order_list[0]['addTime'] + 9200 > nowTime: #避免续费订单重复支付
  298. logger.info('-----------------------paypal重复异步回调')
  299. return HttpResponse('success')
  300. userid = order_list[0]['userID__userID']
  301. username = order_list[0]['userID__username']
  302. UID = order_list[0]['UID']
  303. channel = order_list[0]['channel']
  304. rank = order_list[0]['rank']
  305. smqs = Store_Meal.objects.filter(id=rank). \
  306. values("day", "bucket_id", "bucket__storeDay", "expire")
  307. bucketId = smqs[0]['bucket_id']
  308. if not smqs.exists():
  309. return HttpResponse('fail')
  310. # ##
  311. ubqs = UID_Bucket.objects.filter(uid=UID).values("id", "bucket_id", "bucket__storeDay",
  312. "bucket__region",
  313. "endTime", "use_status")
  314. expire = smqs[0]['expire']
  315. # if order_list[0]['isSelectDiscounts'] == 1:
  316. # expire = smqs[0]['expire'] * 2
  317. # 是否有促销
  318. # nowTime = int(time.time())
  319. # promotion = PromotionRuleModel.objects.filter(status=1, startTime__lte=nowTime,
  320. # endTime__gte=nowTime).values('id', 'ruleConfig')
  321. # if promotion.exists():
  322. # promotion_rule_id = promotion[0]['id']
  323. # expire = expire * 2
  324. with transaction.atomic():
  325. if ubqs.exists():
  326. ubq = ubqs[0]
  327. if ubq['use_status'] == 1 and ubq['bucket_id'] == bucketId: # 套餐使用中并且相同套餐叠加过期时间
  328. endTime = CommonService.calcMonthLater(expire, ubq['endTime'])
  329. UID_Bucket.objects.filter(id=ubq['id']).update \
  330. (uid=UID, channel=channel, bucket_id=bucketId,
  331. endTime=endTime, updateTime=nowTime)
  332. else: # 已过期或者不相同的套餐加入未使用的关联套餐表
  333. has_unused = Unused_Uid_Meal.objects.filter(uid=UID, bucket_id=bucketId).values("id")
  334. # nums = 2 if order_list[0]['isSelectDiscounts'] == 1 else 1
  335. # if promotion.exists():
  336. nums = 1
  337. if has_unused.exists():
  338. Unused_Uid_Meal.objects.filter(id=has_unused[0]['id']).update(num=F('num') + nums)
  339. else:
  340. Unused_Uid_Meal.objects.create(uid=UID, channel=channel, addTime=nowTime, num=nums,
  341. expire=smqs[0]['expire'], bucket_id=bucketId)
  342. UID_Bucket.objects.filter(id=ubq['id']).update(has_unused=1)
  343. uid_bucket_id = ubq['id']
  344. else:
  345. endTime = CommonService.calcMonthLater(expire)
  346. ub_cqs = UID_Bucket.objects.create \
  347. (uid=UID, channel=channel, bucket_id=bucketId, endTime=endTime, addTime=nowTime,
  348. updateTime=nowTime, use_status=1)
  349. uid_bucket_id = ub_cqs.id
  350. dvq = Device_Info.objects.filter(UID=UID, vodPrimaryUserID='', vodPrimaryMaster='')
  351. if dvq.exists():
  352. dvq_set_update_dict = {
  353. 'vodPrimaryUserID': userid,
  354. 'vodPrimaryMaster': username
  355. }
  356. dvq.update(**dvq_set_update_dict)
  357. # uid_main_exist = UIDMainUser.objects.filter(UID=UID)
  358. # if not uid_main_exist.exists():
  359. # uid_main_dict = {
  360. # 'UID': UID,
  361. # 'user_id': userid
  362. # }
  363. # UIDMainUser.objects.create(**uid_main_dict)
  364. orderID = CommonService.createOrderID()
  365. Order_Model.objects.create(orderID=orderID, UID=UID, channel=channel, userID_id=userid,
  366. desc=order_list[0]['desc'], payType=order_list[0]['payType'], payTime=nowTime,
  367. price=amount.get('total'), currency=order_list[0]['currency'], addTime=nowTime, updTime=nowTime,
  368. pay_url='', isSelectDiscounts=0,
  369. commodity_code=order_list[0]['commodity_code'], commodity_type=order_list[0]['commodity_type'],
  370. rank_id=rank, paymentID='', coupon_id='',uid_bucket_id=uid_bucket_id,status=1,
  371. agreement_id=agreement_id,plan_id=order_list[0]['plan_id'], ai_rank_id=1)
  372. datetime = time.strftime("%Y-%m-%d", time.localtime())
  373. sys_msg_text_list = ['温馨提示:尊敬的客户,您的' + UID + '设备在' + datetime + '已成功续订云存套餐',
  374. 'Dear customer,you already subscribed the cloud storage package successfully for device ' + UID + ' on ' + time.strftime(
  375. "%b %dth,%Y", time.localtime())]
  376. if order_list[0]['payType'] == 1:
  377. lang = 'en'
  378. else:
  379. lang = 'cn'
  380. CloudStorage.CloudStorageView.do_vod_msg_Notice(self, UID, channel, userid, lang,
  381. sys_msg_text_list, 'SMS_219738485')
  382. logger.info('-----------------------result')
  383. logger.info('success')
  384. #更新agreement
  385. billing_agreement_update_attributes = [
  386. {
  387. "op": "replace",
  388. "path": "/",
  389. "value": {
  390. "description": orderID,
  391. }
  392. }
  393. ]
  394. billing_agreement.replace(billing_agreement_update_attributes)
  395. return HttpResponse('success')
  396. except Exception as e:
  397. print(e)
  398. logger.info('-----------------------paypal异步回调失败')
  399. logger.info(e)
  400. return HttpResponse('fail')
  401. return HttpResponse('fail')
  402. def do_test(self, request_dict, request, response):
  403. paypalrestsdk.configure(PAYPAL_CRD)
  404. billing_agreement = paypalrestsdk.BillingAgreement
  405. billing_agreement = billing_agreement.find("I-17SRPP7TS71U")
  406. print("Got Billing Agreement Details for Billing Agreement[%s]" % (
  407. billing_agreement.id))
  408. exit(billing_agreement)
  409. #normal_pay
  410. # json_str = '{"id":"WH-8SU832847J141682K-0FF265943E8692615","event_version":"1.0","create_time":"2022-01-10T06:31:49.863Z","resource_type":"sale","event_type":"PAYMENT.SALE.COMPLETED","summary":"Payment completed for $ 0.02 USD","resource":{"amount":{"total":"0.02","currency":"USD","details":{"subtotal":"0.02"}},"payment_mode":"INSTANT_TRANSFER","create_time":"2022-01-10T06:31:45Z","transaction_fee":{"currency":"USD","value":"0.02"},"parent_payment":"PAYID-MHN5E5Y1RH70069CT417990V","update_time":"2022-01-10T06:31:45Z","protection_eligibility_type":"ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE","application_context":{"related_qualifiers":[{"id":"0FJ93448LU7282046","type":"CART"}]},"protection_eligibility":"ELIGIBLE","links":[{"method":"GET","rel":"self","href":"https://api.sandbox.paypal.com/v1/payments/sale/6N498138TH641260G"},{"method":"POST","rel":"refund","href":"https://api.sandbox.paypal.com/v1/payments/sale/6N498138TH641260G/refund"},{"method":"GET","rel":"parent_payment","href":"https://api.sandbox.paypal.com/v1/payments/payment/PAYID-MHN5E5Y1RH70069CT417990V"}],"id":"6N498138TH641260G","state":"completed","invoice_number":""},"links":[{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-8SU832847J141682K-0FF265943E8692615","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-8SU832847J141682K-0FF265943E8692615/resend","rel":"resend","method":"POST"}]}'
  411. json_agreement_str = '{"id":"WH-9BE23393R5338163R-48P08088YL173821A","event_version":"1.0","create_time":"2022-01-10T10:27:42.925Z","resource_type":"sale","event_type":"PAYMENT.SALE.COMPLETED","summary":"Payment completed for $ 0.02 USD","resource":{"billing_agreement_id":"I-K8PCK2NJC6N6","amount":{"total":"0.02","currency":"USD","details":{"subtotal":"0.02"}},"payment_mode":"INSTANT_TRANSFER","update_time":"2022-01-10T10:27:19Z","create_time":"2022-01-10T10:27:19Z","protection_eligibility_type":"ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE","transaction_fee":{"currency":"USD","value":"0.02"},"protection_eligibility":"ELIGIBLE","links":[{"method":"GET","rel":"self","href":"https://api.sandbox.paypal.com/v1/payments/sale/4H259512Y67055105"},{"method":"POST","rel":"refund","href":"https://api.sandbox.paypal.com/v1/payments/sale/4H259512Y67055105/refund"}],"id":"4H259512Y67055105","state":"completed","invoice_number":""},"links":[{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-9BE23393R5338163R-48P08088YL173821A","rel":"self","method":"GET"},{"href":"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-9BE23393R5338163R-48P08088YL173821A/resend","rel":"resend","method":"POST"}]}'
  412. header = {'wsgi.file_wrapper': '<class gunicorn.http.wsgi.FileWrapper>', 'wsgi.version': '(1, 0)', 'HTTP_CONNECTION': 'close', 'wsgi.url_scheme': 'http', 'HTTP_PAYPAL_CERT_URL': 'https://api.sandbox.paypal.com/v1/notifications/certs/CERT-360caa42-fca2a594-7a8abba8', 'HTTP_PAYPAL_TRANSMISSION_ID': '022fbbc0-7139-11ec-afa1-0114a54fc1fc', 'SERVER_NAME': '0.0.0.0', 'HTTP_CORRELATION_ID': 'be4c80f0a6c05', 'REMOTE_ADDR': '127.0.0.1', 'HTTP_PAYPAL_TRANSMISSION_SIG': 'IM3Xwyjw5YUgBKPsgyjPdMAh6DSFTtqdwy8zbJBXBhFyB77B6mEqnRfhtEgwwBhag6HsStmKBGIScFhs5Nuraru7DbT4+7Tu5fNx3oQIHeHtR/FYZoQcv86bjZ9cq+Xo04HmhUfgBAsSetS+CuY5TsN60d1m8Hld1MTDjk1UuSbk8HA3dBLiMzWT7wUw3/SUau/C7TtLnWGmdJlkFne+b/5s0+HsuXn3wQQCDIHO0sBMBo72NdlyMlLIunSdoEJ61pKi2U1jQ6qqe/59IrY2q4ufx9D6JZ4bUB6z3NQZ+Gm7zrlKabT6HkVovLJbuBgRgRWWUoY02CuVXZ9w4AzVNQ==', 'REMOTE_PORT': '58060', 'HTTP_ACCEPT': '*/*', 'CONTENT_TYPE': 'application/json', 'HTTP_USER_AGENT': 'PayPal/AUHR-214.0-56015767', 'SCRIPT_NAME': '', 'HTTP_X_FORWARDED_FOR': '173.0.80.117', 'HTTP_HOST': 'test.zositechc.cn:443', 'wsgi.multiprocess': True, 'SERVER_PROTOCOL': 'HTTP/1.0', 'PATH_INFO': '/payCycle/paypalCycleNotify', 'SERVER_SOFTWARE': 'gunicorn/19.7.1', 'wsgi.input': '<gunicorn.http.body.Body object at 0x7fb966cddfd0>', 'REQUEST_METHOD': 'POST', 'wsgi.errors': '<gunicorn.http.wsgi.WSGIErrorsWrapper object at 0x7fb966cdda90>', 'CONTENT_LENGTH': '1226', 'wsgi.run_once': False, 'HTTP_X_B3_SPANID': 'e8ede80526720f95', 'HTTP_PAYPAL_AUTH_ALGO': 'SHA256withRSA', 'QUERY_STRING': '', 'HTTP_PAYPAL_TRANSMISSION_TIME': '2022-01-09T10:43:40Z', 'wsgi.multithread': False, 'HTTP_HTTP_X_FORWARDED_FOR': '173.0.80.117', 'HTTP_X_REAL_IP': '173.0.80.117', 'RAW_URI': '/payCycle/paypalCycleNotify', 'HTTP_PAYPAL_AUTH_VERSION': 'v2', 'gunicorn.socket': '<socket.socket fd=51, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(127.0.0.1, 8082), raddr=(127.0.0.1, 58060)>', 'SERVER_PORT': '8082'}
  413. json_obj = json.loads(json_agreement_str)
  414. paypal_body = json_obj.get('resource')
  415. billing_agreement_id = paypal_body.get('billing_agreement_id')
  416. amount = paypal_body.get('amount')
  417. if not billing_agreement_id:
  418. return HttpResponse('success')
  419. nowTime = int(time.time())
  420. transmission_id = header.get('HTTP_PAYPAL_TRANSMISSION_ID',None)
  421. transmission_time = header.get('HTTP_PAYPAL_TRANSMISSION_TIME',None)
  422. webhook_id = '6TS30758D98835230'
  423. cert_url = header.get('HTTP_PAYPAL_CERT_URL',None)
  424. transmission_sig = header.get('HTTP_PAYPAL_TRANSMISSION_SIG',None)
  425. auth_algo = header.get('HTTP_PAYPAL_AUTH_ALGO',None)
  426. resource_type = json_obj.get('resource_type')
  427. # return HttpResponse(resource_type)
  428. transmission_id = 'f42509f0-71ff-11ec-a473-05e6d85b61e7'
  429. transmission_time = '2022-01-10T10:27:46Z'
  430. webhook_id = '3J888119TD851704M'
  431. cert_url = 'https://api.sandbox.paypal.com/v1/notifications/certs/CERT-360caa42-fca2a594-7a8abba8'
  432. transmission_sig = 'R6sBDhsoq5+FRQHWe+8tSeKJMlRDnt9F2SlWlWVVEfDu9mvQ0zKl74bwcN1zMbvH4o7fWVNbwkcPW70/t4O0YBsj9BcMwL8hDxcuWuHp20RBzaI2dlBpdPEke19wr/fhJKGZCDYuvptV2RJGCSePBn3gKs7hkY5ribELPDqHuajlgVxMmoXm/+CHrMmPo6gSGgTuEMzEn4/ENuj3uJoCkcYqsFx3tUHg6eakUvQ+vYAyflRx9hX7QXEQHp15PWLgGzHkm9zGmnX6YoG5keo5MbJEYh9LfHJjmHmHVErvOtHebJxfTEDZwGoqw+WHr3KqnP4L1gaUj7XIXsQzbiFTBg=='
  433. auth_algo = 'SHA256withRSA'
  434. resource_type = 'sale'
  435. self.get_plan_desc('P-4CG284532S612303METMEINY')
  436. if resource_type == 'sale' and paypal_body.get('state') == 'completed':
  437. # paypalrestsdk.configure(PAYPAL_CRD)
  438. # response = paypalrestsdk.WebhookEvent.verify(
  439. # transmission_id, transmission_time, webhook_id, json_agreement_str, cert_url, transmission_sig, auth_algo)
  440. response = True
  441. if response:
  442. try:
  443. agreement_id = paypal_body.get('billing_agreement_id')
  444. order_qs = Order_Model.objects.filter(agreement_id=agreement_id, status=1)
  445. if not order_qs:
  446. return HttpResponse('failss')
  447. order_list = order_qs.values("UID", "channel", "commodity_code", "rank", "isSelectDiscounts",
  448. "userID__userID","uid_bucket_id",
  449. "userID__username",'plan_id','addTime','desc','payType','currency','commodity_type')
  450. plan_id = order_list[0]['plan_id']
  451. # plan_cycle = self.get_plan_desc(plan_id)
  452. # 订阅续费订单(如果查到的本地订单已经付过了且包中的完成周期数`不是0, 则说明是续费订单, 本地可以新建一个订单标记是续费的)
  453. nowTime = int(time.time())
  454. if(order_list[0]['addTime']+600 > nowTime):
  455. return HttpResponse('success')
  456. userid = order_list[0]['userID__userID']
  457. username = order_list[0]['userID__username']
  458. UID = order_list[0]['UID']
  459. channel = order_list[0]['channel']
  460. rank = order_list[0]['rank']
  461. smqs = Store_Meal.objects.filter(id=rank). \
  462. values("day", "bucket_id", "bucket__storeDay", "expire")
  463. bucketId = smqs[0]['bucket_id']
  464. if not smqs.exists():
  465. return HttpResponse('fail')
  466. # ##
  467. ubqs = UID_Bucket.objects.filter(uid=UID).values("id", "bucket_id", "bucket__storeDay",
  468. "bucket__region",
  469. "endTime", "use_status")
  470. expire = smqs[0]['expire']
  471. # if order_list[0]['isSelectDiscounts'] == 1:
  472. # expire = smqs[0]['expire'] * 2
  473. # 是否有促销
  474. # nowTime = int(time.time())
  475. # promotion = PromotionRuleModel.objects.filter(status=1, startTime__lte=nowTime,
  476. # endTime__gte=nowTime).values('id', 'ruleConfig')
  477. # if promotion.exists():
  478. # promotion_rule_id = promotion[0]['id']
  479. # expire = expire * 2
  480. with transaction.atomic():
  481. if ubqs.exists():
  482. ubq = ubqs[0]
  483. if ubq['use_status'] == 1 and ubq['bucket_id'] == bucketId: # 套餐使用中并且相同套餐叠加过期时间
  484. endTime = CommonService.calcMonthLater(expire, ubq['endTime'])
  485. UID_Bucket.objects.filter(id=ubq['id']).update \
  486. (uid=UID, channel=channel, bucket_id=bucketId,
  487. endTime=endTime, updateTime=nowTime)
  488. else: # 已过期或者不相同的套餐加入未使用的关联套餐表
  489. has_unused = Unused_Uid_Meal.objects.filter(uid=UID, bucket_id=bucketId).values("id")
  490. # nums = 2 if order_list[0]['isSelectDiscounts'] == 1 else 1
  491. # if promotion.exists():
  492. nums = 1
  493. if has_unused.exists():
  494. Unused_Uid_Meal.objects.filter(id=has_unused[0]['id']).update(num=F('num') + nums)
  495. else:
  496. Unused_Uid_Meal.objects.create(uid=UID, channel=channel, addTime=nowTime, num=nums,
  497. expire=smqs[0]['expire'], bucket_id=bucketId)
  498. UID_Bucket.objects.filter(id=ubq['id']).update(has_unused=1)
  499. uid_bucket_id = ubq['id']
  500. else:
  501. endTime = CommonService.calcMonthLater(expire)
  502. ub_cqs = UID_Bucket.objects.create \
  503. (uid=UID, channel=channel, bucket_id=bucketId, endTime=endTime, addTime=nowTime,
  504. updateTime=nowTime, use_status=1)
  505. uid_bucket_id = ub_cqs.id
  506. dvq = Device_Info.objects.filter(UID=UID, vodPrimaryUserID='', vodPrimaryMaster='')
  507. if dvq.exists():
  508. dvq_set_update_dict = {
  509. 'vodPrimaryUserID': userid,
  510. 'vodPrimaryMaster': username
  511. }
  512. dvq.update(**dvq_set_update_dict)
  513. # uid_main_exist = UIDMainUser.objects.filter(UID=UID)
  514. # if not uid_main_exist.exists():
  515. # uid_main_dict = {
  516. # 'UID': UID,
  517. # 'user_id': userid
  518. # }
  519. # UIDMainUser.objects.create(**uid_main_dict)
  520. orderID = CommonService.createOrderID()
  521. Order_Model.objects.create(orderID=orderID, UID=UID, channel=channel, userID_id=userid,
  522. desc=order_list[0]['desc'], payType=order_list[0]['payType'], payTime=nowTime,
  523. price=amount.get('total'), currency=order_list[0]['currency'], addTime=nowTime, updTime=nowTime,
  524. pay_url='', isSelectDiscounts=0,
  525. commodity_code=order_list[0]['commodity_code'], commodity_type=order_list[0]['commodity_type'],
  526. rank_id=rank, paymentID='', coupon_id='',uid_bucket_id=uid_bucket_id,status=1,agreement_id=agreement_id,plan_id=order_list[0]['plan_id'])
  527. datetime = time.strftime("%Y-%m-%d", time.localtime())
  528. sys_msg_text_list = ['温馨提示:尊敬的客户,您的' + UID + '设备在' + datetime + '已成功续订云存套餐',
  529. 'Dear customer,you already subscribed the cloud storage package successfully for device ' + UID + ' on ' + time.strftime(
  530. "%b %dth,%Y", time.localtime())]
  531. if order_list[0]['payType'] == 1:
  532. lang = 'en'
  533. else:
  534. lang = 'cn'
  535. CloudStorage.CloudStorageView.do_vod_msg_Notice(self, UID, channel, userid, lang,
  536. sys_msg_text_list, 'SMS_219738485')
  537. return HttpResponse('success')
  538. except Exception as e:
  539. print(e)
  540. return HttpResponse('fail')
  541. return HttpResponse('fail')
  542. def get_plan_desc(self,plan_id):
  543. paypalrestsdk.configure(PAYPAL_CRD)
  544. billing_plan = paypalrestsdk.BillingPlan.find(plan_id)
  545. print("Got Billing Plan Details for Billing Plan[%s]" % (billing_plan.id))
  546. exit()
  547. class payCycle(View):
  548. def get(self, request, *args, **kwargs):
  549. request.encoding = 'utf-8'
  550. operation = kwargs.get('operation')
  551. return self.validation(request.GET, request, operation)
  552. def post(self, request, *args, **kwargs):
  553. request.encoding = 'utf-8'
  554. operation = kwargs.get('operation')
  555. return self.validation(request.POST, request, operation)
  556. def validation(self, request_dict, request, operation):
  557. response = ResponseObject()
  558. token = request_dict.get('token', None)
  559. # 设备主键uid
  560. tko = TokenObject(token)
  561. response.lang = tko.lang
  562. if tko.code != 0:
  563. return response.json(tko.code)
  564. userID = tko.userID
  565. if operation is None:
  566. return response.json(444, 'error path')
  567. elif operation == 'queryPayCycle': # paypal成功订阅回调
  568. return self.do_query_pay_cycle(request_dict,userID, response)
  569. elif operation == 'cancelPayCycle': # 取消自动续费
  570. return self.do_cancel_pay_cycle(request_dict,userID, response)
  571. def do_query_pay_cycle(self, request_dict, userID, response):
  572. lang = request_dict.get('lang', 'en')
  573. uid = request_dict.get('uid',None)
  574. orderObject = Order_Model.objects.filter(userID=userID,status=1,rank__lang__lang=lang).annotate(rank__title=F('rank__lang__title'), rank__content=F('rank__lang__content'))
  575. if uid:
  576. orderObject = orderObject.filter(UID=uid)
  577. orderObject = orderObject.filter(~Q(agreement_id = ''))
  578. if not orderObject.exists():
  579. return response.json(0, {'data':[], 'count': 0})
  580. orderQuery = orderObject.values("orderID", "UID", "channel", "desc", "price", "currency",
  581. "addTime",
  582. "updTime", "paypal", "rank__day", "payType",
  583. "rank__price", "status",
  584. "rank__lang__content", "rank__lang__title", "rank__currency",
  585. "rank_id", "rank__expire","agreement_id").order_by('addTime')
  586. new_data = []
  587. values = []
  588. for d in orderQuery:
  589. if d['agreement_id'] not in values:
  590. new_data.append(d)
  591. values.append(d['agreement_id'])
  592. count = len(new_data)
  593. return response.json(0, {'data': new_data, 'count': count})
  594. def do_cancel_pay_cycle(self, request_dict, userID, response):
  595. orderID = request_dict.get('orderID', 'None')
  596. orderObject = Order_Model.objects.filter(orderID=orderID)
  597. orderObject = orderObject.filter(~Q(agreement_id = '')).values("agreement_id")
  598. if not orderObject.exists():
  599. return response.json(800)
  600. paypalrestsdk.configure(PAYPAL_CRD)
  601. BILLING_AGREEMENT_ID = orderObject[0]['agreement_id']
  602. try:
  603. billing_agreement = paypalrestsdk.BillingAgreement.find(BILLING_AGREEMENT_ID)
  604. if billing_agreement.state != 'Active':
  605. Order_Model.objects.filter(agreement_id=BILLING_AGREEMENT_ID).update(agreement_id='')
  606. return response.json(0)
  607. cancel_note = {"note": "Canceling the agreement"}
  608. if billing_agreement.cancel(cancel_note):
  609. Order_Model.objects.filter(agreement_id=BILLING_AGREEMENT_ID).update(agreement_id='')
  610. return response.json(0)
  611. else:
  612. return response.json(10052)
  613. except Exception as e:
  614. return response.json(10052)