PaymentCycle.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  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
  12. from Controller import CloudStorage
  13. from Model.models import PayCycleConfigModel, Store_Meal, UID_Bucket, PromotionRuleModel, \
  14. Unused_Uid_Meal, Device_Info, CouponModel, Order_Model, PaypalWebHookEvent
  15. from Object.ResponseObject import ResponseObject
  16. from Object.TokenObject import TokenObject
  17. from Service.CommonService import CommonService
  18. # 周期扣款相关
  19. class Paypal:
  20. # 检查是否有重复订阅
  21. def checkSubscriptions(userID, uid, rank):
  22. hasOrder = Order_Model.objects.filter(UID=uid, rank=rank)
  23. hasOrder = hasOrder.filter(~Q(agreement_id='')).values('agreement_id', 'orderID').order_by('-addTime')[0:1]
  24. if not hasOrder.exists():
  25. return True
  26. paypalrestsdk.configure(PAYPAL_CRD)
  27. billing_agreement = paypalrestsdk.BillingAgreement.find(hasOrder[0]['agreement_id'])
  28. if billing_agreement.state == 'Active':
  29. return False
  30. return True
  31. def subscriptions(store_info, lang, orderID, price):
  32. logger = logging.getLogger('pay')
  33. cycle_config = PayCycleConfigModel.objects.filter(id=store_info['cycle_config_id']).values()
  34. if not cycle_config:
  35. logger.info('----创建订阅失败----')
  36. logger.info('订阅配置失败')
  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. logger.info('----创建计划失败----')
  92. logger.info(billing_plan.error)
  93. return False
  94. now_time = int(time.time())
  95. if cycle_config[0]['frequency'] == "DAY":
  96. start_date_timestamp = now_time + 86400 - 3600 # 下次扣款为明天,提前1个小时扣款
  97. start_date_str = CommonService.timestamp_to_str(start_date_timestamp, "%Y-%m-%dT%H:%M:%SZ")
  98. elif cycle_config[0]['frequency'] == "MONTH":
  99. start_date_timestamp = CommonService.calcMonthLater(1, now_time) - (5 * 86400) # 下次扣款为下个月提前5天扣款
  100. start_date_str = CommonService.timestamp_to_str(start_date_timestamp, "%Y-%m-%dT%H:%M:%SZ")
  101. # 订阅
  102. billingAgreement = {
  103. "name": store_info['lang__content'],
  104. "description": orderID,
  105. "start_date": start_date_str,
  106. "plan": {
  107. "id": plan_id
  108. },
  109. "payer": {
  110. "payment_method": "paypal"
  111. },
  112. }
  113. billing_agreement = paypalrestsdk.BillingAgreement(billingAgreement)
  114. # print(billing_agreement.create())
  115. if billing_agreement.create():
  116. for link in billing_agreement.links:
  117. if link.rel == "approval_url":
  118. return {"plan_id": plan_id, "url": link.href}
  119. else:
  120. logger.info('----创建订阅失败----')
  121. logger.info(billing_agreement.error)
  122. return False
  123. class PaypalCycleNotify(View):
  124. def get(self, request, *args, **kwargs):
  125. request.encoding = 'utf-8'
  126. operation = kwargs.get('operation')
  127. return self.validation(request.GET, request, operation)
  128. def post(self, request, *args, **kwargs):
  129. request.encoding = 'utf-8'
  130. operation = kwargs.get('operation')
  131. return self.validation(request.POST, request, operation)
  132. def validation(self, request_dict, request, operation):
  133. response = ResponseObject()
  134. if operation is None:
  135. return response.json(444, 'error path')
  136. elif operation == 'paypalCycleReturn': # paypal成功订阅回调
  137. return self.do_paypal_cycle_return(request_dict, response)
  138. elif operation == 'paypalCycleNotify': # paypal 周期付款回调
  139. return self.do_paypal_webhook_notify(request_dict, request, response)
  140. elif operation == 'subscriptionBreakNotify': # paypal 订阅相关回调
  141. return self.do_subscription_break_notify(request_dict, request, response)
  142. def do_paypal_cycle_return(self, request_dict, response):
  143. lang = request_dict.get('lang', 'en')
  144. token = request_dict.get('token', None)
  145. logger = logging.getLogger('pay')
  146. logger.info('--------进入paypay首次订阅付款回调--------')
  147. logger.info(request_dict)
  148. paypalrestsdk.configure(PAYPAL_CRD)
  149. billing_agreement = paypalrestsdk.BillingAgreement()
  150. billing_agreement_response = billing_agreement.execute(token)
  151. if billing_agreement_response.error:
  152. logger.info('----付款失败----')
  153. logger.info(billing_agreement_response.error)
  154. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  155. if lang != 'cn':
  156. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  157. return HttpResponseRedirect(red_url)
  158. orderID = billing_agreement_response.description
  159. state = billing_agreement_response.state
  160. nowTime = int(time.time())
  161. promotion_rule_id = ''
  162. logger.info('----订阅详情----')
  163. logger.info(billing_agreement_response)
  164. agreement_id = billing_agreement_response.id
  165. order_qs = Order_Model.objects.filter(orderID=orderID, status=0)
  166. order_list = order_qs.values("UID", "channel", "commodity_code", "rank", "isSelectDiscounts",
  167. "userID__userID",
  168. "userID__username", 'coupon_id')
  169. if not orderID:
  170. logger.info('----订阅订单号失效----')
  171. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  172. if lang != 'cn':
  173. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  174. return HttpResponseRedirect(red_url)
  175. UID = order_list[0]['UID']
  176. if state != 'Active':
  177. order_qs.update(status=2, promotion_rule_id=promotion_rule_id)
  178. logger.info('----UID:{UID},用户名:{last_time} {first_time}首次订阅付款失败----'.format
  179. (UID=UID,
  180. last_time=billing_agreement_response.payer.payer_info.last_name,
  181. first_time=billing_agreement_response.payer.payer_info.first_time,
  182. ))
  183. logger.info('billing_agreement_state')
  184. logger.info(state)
  185. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  186. if lang != 'cn':
  187. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  188. return HttpResponseRedirect(red_url)
  189. try:
  190. userid = order_list[0]['userID__userID']
  191. username = order_list[0]['userID__username']
  192. channel = order_list[0]['channel']
  193. rank = order_list[0]['rank']
  194. smqs = Store_Meal.objects.filter(id=rank). \
  195. values("day", "bucket_id", "bucket__storeDay", "expire")
  196. bucketId = smqs[0]['bucket_id']
  197. if not smqs.exists():
  198. logger.info('----订阅套餐失效----')
  199. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  200. if lang != 'cn':
  201. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  202. return HttpResponseRedirect(red_url)
  203. # ##
  204. ubqs = UID_Bucket.objects.filter(uid=UID).values("id", "bucket_id", "bucket__storeDay", "bucket__region",
  205. "endTime", "use_status")
  206. expire = smqs[0]['expire']
  207. if order_list[0]['isSelectDiscounts'] == 1:
  208. expire = smqs[0]['expire'] * 2
  209. # 是否有促销
  210. promotion = PromotionRuleModel.objects.filter(status=1, startTime__lte=nowTime,
  211. endTime__gte=nowTime).values('id', 'ruleConfig')
  212. if promotion.exists():
  213. promotion_rule_id = promotion[0]['id']
  214. expire = expire * 2
  215. with transaction.atomic():
  216. if ubqs.exists():
  217. ubq = ubqs[0]
  218. if ubq['use_status'] == 1 and ubq['bucket_id'] == bucketId: # 套餐使用中并且相同套餐叠加过期时间
  219. endTime = CommonService.calcMonthLater(expire, ubq['endTime'])
  220. UID_Bucket.objects.filter(id=ubq['id']).update \
  221. (uid=UID, channel=channel, bucket_id=bucketId,
  222. endTime=endTime, updateTime=nowTime)
  223. else: # 已过期或者不相同的套餐加入未使用的关联套餐表
  224. has_unused = Unused_Uid_Meal.objects.filter(uid=UID, bucket_id=bucketId).values("id")
  225. nums = 2 if order_list[0]['isSelectDiscounts'] == 1 else 1
  226. if promotion.exists():
  227. nums = nums + 1
  228. if has_unused.exists():
  229. Unused_Uid_Meal.objects.filter(id=has_unused[0]['id']).update(num=F('num') + nums)
  230. else:
  231. Unused_Uid_Meal.objects.create(uid=UID, channel=channel, addTime=nowTime, num=nums,
  232. expire=smqs[0]['expire'], bucket_id=bucketId)
  233. UID_Bucket.objects.filter(id=ubq['id']).update(has_unused=1)
  234. uid_bucket_id = ubq['id']
  235. else:
  236. endTime = CommonService.calcMonthLater(expire)
  237. ub_cqs = UID_Bucket.objects.create \
  238. (uid=UID, channel=channel, bucket_id=bucketId, endTime=endTime, addTime=nowTime,
  239. updateTime=nowTime, use_status=1)
  240. uid_bucket_id = ub_cqs.id
  241. dvq = Device_Info.objects.filter(UID=UID, vodPrimaryUserID='', vodPrimaryMaster='')
  242. if dvq.exists():
  243. dvq_set_update_dict = {
  244. 'vodPrimaryUserID': userid,
  245. 'vodPrimaryMaster': username
  246. }
  247. dvq.update(**dvq_set_update_dict)
  248. # uid_main_exist = UIDMainUser.objects.filter(UID=UID)
  249. # if not uid_main_exist.exists():
  250. # uid_main_dict = {
  251. # 'UID': UID,
  252. # 'user_id': userid
  253. # }
  254. # UIDMainUser.objects.create(**uid_main_dict)
  255. # 核销coupon
  256. if order_list[0]['coupon_id']:
  257. CouponModel.objects.filter(id=order_list[0]['coupon_id']).update(use_status=2, update_time=nowTime)
  258. order_qs.update(status=1, updTime=nowTime, uid_bucket_id=uid_bucket_id,
  259. promotion_rule_id=promotion_rule_id, agreement_id=agreement_id)
  260. # 如果存在序列号,消息提示用序列号
  261. device_name = CommonService.query_serial_with_uid(uid=UID)
  262. datetime = time.strftime("%Y-%m-%d", time.localtime())
  263. sys_msg_text_list = [
  264. '温馨提示:尊敬的客户,您的' + device_name + '设备在' + datetime + '已成功订阅云存套餐',
  265. 'Dear customer,you already subscribed the cloud storage package successfully for device ' + device_name + ' on ' + time.strftime(
  266. "%b %dth,%Y", time.localtime())]
  267. CloudStorage.CloudStorageView().do_vod_msg_notice(UID, channel, userid, lang, sys_msg_text_list,
  268. 'SMS_219738485')
  269. # return response.json(0)
  270. red_url = "{SERVER_DOMAIN_SSL}web/paid2/success.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  271. if lang != 'cn':
  272. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_success.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  273. logger.info('{UID}成功开通paypal自动续费:----'.format(UID=UID))
  274. return HttpResponseRedirect(red_url)
  275. except Exception as e:
  276. print(repr(e))
  277. logger.info('do_paypal_cycle_return支付失败:----')
  278. logger.info('{UID}开通paypal自动续费失败'.format(UID=UID))
  279. logger.info("错误行数:{errLine}".format(errLine=e.__traceback__.tb_lineno))
  280. logger.info(repr(e))
  281. if order_qs:
  282. order_qs.update(status=10, promotion_rule_id=promotion_rule_id)
  283. red_url = "{SERVER_DOMAIN_SSL}web/paid2/fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  284. if lang != 'cn':
  285. red_url = "{SERVER_DOMAIN_SSL}web/paid2/en_fail.html".format(SERVER_DOMAIN_SSL=SERVER_DOMAIN_SSL)
  286. return HttpResponseRedirect(red_url)
  287. @staticmethod
  288. def paypal_webhook_log(**params):
  289. """
  290. webhook日志存库
  291. @param params:
  292. @return:
  293. """
  294. logger = logging.getLogger('pay')
  295. try:
  296. params['agreement_desc'] = 'webhook'
  297. PaypalWebHookEvent.objects.create(**params)
  298. logger.info('《Webhook日志存库Success......》')
  299. return True
  300. except Exception as e:
  301. logger.info(e.args)
  302. ex = traceback.format_exc()
  303. logger.info(ex)
  304. return True
  305. def do_paypal_webhook_notify(self, request_dict, request, response):
  306. logger = logging.getLogger('pay')
  307. logger.info('--------进入周期扣款钩子--------')
  308. if not request.body:
  309. logger.info('PayPal周期扣款失败---缺失请求体')
  310. return HttpResponse('fail', status=500)
  311. json_agreement_str = request.body.decode("utf-8")
  312. json_obj = json.loads(json_agreement_str)
  313. header = request.META
  314. paypal_body = json_obj.get('resource')
  315. logger.info('----请求体数据:{}----'.format(json_agreement_str))
  316. logger.info('----请求头数据:{}----'.format(header))
  317. try:
  318. transmission_id = header.get('HTTP_PAYPAL_TRANSMISSION_ID', None)
  319. transmission_time = header.get('HTTP_PAYPAL_TRANSMISSION_TIME', None)
  320. cert_url = header.get('HTTP_PAYPAL_CERT_URL', None)
  321. transmission_sig = header.get('HTTP_PAYPAL_TRANSMISSION_SIG', None)
  322. auth_algo = header.get('HTTP_PAYPAL_AUTH_ALGO', None)
  323. event_type = json_obj.get('event_type')
  324. summary = json_obj.get('summary')
  325. resource_type = json_obj.get('resource_type')
  326. billing_agreement_id = paypal_body.get('billing_agreement_id')
  327. paypal_transaction_id = paypal_body.get('id')
  328. amount = paypal_body.get('amount')
  329. PaypalWebHookEventInsert = {
  330. 'webhook_event_id': json_obj.get('id'),
  331. 'resource_type': json_obj.get('resource_type'),
  332. 'event_type': 1,
  333. 'summary': summary,
  334. 'trade_no': paypal_transaction_id,
  335. 'resource': json_agreement_str,
  336. 'created_time': int(time.time()),
  337. }
  338. self.paypal_webhook_log(**PaypalWebHookEventInsert)
  339. if event_type != 'PAYMENT.SALE.COMPLETED':
  340. logger.info('----event_type异常:{}----'.format(event_type))
  341. self.find_subscription_transactions(billing_agreement_id)
  342. if resource_type == 'sale' and paypal_body.get('state') == 'completed':
  343. paypalrestsdk.configure(PAYPAL_CRD)
  344. response = paypalrestsdk.WebhookEvent.verify(
  345. transmission_id, transmission_time, PAYPAL_WEB_HOOK_ID, json_agreement_str, cert_url,
  346. transmission_sig, auth_algo)
  347. if not response:
  348. logger.info('PayPal周期扣款失败---签名验证失败')
  349. return HttpResponse('Fail', status=500)
  350. else:
  351. logger.info('PayPal周期扣款失败,付款状态有误,resource_type:{},state:{}----'.
  352. format(resource_type, paypal_body.get('state')))
  353. return HttpResponse('Fail', status=500)
  354. if not billing_agreement_id:
  355. # 记录钩子日志
  356. PaypalWebHookEvent.objects.create(**PaypalWebHookEventInsert)
  357. # 普通支付,更新paypal交易id
  358. paymentID = paypal_body.get('parent_payment')
  359. if paymentID and paypal_transaction_id:
  360. Order_Model.objects.filter(paymentID=paymentID).update(
  361. updTime=int(time.time()),
  362. trade_no=paypal_transaction_id
  363. )
  364. logger.info('PayPal周期扣款成功---更新交易id:{}'.format(paypal_transaction_id))
  365. return HttpResponse('success')
  366. else:
  367. logger.info('PayPal周期扣款失败---paymentID:{}或paypal_transaction_id:{}为空'.
  368. format(paymentID, paypal_transaction_id))
  369. return HttpResponse('fail', status=500)
  370. agreement_id = paypal_body.get('billing_agreement_id')
  371. billing_agreement = paypalrestsdk.BillingAgreement.find(agreement_id)
  372. logger.info('billing_agreement:{}'.format(billing_agreement))
  373. # 记录钩子日志
  374. PaypalWebHookEventInsert['agreement_desc'] = repr(billing_agreement)
  375. PaypalWebHookEventInsert['agreement_id'] = agreement_id
  376. PaypalWebHookEventInsert['orderID'] = billing_agreement.description
  377. PaypalWebHookEvent.objects.create(**PaypalWebHookEventInsert)
  378. # 查询订单数据
  379. order_id = billing_agreement.description
  380. order_qs = Order_Model.objects.filter(orderID=order_id).values('UID', 'channel', 'commodity_code', 'rank',
  381. 'isSelectDiscounts', 'plan_id', 'desc',
  382. 'payType', 'currency', 'addTime',
  383. 'commodity_type', 'updTime',
  384. 'userID__userID', 'uid_bucket_id',
  385. 'userID__username'
  386. )
  387. if not order_qs.exists():
  388. logger.info('PayPal周期扣款失败---订单数据不存在')
  389. return HttpResponse('fail', status=500)
  390. UID = order_qs[0]['UID']
  391. # PayPal周期扣款首次扣款
  392. if billing_agreement.agreement_details.cycles_completed == '0':
  393. # 更新order表,paypal的商家交易号
  394. order_qs.update(updTime=int(time.time()), trade_no=paypal_transaction_id)
  395. logger.info('{} PayPal周期扣款首次扣款成功'.format(UID))
  396. return HttpResponse('success')
  397. nowTime = int(time.time())
  398. if order_qs[0]['addTime'] + 9200 > nowTime: # 避免续费订单重复支付
  399. logger.info('{} PayPal周期扣款失败---续费订单已创建'.format(UID))
  400. return HttpResponse('success')
  401. desc = order_qs[0]['desc']
  402. pay_type = order_qs[0]['payType']
  403. currency = order_qs[0]['currency']
  404. commodity_code = order_qs[0]['commodity_code']
  405. commodity_type = order_qs[0]['commodity_type']
  406. plan_id = order_qs[0]['plan_id']
  407. userid = order_qs[0]['userID__userID']
  408. username = order_qs[0]['userID__username']
  409. channel = order_qs[0]['channel']
  410. rank = order_qs[0]['rank']
  411. store_meal_qs = Store_Meal.objects.filter(id=rank).values("day", "bucket_id", "bucket__storeDay", "expire")
  412. if not store_meal_qs.exists():
  413. logger.info('{} PayPal周期扣款失败---套餐数据不存在'.format(UID))
  414. return HttpResponse('fail', status=500)
  415. bucketId = store_meal_qs[0]['bucket_id']
  416. expire = store_meal_qs[0]['expire']
  417. ubqs = UID_Bucket.objects.filter(uid=UID).values("id", "bucket_id", "bucket__storeDay", "bucket__region",
  418. "endTime", "use_status")
  419. with transaction.atomic():
  420. if ubqs.exists():
  421. ubq = ubqs[0]
  422. if ubq['use_status'] == 1 and ubq['bucket_id'] == bucketId: # 套餐使用中并且相同套餐叠加过期时间
  423. endTime = CommonService.calcMonthLater(expire, ubq['endTime'])
  424. UID_Bucket.objects.filter(id=ubq['id']).update \
  425. (uid=UID, channel=channel, bucket_id=bucketId,
  426. endTime=endTime, updateTime=nowTime)
  427. else: # 已过期或者不相同的套餐加入未使用的关联套餐表
  428. has_unused = Unused_Uid_Meal.objects.filter(uid=UID, bucket_id=bucketId).values("id")
  429. nums = 1
  430. if has_unused.exists():
  431. Unused_Uid_Meal.objects.filter(id=has_unused[0]['id']).update(num=F('num') + nums)
  432. else:
  433. Unused_Uid_Meal.objects.create(uid=UID, channel=channel, addTime=nowTime, num=nums,
  434. expire=expire, bucket_id=bucketId)
  435. UID_Bucket.objects.filter(id=ubq['id']).update(has_unused=1)
  436. uid_bucket_id = ubq['id']
  437. else:
  438. endTime = CommonService.calcMonthLater(expire)
  439. ub_cqs = UID_Bucket.objects.create \
  440. (uid=UID, channel=channel, bucket_id=bucketId, endTime=endTime, addTime=nowTime,
  441. updateTime=nowTime, use_status=1)
  442. uid_bucket_id = ub_cqs.id
  443. dvq = Device_Info.objects.filter(UID=UID, vodPrimaryUserID='', vodPrimaryMaster='')
  444. if dvq.exists():
  445. dvq_set_update_dict = {
  446. 'vodPrimaryUserID': userid,
  447. 'vodPrimaryMaster': username
  448. }
  449. dvq.update(**dvq_set_update_dict)
  450. orderID = CommonService.createOrderID()
  451. store_meal_qs = Store_Meal.objects.filter(id=rank, lang__lang='cn', is_show=0).values('lang__title',
  452. 'lang__content')
  453. if store_meal_qs.exists():
  454. store_meal_name = store_meal_qs[0]['lang__title'] + '-' + store_meal_qs[0]['lang__content']
  455. else:
  456. store_meal_name = '未知套餐'
  457. Order_Model.objects.create(orderID=orderID, UID=UID, channel=channel, userID_id=userid,
  458. desc=desc, payType=pay_type, payTime=nowTime, price=amount.get('total'),
  459. currency=order_qs[0]['currency'], addTime=nowTime, updTime=nowTime,
  460. pay_url='', isSelectDiscounts=0, commodity_code=commodity_code,
  461. commodity_type=commodity_type, rank_id=rank, paymentID='',
  462. coupon_id='', uid_bucket_id=uid_bucket_id, status=1,
  463. agreement_id=agreement_id, store_meal_name=store_meal_name,
  464. plan_id=plan_id, ai_rank_id=1, trade_no=paypal_transaction_id)
  465. # 如果存在序列号,消息提示用序列号
  466. device_name = CommonService.query_serial_with_uid(uid=UID)
  467. datetime = time.strftime("%Y-%m-%d", time.localtime())
  468. sys_msg_text_list = [
  469. '温馨提示:尊敬的客户,您的' + device_name + '设备在' + datetime + '已成功续订云存套餐',
  470. 'Dear customer,you already subscribed the cloud storage package successfully for device ' + device_name + ' on ' + time.strftime(
  471. "%b %dth,%Y", time.localtime())]
  472. if pay_type == 1:
  473. lang = 'en'
  474. else:
  475. lang = 'cn'
  476. CloudStorage.CloudStorageView().do_vod_msg_notice(UID, channel, userid, lang,
  477. sys_msg_text_list, 'SMS_219738485')
  478. # 更新agreement
  479. billing_agreement_update_attributes = [
  480. {
  481. "op": "replace",
  482. "path": "/",
  483. "value": {
  484. "description": orderID,
  485. }
  486. }
  487. ]
  488. billing_agreement.replace(billing_agreement_update_attributes)
  489. logger.info('{} PayPal周期扣款成功'.format(UID))
  490. return HttpResponse('success')
  491. except Exception as e:
  492. logger.info('PayPal周期扣款异常: errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  493. return HttpResponse('fail', status=500)
  494. @staticmethod
  495. def find_subscription_transactions(billing_agreement_id):
  496. """
  497. 列出当前订阅扣款事务
  498. @param billing_agreement_id: 协议id
  499. @return:
  500. """
  501. if not billing_agreement_id:
  502. return False
  503. logger = logging.getLogger('pay')
  504. try:
  505. paypalrestsdk.configure(PAYPAL_CRD)
  506. billing_agreement = paypalrestsdk.BillingAgreement.find(billing_agreement_id)
  507. today = date_time.date.today()
  508. oneday = date_time.timedelta(days=1)
  509. yesterday = today - oneday
  510. start_date = yesterday.strftime('%Y-%m-%d')
  511. end_date = today.strftime('%Y-%m-%d')
  512. transactions = billing_agreement.search_transactions(start_date, end_date)
  513. logger.info('--->列出当前扣款事务{}'.format(transactions))
  514. except Exception as e:
  515. logger.info('出错了~查询订阅的事务异常,errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
  516. def do_subscription_break_notify(self, request_dict, request, response):
  517. logger = logging.getLogger('pay')
  518. logger.info('--------进入订阅失败,付款失败,暂停--------')
  519. json_agreement_str = request.body.decode("utf-8")
  520. json_obj = json.loads(json_agreement_str)
  521. header = request.META
  522. paypal_body = json_obj.get('resource')
  523. logger.info('----主体信息----')
  524. logger.info(json_agreement_str)
  525. logger.info('----进入订阅失败头部信息----')
  526. logger.info(header)
  527. try:
  528. transmission_id = header.get('HTTP_PAYPAL_TRANSMISSION_ID', None)
  529. transmission_time = header.get('HTTP_PAYPAL_TRANSMISSION_TIME', None)
  530. cert_url = header.get('HTTP_PAYPAL_CERT_URL', None)
  531. transmission_sig = header.get('HTTP_PAYPAL_TRANSMISSION_SIG', None)
  532. auth_algo = header.get('HTTP_PAYPAL_AUTH_ALGO', None)
  533. event_type = json_obj.get('event_type')
  534. summary = json_obj.get('summary')
  535. resource_type = json_obj.get('resource_type')
  536. paypal_transaction_id = paypal_body.get('id')
  537. amount = paypal_body.get('amount')
  538. # self.get_plan_desc('P-4CG284532S612303METMEINY')
  539. paypalrestsdk.configure(PAYPAL_CRD)
  540. response = paypalrestsdk.WebhookEvent.verify(
  541. transmission_id, transmission_time, PAYPAL_WEB_HOOK_ID_TWO, json_agreement_str, cert_url,
  542. transmission_sig, auth_algo)
  543. logger.info('----验证签名----')
  544. logger.info(response)
  545. if not response:
  546. return HttpResponse('Fail', status=500)
  547. event_type_code = 0
  548. billing_agreement_id = ''
  549. if event_type == 'PAYMENT.SALE.COMPLETED':
  550. event_type_code = 1
  551. billing_agreement_id = paypal_body.get('billing_agreement_id')
  552. elif event_type == 'PAYMENT.SALE.REVERSED':
  553. billing_agreement_id = paypal_body.get('billing_agreement_id')
  554. event_type_code = 2
  555. elif event_type == 'BILLING.SUBSCRIPTION.CANCELLED':
  556. billing_agreement_id = paypal_body.get('id')
  557. event_type_code = 3
  558. elif event_type == 'BILLING.SUBSCRIPTION.SUSPENDED':
  559. billing_agreement_id = paypal_body.get('id')
  560. event_type_code = 4
  561. elif event_type == 'BILLING.SUBSCRIPTION.PAYMENT.FAILED':
  562. billing_agreement_id = paypal_body.get('id')
  563. event_type_code = 5
  564. elif event_type == 'PAYMENT.SALE.REFUNDED':
  565. billing_agreement_id = paypal_body.get('billing_agreement_id')
  566. event_type_code = 6
  567. PaypalWebHookEventInsert = {
  568. 'webhook_event_id': json_obj.get('id'),
  569. 'resource_type': resource_type,
  570. 'event_type': event_type_code,
  571. 'summary': summary,
  572. 'trade_no': paypal_transaction_id,
  573. 'resource': json_agreement_str,
  574. 'created_time': int(time.time()),
  575. }
  576. if not billing_agreement_id:
  577. # 记录钩子日志
  578. PaypalWebHookEvent.objects.create(**PaypalWebHookEventInsert)
  579. return HttpResponse('success')
  580. billing_agreement = paypalrestsdk.BillingAgreement.find(billing_agreement_id)
  581. # 记录钩子日志
  582. PaypalWebHookEventInsert['agreement_desc'] = repr(billing_agreement)
  583. PaypalWebHookEventInsert['agreement_id'] = billing_agreement_id
  584. PaypalWebHookEventInsert['orderID'] = billing_agreement.description
  585. PaypalWebHookEvent.objects.create(**PaypalWebHookEventInsert)
  586. return HttpResponse('success')
  587. except Exception as e:
  588. print(e)
  589. logger.info('----进入订阅失败----')
  590. logger.info('do_paypal_webhook_notify支付失败:----')
  591. logger.info("错误行数:{errLine}".format(errLine=e.__traceback__.tb_lineno))
  592. logger.info(repr(e))
  593. return HttpResponse('fail', status=500)
  594. def get_plan_desc(self, plan_id):
  595. paypalrestsdk.configure(PAYPAL_CRD)
  596. billing_plan = paypalrestsdk.BillingPlan.find(plan_id)
  597. print("Got Billing Plan Details for Billing Plan[%s]" % (billing_plan.id))
  598. exit()
  599. class payCycle(View):
  600. def get(self, request, *args, **kwargs):
  601. request.encoding = 'utf-8'
  602. operation = kwargs.get('operation')
  603. return self.validation(request.GET, request, operation)
  604. def post(self, request, *args, **kwargs):
  605. request.encoding = 'utf-8'
  606. operation = kwargs.get('operation')
  607. return self.validation(request.POST, request, operation)
  608. def validation(self, request_dict, request, operation):
  609. response = ResponseObject()
  610. token = request_dict.get('token', None)
  611. # 设备主键uid
  612. tko = TokenObject(token)
  613. response.lang = tko.lang
  614. if tko.code != 0:
  615. return response.json(tko.code)
  616. userID = tko.userID
  617. if operation is None:
  618. return response.json(444, 'error path')
  619. elif operation == 'queryPayCycle': # paypal成功订阅回调
  620. return self.do_query_pay_cycle(request_dict, userID, response)
  621. elif operation == 'cancelPayCycle': # 取消自动续费
  622. return self.do_cancel_pay_cycle(request_dict, userID, response)
  623. def do_query_pay_cycle(self, request_dict, userID, response):
  624. lang = request_dict.get('lang', 'en')
  625. uid = request_dict.get('uid', None)
  626. orderObject = Order_Model.objects.filter(userID=userID, status=1, rank__lang__lang=lang).annotate(
  627. rank__title=F('rank__lang__title'), rank__content=F('rank__lang__content'))
  628. if uid:
  629. orderObject = orderObject.filter(UID=uid)
  630. orderObject = orderObject.filter(~Q(agreement_id=''))
  631. if not orderObject.exists():
  632. return response.json(0, {'data': [], 'count': 0})
  633. orderQuery = orderObject.values("orderID", "UID", "channel", "desc", "price", "currency",
  634. "addTime",
  635. "updTime", "paypal", "rank__day", "payType",
  636. "rank__price", "status",
  637. "rank__lang__content", "rank__lang__title", "rank__currency",
  638. "rank_id", "rank__expire", "agreement_id").order_by('addTime')
  639. new_data = []
  640. values = []
  641. for d in orderQuery:
  642. if d['agreement_id'] not in values:
  643. new_data.append(d)
  644. values.append(d['agreement_id'])
  645. count = len(new_data)
  646. return response.json(0, {'data': new_data, 'count': count})
  647. def do_cancel_pay_cycle(self, request_dict, userID, response):
  648. orderID = request_dict.get('orderID', 'None')
  649. orderObject = Order_Model.objects.filter(orderID=orderID)
  650. orderObject = orderObject.filter(~Q(agreement_id='')).values("agreement_id")
  651. if not orderObject.exists():
  652. return response.json(800)
  653. paypalrestsdk.configure(PAYPAL_CRD)
  654. BILLING_AGREEMENT_ID = orderObject[0]['agreement_id']
  655. try:
  656. billing_agreement = paypalrestsdk.BillingAgreement.find(BILLING_AGREEMENT_ID)
  657. if billing_agreement.state != 'Active':
  658. Order_Model.objects.filter(agreement_id=BILLING_AGREEMENT_ID).update(agreement_id='')
  659. return response.json(0)
  660. cancel_note = {"note": "Canceling the agreement"}
  661. if billing_agreement.cancel(cancel_note):
  662. Order_Model.objects.filter(agreement_id=BILLING_AGREEMENT_ID).update(agreement_id='')
  663. return response.json(0)
  664. else:
  665. return response.json(10052)
  666. except Exception as e:
  667. return response.json(10052)