| @@ -99,6 +99,11 @@ urlpatterns += [ | ||
| 99 | 99 | url(r'^wx/notify_url$', pay_views.wx_notify_url_api, name='wx_notify_url_api'), # 支付异步通知回调地址 | 
| 100 | 100 | ] | 
| 101 | 101 |  | 
| 102 | +# 提现相关 | |
| 103 | +urlpatterns += [ | |
| 104 | + url(r'^wx/balance_withdraw$', pay_views.wx_balance_withdraw_api, name='wx_balance_withdraw_api'), # 余额提现: 企业付款/现金红包 | |
| 105 | +] | |
| 106 | + | |
| 102 | 107 | # 分享相关 | 
| 103 | 108 | urlpatterns += [ | 
| 104 | 109 | url(r'^wx/jsapi_signature$', wechat_views.wx_jsapi_signature_api, name='wx_jsapi_signature_api'), # jsapi_signature | 
| @@ -261,6 +261,11 @@ WECHAT = { | ||
| 261 | 261 | 'appsecret': '', | 
| 262 | 262 | 'mchID': '', | 
| 263 | 263 | 'apiKey': '', | 
| 264 | + 'mch_cert': '', | |
| 265 | + 'mch_key': '', | |
| 266 | +        'redpacket': { | |
| 267 | + | |
| 268 | + } | |
| 264 | 269 | }, | 
| 265 | 270 | } | 
| 266 | 271 |  | 
| @@ -13,7 +13,7 @@ from account.models import LensmanIncomeExpensesInfo, LensmanInfo, UserIncomeExp | ||
| 13 | 13 | from group.models import GroupPhotoInfo, GroupPhotoOrderInfo | 
| 14 | 14 | from pay.models import OrderInfo | 
| 15 | 15 | from photo.models import PhotosInfo | 
| 16 | -from utils.error.errno_utils import GroupPhotoStatusCode, OrderStatusCode | |
| 16 | +from utils.error.errno_utils import GroupPhotoStatusCode, OrderStatusCode, UserStatusCode, WithdrawStatusCode | |
| 17 | 17 | from utils.error.response_utils import response | 
| 18 | 18 | from utils.page_utils import pagination | 
| 19 | 19 | from utils.redis.rkeys import LENSMAN_PHOTO_PRICE | 
| @@ -329,3 +329,42 @@ def wx_notify_url_api(request): | ||
| 329 | 329 | order_paid_success(order) | 
| 330 | 330 |  | 
| 331 | 331 | return HttpResponse(settings.WXPAY_NOTIFY_SUCCESS) | 
| 332 | + | |
| 333 | + | |
| 334 | +def wx_balance_withdraw_api(request): | |
| 335 | +    user_id = request.POST.get('user_id', '') | |
| 336 | + | |
| 337 | + # 用户校验 | |
| 338 | + try: | |
| 339 | + user = UserInfo.objects.get(user_id=user_id) | |
| 340 | + except UserInfo.DoesNotExist: | |
| 341 | + return response(UserStatusCode.USER_NOT_FOUND) | |
| 342 | + | |
| 343 | + # JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付,统一下单接口trade_type的传参可参考这里 | |
| 344 | +    trade_type = request.POST.get('trade_type', '') | |
| 345 | + # TRANSFER--企业付款、PACKET--现金红包, 余额提现接口withdraw_type的传参可参考这里 | |
| 346 | +    withdraw_type = request.POST.get('withdraw_type', 'TRANSFER') | |
| 347 | +    amount = int(request.POST.get('amount', 0)) | |
| 348 | + | |
| 349 | + if user.balance < amount: | |
| 350 | + return response(WithdrawStatusCode.BALANCE_NOT_ENOUGH) | |
| 351 | + | |
| 352 | + # 根据 trade_type 获取 wechat 配置 | |
| 353 | +    wechat = WECHAT.get(trade_type, {}) | |
| 354 | + # WeChatPay 初始化 | |
| 355 | +    wxpay = WeChatPay(wechat.get('appID'), wechat.get('apiKey'), wechat.get('mchID'), mch_cert=wechat.get('mch_cert'), mch_key=wechat.get('mch_key')) | |
| 356 | + | |
| 357 | + if withdraw_type == 'TRANSFER': | |
| 358 | + wxpay.transfer.transfer(user.wx_uid, amount, u'摄影师余额提现,企业付款', check_name='NO_CHECK') | |
| 359 | + elif withdraw_type == 'PACKET': | |
| 360 | + wxpay.redpack.send( | |
| 361 | + user.wx_uid, | |
| 362 | + amount, | |
| 363 | +            send_name=wechat.get('redpacket', {}).get('SEND_NAME'), | |
| 364 | +            nick_name=wechat.get('redpacket', {}).get('NICK_NAME'), | |
| 365 | +            act_name=wechat.get('redpacket', {}).get('ACT_NAME'), | |
| 366 | +            wishing=wechat.get('redpacket', {}).get('WISHING'), | |
| 367 | +            remark=wechat.get('redpacket', {}).get('REMARK'), | |
| 368 | + ) | |
| 369 | + | |
| 370 | +    return response(200, 'Withdraw Success', u'提现成功', {}) | 
| @@ -75,6 +75,11 @@ class OrderStatusCode(BaseStatusCode): | ||
| 75 | 75 | NO_DETAIL_PERMISSION = StatusCodeField(404015, u'No Detail Permission', description=u'没有详情权限') | 
| 76 | 76 |  | 
| 77 | 77 |  | 
| 78 | +class WithdrawStatusCode(BaseStatusCode): | |
| 79 | + """ 提现相关错误码 4041xx """ | |
| 80 | + BALANCE_NOT_ENOUGH = StatusCodeField(404100, u'Balance Not Enough', description=u'提现金额不足') | |
| 81 | + | |
| 82 | + | |
| 78 | 83 | class MessageStatusCode(BaseStatusCode): | 
| 79 | 84 | """ 消息相关错误码 4090xx """ | 
| 80 | 85 | MESSAGE_NOT_FOUND = StatusCodeField(409001, u'Message Not Found', description=u'消息不存在') | 
| @@ -2,11 +2,18 @@ | ||
| 2 | 2 |  | 
| 3 | 3 | from django.http import JsonResponse | 
| 4 | 4 |  | 
| 5 | +from utils.error.errno_utils import StatusCodeField | |
| 5 | 6 |  | 
| 6 | -def response(status_code, data={}): | |
| 7 | -    return JsonResponse({ | |
| 7 | + | |
| 8 | +def response_data(status_code, message=None, description=None, data={}): | |
| 9 | +    return { | |
| 8 | 10 | 'status': status_code, | 
| 9 | - 'message': status_code.message, | |
| 10 | - 'description': status_code.description, | |
| 11 | + 'message': message, | |
| 12 | + 'description': description, | |
| 11 | 13 | 'data': data, | 
| 12 | - }) | |
| 14 | + } | |
| 15 | + | |
| 16 | + | |
| 17 | +def response(status_code, message=None, description=None, data={}): | |
| 18 | + message, description = (status_code.message, status_code.description) if isinstance(status_code, StatusCodeField) else (message, description) | |
| 19 | + return JsonResponse(response_data(status_code, message, description, data)) |