PaymentCycle.py 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. import datetime as date_time
  2. import json
  3. import logging
  4. import time
  5. import traceback
  6. import paypalrestsdk
  7. from django.db import transaction
  8. from django.db.models import Q, F
  9. from django.http import HttpResponseRedirect, HttpResponse
  10. from django.views.generic.base import View
  11. from Ansjer.config import PAYPAL_CRD, SERVER_DOMAIN_SSL, PAYPAL_WEB_HOOK_ID, PAYPAL_WEB_HOOK_ID_TWO, CONFIG_INFO, \
  12. CONFIG_US, CONFIG_EUR
  13. from Controller import CloudStorage
  14. from Model.models import PayCycleConfigModel, Store_Meal, UID_Bucket, PromotionRuleModel, \
  15. Unused_Uid_Meal, Device_Info, CouponModel, Order_Model, PaypalWebHookEvent, CountryModel, AiService
  16. from Object.ResponseObject import ResponseObject
  17. from Object.TokenObject import TokenObject
  18. from Service.CommonService import CommonService
  19. PAY_LOGGER = logging.getLogger('pay')
  20. # 周期扣款相关
  21. class Paypal:
  22. # 检查是否有重复订阅
  23. def checkSubscriptions(userID, uid, rank, app_type):
  24. hasOrder = Order_Model.objects.filter(UID=uid, app_type=app_type)
  25. hasOrder = hasOrder.filter(~Q(agreement_id='')).values('agreement_id', 'orderID').order_by('-addTime')[0:1]
  26. if not hasOrder.exists():
  27. return True
  28. if app_type == 1:
  29. paypalrestsdk.configure(PAYPAL_CRD['Zosi'])
  30. elif app_type == 2:
  31. paypalrestsdk.configure(PAYPAL_CRD['Vsees'])
  32. billing_agreement = paypalrestsdk.BillingAgreement.find(hasOrder[0]['agreement_id'])
  33. if billing_agreement.state == 'Active':
  34. return False
  35. return True
  36. def subscriptions(store_info, lang, orderID, price, app_type):
  37. logger = logging.getLogger('pay')
  38. cycle_config = PayCycleConfigModel.objects.filter(id=store_info['cycle_config_id']).values()
  39. if not cycle_config:
  40. logger.info('----创建订阅失败----')
  41. logger.info('订阅配置失败')
  42. return False
  43. cal_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  44. if lang != 'cn':
  45. cal_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  46. return_url = "{SERVER_DOMAIN_SSL}payCycle/paypalCycleReturn?lang={lang}&app_type={app_type}". \
  47. format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL, lang=lang, app_type=app_type)
  48. # call_sub_url = "http://binbin.uicp.vip/cloudstorage/dopaypalcallback?orderID={orderID}".format(
  49. # SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL, orderID=orderID)
  50. # exit(price)
  51. BillingPlan = {
  52. "description": orderID,
  53. "merchant_preferences": {
  54. "auto_bill_amount": "YES",
  55. "cancel_url": cal_url, # 取消协议url
  56. "initial_fail_amount_action": "CANCEL",
  57. "max_fail_attempts": "1", # 允许的最大失败付款尝试次数
  58. "return_url": return_url, # 客户批准协议的url
  59. # "notify_url": "http://www.notify.com", #通知客户协议已创建的 URL。只读并保留供将来使用。
  60. "setup_fee": {
  61. "currency": store_info['currency'],
  62. "value": price,
  63. }
  64. },
  65. "name": store_info['lang__content'],
  66. "payment_definitions": [
  67. {
  68. "amount": {
  69. "currency": store_info['currency'],
  70. "value": store_info['price']
  71. },
  72. # "charge_models": [
  73. # {
  74. # "amount": {
  75. # "currency": "USD",
  76. # "value": "20"
  77. # },
  78. # "type": "TAX" #税金
  79. # }
  80. # ],
  81. "cycles": cycle_config[0]['cycles'],
  82. "frequency": cycle_config[0]['frequency'],
  83. "frequency_interval": cycle_config[0]['frequencyInterval'],
  84. "name": store_info['lang__title'],
  85. "type": "REGULAR"
  86. },
  87. ],
  88. "type": "INFINITE",
  89. }
  90. if app_type == 1:
  91. paypalrestsdk.configure(PAYPAL_CRD['Zosi'])
  92. elif app_type == 2:
  93. paypalrestsdk.configure(PAYPAL_CRD['Vsees'])
  94. billing_plan = paypalrestsdk.BillingPlan(BillingPlan)
  95. if billing_plan.create():
  96. billing_plan.activate() # 激活
  97. plan_id = billing_plan.id
  98. else:
  99. logger.info('----创建计划失败----')
  100. logger.info(billing_plan.error)
  101. return False
  102. now_time = int(time.time())
  103. if cycle_config[0]['frequency'] == "DAY":
  104. start_date_timestamp = now_time + 86400 - 3600 # 下次扣款为明天,提前1个小时扣款
  105. start_date_str = CommonService.timestamp_to_str(start_date_timestamp, "%Y-%m-%dT%H:%M:%SZ")
  106. elif cycle_config[0]['frequency'] == "MONTH":
  107. start_date_timestamp = CommonService.calcMonthLater(1, now_time) - (5 * 86400) # 下次扣款为下个月提前5天扣款
  108. start_date_str = CommonService.timestamp_to_str(start_date_timestamp, "%Y-%m-%dT%H:%M:%SZ")
  109. # 订阅
  110. billingAgreement = {
  111. "name": store_info['lang__content'],
  112. "description": orderID,
  113. "start_date": start_date_str,
  114. "plan": {
  115. "id": plan_id
  116. },
  117. "payer": {
  118. "payment_method": "paypal"
  119. },
  120. }
  121. billing_agreement = paypalrestsdk.BillingAgreement(billingAgreement)
  122. # print(billing_agreement.create())
  123. if billing_agreement.create():
  124. for link in billing_agreement.links:
  125. if link.rel == "approval_url":
  126. return {"plan_id": plan_id, "url": link.href}
  127. else:
  128. logger.info('----创建订阅失败----')
  129. logger.info(billing_agreement.error)
  130. return False
  131. class PaypalCycleNotify(View):
  132. def get(self, request, *args, **kwargs):
  133. request.encoding = 'utf-8'
  134. operation = kwargs.get('operation')
  135. return self.validation(request.GET, request, operation)
  136. def post(self, request, *args, **kwargs):
  137. request.encoding = 'utf-8'
  138. operation = kwargs.get('operation')
  139. return self.validation(request.POST, request, operation)
  140. def validation(self, request_dict, request, operation):
  141. response = ResponseObject()
  142. if operation is None:
  143. return response.json(444, 'error path')
  144. elif operation == 'paypalCycleReturn': # paypal成功订阅回调
  145. return self.do_paypal_cycle_return(request_dict, response)
  146. elif operation == 'paypalCycleNotify': # paypal 周期付款回调
  147. return self.do_paypal_webhook_notify(request_dict, request, response)
  148. elif operation == 'subscriptionBreakNotify': # paypal 订阅相关回调
  149. return self.do_subscription_break_notify(request_dict, request, response)
  150. def create_vod(self, uid, expire, is_ai, now_time, channel, bucket_id, order_id, userid, username):
  151. ubqs = UID_Bucket.objects.filter(uid=uid).values("id", "endTime", "use_status")
  152. end_time = CommonService.calcMonthLater(expire)
  153. use_flag = True
  154. with transaction.atomic():
  155. if ubqs.exists():
  156. ubq = ubqs[0]
  157. uid_bucket_id = ubq['id']
  158. if ubq['use_status'] == 1 and ubq['endTime'] > now_time: # 套餐使用中并且未过期,加入未使用的关联套餐表
  159. Unused_Uid_Meal.objects.create(uid=uid, channel=channel, addTime=now_time, is_ai=is_ai,
  160. order_id=order_id, expire=expire, bucket_id=bucket_id)
  161. UID_Bucket.objects.filter(id=uid_bucket_id).update(has_unused=1)
  162. use_flag = False
  163. else: # 云存服务已过期则重新开通云存服务
  164. UID_Bucket.objects.filter(id=uid_bucket_id).update(uid=uid, channel=channel,
  165. bucket_id=bucket_id,
  166. endTime=end_time,
  167. updateTime=now_time,
  168. use_status=1,
  169. orderId=order_id)
  170. else:
  171. ub_cqs = UID_Bucket.objects.create \
  172. (uid=uid, channel=channel, bucket_id=bucket_id, endTime=end_time, addTime=now_time,
  173. updateTime=now_time, use_status=1, orderId=order_id)
  174. uid_bucket_id = ub_cqs.id
  175. dvq = Device_Info.objects.filter(UID=uid, vodPrimaryUserID='', vodPrimaryMaster='')
  176. if dvq.exists():
  177. dvq_set_update_dict = {
  178. 'vodPrimaryUserID': userid,
  179. 'vodPrimaryMaster': username
  180. }
  181. dvq.update(**dvq_set_update_dict)
  182. # 开通AI服务
  183. if is_ai and use_flag:
  184. ai_service = AiService.objects.filter(uid=uid, channel=channel)
  185. if ai_service.exists(): # 有正在使用的套餐,套餐结束时间保存为套餐有效期
  186. ai_service.update(updTime=now_time, use_status=1, orders_id=order_id, endTime=end_time)
  187. else:
  188. AiService.objects.create(uid=uid, channel=channel, detect_status=1, addTime=now_time,
  189. updTime=now_time, endTime=end_time, use_status=1, orders_id=order_id)
  190. return uid_bucket_id
  191. def do_paypal_cycle_return(self, request_dict, response):
  192. lang = request_dict.get('lang', 'en')
  193. token = request_dict.get('token', None)
  194. app_type = request_dict.get('app_type', 1)
  195. logger = logging.getLogger('pay')
  196. logger.info('--------进入paypay首次订阅付款回调--------')
  197. logger.info(request_dict)
  198. if app_type == 1:
  199. paypalrestsdk.configure(PAYPAL_CRD['Zosi'])
  200. elif app_type == 2:
  201. paypalrestsdk.configure(PAYPAL_CRD['Vsees'])
  202. billing_agreement = paypalrestsdk.BillingAgreement()
  203. billing_agreement_response = billing_agreement.execute(token)
  204. if billing_agreement_response.error:
  205. logger.info('----付款失败----')
  206. logger.info(billing_agreement_response.error)
  207. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  208. if lang != 'cn':
  209. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  210. return HttpResponseRedirect(red_url)
  211. orderID = billing_agreement_response.description
  212. state = billing_agreement_response.state
  213. nowTime = int(time.time())
  214. promotion_rule_id = ''
  215. logger.info('----订阅详情----')
  216. logger.info(billing_agreement_response)
  217. agreement_id = billing_agreement_response.id
  218. order_qs = Order_Model.objects.filter(orderID=orderID, status=0)
  219. order_list = order_qs.values("UID", "channel", "commodity_code", "rank", "isSelectDiscounts",
  220. "userID__userID",
  221. "userID__username", 'coupon_id')
  222. if not orderID:
  223. logger.info('----订阅订单号失效----')
  224. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  225. if lang != 'cn':
  226. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  227. return HttpResponseRedirect(red_url)
  228. UID = order_list[0]['UID']
  229. if state != 'Active':
  230. order_qs.update(status=2, promotion_rule_id=promotion_rule_id)
  231. logger.info('----UID:{UID},用户名:{last_time} {first_time}首次订阅付款失败----'.format
  232. (UID=UID,
  233. last_time=billing_agreement_response.payer.payer_info.last_name,
  234. first_time=billing_agreement_response.payer.payer_info.first_time,
  235. ))
  236. logger.info('billing_agreement_state')
  237. logger.info(state)
  238. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  239. if lang != 'cn':
  240. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  241. return HttpResponseRedirect(red_url)
  242. try:
  243. userid = order_list[0]['userID__userID']
  244. username = order_list[0]['userID__username']
  245. channel = order_list[0]['channel']
  246. rank = order_list[0]['rank']
  247. smqs = Store_Meal.objects.filter(id=rank).values("day", "bucket_id", "bucket__storeDay", "expire", "is_ai")
  248. bucketId = smqs[0]['bucket_id']
  249. if not smqs.exists():
  250. logger.info('----订阅套餐失效----')
  251. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  252. if lang != 'cn':
  253. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  254. return HttpResponseRedirect(red_url)
  255. # ##
  256. ubqs = UID_Bucket.objects.filter(uid=UID).values("id", "bucket_id", "bucket__storeDay", "bucket__region",
  257. "endTime", "use_status")
  258. expire = smqs[0]['expire']
  259. is_ai = smqs[0]['is_ai']
  260. if order_list[0]['isSelectDiscounts'] == 1:
  261. expire = smqs[0]['expire'] * 2
  262. # 是否有促销
  263. promotion = PromotionRuleModel.objects.filter(status=1, startTime__lte=nowTime,
  264. endTime__gte=nowTime).values('id', 'ruleConfig')
  265. if promotion.exists():
  266. promotion_rule_id = promotion[0]['id']
  267. expire = expire * 2
  268. endTime = CommonService.calcMonthLater(expire)
  269. use_flag = True
  270. with transaction.atomic():
  271. if ubqs.exists():
  272. ubq = ubqs[0]
  273. uid_bucket_id = ubq['id']
  274. if ubq['use_status'] == 1 and ubq['endTime'] > nowTime: # 套餐使用中并且未过期,加入未使用的关联套餐表
  275. Unused_Uid_Meal.objects.create(uid=UID, channel=channel, addTime=nowTime, is_ai=is_ai,
  276. order_id=orderID, expire=expire, bucket_id=bucketId)
  277. update_status = UID_Bucket.objects.filter(id=uid_bucket_id).update(has_unused=1)
  278. use_flag = False
  279. else: # 云存服务已过期则重新开通云存服务
  280. update_status = UID_Bucket.objects.filter(id=uid_bucket_id).update(uid=UID, channel=channel,
  281. bucket_id=bucketId,
  282. endTime=endTime,
  283. updateTime=nowTime,
  284. use_status=1,
  285. orderId=orderID)
  286. else:
  287. ub_cqs = UID_Bucket.objects.create \
  288. (uid=UID, channel=channel, bucket_id=bucketId, endTime=endTime, addTime=nowTime,
  289. updateTime=nowTime, use_status=1, orderId=orderID)
  290. update_status = True
  291. uid_bucket_id = ub_cqs.id
  292. dvq = Device_Info.objects.filter(UID=UID, vodPrimaryUserID='', vodPrimaryMaster='')
  293. if dvq.exists():
  294. dvq_set_update_dict = {
  295. 'vodPrimaryUserID': userid,
  296. 'vodPrimaryMaster': username
  297. }
  298. dvq.update(**dvq_set_update_dict)
  299. # 开通AI服务
  300. if is_ai and use_flag:
  301. ai_service = AiService.objects.filter(uid=UID, channel=channel)
  302. if ai_service.exists(): # 有正在使用的套餐,套餐结束时间保存为套餐有效期
  303. ai_service.update(updTime=nowTime, use_status=1, orders_id=orderID, endTime=endTime)
  304. else:
  305. AiService.objects.create(uid=UID, channel=channel, detect_status=1, addTime=nowTime,
  306. updTime=nowTime, endTime=endTime, use_status=1, orders_id=orderID)
  307. # uid_main_exist = UIDMainUser.objects.filter(UID=UID)
  308. # if not uid_main_exist.exists():
  309. # uid_main_dict = {
  310. # 'UID': UID,
  311. # 'user_id': userid
  312. # }
  313. # UIDMainUser.objects.create(**uid_main_dict)
  314. # 核销coupon
  315. if order_list[0]['coupon_id']:
  316. CouponModel.objects.filter(id=order_list[0]['coupon_id']).update(use_status=2, update_time=nowTime)
  317. logger.info(
  318. 'uid:{},uid_bucket_id:{},update_status:{},order_id:{}'.format(UID, uid_bucket_id, update_status,
  319. orderID))
  320. order_qs.update(status=1, updTime=nowTime, uid_bucket_id=uid_bucket_id, create_vod=1, payTime=nowTime,
  321. promotion_rule_id=promotion_rule_id, agreement_id=agreement_id)
  322. # 如果存在序列号,消息提示用序列号
  323. device_name = CommonService.query_serial_with_uid(uid=UID)
  324. datetime = time.strftime("%Y-%m-%d", time.localtime())
  325. sys_msg_text_list = [
  326. '温馨提示:尊敬的客户,您的' + device_name + '设备在' + datetime + '已成功订阅云存套餐',
  327. 'Dear customer, you have now successfully subscribed to the cloud storage plan for device ' + device_name + ' on ' + time.strftime(
  328. "%b %dth,%Y", time.localtime())]
  329. CloudStorage.CloudStorageView().do_vod_msg_notice(UID, channel, userid, lang, sys_msg_text_list,
  330. 'SMS_219738485')
  331. # return response.json(0)
  332. red_url = "{SERVER_DOMAIN_SSL}web/paid2/success.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  333. if lang != 'cn':
  334. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_success.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  335. logger.info('{UID}成功开通paypal自动续费:----'.format(UID=UID))
  336. return HttpResponseRedirect(red_url)
  337. except Exception as e:
  338. print(repr(e))
  339. logger.info('do_paypal_cycle_return支付失败:----')
  340. logger.info('{UID}开通paypal自动续费失败'.format(UID=UID))
  341. logger.info("错误行数:{errLine}".format(errLine=e.__traceback__.tb_lineno))
  342. logger.info(repr(e))
  343. if order_qs:
  344. order_qs.update(status=10, promotion_rule_id=promotion_rule_id)
  345. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  346. if lang != 'cn':
  347. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  348. return HttpResponseRedirect(red_url)
  349. def do_paypal_webhook_notify(self, request_dict, request, response):
  350. PAY_LOGGER.info('--------进入周期扣款钩子--------')
  351. if not request.body:
  352. PAY_LOGGER.info('PayPal周期扣款失败---缺失请求体')
  353. return HttpResponse('fail', status=500)
  354. json_agreement_str = request.body.decode("utf-8")
  355. json_obj = json.loads(json_agreement_str)
  356. header = request.META
  357. paypal_body = json_obj.get('resource')
  358. PAY_LOGGER.info('----请求体数据:{}----'.format(json_agreement_str))
  359. PAY_LOGGER.info('----请求头数据:{}----'.format(header))
  360. try:
  361. transmission_id = header.get('HTTP_PAYPAL_TRANSMISSION_ID', None)
  362. transmission_time = header.get('HTTP_PAYPAL_TRANSMISSION_TIME', None)
  363. cert_url = header.get('HTTP_PAYPAL_CERT_URL', None)
  364. transmission_sig = header.get('HTTP_PAYPAL_TRANSMISSION_SIG', None)
  365. auth_algo = header.get('HTTP_PAYPAL_AUTH_ALGO', None)
  366. event_type = json_obj.get('event_type')
  367. summary = json_obj.get('summary')
  368. resource_type = json_obj.get('resource_type')
  369. agreement_id = paypal_body.get('billing_agreement_id')
  370. paypal_transaction_id = paypal_body.get('id')
  371. amount = paypal_body.get('amount')
  372. fee = paypal_body['transaction_fee']['value']
  373. PaypalWebHookEventInsert = {
  374. 'webhook_event_id': json_obj.get('id'),
  375. 'resource_type': json_obj.get('resource_type'),
  376. 'event_type': 1,
  377. 'summary': summary,
  378. 'trade_no': paypal_transaction_id,
  379. 'resource': json_agreement_str,
  380. 'created_time': int(time.time()),
  381. }
  382. if event_type != 'PAYMENT.SALE.COMPLETED':
  383. PAY_LOGGER.info('----event_type异常:{}----'.format(event_type))
  384. if resource_type == 'sale' and paypal_body.get('state') == 'completed':
  385. paypalrestsdk.configure(PAYPAL_CRD['Zosi'])
  386. response = paypalrestsdk.WebhookEvent.verify(
  387. transmission_id, transmission_time, PAYPAL_WEB_HOOK_ID, json_agreement_str, cert_url,
  388. transmission_sig, auth_algo)
  389. if not response:
  390. paypalrestsdk.configure(PAYPAL_CRD['Vsees'])
  391. response = paypalrestsdk.WebhookEvent.verify(
  392. transmission_id, transmission_time, PAYPAL_WEB_HOOK_ID, json_agreement_str, cert_url,
  393. transmission_sig, auth_algo)
  394. if not response:
  395. PAY_LOGGER.info('PayPal周期扣款失败---签名验证失败')
  396. return HttpResponse('Fail', status=500)
  397. else:
  398. PAY_LOGGER.info('PayPal周期扣款失败,付款状态有误,resource_type:{},state:{}----'.
  399. format(resource_type, paypal_body.get('state')))
  400. return HttpResponse('Fail', status=500)
  401. nowTime = int(time.time())
  402. if not agreement_id:
  403. # 记录钩子日志
  404. PaypalWebHookEvent.objects.create(**PaypalWebHookEventInsert)
  405. # 普通支付,更新paypal交易id
  406. paymentID = paypal_body.get('parent_payment')
  407. if paymentID and paypal_transaction_id:
  408. # 查询客户地区信息,地区跟服务器配置不匹配,返回500
  409. order_qs = Order_Model.objects.filter(paymentID=paymentID).values('UID', 'userID__region_country',
  410. 'create_vod', 'orderID',
  411. 'channel', 'rank__is_ai',
  412. 'rank__expire',
  413. 'rank__bucket__id',
  414. 'isSelectDiscounts',
  415. 'userID__username',
  416. 'userID__userID', 'coupon_id')
  417. if not order_qs.exists():
  418. PAY_LOGGER.info('PayPal周期扣款失败---根据paymentID查询订单数据不存在')
  419. return HttpResponse('Fail', status=500)
  420. # 判断用户地区是否跟服务器地区匹配
  421. uid = order_qs[0]['UID']
  422. country_id = order_qs[0]['userID__region_country']
  423. if not self.config_match_region(uid, country_id):
  424. return HttpResponse('Fail', status=500)
  425. if order_qs[0]['create_vod']:
  426. order_qs.update(status=1, trade_no=paypal_transaction_id, updTime=nowTime, fee=fee)
  427. else:
  428. # 是否有促销
  429. expire = order_qs[0]['rank__expire']
  430. isSelectDiscounts = order_qs[0]['isSelectDiscounts']
  431. is_ai = order_qs[0]['rank__is_ai']
  432. channel = order_qs[0]['channel']
  433. bucketId = order_qs[0]['rank__bucket__id']
  434. userid = order_qs[0]['userID__userID']
  435. username = order_qs[0]['userID__username']
  436. order_id = order_qs[0]['orderID']
  437. coupon_id = order_qs[0]['coupon_id']
  438. promotion = PromotionRuleModel.objects.filter(status=1, startTime__lte=nowTime,
  439. endTime__gte=nowTime).values('id', 'ruleConfig')
  440. if promotion.exists():
  441. promotion_rule_id = promotion[0]['id']
  442. expire = expire * 2
  443. else:
  444. promotion_rule_id = ''
  445. if isSelectDiscounts:
  446. expire = expire * 2
  447. uid_bucket_id = self.create_vod(uid, expire, is_ai, nowTime, channel, bucketId, order_id,
  448. userid, username)
  449. order_qs.update(status=1, trade_no=paypal_transaction_id, updTime=nowTime,
  450. uid_bucket_id=uid_bucket_id, create_vod=1, fee=fee,
  451. promotion_rule_id=promotion_rule_id, agreement_id=agreement_id)
  452. # 核销coupon
  453. if coupon_id:
  454. CouponModel.objects.filter(id=coupon_id).update(use_status=2, update_time=nowTime)
  455. PAY_LOGGER.info('PayPal周期扣款成功---更新交易id:{}'.format(paypal_transaction_id))
  456. return HttpResponse('success')
  457. else:
  458. PAY_LOGGER.info('PayPal周期扣款失败---paymentID:{}或paypal_transaction_id:{}为空'.
  459. format(paymentID, paypal_transaction_id))
  460. return HttpResponse('fail', status=500)
  461. billing_agreement = paypalrestsdk.BillingAgreement.find(agreement_id)
  462. PAY_LOGGER.info('billing_agreement:{}'.format(billing_agreement))
  463. # 记录钩子日志
  464. PaypalWebHookEventInsert['agreement_desc'] = repr(billing_agreement)
  465. PaypalWebHookEventInsert['agreement_id'] = agreement_id
  466. PaypalWebHookEventInsert['orderID'] = billing_agreement.description
  467. paypal_webhook_event_qs = PaypalWebHookEvent.objects.create(**PaypalWebHookEventInsert)
  468. # 查询订单数据
  469. order_id = billing_agreement.description
  470. order_qs = Order_Model.objects.filter(orderID=order_id).values('UID', 'channel', 'commodity_code', 'rank',
  471. 'isSelectDiscounts', 'plan_id', 'desc',
  472. 'payType', 'currency', 'addTime',
  473. 'commodity_type', 'updTime', 'order_type',
  474. 'userID__userID', 'uid_bucket_id',
  475. 'userID__username', 'userID__region_country',
  476. 'create_vod', 'coupon_id')
  477. if not order_qs.exists():
  478. PAY_LOGGER.info('PayPal周期扣款失败---根据order_id查询订单数据不存在')
  479. return HttpResponse('fail', status=500)
  480. # 判断用户地区是否跟服务器地区匹配
  481. uid = order_qs[0]['UID']
  482. country_id = order_qs[0]['userID__region_country']
  483. if not self.config_match_region(uid, country_id):
  484. return HttpResponse('Fail', status=500)
  485. UID = order_qs[0]['UID']
  486. isSelectDiscounts = order_qs[0]['isSelectDiscounts']
  487. coupon_id = order_qs[0]['coupon_id']
  488. userid = order_qs[0]['userID__userID']
  489. username = order_qs[0]['userID__username']
  490. channel = order_qs[0]['channel']
  491. rank = order_qs[0]['rank']
  492. store_meal_qs = Store_Meal.objects.filter(id=rank).values("bucket_id", "expire", "is_ai")
  493. if not store_meal_qs.exists():
  494. PAY_LOGGER.info('{} PayPal周期扣款失败---套餐数据不存在'.format(UID))
  495. return HttpResponse('fail', status=500)
  496. expire = store_meal_qs[0]['expire']
  497. is_ai = store_meal_qs[0]['is_ai']
  498. bucketId = store_meal_qs[0]['bucket_id']
  499. # PayPal周期扣款首次扣款
  500. if billing_agreement.agreement_details.cycles_completed == '0':
  501. if order_qs[0]['create_vod']:
  502. order_qs.update(status=1, trade_no=paypal_transaction_id, updTime=nowTime, fee=fee)
  503. else:
  504. # 是否有促销
  505. promotion = PromotionRuleModel.objects.filter(status=1, startTime__lte=nowTime,
  506. endTime__gte=nowTime).values('id', 'ruleConfig')
  507. if promotion.exists():
  508. promotion_rule_id = promotion[0]['id']
  509. expire = expire * 2
  510. else:
  511. promotion_rule_id = ''
  512. if isSelectDiscounts:
  513. expire = expire * 2
  514. uid_bucket_id = self.create_vod(UID, expire, is_ai, nowTime, channel, bucketId, order_id,
  515. userid, username)
  516. order_qs.update(status=1, trade_no=paypal_transaction_id, updTime=nowTime,
  517. uid_bucket_id=uid_bucket_id, create_vod=1, fee=fee,
  518. promotion_rule_id=promotion_rule_id, agreement_id=agreement_id)
  519. # 核销coupon
  520. if coupon_id:
  521. CouponModel.objects.filter(id=coupon_id).update(use_status=2, update_time=nowTime)
  522. PAY_LOGGER.info('{} PayPal周期扣款首次扣款成功'.format(UID))
  523. return HttpResponse('success')
  524. if order_qs[0]['addTime'] + 9200 > nowTime: # 避免续费订单重复支付
  525. PAY_LOGGER.info('{} PayPal周期扣款失败---续费订单已创建'.format(UID))
  526. return HttpResponse('success')
  527. desc = order_qs[0]['desc']
  528. pay_type = order_qs[0]['payType']
  529. commodity_code = order_qs[0]['commodity_code']
  530. commodity_type = order_qs[0]['commodity_type']
  531. plan_id = order_qs[0]['plan_id']
  532. order_type = order_qs[0]['order_type']
  533. endTime = CommonService.calcMonthLater(expire)
  534. use_flag = True
  535. orderID = CommonService.createOrderID()
  536. ubqs = UID_Bucket.objects.filter(uid=UID).values("id", "endTime", "use_status")
  537. with transaction.atomic():
  538. if ubqs.exists():
  539. ubq = ubqs[0]
  540. uid_bucket_id = ubq['id']
  541. if ubq['use_status'] == 1 and ubq['endTime'] > nowTime: # 套餐使用中并且未过期
  542. Unused_Uid_Meal.objects.create(uid=UID, channel=channel, addTime=nowTime, is_ai=is_ai,
  543. order_id=orderID, expire=expire, bucket_id=bucketId)
  544. update_status = UID_Bucket.objects.filter(id=ubq['id']).update(has_unused=1)
  545. use_flag = False
  546. else: # 已过期或者不相同的套餐加入未使用的关联套餐表
  547. update_status = UID_Bucket.objects.filter(id=uid_bucket_id).update(uid=UID, channel=channel,
  548. bucket_id=bucketId,
  549. endTime=endTime,
  550. updateTime=nowTime,
  551. use_status=1,
  552. orderId=orderID)
  553. else:
  554. ub_cqs = UID_Bucket.objects.create(uid=UID, channel=channel, bucket_id=bucketId, endTime=endTime,
  555. addTime=nowTime, updateTime=nowTime, use_status=1,
  556. orderId=orderID)
  557. uid_bucket_id = ub_cqs.id
  558. update_status = True
  559. PAY_LOGGER.info(
  560. 'uid:{},uid_bucket_id:{},update_status:{},order_id:{}'.format(UID, uid_bucket_id, update_status,
  561. orderID))
  562. dvq = Device_Info.objects.filter(UID=UID, vodPrimaryUserID='', vodPrimaryMaster='')
  563. if dvq.exists():
  564. dvq_set_update_dict = {
  565. 'vodPrimaryUserID': userid,
  566. 'vodPrimaryMaster': username
  567. }
  568. dvq.update(**dvq_set_update_dict)
  569. store_meal_qs = Store_Meal.objects.filter(id=rank, lang__lang='cn', is_show=0).values('lang__title',
  570. 'lang__content')
  571. if store_meal_qs.exists():
  572. store_meal_name = store_meal_qs[0]['lang__title'] + '-' + store_meal_qs[0]['lang__content']
  573. else:
  574. store_meal_name = '未知套餐'
  575. Order_Model.objects.create(orderID=orderID, UID=UID, channel=channel, userID_id=userid,
  576. desc=desc, payType=pay_type, payTime=nowTime,
  577. price=amount.get('total'), app_type=app_type,
  578. currency=order_qs[0]['currency'], addTime=nowTime,
  579. updTime=nowTime, order_type=order_type,
  580. pay_url='', isSelectDiscounts=0, fee=fee,
  581. commodity_code=commodity_code, create_vod=1,
  582. commodity_type=commodity_type, rank_id=rank, paymentID='',
  583. coupon_id='', uid_bucket_id=uid_bucket_id, status=1,
  584. agreement_id=agreement_id, store_meal_name=store_meal_name,
  585. plan_id=plan_id, ai_rank_id=1, trade_no=paypal_transaction_id)
  586. # 开通AI服务
  587. if is_ai and use_flag:
  588. ai_service = AiService.objects.filter(uid=uid, channel=channel)
  589. if ai_service.exists(): # 有正在使用的套餐,套餐结束时间保存为套餐有效期
  590. ai_service.update(updTime=nowTime, use_status=1, orders_id=orderID, endTime=endTime)
  591. else:
  592. AiService.objects.create(uid=uid, channel=channel, detect_status=1, addTime=nowTime,
  593. updTime=nowTime, endTime=endTime, use_status=1, orders_id=orderID)
  594. # 如果存在序列号,消息提示用序列号
  595. device_name = CommonService.query_serial_with_uid(uid=UID)
  596. datetime = time.strftime("%Y-%m-%d", time.localtime())
  597. sys_msg_text_list = [
  598. '温馨提示:尊敬的客户,您的' + device_name + '设备在' + datetime + '已成功续订云存套餐',
  599. 'Dear customer,you already subscribed the cloud storage package successfully for device ' + device_name + ' on ' + time.strftime(
  600. "%b %dth,%Y", time.localtime())]
  601. if pay_type == 1:
  602. lang = 'en'
  603. else:
  604. lang = 'cn'
  605. CloudStorage.CloudStorageView().do_vod_msg_notice(UID, channel, userid, lang,
  606. sys_msg_text_list, 'SMS_219738485')
  607. # 更新agreement
  608. billing_agreement_update_attributes = [
  609. {
  610. "op": "replace",
  611. "path": "/",
  612. "value": {
  613. "description": orderID,
  614. }
  615. }
  616. ]
  617. update_status = billing_agreement.replace(billing_agreement_update_attributes)
  618. # 记录新订单号和更新状态
  619. PaypalWebHookEvent.objects.filter(id=paypal_webhook_event_qs.id).update(newOrderID=orderID,
  620. update_status=update_status)
  621. PAY_LOGGER.info('{} PayPal周期扣款成功'.format(UID))
  622. return HttpResponse('success')
  623. except Exception as e:
  624. PAY_LOGGER.info('PayPal周期扣款异常: errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  625. return HttpResponse('fail', status=500)
  626. @staticmethod
  627. def config_match_region(uid, country_id):
  628. """
  629. 判断用户地区是否跟服务器地区匹配
  630. @param uid: uid
  631. @param country_id: 国家表id
  632. @return: bool
  633. """
  634. country_qs = CountryModel.objects.filter(id=country_id).values('region_id')
  635. # 不确定用户地区信息,默认美洲
  636. if not country_qs.exists():
  637. if CONFIG_INFO == CONFIG_EUR:
  638. PAY_LOGGER.info(
  639. 'PayPal周期扣款失败---不确定地区的用户请求欧洲服,uid:{},country_id:{}'.format(uid, country_id))
  640. return False
  641. else:
  642. return True
  643. region_id = country_qs[0]['region_id']
  644. PAY_LOGGER.info('uid{}的用户地区信息: country_id:{}, region_id:{}'.format(uid, country_id, region_id))
  645. if (CONFIG_INFO == CONFIG_US and region_id == 4) or (CONFIG_INFO == CONFIG_EUR and region_id != 4):
  646. PAY_LOGGER.info('PayPal周期扣款失败---服务器跟用户地区不匹配')
  647. return False
  648. return True
  649. def do_subscription_break_notify(self, request_dict, request, response):
  650. logger = logging.getLogger('pay')
  651. logger.info('--------进入订阅失败,付款失败,暂停--------')
  652. json_agreement_str = request.body.decode("utf-8")
  653. json_obj = json.loads(json_agreement_str)
  654. header = request.META
  655. paypal_body = json_obj.get('resource')
  656. logger.info('----主体信息----')
  657. logger.info(json_agreement_str)
  658. logger.info('----进入订阅失败头部信息----')
  659. logger.info(header)
  660. try:
  661. transmission_id = header.get('HTTP_PAYPAL_TRANSMISSION_ID', None)
  662. transmission_time = header.get('HTTP_PAYPAL_TRANSMISSION_TIME', None)
  663. cert_url = header.get('HTTP_PAYPAL_CERT_URL', None)
  664. transmission_sig = header.get('HTTP_PAYPAL_TRANSMISSION_SIG', None)
  665. auth_algo = header.get('HTTP_PAYPAL_AUTH_ALGO', None)
  666. event_type = json_obj.get('event_type')
  667. summary = json_obj.get('summary')
  668. resource_type = json_obj.get('resource_type')
  669. paypal_transaction_id = paypal_body.get('id')
  670. amount = paypal_body.get('amount')
  671. # self.get_plan_desc('P-4CG284532S612303METMEINY')
  672. paypalrestsdk.configure(PAYPAL_CRD['Zosi'])
  673. response = paypalrestsdk.WebhookEvent.verify(
  674. transmission_id, transmission_time, PAYPAL_WEB_HOOK_ID_TWO, json_agreement_str, cert_url,
  675. transmission_sig, auth_algo)
  676. logger.info('----验证签名----')
  677. logger.info(response)
  678. if not response:
  679. paypalrestsdk.configure(PAYPAL_CRD['Vsees'])
  680. response = paypalrestsdk.WebhookEvent.verify(
  681. transmission_id, transmission_time, PAYPAL_WEB_HOOK_ID_TWO, json_agreement_str, cert_url,
  682. transmission_sig, auth_algo)
  683. logger.info('----验证签名----')
  684. logger.info(response)
  685. if not response:
  686. return HttpResponse('Fail', status=500)
  687. event_type_code = 0
  688. billing_agreement_id = ''
  689. if event_type == 'PAYMENT.SALE.COMPLETED':
  690. event_type_code = 1
  691. billing_agreement_id = paypal_body.get('billing_agreement_id')
  692. elif event_type == 'PAYMENT.SALE.REVERSED':
  693. billing_agreement_id = paypal_body.get('billing_agreement_id')
  694. event_type_code = 2
  695. elif event_type == 'BILLING.SUBSCRIPTION.CANCELLED':
  696. billing_agreement_id = paypal_body.get('id')
  697. event_type_code = 3
  698. elif event_type == 'BILLING.SUBSCRIPTION.SUSPENDED':
  699. billing_agreement_id = paypal_body.get('id')
  700. event_type_code = 4
  701. elif event_type == 'BILLING.SUBSCRIPTION.PAYMENT.FAILED':
  702. billing_agreement_id = paypal_body.get('id')
  703. event_type_code = 5
  704. elif event_type == 'PAYMENT.SALE.REFUNDED':
  705. billing_agreement_id = paypal_body.get('billing_agreement_id')
  706. event_type_code = 6
  707. PaypalWebHookEventInsert = {
  708. 'webhook_event_id': json_obj.get('id'),
  709. 'resource_type': resource_type,
  710. 'event_type': event_type_code,
  711. 'summary': summary,
  712. 'trade_no': paypal_transaction_id,
  713. 'resource': json_agreement_str,
  714. 'created_time': int(time.time()),
  715. }
  716. if not billing_agreement_id:
  717. # 记录钩子日志
  718. PaypalWebHookEvent.objects.create(**PaypalWebHookEventInsert)
  719. return HttpResponse('success')
  720. billing_agreement = paypalrestsdk.BillingAgreement.find(billing_agreement_id)
  721. # 记录钩子日志
  722. PaypalWebHookEventInsert['agreement_desc'] = repr(billing_agreement)
  723. PaypalWebHookEventInsert['agreement_id'] = billing_agreement_id
  724. PaypalWebHookEventInsert['orderID'] = billing_agreement.description
  725. PaypalWebHookEvent.objects.create(**PaypalWebHookEventInsert)
  726. return HttpResponse('success')
  727. except Exception as e:
  728. print(e)
  729. logger.info('----进入订阅失败----')
  730. logger.info('do_paypal_webhook_notify支付失败:----')
  731. logger.info("错误行数:{errLine}".format(errLine=e.__traceback__.tb_lineno))
  732. logger.info(repr(e))
  733. return HttpResponse('fail', status=500)
  734. def get_plan_desc(self, plan_id):
  735. paypalrestsdk.configure(PAYPAL_CRD)
  736. billing_plan = paypalrestsdk.BillingPlan.find(plan_id)
  737. print("Got Billing Plan Details for Billing Plan[%s]" % (billing_plan.id))
  738. exit()
  739. class payCycle(View):
  740. def get(self, request, *args, **kwargs):
  741. request.encoding = 'utf-8'
  742. operation = kwargs.get('operation')
  743. return self.validation(request.GET, request, operation)
  744. def post(self, request, *args, **kwargs):
  745. request.encoding = 'utf-8'
  746. operation = kwargs.get('operation')
  747. return self.validation(request.POST, request, operation)
  748. def validation(self, request_dict, request, operation):
  749. response = ResponseObject()
  750. token = request_dict.get('token', None)
  751. # 设备主键uid
  752. tko = TokenObject(token)
  753. response.lang = tko.lang
  754. if tko.code != 0:
  755. return response.json(tko.code)
  756. userID = tko.userID
  757. if operation is None:
  758. return response.json(444, 'error path')
  759. elif operation == 'queryPayCycle': # paypal成功订阅回调
  760. return self.do_query_pay_cycle(request_dict, userID, response)
  761. elif operation == 'cancelPayCycle': # 取消自动续费
  762. return self.do_cancel_pay_cycle(request_dict, userID, response)
  763. def do_query_pay_cycle(self, request_dict, userID, response):
  764. lang = request_dict.get('lang', 'en')
  765. uid = request_dict.get('uid', None)
  766. orderObject = Order_Model.objects.filter(userID=userID, status=1, rank__lang__lang=lang).annotate(
  767. rank__title=F('rank__lang__title'), rank__content=F('rank__lang__content'))
  768. if uid:
  769. orderObject = orderObject.filter(UID=uid)
  770. orderObject = orderObject.filter(~Q(agreement_id=''))
  771. if not orderObject.exists():
  772. return response.json(0, {'data': [], 'count': 0})
  773. orderQuery = orderObject.values("orderID", "UID", "channel", "desc", "price", "currency",
  774. "addTime",
  775. "updTime", "paypal", "rank__day", "payType",
  776. "rank__price", "status",
  777. "rank__lang__content", "rank__lang__title", "rank__currency",
  778. "rank_id", "rank__expire", "agreement_id").order_by('addTime')
  779. new_data = []
  780. values = []
  781. for d in orderQuery:
  782. if d['agreement_id'] not in values:
  783. new_data.append(d)
  784. values.append(d['agreement_id'])
  785. count = len(new_data)
  786. return response.json(0, {'data': new_data, 'count': count})
  787. def do_cancel_pay_cycle(self, request_dict, userID, response):
  788. orderID = request_dict.get('orderID', 'None')
  789. orderObject = Order_Model.objects.filter(orderID=orderID)
  790. orderObject = orderObject.filter(~Q(agreement_id='')).values("agreement_id", "app_type")
  791. if not orderObject.exists():
  792. return response.json(800)
  793. app_type = orderObject[0]['app_type']
  794. if app_type == 1:
  795. paypalrestsdk.configure(PAYPAL_CRD['Zosi'])
  796. elif app_type == 2:
  797. paypalrestsdk.configure(PAYPAL_CRD['Vsees'])
  798. BILLING_AGREEMENT_ID = orderObject[0]['agreement_id']
  799. try:
  800. billing_agreement = paypalrestsdk.BillingAgreement.find(BILLING_AGREEMENT_ID)
  801. if billing_agreement.state != 'Active':
  802. Order_Model.objects.filter(agreement_id=BILLING_AGREEMENT_ID).update(agreement_id='')
  803. return response.json(0)
  804. cancel_note = {"note": "Canceling the agreement"}
  805. if billing_agreement.cancel(cancel_note):
  806. Order_Model.objects.filter(agreement_id=BILLING_AGREEMENT_ID).update(agreement_id='')
  807. return response.json(0)
  808. else:
  809. return response.json(10052)
  810. except Exception as e:
  811. return response.json(10052)