importir

Error
 avatar
unknown
python
3 years ago
19 kB
3
Indexable
import json
import re
import requests
import traceback
from typing import Dict, List

from requests.exceptions import ConnectionError, Timeout

from django.conf import settings
from django.utils import timezone

from apps.utils.logger import create_mongo_logger
from apps.utils.image import get_image_url
from apps.utils.cache import cache_wrapper
from apps.helpers import currency, getSetting, updateOrCreateSetting
from apps.models_helper.setting_tb import SettingTb

from sentry_sdk import capture_exception

CONFIG_INCOMPLETE = 'Incomplete configuration for importir API.'
CONNECTION_FAILED = 'Failed connecting to importir.org'

def get_file_name(cd):
    """
    Get filename from content-disposition
    """
    if not cd:
        return None
    fname = re.findall('filename=(.+)', cd)
    if len(fname) == 0:
        return None
    return fname[0]

class ImportirAPI:
    # docs: https://importir.docs.apiary.io/

    def __init__(self):
        self.retry_count = 0
        self.base_url = getattr(settings, "IMPORTIR_BASE_URL", None)
        self.is_demo = getattr(settings, "IMPORTIR_IS_DEMO", True)
        self.timeout = getattr(settings, "IMPORTIR_TIMEOUT", 20)

        self.api_token = None
        setting_token = SettingTb.objects.filter(setting_key='importir_api_token')\
                                         .first()
        if setting_token is None:
            token_response = self.generate_token()
            if token_response.get('action'):
                self.api_token = token_response.get('result')\
                                               .get('message', {})\
                                               .get('token', '')

                SettingTb.objects.create(
                    setting_desc='Importir API Token', 
                    setting_key='importir_api_token',
                    setting_title='Importir API Token',
                    setting_value=self.api_token
                )
        else:
            self.api_token = setting_token.setting_value

        setting_token.last_used_at = timezone.now()
        setting_token.save(update_fields=['last_used_at'])
        
    def generate_token(self):
        """
        Generate auth token for importir API
        """
        ret = {'action': False, 'status_code': 500, 'result': None, 'message': ''}

        endpoint = '/api/vendor/generate-token'
        api_url = self.base_url + endpoint

        user_email = getattr(settings, 'IMPORTIR_USER_EMAIL', None)
        api_key = getattr(settings, 'IMPORTIR_API_KEY', None)

        if not user_email or not api_key:
            ret['message'] = CONFIG_INCOMPLETE
            return ret

        request_params = {'email': user_email, 'api_key': api_key}
        try:
            response = requests.get(api_url, params=request_params)
            
            ret['status_code'] = response.status_code
            if response.status_code == 200:
                ret['action'] = True
                ret['result'] = response.json()

                if isinstance(ret['result'], dict) and ret.get('result').get('status', True) == False:
                    ret['action'] = False
        except (ConnectionError, Timeout) as err:
            ret['message'] = CONNECTION_FAILED
        except Exception as err:
            capture_exception(traceback.format_exc())

        log_data = {
            'METHOD': 'GET',
            'URL': api_url,
            'STATUS_CODE': ret['status_code'],
            'REQUEST_PARAMETERS': request_params,
            'RESPONSE_DATA': ret
        }

        # logger = create_mongo_logger('importir', collection_name="mongolog-request")
        # logger.info(log_data)

        return ret

    def send(self, method, endpoint, request_data={}, request_files={}, request_params={}, request_headers={}, is_json=False):
        ret = {'action': False, 'status_code': 500, 'message': '', 'result': None}

        if not all([self.base_url, self.api_token]):
            ret['message'] = CONFIG_INCOMPLETE
            return ret

        api_headers = {
            # 'Content-Type': content_type,
            'Authorization': 'Bearer ' + self.api_token
        }

        api_headers.update({**request_headers})

        api_url = self.base_url + endpoint

        log_data = {
            'METHOD': method,
            'HEADERS': api_headers,
            'REQUEST_DATA': request_data,
            'URL': api_url
        }

        try:
            if method in ['post', 'POST']:
                # print("request data: ", request_data)
                # print("request files: ", request_files)
                if is_json:
                    response = requests.post(api_url, headers=api_headers, data=json.dumps(request_data), timeout=7) # files=request_files
                else:
                    response = requests.post(api_url, headers=api_headers, data=request_data, files=request_files, timeout=7)
            elif method in ['get', 'GET']:
                response = requests.get(api_url, headers=api_headers, params=request_params, timeout=self.timeout)

            log_data['STATUS_CODE'] = response.status_code
            log_data['FULL_URL'] = response.request.url

            ret['status_code'] = response.status_code
            if response.status_code == 200:
                ret['result'] = response.json()
                ret['action'] = True

                if isinstance(ret['result'], dict):
                    # validasi api create shipping
                    if ret.get('result').get('status', True) == False:
                        ret['action'] = False

                    # validasi api product 1688 & checkout
                    importir_success_status = ['success_product_feeds_200', 'success_product_detail_200', 
                                               'success_product_description_view_200', 'success_checkout_200', 
                                               'success_categories_200', 'success_category_detail_200']
                    if ret.get('result').get('body') and ret.get('result').get('body').get('status_code') not in importir_success_status:
                        ret['action'] = False

        except (ConnectionError, Timeout) as err:
            ret['message'] = CONNECTION_FAILED
        except Exception as err:
            capture_exception(traceback.format_exc())

        log_data['RESPONSE_DATA'] = ret
        # logger = create_mongo_logger('importir', collection_name="mongolog-request")
        # logger.info(log_data)

        # If response is `Token is Expired`, retry request with new token
        if self.retry_count == 0 and isinstance(ret.get('result'), dict) and ret.get('result').get('data') == 'Token is Expired':
            self.retry_count += 1
            token_response = self.generate_token()
            if token_response.get('action'):
                self.api_token = token_response.get('result')\
                                               .get('message', {})\
                                               .get('token', '')

                _ = updateOrCreateSetting('importir_api_token', self.api_token, 'Importir API Token')
                return self.send(method, endpoint, request_data, request_files, request_params, request_headers, is_json)

        return ret

    def get_file(self, file_url):
        """
        Return response and filename
        """
        response = None
        try:
            response = requests.get(file_url, allow_redirects=True)
            if response.status_code == 200:
                file_name = get_file_name(response.headers.get('content-disposition')) or file_url.rsplit('/', 1)[1]
                return response, file_name
        except (ConnectionError, Timeout) as err:
            pass
        except Exception as err:
            response = None
            print("Exception get_file: ", str(err))

        return response, None

    def create_shipping_step_one(self, cart_tk: "CartTitipkirimin"):
        endpoint = '/api/shipping/step/1'

        estimation_weight = cart_tk.estimation_weight
        estimation_weight_kg = estimation_weight / 1000 if estimation_weight else 0

        request_data = {
            'freight': 10, # Laut LCL
            'volume': cart_tk.estimation_volume,
            'weight': estimation_weight_kg,
            'is_demo': 1 if self.is_demo else 0,
            'use_wood': 1 if cart_tk.is_wood else 0,
            'wood_note': cart_tk.wood_note
        }

        request_headers = {
            'lang': 'id',
            'currency': 'idr'
        }

        return self.send('POST', endpoint, request_data, request_headers=request_headers)

    def create_shipping_step_two(self, shipping_id: int):
        endpoint = '/api/shipping/step/2'
        
        request_data = {
            'id': shipping_id, # `shipping_id` from step 1
            'other_price_rmb': '',
        }

        request_headers = {
            'lang': 'id',
            'currency': 'idr'
        }

        return self.send('POST', endpoint, request_data, request_headers=request_headers)

    def create_shipping_step_three(self, shipping_id: int, cart_tk: "CartTitipkirimin"):
        endpoint = '/api/shipping/step/3'

        request_data = {
            'id': shipping_id, # `shipping_id` from step 1
            'supplier_email': cart_tk.shipper_email,
            'supplier_phone': cart_tk.shipper_phone_number,
            'wechat_id': ''
        }

        request_files = {
            # 'supplier_bank_info': '',
            # 'invoice_file': '',
            # 'packing_ist_file': ''
        }

        # content_type = 'application/x-www-form-urlencoded'
        if cart_tk.invoice_file:
            response, filename = self.get_file(get_image_url(cart_tk.invoice_file))
            if response:
                request_files.update({'invoice_file': (filename, response.content, response.headers.get('Content-Type'))})
        #         content_type = 'multipart/form-data'

        request_headers = {
            'lang': 'id',
            'currency': 'idr'
        }

        return self.send('POST', endpoint, request_data, request_files, request_headers=request_headers)

    def create_shipping_step_four(self, shipping_id: int):
        endpoint = '/api/shipping/step/4'
        
        request_data = {
            'id': shipping_id, # `shipping_id` from step 1
            'note': ''
        }

        request_headers = {
            'lang': 'id',
            'currency': 'idr'
        }

        return self.send('POST', endpoint, request_data, request_headers=request_headers)

    def shipping_save_product(self, shipping_id: int, cart_detail: "CartDetail"):
        endpoint = '/api/shipping/save-product/{}'.format(shipping_id)
        
        product_type = cart_detail.get_product_type
        product_price = cart_detail.price_original \
                        if cart_detail.currency == 'CNY' \
                        else currency(cart_detail.price_original, from_money=cart_detail.currency, to_money='CNY')

        price_per_unit = product_price / cart_detail.quantity
        is_lartas = 1 if cart_detail.additional_document == 'laporan' else 0 
        hscode = cart_detail.hscode # object
        hscode_number = ''
        if hscode is not None:
            hscode_number = (hscode.hscode).replace('.', '')

        request_data = {
            'product_name': cart_detail.product.product_title,
            'hscode': hscode_number,
            'is_lartas': is_lartas,
            'quantity': cart_detail.quantity,
            'price_per_unit': price_per_unit,
            'category[]': product_type.name
        }

        request_files = {
            # 'cover_product': ''
        }

        if cart_detail.product.image_file:
            response, filename = self.get_file(get_image_url(cart_detail.product.image_file))
            if response:
                request_files.update({'cover_product': (filename, response.content, response.headers.get('Content-Type'))})

        request_headers = {
            'lang': 'id',
            'currency': 'idr'
        }

        return self.send('POST', endpoint, request_data, request_files, request_headers=request_headers)

    def chat_list(self, shipping_id: int):
        """
        Get chat list from Importir API

        Args:
            shipping_id (int): Importir shipping ID (see: Cart.additional_json)
        """
        endpoint = '/api/shipping-chat/detail/{}'.format(shipping_id)

        request_headers = {
            'lang': 'id',
            'currency': 'idr'
        }

        return self.send('GET', endpoint, request_headers=request_headers)

    def post_chat(self, shipping_id: int, message: str):
        """
        Send chat to Importir API

        Args:
            shipping_id (int): Importir shipping ID (see: Cart.additional_json)
        """
        endpoint = '/api/shipping-chat/send-message/{}'.format(shipping_id)

        request_data = {
            'message': message
        }

        request_headers = {
            'lang': 'id',
            'currency': 'idr'
        }

        return self.send('POST', endpoint, request_data, request_headers=request_headers)

    def get_shipping_detail(self, shipping_id: int):
        """
        Get shipping detail from ImportirAPI

        Args:
            shipping_id (int): Importir Shipping ID
        """
        endpoint = '/api/shipping/detail/{}'.format(shipping_id)

        request_headers = {
            'lang': 'id',
            'currency': 'idr'
        }

        return self.send('GET', endpoint, request_headers=request_headers)

    def get_shipping_status(self, shipping_id: int):
        """
        Get shipping status from ImportirAPI

        Args:
            shipping_id (int): Importir Shipping ID
        """
        endpoint = '/api/shipping-status/{}'.format(shipping_id)

        request_headers = {
            'lang': 'id',
            'currency': 'idr'
        }

        return self.send('GET', endpoint, request_headers=request_headers)

    def get_product_feeds(self, query_params: Dict={}) -> Dict:
        """
        Get 1688 product feeds from importir

        Args:
            query_params (Dict): request query parameters

        Returns:
            Dict: Json response from importir
        """
        endpoint = '/api/product/feeds'
        if not isinstance(query_params, dict):
            query_params = {}

        def cache_validator(response):
            if response.get('action'): return True
            return False

        return cache_wrapper(key='importir:1688_product_feeds', timeout=3600, validator=cache_validator)(self.send)(method='GET', endpoint=endpoint, request_params=query_params, request_data={}, request_files={}, request_headers={}, is_json=False)

    def get_product_detail(self, product_id) -> Dict:
        """
        Get 1688 product detail from importir

        Args:
            product_id (int): 1688 product ID

        Returns:
            Dict: Json response from importir
        """
        if not product_id:
            ret = {'action': False, 'status_code': 500, 'message': 'Invalid product ID'}
            return ret

        endpoint = '/api/product/detail/1688/%s' % product_id

        return self.send('GET', endpoint)

    def get_product_description(self, product_id: int) -> Dict:
        """
        Get 1688 product description from importir

        Args:
            product_id (int): 1688 product ID

        Returns:
            Dict: Json response from importir
        """
        if not product_id or not isinstance(product_id, int):
            ret = {'action': False, 'status_code': 500, 'message': 'Invalid product ID'}
            return ret

        endpoint = '/product/description-view/%s' % product_id

        return self.send('GET', endpoint)

    def get_categories(self) -> Dict:
        """
        Get product categories from importir
        """
        endpoint = '/api/category/categories'

        return self.send('GET', endpoint)

    def get_sub_categories(self, category_slug: str) -> Dict:
        """
        Get list sub categories from importir

        Args:
            category_slug (str): category slug
        """
        endpoint = '/api/category/detail' 

        request_params = {
            'slug': category_slug
        }

        return self.send('GET', endpoint, request_params=request_params)

    def checkout(self, product_id: int, cart_details) -> Dict:
        """
        Checkout Importir
        """
        endpoint = '/api/cart/order-vendor'

        quantity = subtotal = weight = 0
        is_multi_price = False
        spec_list = []
        price_multiple_desc_list = []
        cart_details = cart_details
        first_cart_detail = cart_details.first()
        for cart_detail in cart_details:
            quantity += cart_detail.quantity
            weight += cart_detail.weight

            product_type = cart_detail.product_type
            margin_percent = product_type.get_1688_margin(cart_detail.customer)
            margin = (100 + margin_percent) / 100

            subtotal += (cart_detail.sub_total / margin)

            additional_info = cart_detail.get_additional_information()
            if additional_info.get('spec_id') and additional_info.get('sku_id'):
                spec_price = cart_detail.price / margin
                spec_list.append({'quantity': cart_detail.quantity, 'price': str(spec_price), 'specId': additional_info.get('spec_id')})
                price_multiple_desc_list.append({'id': str(additional_info.get('sku_id')), 'qty': cart_detail.quantity})

            if not is_multi_price:
                is_multi_price = additional_info.get('is_multi_price') == 1

        request_data = {
            'product_id': product_id,
            'specIds': spec_list,
            'note': first_cart_detail.product.product_title if first_cart_detail else '',
            'note_ori': first_cart_detail.product.product_title if first_cart_detail else '',
            'quantity': quantity,
            'price': str(subtotal),
            'freight': 'air' if first_cart_detail.package_track == 'udara' else 'sea',
            'is_parse': 0,
            'is_multiple_price': 1 if not is_multi_price else 0,
            'price_type': 'RANGE' if is_multi_price else 'OTHERS',
            'price_multiple_desc': price_multiple_desc_list,
            'weight': weight,
            'cbm': 0
        }

        request_headers = {
            'Content-Type': 'application/json',
            'lang': 'id',
            'currency': 'idr'
        }

        return self.send('POST', endpoint, request_data=request_data, request_headers=request_headers, is_json=True)

    def get_official_order_status(self, order_id: int) -> Dict:
        """
        Get official 1688 order status from importir

        Args:
            order_id (int): Importir order ID
        """
        endpoint = '/api/vendor/tracking/official-last-status'

        request_params = {
            'order_id': order_id
        }

        request_headers = {
            'lang': 'id',
            'currency': 'idr'
        }

        return self.send('GET', endpoint, request_params=request_params, request_headers=request_headers)