def test_handle_notify_request_payment_failed(provider_base_config, order):
    """Test request notify helper returns http 204 and order status is correct when payment fails"""
    order.order_number = "abc123"
    order.status = OrderStatus.PAID
    order.save()
    refund = OrderRefundFactory(
        order=order, refund_id="1234567", amount=order.total_price
    )

    params = {
        "AUTHCODE": "8CF2D0EA9947D09B707E3C2953EF3014F1AD12D2BB0DCDBAC3ABD4601B50462B",
        "RETURN_CODE": "1",
        "REFUND_ID": "1234567",
    }

    rf = RequestFactory()
    request = rf.get("/payments/notify_refund/", params)
    payment_provider = create_bambora_provider(provider_base_config, request)

    assert refund.status == OrderRefundStatus.PENDING
    lease_status = refund.order.lease.status

    returned = payment_provider.handle_notify_refund_request()

    refund = OrderRefund.objects.get(refund_id=params.get("REFUND_ID"))
    order = refund.order

    assert refund.status == OrderRefundStatus.REJECTED
    # The order status shouldn't change
    assert order.status == OrderStatus.PAID
    assert order.lease.status == lease_status

    assert isinstance(returned, HttpResponse)
    assert returned.status_code == 204
def test_initiate_duplicated_payment_new_token_after_expiry(
        provider_base_config, order):
    request = RequestFactory().request()

    payment_provider = create_bambora_provider(provider_base_config, request)
    assert OrderToken.objects.count() == 0

    with mock.patch(
            "payments.providers.bambora_payform.requests.post",
            side_effect=mocked_response_create,
    ) as mock_call:
        payment_provider.initiate_payment(order)
        assert OrderToken.objects.count() == 1
        token = OrderToken.objects.first()

        assert token.order == order
        assert token.valid_until.isoformat(
        ) == "2019-01-20T21:59:59.999999+00:00"

        # Try to pay again 7 days after the day when the payment was created
        with freeze_time("2019-01-21T08:00:00Z"):
            url = payment_provider.initiate_payment(order)

        # Verify that the return URL passed includes the application language
        assert order.lease.application.language in mock_call.call_args.kwargs.get(
            "json").get("payment_method").get("return_url")

    assert OrderToken.objects.count() == 2

    assert url.startswith(payment_provider.url_payment_api)
    assert "token/token123" in url
def test_handle_notify_request_payment_failed(provider_base_config, order,
                                              order_status,
                                              expected_order_status):
    """Test request notify helper returns http 204 and order status is correct when payment fails"""
    order.order_number = "abc123"
    order.status = order_status
    order.save()

    params = {
        "VENE_UI_RETURN_URL": "http%3A%2F%2F127.0.0.1%3A8000%2Fv1",
        "AUTHCODE":
        "ED754E8F2E7FE0CC269B9F6A1C197F19B8393F37A1B63BE1E889D53F87A5FCA1",
        "RETURN_CODE": "1",
        "ORDER_NUMBER": "abc123-1602145394.662132",
        "SETTLED": "1",
    }

    rf = RequestFactory()
    request = rf.get("/payments/notify/", params)
    payment_provider = create_bambora_provider(provider_base_config, request)
    returned = payment_provider.handle_notify_request()
    order_after = Order.objects.get(
        order_number=params.get("ORDER_NUMBER").split("-")[0])
    assert order_after.status == expected_order_status
    assert isinstance(returned, HttpResponse)
    assert returned.status_code == 204
def test_handle_notify_request_success(
    provider_base_config, order: Order,
):
    """Test request notify helper returns http 204 and order status is correct when successful"""
    order.order_number = "abc123"
    order.status = OrderStatus.PAID
    order.lease.status = LeaseStatus.PAID
    order.lease.save()
    order.save()
    refund = OrderRefundFactory(
        order=order, refund_id="1234567", amount=order.total_price
    )

    rf = RequestFactory()
    request = rf.get("/payments/notify_refund/", notify_success_params)
    payment_provider = create_bambora_provider(provider_base_config, request)

    assert refund.status == OrderRefundStatus.PENDING

    returned = payment_provider.handle_notify_refund_request()

    refund = OrderRefund.objects.get(refund_id=notify_success_params.get("REFUND_ID"))
    order = refund.order

    assert refund.status == OrderRefundStatus.ACCEPTED
    assert order.status == OrderStatus.REFUNDED
    assert order.lease.status == LeaseStatus.TERMINATED

    assert isinstance(returned, HttpResponse)
    assert returned.status_code == 204
def test_generate_sticker_number_for_ws_lease(provider_base_config,
                                              order: Order):
    create_ws_sticker_sequences()

    order.status = OrderStatus.OFFERED
    order.order_number = "abc123"
    order.save()

    application = order.lease.application
    application.area_type = ApplicationAreaType.UNMARKED
    application.save()

    rf = RequestFactory()
    request = rf.get("/payments/success/", success_params)
    payment_provider = create_bambora_provider(provider_base_config, request)
    returned = payment_provider.handle_success_request()

    order_after = Order.objects.get(
        order_number=success_params.get("ORDER_NUMBER").split("-")[0])
    assert order_after.status == OrderStatus.PAID
    assert order_after.lease.application.status == ApplicationStatus.HANDLED
    assert order_after.lease.sticker_number == 1

    assert isinstance(returned, HttpResponse)
    url = returned.url
    assert "/payment-result" in url
    assert "payment_status=success" in url
def test_get_payment_email_url(provider_base_config, order):
    request = RequestFactory().request()
    payment_provider = create_bambora_provider(provider_base_config, request)
    lang = "fi"
    url = payment_provider.get_payment_email_url(order, lang)
    ui_return_url = settings.VENE_UI_RETURN_URL

    assert (
        url ==
        f"{ui_return_url.format(LANG=lang)}/payment?order_number={order.order_number}"
    )
def test_initiate_refund_no_order_email(provider_base_config: dict, order: Order):
    """Test the request creator constructs the payload base and returns a url that contains a token"""
    request = RequestFactory().request()
    order.status = OrderStatus.PAID
    order.email = None
    order.lease.status = LeaseStatus.PAID
    order.lease.save()
    order.save()

    OrderToken.objects.create(
        order=order, token="12345", valid_until=now() - relativedelta(days=7)
    )

    if hasattr(order.product, "price_for_tier"):
        place_price = order.product.price_for_tier(order.lease.berth.pier.price_tier)
        area = order.lease.berth.pier.harbor
    else:
        # Winter products are priced per m2
        place_price = rounded(
            order.product.price_value
            * order.lease.place.place_type.width
            * order.lease.place.place_type.length,
            round_to_nearest=1,
        )
        area = order.lease.place.winter_storage_section.area

    products = [
        {
            "id": get_talpa_product_id(order.product.id, area, False),
            "product_id": 1123,
            "title": order.product.name,
            "count": 1,
            "pretax_price": price_as_fractional_int(
                convert_aftertax_to_pretax(place_price, order.product.tax_percentage)
            ),
            "tax": int(order.product.tax_percentage),
            "price": price_as_fractional_int(place_price),
            "type": 1,
        }
    ]

    payment_provider = create_bambora_provider(provider_base_config, request)
    with mock.patch(
        "payments.providers.bambora_payform.requests.post",
        side_effect=mocked_refund_response_create,
    ), mock.patch(
        "payments.providers.bambora_payform.BamboraPayformProvider.get_payment_details",
        side_effect=mocked_refund_payment_details(products=products),
    ):
        refund = payment_provider.initiate_refund(order)

    assert refund.refund_id == "123456"
def test_initiate_refund_refunded_amount_does_not_match(
    provider_base_config: dict, order: Order
):
    """Test the request creator constructs the payload base and returns a url that contains a token"""
    request = RequestFactory().request()
    order.status = OrderStatus.PAID
    order.lease.status = LeaseStatus.PAID
    order.lease.save()
    order.save()

    OrderToken.objects.create(
        order=order, token="98765", valid_until=now() - relativedelta(hours=1)
    )
    OrderToken.objects.create(
        order=order, token="12345", valid_until=now() - relativedelta(days=7)
    )
    place_price = order.total_price + 10

    products = [
        {
            "id": "123123123",
            "product_id": 1123,
            "title": order.product.name,
            "count": 1,
            "pretax_price": price_as_fractional_int(
                convert_aftertax_to_pretax(place_price, order.product.tax_percentage)
            ),
            "tax": int(order.product.tax_percentage),
            "price": price_as_fractional_int(place_price),
            "type": 1,
        }
    ]

    payment_provider = create_bambora_provider(provider_base_config, request)
    with mock.patch(
        "payments.providers.bambora_payform.requests.post",
        side_effect=mocked_refund_response_create,
    ), mock.patch(
        "payments.providers.bambora_payform.BamboraPayformProvider.get_payment_details",
        side_effect=mocked_refund_payment_details(products=products),
    ), pytest.raises(
        RefundPriceError
    ) as exception:
        payment_provider.initiate_refund(order)

    assert (
        f"The amount to be refunded ({currency_format(place_price)}) "
        f"does not match the amount paid ({currency_format(order.total_price)})"
    ) in str(exception)
    assert not OrderRefund.objects.exists()
def test_initiate_payment_success(provider_base_config: dict, order: Order):
    """Test the request creator constructs the payload base and returns a url that contains a token"""
    request = RequestFactory().request()

    payment_provider = create_bambora_provider(provider_base_config, request)
    with mock.patch(
            "payments.providers.bambora_payform.requests.post",
            side_effect=mocked_response_create,
    ) as mock_call:
        url = payment_provider.initiate_payment(order)
        assert url.startswith(payment_provider.url_payment_api)
        assert "token/token123" in url
        # Verify that the return URL passed includes the application language
        assert order.lease.application.language in mock_call.call_args.kwargs.get(
            "json").get("payment_method").get("return_url")
def test_handle_notify_request_unknown_error(provider_base_config, order):
    """Test request notify helper returns http 204 when status code is unknown"""
    params = {
        "VENE_UI_RETURN_URL": "http%3A%2F%2F127.0.0.1%3A8000%2Fv1",
        "AUTHCODE":
        "3CD17A51E89C0A6DDDCA743AFCBD5DC40E8FF8AB97756089E75EC953A19A938C",
        "RETURN_CODE": "15",
        "ORDER_NUMBER": "abc123-1602145394.662132",
        "SETTLED": "1",
    }
    rf = RequestFactory()
    request = rf.get("/payments/notify/", params)
    payment_provider = create_bambora_provider(provider_base_config, request)
    returned = payment_provider.handle_notify_request()
    assert isinstance(returned, HttpResponse)
    assert returned.status_code == 204
def test_handle_notify_request_order_not_found(provider_base_config, order):
    """Test request notify helper returns http 204 when order can't be found"""
    params = {
        "VENE_UI_RETURN_URL": "http%3A%2F%2F127.0.0.1%3A8000%2Fv1",
        "AUTHCODE":
        "83F6C12E8D894B433CB2B6A2A56709CF0AE26665768ED74FB28922F6ED128301",
        "RETURN_CODE": "0",
        "ORDER_NUMBER": "abc567-1602145394.662132",
        "SETTLED": "1",
    }
    rf = RequestFactory()
    request = rf.get("/payments/notify/", params)
    payment_provider = create_bambora_provider(provider_base_config, request)
    returned = payment_provider.handle_notify_request()
    assert isinstance(returned, HttpResponse)
    assert returned.status_code == 204
def test_initiate_refund_error_unavailable(provider_base_config, order: Order):
    """Test the request creator raises service unavailable if request doesn't go through"""
    request = RequestFactory().request()

    invalid_config = {
        **provider_base_config,
        "VENE_PAYMENTS_BAMBORA_API_URL": FAKE_BAMBORA_API_URL,
    }
    unavailable_payment_provider = create_bambora_provider(invalid_config, request)

    with mock.patch(
        "payments.providers.bambora_payform.requests.post",
        side_effect=mocked_response_create,
    ):
        with pytest.raises(ServiceUnavailableError):
            unavailable_payment_provider.initiate_payment(order)
def test_initiate_refund_another_pending_refund(
    provider_base_config: dict, order: Order
):
    """Test the request creator constructs the payload base and returns a url that contains a token"""
    request = RequestFactory().request()
    order.status = OrderStatus.PAID
    order.lease.status = LeaseStatus.PAID
    order.lease.save()
    order.save()
    OrderRefundFactory(order=order, status=OrderRefundStatus.PENDING)

    OrderToken.objects.create(
        order=order, token="98765", valid_until=now() - relativedelta(hours=1)
    )
    OrderToken.objects.create(
        order=order, token="12345", valid_until=now() - relativedelta(days=7)
    )

    products = [
        {
            "id": "123123123",
            "product_id": 1123,
            "title": order.product.name,
            "count": 1,
            "pretax_price": 100,
            "tax": 24,
            "price": 100,
            "type": 1,
        }
    ]

    assert OrderRefund.objects.count() == 1

    payment_provider = create_bambora_provider(provider_base_config, request)
    with mock.patch(
        "payments.providers.bambora_payform.requests.post",
        side_effect=mocked_refund_response_create,
    ), mock.patch(
        "payments.providers.bambora_payform.BamboraPayformProvider.get_payment_details",
        side_effect=mocked_refund_payment_details(products=products),
    ), pytest.raises(
        ValidationError
    ) as exception:
        payment_provider.initiate_refund(order)

    assert "Cannot refund an order that has another pending refund" in str(exception)
    assert OrderRefund.objects.count() == 1
def test_handle_success_request_maintenance_break(provider_base_config, order):
    """Test request helper reacts to maintenance break error by returning a failure url"""
    params = {
        "VENE_UI_RETURN_URL": "http%3A%2F%2F127.0.0.1%3A8000%2Fv1",
        "AUTHCODE":
        "144662CEBD9861D4526C4147D10FB6C50FE1E02453701F8972B699CFD1F4A99E",
        "RETURN_CODE": "10",
        "ORDER_NUMBER": "abc123-1602145394.662132",
        "SETTLED": "1",
    }
    rf = RequestFactory()
    request = rf.get("/payments/success/", params)
    payment_provider = create_bambora_provider(provider_base_config, request)
    returned = payment_provider.handle_success_request()
    # TODO Handling isn't final yet so there might be something extra that needs to be tested here
    assert isinstance(returned, HttpResponse)
    assert "payment_status=failure" in returned.url
def test_handle_success_request_return_url_missing(provider_base_config,
                                                   order):
    """Test the handler returns a bad request object if return URL is missing from params"""
    params = {
        "AUTHCODE":
        "DD789BA71ACD627892517745AF4C4CE2068F006C602CD54264E1FC5E4C2EE6CF",
        "RETURN_CODE": "0",
        "ORDER_NUMBER": "abc123-1602145394.662132",
        "SETTLED": "0",
    }
    rf = RequestFactory()
    request = rf.get("/payments/success/", params)
    payment_provider = create_bambora_provider(provider_base_config, request)

    returned = payment_provider.handle_success_request()
    assert isinstance(returned, HttpResponseServerError)
    assert returned.status_code == 500
def test_handle_success_request_status_not_updated(provider_base_config,
                                                   order):
    """Test request helper reacts to transaction status update error by returning a failure url"""
    params = {
        "VENE_UI_RETURN_URL": "http%3A%2F%2F127.0.0.1%3A8000%2Fv1",
        "AUTHCODE":
        "D9170B2C0C0F36E467517E0DF2FC7D89BBC7237597B6BE3B0DE11518F93D7342",
        "RETURN_CODE": "4",
        "ORDER_NUMBER": "abc123-1602145394.662132",
        "SETTLED": "1",
    }
    rf = RequestFactory()
    request = rf.get("/payments/success/", params)
    payment_provider = create_bambora_provider(provider_base_config, request)
    returned = payment_provider.handle_success_request()
    # TODO Handling isn't final yet so there might be something extra that needs to be tested here
    assert isinstance(returned, HttpResponse)
    assert "payment_status=failure" in returned.url
def test_handle_notify_request_success(
    provider_base_config,
    berth_order: Order,
    order_status: OrderStatus,
    expected_order_status: OrderStatus,
):
    """Test request notify helper returns http 204 and order status is correct when successful"""
    berth_order.order_number = "abc123"
    berth_order.status = order_status
    berth_order.save()

    rf = RequestFactory()
    request = rf.get("/payments/notify/", success_params)
    payment_provider = create_bambora_provider(provider_base_config, request)
    returned = payment_provider.handle_notify_request()
    order_after = Order.objects.get(
        order_number=success_params.get("ORDER_NUMBER").split("-")[0])
    assert order_after.status == expected_order_status
    assert isinstance(returned, HttpResponse)
    assert returned.status_code == 204
def test_initiate_refund_invalid_order_status(
    provider_base_config: dict, order: Order, order_status
):
    """Test the request creator constructs the payload base and returns a url that contains a token"""
    request = RequestFactory().request()
    order.status = order_status
    order.save()

    OrderToken.objects.create(
        order=order, token="98765", valid_until=now() - relativedelta(hours=1)
    )
    OrderToken.objects.create(
        order=order, token="12345", valid_until=now() - relativedelta(days=7)
    )

    payment_provider = create_bambora_provider(provider_base_config, request)
    with pytest.raises(ValidationError) as exception:
        payment_provider.initiate_refund(order)

    assert "Cannot refund an order that is not paid" in str(exception)
def test_initiate_refund_no_order_email_or_application(
    provider_base_config: dict, order: Order
):
    """Test the request creator constructs the payload base and returns a url that contains a token"""
    request = RequestFactory().request()
    order.status = OrderStatus.PAID
    order.customer_email = None
    order.lease.status = LeaseStatus.PAID
    order.lease.application = None
    order.lease.save()
    order.save()

    OrderToken.objects.create(
        order=order, token="12345", valid_until=now() - relativedelta(days=7)
    )

    payment_provider = create_bambora_provider(provider_base_config, request)
    with pytest.raises(ValidationError) as exception:
        payment_provider.initiate_refund(order)

    assert "Cannot refund an order that has no email" in str(exception)
def test_handle_initiate_refund_error_validation(order, provider_base_config):
    """Test the response handler raises PayloadValidationError as expected"""
    r = {"result": 1, "type": "e-payment", "errors": ["Invalid auth code"]}
    OrderToken.objects.create(
        order=order, token="12345", valid_until=now() - relativedelta(days=7)
    )
    order.status = OrderStatus.PAID
    order.customer_email = "*****@*****.**"
    order.lease.status = LeaseStatus.PAID
    order.lease.save()
    order.save()
    request = RequestFactory().request()

    payment_provider = create_bambora_provider(provider_base_config, request)

    with mock.patch(
        "payments.providers.bambora_payform.requests.post",
        side_effect=[MockResponse(data=r)],
    ):
        with pytest.raises(PayloadValidationError):
            payment_provider.initiate_refund(order)
def test_initiate_duplicated_payment(provider_base_config, order):
    request = RequestFactory().request()

    payment_provider = create_bambora_provider(provider_base_config, request)
    assert OrderToken.objects.count() == 0

    with mock.patch(
            "payments.providers.bambora_payform.requests.post",
            side_effect=mocked_response_create,
    ) as mock_call:
        payment_provider.initiate_payment(order)
        assert OrderToken.objects.count() == 1
        url = payment_provider.initiate_payment(order)
        # Verify that the return URL passed includes the application language
        assert order.lease.application.language in mock_call.call_args.kwargs.get(
            "json").get("payment_method").get("return_url")

    assert OrderToken.objects.count() == 1
    assert OrderToken.objects.first().order == order

    assert url.startswith(payment_provider.url_payment_api)
    assert "token/token123" in url
def test_handle_notify_request_success_for_ap_order(
    provider_base_config,
    berth_order: Order,
):
    berth_order.order_number = "abc123"
    berth_order.status = OrderStatus.OFFERED
    berth_order.order_type = OrderType.ADDITIONAL_PRODUCT_ORDER
    berth_order.save()

    rf = RequestFactory()
    request = rf.get("/payments/notify/", success_params)
    payment_provider = create_bambora_provider(provider_base_config, request)
    returned = payment_provider.handle_notify_request()
    order_after = Order.objects.get(
        order_number=success_params.get("ORDER_NUMBER").split("-")[0])
    assert order_after.status == OrderStatus.PAID
    # it should not change the application and lease status in case of additional product order
    assert order_after.lease.application.status == ApplicationStatus.PENDING
    assert order_after.lease.status == LeaseStatus.DRAFTED

    assert isinstance(returned, HttpResponse)
    assert returned.status_code == 204
def test_handle_success_request_success(provider_base_config, order: Order):
    """Test request helper changes the order status to PAID

    Also check it returns a success url with order number"""
    order.status = OrderStatus.OFFERED
    order.order_number = "abc123"
    order.save()

    rf = RequestFactory()
    request = rf.get("/payments/success/", success_params)
    payment_provider = create_bambora_provider(provider_base_config, request)
    returned = payment_provider.handle_success_request()

    order_after = Order.objects.get(
        order_number=success_params.get("ORDER_NUMBER").split("-")[0])
    assert order_after.status == OrderStatus.PAID
    assert order_after.lease.application.status == ApplicationStatus.HANDLED

    assert isinstance(returned, HttpResponse)
    url = returned.url
    assert "/payment-result" in url
    assert "payment_status=success" in url
def test_handle_success_request_payment_failed(provider_base_config, order):
    """Test request helper changes the order status to rejected and returns a failure url"""
    order.status = OrderStatus.OFFERED
    order.order_number = "abc123-1602145394.662132"
    order.save()

    params = {
        "VENE_UI_RETURN_URL": "http%3A%2F%2F127.0.0.1%3A8000%2Fv1",
        "AUTHCODE":
        "ED754E8F2E7FE0CC269B9F6A1C197F19B8393F37A1B63BE1E889D53F87A5FCA1",
        "RETURN_CODE": "1",
        "ORDER_NUMBER": "abc123-1602145394.662132",
        "SETTLED": "1",
    }
    rf = RequestFactory()
    request = rf.get("/payments/success/", params)
    payment_provider = create_bambora_provider(provider_base_config, request)
    returned = payment_provider.handle_success_request()
    order_after = Order.objects.get(order_number=params.get("ORDER_NUMBER"))
    assert order_after.status == OrderStatus.OFFERED
    assert isinstance(returned, HttpResponse)
    assert "payment_status=failure" in returned.url
def test_duplicate_payments_tokens_cancelled_notify_payment(
        provider_base_config, order: Order):
    # Fake the Payment initiate flow
    order.status = OrderStatus.OFFERED
    order.order_number = "abc123"
    order.save()

    request = RequestFactory().request()
    payment_provider = create_bambora_provider(provider_base_config, request)

    assert OrderToken.objects.count() == 0

    with mock.patch(
            "payments.providers.bambora_payform.requests.post",
            side_effect=mocked_response_create,
    ) as mock_call:
        payment_provider.initiate_payment(order)
        assert OrderToken.objects.count() == 1
        token = OrderToken.objects.first()

        assert token.order == order
        assert token.valid_until.isoformat(
        ) == "2019-01-20T21:59:59.999999+00:00"

        # Try to pay again 7 days after the day when the payment was created
        with freeze_time("2019-01-21T08:00:00Z"):
            url = payment_provider.initiate_payment(order)

        # Verify that the return URL passed includes the application language
        assert order.lease.application.language in mock_call.call_args.kwargs.get(
            "json").get("payment_method").get("return_url")

    assert OrderToken.objects.count() == 2

    assert url.startswith(payment_provider.url_payment_api)
    assert "token/token123" in url

    # Fake the part where Bambora notifies the success/failure
    params = {
        "VENE_UI_RETURN_URL": "http%3A%2F%2F127.0.0.1%3A8000%2Fv1",
        "AUTHCODE":
        "DD789BA71ACD627892517745AF4C4CE2068F006C602CD54264E1FC5E4C2EE6CF",
        "RETURN_CODE": "1",
        "ORDER_NUMBER": "abc123-1602145394.662132",
    }
    rf = RequestFactory()
    request = rf.get("/payments/success/", params)
    payment_provider = create_bambora_provider(provider_base_config, request)
    payment_provider.handle_notify_request()

    order_after = Order.objects.get(
        order_number=params.get("ORDER_NUMBER").split("-")[0])

    assert all([token.cancelled for token in order_after.tokens.all()])

    with mock.patch(
            "payments.providers.bambora_payform.requests.post",
            side_effect=mocked_response_create,
    ):
        with freeze_time("2019-01-31T08:00:00Z"):
            payment_provider.initiate_payment(order)

    # The last token shouldn't be cancelled
    assert OrderToken.objects.count() == 3
    assert OrderToken.objects.filter(cancelled=True).count() == 2
    assert OrderToken.objects.filter(cancelled=False).count() == 1