Example #1
0
def atol_receive_receipt_report(self, receipt_id):
    """
    Attempt to retrieve a receipt report for given receipt_id
    If received an unrecoverable error, then stop any further attempts to receive the report
    """
    atol = AtolAPI()
    Receipt = apps.get_model('atol', 'Receipt')
    receipt = Receipt.objects.get(id=receipt_id)

    if not receipt.uuid:
        logger.error('receipt %s does not have a uuid', receipt.id)
        return

    if receipt.status not in [ReceiptStatus.initiated, ReceiptStatus.retried]:
        logger.error('receipt %s has invalid status: %s', receipt.uuid,
                     receipt.status)
        return

    try:
        report = atol.report(receipt.uuid)
    except AtolUnrecoverableError as exc:
        logger.error('unable to fetch report for receipt %s due to %s',
                     receipt.id,
                     exc,
                     exc_info=True)
        receipt.declare_failed()
    except AtolReceiptNotProcessed as exc:
        logger.warning('unable to fetch report for receipt %s due to %s',
                       receipt.id,
                       exc,
                       exc_info=True)
        logger.info('repeat receipt registration: id %s; old internal_uuid %s',
                    receipt.id, receipt.internal_uuid)
        with transaction.atomic():
            receipt.internal_uuid = uuid4()
            receipt.status = ReceiptStatus.retried
            receipt.save(update_fields=['internal_uuid', 'status'])
            transaction.on_commit(lambda: atol_create_receipt.apply_async(
                args=(receipt.id, ), countdown=60))
    except Exception as exc:
        logger.warning('failed to fetch report for receipt %s due to %s',
                       receipt.id,
                       exc,
                       exc_info=True)
        try:
            countdown = 60 * int(math.exp(self.request.retries))
            logger.info(
                'retrying to receive receipt %s with countdown %s due to %s',
                receipt.id, countdown, exc)
            self.retry(countdown=countdown)
        except MaxRetriesExceededError:
            logger.error('run out of attempts to create receipt %s due to %s',
                         receipt.id, exc)
            receipt.declare_failed()
    else:
        with transaction.atomic():
            receipt.receive(content=report.data)
Example #2
0
def test_atol_sell_expired_token_is_renewed(set_atol_token, caches):
    atol = AtolAPI()
    set_atol_token('12345')

    assert caches['default'].get(ATOL_AUTH_CACHE_KEY) == '12345'

    receipt_uuid = str(uuid4())

    with responses.RequestsMock(
            assert_all_requests_are_fired=True) as resp_mock:
        resp_mock.add(responses.POST,
                      ATOL_BASE_URL + '/ATOL-ProdTest-1/sell',
                      status=401)
        resp_mock.add(responses.POST,
                      ATOL_BASE_URL + '/getToken',
                      status=200,
                      json={
                          'code': 0,
                          'token': 'foobar'
                      })
        resp_mock.add(responses.POST,
                      ATOL_BASE_URL + '/ATOL-ProdTest-1/sell',
                      status=200,
                      json={'uuid': receipt_uuid})

        sell_params = dict(timestamp=datetime.now(),
                           transaction_uuid=str(uuid4()),
                           purchase_name=u'Стандартная подписка на 1 месяц',
                           purchase_price='199.99',
                           user_email='*****@*****.**',
                           user_phone='+75551234567')

        receipt = atol.sell(**sell_params)
        assert receipt.uuid == receipt_uuid
        assert resp_mock.calls[0].request.headers['Token'] == '12345'

    # token is updated
    assert caches['default'].get(ATOL_AUTH_CACHE_KEY) == 'foobar'

    with responses.RequestsMock(
            assert_all_requests_are_fired=True) as resp_mock:
        anoher_receipt_uuid = str(uuid4())
        resp_mock.add(responses.POST,
                      ATOL_BASE_URL + '/ATOL-ProdTest-1/sell',
                      status=200,
                      json={'uuid': anoher_receipt_uuid})

        sell_params = dict(timestamp=datetime.now(),
                           transaction_uuid=str(uuid4()),
                           purchase_name=u'Стандартная подписка на 1 месяц',
                           purchase_price='199.99',
                           user_email='*****@*****.**',
                           user_phone='+75551234567')

        receipt = atol.sell(**sell_params)
        assert receipt.uuid == anoher_receipt_uuid
        assert resp_mock.calls[0].request.headers['Token'] == 'foobar'
Example #3
0
def test_atol_report_unrecoverable_errors(status, params, set_atol_token):
    atol = AtolAPI()
    set_atol_token('12345')
    payment_uuid = str(uuid4())

    responses.add(responses.GET, ATOL_BASE_URL + '/ATOL-ProdTest-1/report/%s' % payment_uuid,
                  status=status, **params)

    with pytest.raises(AtolUnrecoverableError):
        atol.report(payment_uuid)
Example #4
0
def test_atol_sell_unrecoverable_errors(status, params, set_atol_token):
    atol = AtolAPI()
    set_atol_token('12345')

    sell_params = dict(timestamp=datetime.now(), transaction_uuid=str(uuid4()),
                       purchase_name=u'Стандартная подписка на 1 месяц', purchase_price='199.99',
                       user_email='*****@*****.**', user_phone='+75551234567')

    responses.add(responses.POST, ATOL_BASE_URL + '/ATOL-ProdTest-1/sell', status=status, **params)

    with pytest.raises(AtolUnrecoverableError):
        atol.sell(**sell_params)
Example #5
0
def test_atol_sell_expired_token_is_failed_to_renew(set_atol_token):
    atol = AtolAPI()
    set_atol_token()

    with responses.RequestsMock(assert_all_requests_are_fired=True) as resp_mock:
        resp_mock.add(responses.POST, ATOL_BASE_URL + '/ATOL-ProdTest-1/sell', status=401)
        resp_mock.add(responses.POST, ATOL_BASE_URL + '/getToken', status=400, json={'code': 12})

        sell_params = dict(timestamp=datetime.now(), transaction_uuid=str(uuid4()),
                           purchase_name=u'Стандартная подписка на 1 месяц', purchase_price='199.99',
                           user_email='*****@*****.**', user_phone='+75551234567')

        with pytest.raises(AtolRecoverableError):
            atol.sell(**sell_params)
Example #6
0
def atol_cancel_receipt(receipt_id):
    atol = AtolAPI()
    Receipt = apps.get_model('atol', 'Receipt')
    receipt = Receipt.objects.get(id=receipt_id)

    params = receipt.get_cancel_receipt_params()

    try:
        receipt_data = atol.sell_refund(**params)
    except Exception as exc:
        logger.warning('cancel: failed to init receipt %s with params %s due to %s', receipt.id, params, exc,
                       exc_info=True, extra={'data': {'payment_params': params}})
        return False
    else:
        logger.info('cancel: receipt %s successfully canceled with data: %s', receipt.id, receipt_data)
        return True
Example #7
0
def test_atol_api_base_url_customizing(settings_url, api_base_url):
    """
    We check base_url in case of specifying RECEIPTS_ATOL_BASE_URL as different values in settings
    :param settings_url: url in settings
    :param api_base_url: url, which must be in AtolAPI
    """
    with override_settings(RECEIPTS_ATOL_BASE_URL=settings_url):
        assert AtolAPI().base_url == api_base_url
Example #8
0
def atol_create_receipt(self, receipt_id):
    """
    Change receipt status and the change date accordingly
    If received an unrecoverable error, stop any further attempts to init a receipt and mark its status as failed
    """
    atol = AtolAPI()
    Receipt = apps.get_model('atol', 'Receipt')
    receipt = Receipt.objects.get(id=receipt_id)

    try:
        params = receipt.get_params()
    except NoEmailAndPhoneError:
        # this email should have been sent, but we got neither email
        logger.warning('unable to init receipt %s due to missing email/phone', receipt.id)
        receipt.declare_failed(status=ReceiptStatus.no_email_phone)
        return

    if receipt.status not in [ReceiptStatus.created, ReceiptStatus.retried]:
        logger.error('receipt %s has invalid status: %s', receipt.uuid, receipt.status)
        return

    try:
        receipt_data = atol.sell(**params)
    except AtolUnrecoverableError as exc:
        logger.error('unable to init receipt %s with params %s due to %s', receipt.id, params, exc,
                     exc_info=True, extra={'data': {'payment_params': params}})
        receipt.declare_failed()
    except Exception as exc:
        logger.warning('failed to init receipt %s with params %s due to %s', receipt.id, params, exc,
                       exc_info=True, extra={'data': {'payment_params': params}})
        try:
            countdown = 60 * int(math.exp(self.request.retries))
            logger.info('retrying to create receipt %s with params %s countdown %s due to %s',
                        receipt.id, params, countdown, exc)
            self.retry(countdown=countdown)
        except MaxRetriesExceededError:
            logger.error('run out of attempts to create receipt %s with params %s due to %s',
                         receipt.id, params, exc)
            receipt.declare_failed()
    else:
        with transaction.atomic():
            receipt.initiate(uuid=receipt_data.uuid)
            transaction.on_commit(
                lambda: atol_receive_receipt_report.apply_async(args=(receipt.id,), countdown=60)
            )
Example #9
0
def atol_receive_receipt_report(self, receipt_id):
    """
    Attempt to retrieve a receipt report for given receipt_id
    If received an unrecoverable error, then stop any further attempts to receive the report
    """
    atol = AtolAPI()
    Receipt = apps.get_model('atol', 'Receipt')
    receipt = Receipt.objects.get(id=receipt_id)

    if not receipt.uuid:
        logger.error('receipt %s does not have a uuid', receipt.id)
        return

    if receipt.status != ReceiptStatus.initiated:
        logger.error('receipt %s has invalid status: %s', receipt.uuid,
                     receipt.status)
        return

    try:
        report = atol.report(receipt.uuid)
    except AtolUnrecoverableError as exc:
        logger.error('unable to fetch report for receipt %s due to %s',
                     receipt.uuid,
                     exc,
                     exc_info=True)
        receipt.declare_failed()
        return
    except Exception as exc:
        logger.warning('failed to fetch report for receipt %s due to %s',
                       receipt_id,
                       exc,
                       exc_info=True)
        try:
            countdown = 60 * int(math.exp(self.request.retries))
            logger.info(
                'retrying to receive receipt %s with countdown %s due to %s',
                receipt.id, countdown, exc)
            self.retry(countdown=countdown)
        except MaxRetriesExceededError:
            logger.error('run out of attempts to create receipt %s due to %s',
                         receipt.id, exc)
            receipt.declare_failed()
    else:
        with transaction.atomic():
            receipt.receive(content=report.data)
Example #10
0
def test_atol_create_refund_receipt_workflow():
    uid = '973f3bef-1c39-40c9-abd0-33a91ab005ca'
    # get Token
    data = {
        'code': 1,
        'text': None,
        'token': '84a50b3a6207421aba46834d650b42a0'
    }
    url = ATOL_BASE_URL + '/getToken'
    responses.add(responses.POST, url, status=200, json=data)

    # sell_refund
    data = {
        'uuid': uid,
        'timestamp': '13.07.2017 18:32:49',
        'status': 'wait',
        'error': None
    }
    url = ATOL_BASE_URL + '/ATOL-ProdTest-1/sell_refund'
    responses.add(responses.POST, url, status=200, json=data)

    atol = AtolAPI()
    now = datetime(2017, 11, 22, 10, 47, 32)
    payment_uuid = str(uuid4())

    sell_refund_params = dict(timestamp=now,
                              transaction_uuid=payment_uuid,
                              purchase_name=None,
                              purchase_price='199.99',
                              payment_type=4,
                              original_fiscal_number=4146968358,
                              user_email='*****@*****.**')

    receipt = atol.sell_refund(**sell_refund_params)

    assert receipt.uuid == '973f3bef-1c39-40c9-abd0-33a91ab005ca'
    assert receipt.data['status'] == 'wait'
Example #11
0
def test_atol_api_base_url():
    """
    We Check base_url in case that RECEIPTS_ATOL_BASE_URL is not specified in settings
    """
    assert AtolAPI().base_url == 'https://online.atol.ru/possystem/v4'
Example #12
0
def test_atol_create_receipt_workflow():
    uid = '973f3bef-1c39-40c9-abd0-33a91ab005ca'
    # get Token
    data = {
        'code': 1,
        'text': None,
        'token': '84a50b3a6207421aba46834d650b42a0'
    }
    url = ATOL_BASE_URL + '/getToken'
    responses.add(responses.POST, url, status=200, json=data)

    # sell
    data = {
        'uuid': uid,
        'timestamp': '13.07.2017 18:32:49',
        'status': 'wait',
        'error': None
    }
    url = ATOL_BASE_URL + '/ATOL-ProdTest-1/sell'
    responses.add(responses.POST, url, status=200, json=data)

    # report error
    data = {
        'uuid': uid,
        'timestamp': '13.07.2017 18:32:49',
        'status': 'wait',
        'error': {
            'error_id': 'e710f5de-0b20-47de-8ae8-d193016c5a4e',
            'code': 34,
            'text': 'Нет информации, попробуйте позднее',
            'type': 'system'
        },
        'payload': None
    }
    url = ATOL_BASE_URL + '/ATOL-ProdTest-1/report/' + uid
    responses.add(responses.GET, url, status=200, json=data)

    # report ok
    data = {
        'uuid': uid,
        'error': None,
        'status': 'done',
        'payload': {
            'total': 199.99,
            'fns_site': 'www.nalog.ru',
            'fn_number': '9999078900003780',
            'shift_number': 114,
            'receipt_datetime': '13.07.2017 18:32:00',
            'fiscal_receipt_number': 1412,
            'fiscal_document_number': 50066,
            'ecr_registration_number': '1029384756033729',
            'fiscal_document_attribute': 2649836604
        },
        'timestamp': '13.07.2017 18:32:50',
        'group_code': 'ATOL-ProdTest-1',
        'daemon_code': 'prod-agent-1',
        'device_code': 'KSR13.11-3-1',
        'external_id': 'TRF20801_1',
        'callback_url': '',
    }
    url = ATOL_BASE_URL + '/ATOL-ProdTest-1/report/' + uid
    responses.add(responses.GET, url, status=200, json=data)

    # sell error
    data = {
        'uuid': uid,
        'timestamp': '13.07.2017 18:32:50',
        'status': 'fail',
        'error': {
            'error_id':
            '01b46c9d-f829-4ecf-b07c-7b096d0b985e',
            'code':
            33,
            'text':
            'В системе существует чек с external_id : ec0ce0c6-7a31-4f45-b94f-a1442be3bb9c '
            'и group_code: ATOL-ProdTest-1',
            'type':
            'system',
        }
    }
    url = ATOL_BASE_URL + '/ATOL-ProdTest-1/sell'
    responses.add(responses.POST, url, status=200, json=data)

    atol = AtolAPI()

    now = datetime(2017, 11, 22, 10, 47, 32)
    payment_uuid = str(uuid4())

    sell_params = dict(timestamp=now,
                       transaction_uuid=payment_uuid,
                       purchase_name=u'Стандартная подписка на 1 месяц',
                       purchase_price='199.99',
                       user_email='*****@*****.**',
                       user_phone='+75551234567')

    receipt = atol.sell(**sell_params)

    assert receipt.uuid == '973f3bef-1c39-40c9-abd0-33a91ab005ca'
    assert receipt.data['status'] == 'wait'

    # report is not ready yet
    with pytest.raises(AtolRecoverableError):
        atol.report(receipt.uuid)

    report = atol.report(receipt.uuid)
    assert report.data['group_code'] == 'ATOL-ProdTest-1'
    assert report.data['status'] == 'done'
    assert report.data['payload']['total'] == 199.99

    # another celery worker somehow requested the same payment receipt
    double_receipt = atol.sell(**sell_params)
    assert double_receipt.uuid == '973f3bef-1c39-40c9-abd0-33a91ab005ca'
    assert double_receipt.data['status'] == 'fail'