diff --git a/api/specs/web-server/_wallets.py b/api/specs/web-server/_wallets.py index 6937661f9fe..03488518d28 100644 --- a/api/specs/web-server/_wallets.py +++ b/api/specs/web-server/_wallets.py @@ -72,6 +72,8 @@ async def update_wallet(wallet_id: WalletID, body: PutWalletBodyParams): @router.post( "/wallets/{wallet_id}/payments", response_model=Envelope[WalletPaymentCreated], + response_description="Successfully initialized", + status_code=status.HTTP_202_ACCEPTED, ) async def create_payment(wallet_id: WalletID, body: CreateWalletPayment): """Creates payment to wallet `wallet_id`""" @@ -87,6 +89,7 @@ async def list_all_payments(params: Annotated[PageQueryParameters, Depends()]): @router.post( "/wallets/{wallet_id}/payments/{payment_id}:cancel", + response_description="Successfully cancelled", status_code=status.HTTP_204_NO_CONTENT, ) async def cancel_payment(wallet_id: WalletID, payment_id: PaymentID): @@ -99,6 +102,8 @@ async def cancel_payment(wallet_id: WalletID, payment_id: PaymentID): @router.post( "/wallets/{wallet_id}/payments-methods:init", response_model=Envelope[PaymentMethodInit], + response_description="Successfully initialized", + status_code=status.HTTP_202_ACCEPTED, ) async def init_creation_of_payment_method(wallet_id: WalletID): ... diff --git a/packages/models-library/src/models_library/api_schemas_webserver/wallets.py b/packages/models-library/src/models_library/api_schemas_webserver/wallets.py index 9eafa4f27e0..7d15ad91a79 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/wallets.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/wallets.py @@ -102,6 +102,24 @@ class Config(OutputSchema.Config): } +class PaymentMethodTransaction(OutputSchema): + # Used ONLY in socketio interface + wallet_id: WalletID + payment_method_id: PaymentMethodID + state: Literal["PENDING", "SUCCESS", "FAILED", "CANCELED"] + + class Config(OutputSchema.Config): + schema_extra: ClassVar[dict[str, Any]] = { + "examples": [ + { + "walletId": 1, + "paymentMethodId": "pm_0987654321", + "state": "SUCCESS", + } + ] + } + + class PaymentMethodGet(OutputSchema): idr: PaymentMethodID wallet_id: WalletID diff --git a/services/payments/VERSION b/services/payments/VERSION index 3eefcb9dd5b..9084fa2f716 100644 --- a/services/payments/VERSION +++ b/services/payments/VERSION @@ -1 +1 @@ -1.0.0 +1.1.0 diff --git a/services/payments/openapi.json b/services/payments/openapi.json index e307f8091b1..f51f7b5b87e 100644 --- a/services/payments/openapi.json +++ b/services/payments/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "simcore-service-payments web API", "description": " Service that manages creation and validation of registration payments", - "version": "1.0.0" + "version": "1.1.0" }, "paths": { "/": { @@ -217,6 +217,15 @@ "message": { "type": "string", "title": "Message" + }, + "saved": { + "allOf": [ + { + "$ref": "#/components/schemas/SavedPaymentMethod" + } + ], + "title": "Saved", + "description": "If the user decided to save the payment methodafter payment it returns the payment-method acknoledgement response.Otherwise it defaults to None." } }, "type": "object", @@ -226,8 +235,20 @@ "title": "AckPayment" }, "AckPaymentMethod": { - "properties": {}, + "properties": { + "success": { + "type": "boolean", + "title": "Success" + }, + "message": { + "type": "string", + "title": "Message" + } + }, "type": "object", + "required": [ + "success" + ], "title": "AckPaymentMethod" }, "Body_login_to_create_access_token": { @@ -311,6 +332,29 @@ "docs_url": "https://foo.io/doc" } }, + "SavedPaymentMethod": { + "properties": { + "success": { + "type": "boolean", + "title": "Success" + }, + "message": { + "type": "string", + "title": "Message" + }, + "payment_method_id": { + "type": "string", + "format": "uuid", + "title": "Payment Method Id" + } + }, + "type": "object", + "required": [ + "success", + "payment_method_id" + ], + "title": "SavedPaymentMethod" + }, "Token": { "properties": { "access_token": { diff --git a/services/payments/setup.cfg b/services/payments/setup.cfg index 8567d7efe3e..b0babddbf41 100644 --- a/services/payments/setup.cfg +++ b/services/payments/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.0.0 +current_version = 1.1.0 commit = True message = services/payments version: {current_version} → {new_version} tag = False @@ -9,5 +9,5 @@ commit_args = --no-verify [tool:pytest] asyncio_mode = auto -markers = +markers = testit: "marks test to run during development" diff --git a/services/payments/src/simcore_service_payments/models/schemas/acknowledgements.py b/services/payments/src/simcore_service_payments/models/schemas/acknowledgements.py index 7a4dc7ffc56..46e9ac6c08f 100644 --- a/services/payments/src/simcore_service_payments/models/schemas/acknowledgements.py +++ b/services/payments/src/simcore_service_payments/models/schemas/acknowledgements.py @@ -8,12 +8,21 @@ class _BaseAck(BaseModel): message: str = Field(default=None) -class AckPayment(_BaseAck): +class AckPaymentMethod(_BaseAck): ... -class AckPaymentMethod(BaseModel): - ... +class SavedPaymentMethod(AckPaymentMethod): + payment_method_id: PaymentMethodID + + +class AckPayment(_BaseAck): + saved: SavedPaymentMethod | None = Field( + default=None, + description="If the user decided to save the payment method" + "after payment it returns the payment-method acknoledgement response." + "Otherwise it defaults to None.", + ) assert PaymentID # nosec diff --git a/services/web/server/requirements/_base.in b/services/web/server/requirements/_base.in index 96b44fce5f8..0ae782e55a1 100644 --- a/services/web/server/requirements/_base.in +++ b/services/web/server/requirements/_base.in @@ -32,6 +32,7 @@ aiosmtplib # email asyncpg # db cryptography # security expiringdict +faker # Only used in dev-mode for proof-of-concepts gunicorn[setproctitle] jinja_app_loader # email json2html diff --git a/services/web/server/requirements/_base.txt b/services/web/server/requirements/_base.txt index e16e0bdc102..d3037c0d2d5 100644 --- a/services/web/server/requirements/_base.txt +++ b/services/web/server/requirements/_base.txt @@ -167,6 +167,8 @@ et-xmlfile==1.1.0 # via openpyxl expiringdict==1.2.1 # via -r requirements/_base.in +faker==19.6.1 + # via -r requirements/_base.in frozenlist==1.3.0 # via # aiohttp @@ -338,7 +340,9 @@ pyjwt==2.4.0 pyrsistent==0.18.1 # via jsonschema python-dateutil==2.8.2 - # via arrow + # via + # arrow + # faker python-engineio==4.3.4 # via python-socketio python-magic==0.4.25 diff --git a/services/web/server/requirements/_test.txt b/services/web/server/requirements/_test.txt index 1c9e06a89c7..f910911d19d 100644 --- a/services/web/server/requirements/_test.txt +++ b/services/web/server/requirements/_test.txt @@ -59,7 +59,9 @@ exceptiongroup==1.1.3 execnet==2.0.2 # via pytest-xdist faker==19.6.1 - # via -r requirements/_test.in + # via + # -c requirements/_base.txt + # -r requirements/_test.in flaky==3.7.0 # via -r requirements/_test.in frozenlist==1.3.0 diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index 515f6527371..49e55bfd7a0 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -3806,8 +3806,8 @@ paths: $ref: '#/components/schemas/CreateWalletPayment' required: true responses: - '200': - description: Successful Response + '202': + description: Successfully initialized content: application/json: schema: @@ -3870,7 +3870,7 @@ paths: in: path responses: '204': - description: Successful Response + description: Successfully cancelled /v0/wallets/{wallet_id}/payments-methods:init: post: tags: @@ -3887,12 +3887,12 @@ paths: name: wallet_id in: path responses: - '200': - description: Successful Response + '202': + description: Successfully initialized content: application/json: schema: - $ref: '#/components/schemas/Envelope_CreatePaymentMethodInitiated_' + $ref: '#/components/schemas/Envelope_PaymentMethodInit_' /v0/wallets/{wallet_id}/payments-methods/{payment_method_id}:cancel: post: tags: @@ -4777,6 +4777,7 @@ components: enum: - AWS - ON_PREMISE + - ON_DEMAND type: string description: An enumeration. CodePageParams: @@ -4804,30 +4805,6 @@ components: title: Cluster Id minimum: 0 type: integer - CreatePaymentMethodInitiated: - title: CreatePaymentMethodInitiated - required: - - walletId - - paymentMethodId - - paymentMethodFormUrl - type: object - properties: - walletId: - title: Walletid - exclusiveMinimum: true - type: integer - minimum: 0 - paymentMethodId: - title: Paymentmethodid - minLength: 1 - type: string - paymentMethodFormUrl: - title: Paymentmethodformurl - maxLength: 2083 - minLength: 1 - type: string - description: Link to external site that holds the payment submission form - format: uri CreateWalletBodyParams: title: CreateWalletBodyParams required: @@ -4997,14 +4974,6 @@ components: $ref: '#/components/schemas/ComputationTaskGet' error: title: Error - Envelope_CreatePaymentMethodInitiated_: - title: Envelope[CreatePaymentMethodInitiated] - type: object - properties: - data: - $ref: '#/components/schemas/CreatePaymentMethodInitiated' - error: - title: Error Envelope_Error_: title: Envelope[Error] type: object @@ -5117,6 +5086,14 @@ components: $ref: '#/components/schemas/PaymentMethodGet' error: title: Error + Envelope_PaymentMethodInit_: + title: Envelope[PaymentMethodInit] + type: object + properties: + data: + $ref: '#/components/schemas/PaymentMethodInit' + error: + title: Error Envelope_PresignedLink_: title: Envelope[PresignedLink] type: object @@ -6906,6 +6883,30 @@ components: title: Created type: string format: date-time + PaymentMethodInit: + title: PaymentMethodInit + required: + - walletId + - paymentMethodId + - paymentMethodFormUrl + type: object + properties: + walletId: + title: Walletid + exclusiveMinimum: true + type: integer + minimum: 0 + paymentMethodId: + title: Paymentmethodid + minLength: 1 + type: string + paymentMethodFormUrl: + title: Paymentmethodformurl + maxLength: 2083 + minLength: 1 + type: string + description: Link to external site that holds the payment submission form + format: uri PaymentTransaction: title: PaymentTransaction required: diff --git a/services/web/server/src/simcore_service_webserver/payments/_methods_api.py b/services/web/server/src/simcore_service_webserver/payments/_methods_api.py index ce4c0f230a1..af31aa9f6ff 100644 --- a/services/web/server/src/simcore_service_webserver/payments/_methods_api.py +++ b/services/web/server/src/simcore_service_webserver/payments/_methods_api.py @@ -6,10 +6,12 @@ import arrow from aiohttp import web +from faker import Faker from models_library.api_schemas_webserver.wallets import ( PaymentMethodGet, PaymentMethodID, PaymentMethodInit, + PaymentMethodTransaction, ) from models_library.users import UserID from models_library.wallets import WalletID @@ -25,11 +27,26 @@ list_successful_payment_methods, udpate_payment_method, ) +from ._socketio import notify_payment_method_acked from .settings import PaymentsSettings, get_plugin_settings _logger = logging.getLogger(__name__) +def _generate_fake_data(fake: Faker): + return { + "idr": fake.uuid4(), + "card_holder_name": fake.name(), + "card_number_masked": f"**** **** **** {fake.credit_card_number()[:4]}", + "card_type": fake.credit_card_provider(), + "expiration_month": fake.random_int(min=1, max=12), + "expiration_year": fake.future_date().year, + "street_address": fake.street_address(), + "zipcode": fake.zipcode(), + "country": fake.country(), + } + + def _to_api_model( entry: PaymentsMethodsDB, payment_method_details_from_gateway: dict[str, Any] ) -> PaymentMethodGet: @@ -112,6 +129,17 @@ async def _complete_create_of_wallet_payment_method( state_message=message, ) + # notify front-end + await notify_payment_method_acked( + app, + user_id=updated.user_id, + payment_method_transaction=PaymentMethodTransaction( + wallet_id=updated.wallet_id, + payment_method_id=updated.payment_method_id, + state=updated.state.value, + ), + ) + return updated @@ -136,8 +164,16 @@ async def cancel_creation_of_wallet_payment_method( "FAKE Payments Gateway: DELETE /payment-methods/%s", payment_method_id ) await asyncio.sleep(1) + # response is OK # ----- + await delete_payment_method( + app, + user_id=user_id, + wallet_id=wallet_id, + payment_method_id=payment_method_id, + ) + async def list_wallet_payment_methods( app: web.Application, *, user_id: UserID, wallet_id: WalletID @@ -161,16 +197,13 @@ async def list_wallet_payment_methods( ) await asyncio.sleep(1) - # returns response bodies - # SEE services/payments/src/simcore_service_payments/models/payments_gateway.py - payment_method_details_from_gateway = PaymentMethodGet.Config.schema_extra[ - "examples" - ][0] - + # response + fake = Faker() + fake.seed_instance(user_id) payments_methods: list[PaymentMethodGet] = [ _to_api_model( ack, - payment_method_details_from_gateway, + payment_method_details_from_gateway=_generate_fake_data(fake), ) for ack in acked ] @@ -198,10 +231,12 @@ async def get_wallet_payment_method( "FAKE Payments Gateway: GET /payment-methods/%s", acked.payment_method_id ) await asyncio.sleep(1) - payment_method_details_from_gateway = PaymentMethodGet.Config.schema_extra[ - "examples" - ][0] - return _to_api_model(acked, payment_method_details_from_gateway) + # response + fake = Faker() + fake.seed_instance(user_id) + return _to_api_model( + acked, payment_method_details_from_gateway=_generate_fake_data(fake) + ) # ----- @@ -225,6 +260,7 @@ async def delete_wallet_payment_method( "FAKE Payments Gateway: DELETE /payment-methods/%s", acked.payment_method_id ) await asyncio.sleep(1) + # response is OK # ------ # delete since it was deleted from gateway diff --git a/services/web/server/src/simcore_service_webserver/payments/_methods_db.py b/services/web/server/src/simcore_service_webserver/payments/_methods_db.py index a756fb67d04..b5838eb171c 100644 --- a/services/web/server/src/simcore_service_webserver/payments/_methods_db.py +++ b/services/web/server/src/simcore_service_webserver/payments/_methods_db.py @@ -26,7 +26,6 @@ _logger = logging.getLogger(__name__) -# class PaymentsMethodsDB(BaseModel): payment_method_id: PaymentMethodID user_id: UserID diff --git a/services/web/server/src/simcore_service_webserver/payments/_socketio.py b/services/web/server/src/simcore_service_webserver/payments/_socketio.py index 8e9b2d3c103..faa4f9a2dda 100644 --- a/services/web/server/src/simcore_service_webserver/payments/_socketio.py +++ b/services/web/server/src/simcore_service_webserver/payments/_socketio.py @@ -1,10 +1,14 @@ from aiohttp import web -from models_library.api_schemas_webserver.wallets import PaymentTransaction +from models_library.api_schemas_webserver.wallets import ( + PaymentMethodTransaction, + PaymentTransaction, +) from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder from ..socketio.messages import ( SOCKET_IO_PAYMENT_COMPLETED_EVENT, + SOCKET_IO_PAYMENT_METHOD_ACKED_EVENT, SocketMessageDict, send_messages, ) @@ -25,3 +29,18 @@ async def notify_payment_completed( } ] await send_messages(app, user_id, messages) + + +async def notify_payment_method_acked( + app: web.Application, + *, + user_id: UserID, + payment_method_transaction: PaymentMethodTransaction, +): + messages: list[SocketMessageDict] = [ + { + "event_type": SOCKET_IO_PAYMENT_METHOD_ACKED_EVENT, + "data": jsonable_encoder(payment_method_transaction, by_alias=True), + } + ] + await send_messages(app, user_id, messages) diff --git a/services/web/server/src/simcore_service_webserver/payments/_tasks.py b/services/web/server/src/simcore_service_webserver/payments/_tasks.py index 39b9bd5d53f..6c95bdef610 100644 --- a/services/web/server/src/simcore_service_webserver/payments/_tasks.py +++ b/services/web/server/src/simcore_service_webserver/payments/_tasks.py @@ -30,33 +30,46 @@ _APP_TASK_KEY = f"{_PERIODIC_TASK_NAME}.task" +def _create_possible_outcomes(accepted, rejected): + return [*(accepted for _ in range(9)), rejected] + + +_POSSIBLE_PAYMENTS_OUTCOMES = _create_possible_outcomes( + accepted={ + "completion_state": PaymentTransactionState.SUCCESS, + }, + rejected={ + "completion_state": PaymentTransactionState.FAILED, + "message": "Payment rejected", + }, +) + + async def _fake_payment_completion(app: web.Application, payment_id: PaymentID): # Fakes processing time settings = get_plugin_settings(app) assert settings.PAYMENTS_FAKE_COMPLETION # nosec await asyncio.sleep(settings.PAYMENTS_FAKE_COMPLETION_DELAY_SEC) - # Three different possible outcomes - possible_outcomes = [ - # 1. Accepted - { - "completion_state": PaymentTransactionState.SUCCESS, - }, - # 2. Rejected - { - "completion_state": PaymentTransactionState.FAILED, - "message": "Payment rejected", - }, - # 3. does not complete ever ??? - ] kwargs: dict[str, Any] = random.choice( # nosec # noqa: S311 # NOSONAR - possible_outcomes + _POSSIBLE_PAYMENTS_OUTCOMES ) _logger.info("Faking payment completion as %s", kwargs) await complete_payment(app, payment_id=payment_id, **kwargs) +_POSSIBLE_PAYMENTS_METHODS_OUTCOMES = _create_possible_outcomes( + accepted={ + "completion_state": InitPromptAckFlowState.SUCCESS, + }, + rejected={ + "completion_state": InitPromptAckFlowState.FAILED, + "message": "Payment method rejected", + }, +) + + async def _fake_payment_method_completion( app: web.Application, payment_method_id: PaymentMethodID ): @@ -65,21 +78,8 @@ async def _fake_payment_method_completion( assert settings.PAYMENTS_FAKE_COMPLETION # nosec await asyncio.sleep(settings.PAYMENTS_FAKE_COMPLETION_DELAY_SEC) - # Three different possible outcomes - possible_outcomes = [ - # 1. Accepted - { - "completion_state": InitPromptAckFlowState.SUCCESS, - }, - # 2. Rejected - { - "completion_state": InitPromptAckFlowState.FAILED, - "message": "Payment method rejected", - }, - # 3. does not complete ever ??? - ] kwargs: dict[str, Any] = random.choice( # nosec # noqa: S311 # NOSONAR - possible_outcomes + _POSSIBLE_PAYMENTS_METHODS_OUTCOMES ) _logger.info("Faking payment-method completion as %s", kwargs) @@ -94,21 +94,16 @@ async def _fake_payment_method_completion( ) async def _run_resilient_task(app: web.Application): """NOTE: Resilient task: if fails, it tries foreever""" + pending = await get_pending_payment_transactions_ids(app) - _logger.debug("Pending transactions: %s", pending) + _logger.debug("Pending payment transactions: %s", pending) if pending: - asyncio.gather( - *[_fake_payment_completion(app, payment_id) for payment_id in pending] - ) + asyncio.gather(*[_fake_payment_completion(app, id_) for id_ in pending]) pending = await get_pending_payment_methods_ids(app) + _logger.debug("Pending payment-methods: %s", pending) if pending: - asyncio.gather( - *[ - _fake_payment_method_completion(app, payment_id) - for payment_id in pending - ] - ) + asyncio.gather(*[_fake_payment_method_completion(app, id_) for id_ in pending]) async def _run_periodically(app: web.Application, wait_period_s: float): diff --git a/services/web/server/src/simcore_service_webserver/socketio/messages.py b/services/web/server/src/simcore_service_webserver/socketio/messages.py index 09fff93aab0..2534c7cd0cf 100644 --- a/services/web/server/src/simcore_service_webserver/socketio/messages.py +++ b/services/web/server/src/simcore_service_webserver/socketio/messages.py @@ -24,6 +24,7 @@ SOCKET_IO_NODE_PROGRESS_EVENT: Final[str] = "nodeProgress" SOCKET_IO_NODE_UPDATED_EVENT: Final[str] = "nodeUpdated" SOCKET_IO_PAYMENT_COMPLETED_EVENT: Final[str] = "paymentCompleted" +SOCKET_IO_PAYMENT_METHOD_ACKED_EVENT: Final[str] = "paymentMethodAcknoledged" SOCKET_IO_PROJECT_PROGRESS_EVENT: Final[str] = "projectProgress" SOCKET_IO_PROJECT_UPDATED_EVENT: Final[str] = "projectStateUpdated" diff --git a/services/web/server/src/simcore_service_webserver/wallets/_payments_handlers.py b/services/web/server/src/simcore_service_webserver/wallets/_payments_handlers.py index 40b61505ba6..fd73738be97 100644 --- a/services/web/server/src/simcore_service_webserver/wallets/_payments_handlers.py +++ b/services/web/server/src/simcore_service_webserver/wallets/_payments_handlers.py @@ -195,7 +195,9 @@ async def init_creation_of_payment_method(request: web.Request): request.app, user_id=req_ctx.user_id, wallet_id=path_params.wallet_id ) - return envelope_json_response(initiated, web.HTTPCreated) + # NOTE: the request has been accepted to create a payment-method + # but it will not be completed until acked (init-promtp-ack flow) + return envelope_json_response(initiated, web.HTTPAccepted) @routes.post( diff --git a/services/web/server/tests/unit/with_dbs/03/wallets/payments/test_payments_methods.py b/services/web/server/tests/unit/with_dbs/03/wallets/payments/test_payments_methods.py index dd57e393f76..75675deb762 100644 --- a/services/web/server/tests/unit/with_dbs/03/wallets/payments/test_payments_methods.py +++ b/services/web/server/tests/unit/with_dbs/03/wallets/payments/test_payments_methods.py @@ -4,6 +4,7 @@ # pylint: disable=too-many-arguments +import pytest from aiohttp import web from aiohttp.test_utils import TestClient from models_library.api_schemas_webserver.wallets import ( @@ -12,6 +13,7 @@ WalletGet, ) from pydantic import parse_obj_as +from pytest_mock import MockerFixture from pytest_simcore.helpers.utils_assert import assert_status from simcore_postgres_database.models.payments_methods import InitPromptAckFlowState from simcore_service_webserver.payments._methods_api import ( @@ -23,9 +25,13 @@ ) +@pytest.mark.acceptance_test( + "Part of https://github.com/ITISFoundation/osparc-simcore/issues/4751" +) async def test_payment_method_worfklow( client: TestClient, logged_user_wallet: WalletGet, + mocker: MockerFixture, ): # preamble assert client.app @@ -33,13 +39,17 @@ async def test_payment_method_worfklow( assert settings.PAYMENTS_FAKE_COMPLETION is False + send_message = mocker.patch( + "simcore_service_webserver.payments._socketio.send_messages", autospec=True + ) + wallet = logged_user_wallet # init Create response = await client.post( f"/v0/wallets/{wallet.wallet_id}/payments-methods:init", ) - data, error = await assert_status(response, web.HTTPCreated) + data, error = await assert_status(response, web.HTTPAccepted) assert error is None inited = PaymentMethodInit.parse_obj(data) @@ -61,6 +71,9 @@ async def test_payment_method_worfklow( message="ACKED by test_add_payment_method_worfklow", ) + assert send_message.called + send_message.assert_called_once() + # Get response = await client.get( f"/v0/wallets/{wallet.wallet_id}/payments-methods/{inited.payment_method_id}" @@ -82,6 +95,13 @@ async def test_payment_method_worfklow( ) await assert_status(response, web.HTTPNoContent) + # Get -> NOT FOUND + response = await client.get( + f"/v0/wallets/{wallet.wallet_id}/payments-methods/{inited.payment_method_id}" + ) + data, _ = await assert_status(response, web.HTTPNotFound) + + # List -> empty response = await client.get(f"/v0/wallets/{wallet.wallet_id}/payments-methods") data, _ = await assert_status(response, web.HTTPOk) assert not data @@ -97,7 +117,7 @@ async def test_init_and_cancel_payment_method( response = await client.post( f"/v0/wallets/{wallet.wallet_id}/payments-methods:init", ) - data, error = await assert_status(response, web.HTTPCreated) + data, error = await assert_status(response, web.HTTPAccepted) assert error is None inited = PaymentMethodInit.parse_obj(data)