canvas 0.53.1__py3-none-any.whl → 0.54.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of canvas might be problematic. Click here for more details.
- {canvas-0.53.1.dist-info → canvas-0.54.0.dist-info}/METADATA +1 -1
- {canvas-0.53.1.dist-info → canvas-0.54.0.dist-info}/RECORD +32 -28
- canvas_cli/apps/plugin/plugin.py +40 -8
- canvas_generated/messages/effects_pb2.py +2 -2
- canvas_generated/messages/effects_pb2.pyi +12 -0
- canvas_generated/messages/events_pb2.py +2 -2
- canvas_generated/messages/events_pb2.pyi +12 -0
- canvas_generated/messages/plugins_pb2.py +9 -1
- canvas_generated/messages/plugins_pb2.pyi +24 -0
- canvas_generated/services/plugin_runner_pb2.py +2 -2
- canvas_generated/services/plugin_runner_pb2_grpc.py +66 -0
- canvas_sdk/caching/client.py +3 -1
- canvas_sdk/caching/plugins.py +7 -4
- canvas_sdk/effects/payment_processor.py +137 -0
- canvas_sdk/handlers/payment_processors/__init__.py +1 -0
- canvas_sdk/handlers/payment_processors/base.py +93 -0
- canvas_sdk/handlers/payment_processors/card.py +200 -0
- canvas_sdk/questionnaires/utils.py +2 -2
- canvas_sdk/templates/utils.py +5 -3
- canvas_sdk/utils/plugins.py +33 -20
- canvas_sdk/v1/data/patient.py +7 -0
- plugin_runner/allowed-module-imports.json +14 -0
- plugin_runner/exceptions.py +4 -0
- plugin_runner/installation.py +43 -14
- plugin_runner/plugin_runner.py +135 -18
- plugin_runner/sandbox.py +1 -0
- protobufs/canvas_generated/messages/effects.proto +8 -0
- protobufs/canvas_generated/messages/events.proto +8 -0
- protobufs/canvas_generated/messages/plugins.proto +16 -1
- protobufs/canvas_generated/services/plugin_runner.proto +4 -0
- {canvas-0.53.1.dist-info → canvas-0.54.0.dist-info}/WHEEL +0 -0
- {canvas-0.53.1.dist-info → canvas-0.54.0.dist-info}/entry_points.txt +0 -0
|
@@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'canvas_generated/messages/plugins.proto\"\x16\n\x14ReloadPluginsRequest\"(\n\x15ReloadPluginsResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x62\x06proto3')
|
|
17
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\'canvas_generated/messages/plugins.proto\"\x16\n\x14ReloadPluginsRequest\"(\n\x15ReloadPluginsResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"%\n\x13ReloadPluginRequest\x12\x0e\n\x06plugin\x18\x01 \x01(\t\"\'\n\x14ReloadPluginResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"%\n\x13UnloadPluginRequest\x12\x0e\n\x06plugin\x18\x01 \x01(\t\"\'\n\x14UnloadPluginResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x62\x06proto3')
|
|
18
18
|
|
|
19
19
|
_globals = globals()
|
|
20
20
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -25,4 +25,12 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|
|
25
25
|
_globals['_RELOADPLUGINSREQUEST']._serialized_end=65
|
|
26
26
|
_globals['_RELOADPLUGINSRESPONSE']._serialized_start=67
|
|
27
27
|
_globals['_RELOADPLUGINSRESPONSE']._serialized_end=107
|
|
28
|
+
_globals['_RELOADPLUGINREQUEST']._serialized_start=109
|
|
29
|
+
_globals['_RELOADPLUGINREQUEST']._serialized_end=146
|
|
30
|
+
_globals['_RELOADPLUGINRESPONSE']._serialized_start=148
|
|
31
|
+
_globals['_RELOADPLUGINRESPONSE']._serialized_end=187
|
|
32
|
+
_globals['_UNLOADPLUGINREQUEST']._serialized_start=189
|
|
33
|
+
_globals['_UNLOADPLUGINREQUEST']._serialized_end=226
|
|
34
|
+
_globals['_UNLOADPLUGINRESPONSE']._serialized_start=228
|
|
35
|
+
_globals['_UNLOADPLUGINRESPONSE']._serialized_end=267
|
|
28
36
|
# @@protoc_insertion_point(module_scope)
|
|
@@ -13,3 +13,27 @@ class ReloadPluginsResponse(_message.Message):
|
|
|
13
13
|
SUCCESS_FIELD_NUMBER: _ClassVar[int]
|
|
14
14
|
success: bool
|
|
15
15
|
def __init__(self, success: bool = ...) -> None: ...
|
|
16
|
+
|
|
17
|
+
class ReloadPluginRequest(_message.Message):
|
|
18
|
+
__slots__ = ("plugin",)
|
|
19
|
+
PLUGIN_FIELD_NUMBER: _ClassVar[int]
|
|
20
|
+
plugin: str
|
|
21
|
+
def __init__(self, plugin: _Optional[str] = ...) -> None: ...
|
|
22
|
+
|
|
23
|
+
class ReloadPluginResponse(_message.Message):
|
|
24
|
+
__slots__ = ("success",)
|
|
25
|
+
SUCCESS_FIELD_NUMBER: _ClassVar[int]
|
|
26
|
+
success: bool
|
|
27
|
+
def __init__(self, success: bool = ...) -> None: ...
|
|
28
|
+
|
|
29
|
+
class UnloadPluginRequest(_message.Message):
|
|
30
|
+
__slots__ = ("plugin",)
|
|
31
|
+
PLUGIN_FIELD_NUMBER: _ClassVar[int]
|
|
32
|
+
plugin: str
|
|
33
|
+
def __init__(self, plugin: _Optional[str] = ...) -> None: ...
|
|
34
|
+
|
|
35
|
+
class UnloadPluginResponse(_message.Message):
|
|
36
|
+
__slots__ = ("success",)
|
|
37
|
+
SUCCESS_FIELD_NUMBER: _ClassVar[int]
|
|
38
|
+
success: bool
|
|
39
|
+
def __init__(self, success: bool = ...) -> None: ...
|
|
@@ -16,7 +16,7 @@ from canvas_generated.messages import events_pb2 as canvas__generated_dot_messag
|
|
|
16
16
|
from canvas_generated.messages import plugins_pb2 as canvas__generated_dot_messages_dot_plugins__pb2
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n-canvas_generated/services/plugin_runner.proto\x12\x06\x63\x61nvas\x1a&canvas_generated/messages/events.proto\x1a\'canvas_generated/messages/plugins.proto2\
|
|
19
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n-canvas_generated/services/plugin_runner.proto\x12\x06\x63\x61nvas\x1a&canvas_generated/messages/events.proto\x1a\'canvas_generated/messages/plugins.proto2\x85\x02\n\x0cPluginRunner\x12\x35\n\x0bHandleEvent\x12\r.canvas.Event\x1a\x15.canvas.EventResponse0\x01\x12@\n\rReloadPlugins\x12\x15.ReloadPluginsRequest\x1a\x16.ReloadPluginsResponse0\x01\x12=\n\x0cReloadPlugin\x12\x14.ReloadPluginRequest\x1a\x15.ReloadPluginResponse0\x01\x12=\n\x0cUnloadPlugin\x12\x14.UnloadPluginRequest\x1a\x15.UnloadPluginResponse0\x01\x62\x06proto3')
|
|
20
20
|
|
|
21
21
|
_globals = globals()
|
|
22
22
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -24,5 +24,5 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'canvas_generated.services.p
|
|
|
24
24
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
|
25
25
|
DESCRIPTOR._options = None
|
|
26
26
|
_globals['_PLUGINRUNNER']._serialized_start=139
|
|
27
|
-
_globals['_PLUGINRUNNER']._serialized_end=
|
|
27
|
+
_globals['_PLUGINRUNNER']._serialized_end=400
|
|
28
28
|
# @@protoc_insertion_point(module_scope)
|
|
@@ -25,6 +25,16 @@ class PluginRunnerStub(object):
|
|
|
25
25
|
request_serializer=canvas__generated_dot_messages_dot_plugins__pb2.ReloadPluginsRequest.SerializeToString,
|
|
26
26
|
response_deserializer=canvas__generated_dot_messages_dot_plugins__pb2.ReloadPluginsResponse.FromString,
|
|
27
27
|
)
|
|
28
|
+
self.ReloadPlugin = channel.unary_stream(
|
|
29
|
+
'/canvas.PluginRunner/ReloadPlugin',
|
|
30
|
+
request_serializer=canvas__generated_dot_messages_dot_plugins__pb2.ReloadPluginRequest.SerializeToString,
|
|
31
|
+
response_deserializer=canvas__generated_dot_messages_dot_plugins__pb2.ReloadPluginResponse.FromString,
|
|
32
|
+
)
|
|
33
|
+
self.UnloadPlugin = channel.unary_stream(
|
|
34
|
+
'/canvas.PluginRunner/UnloadPlugin',
|
|
35
|
+
request_serializer=canvas__generated_dot_messages_dot_plugins__pb2.UnloadPluginRequest.SerializeToString,
|
|
36
|
+
response_deserializer=canvas__generated_dot_messages_dot_plugins__pb2.UnloadPluginResponse.FromString,
|
|
37
|
+
)
|
|
28
38
|
|
|
29
39
|
|
|
30
40
|
class PluginRunnerServicer(object):
|
|
@@ -42,6 +52,18 @@ class PluginRunnerServicer(object):
|
|
|
42
52
|
context.set_details('Method not implemented!')
|
|
43
53
|
raise NotImplementedError('Method not implemented!')
|
|
44
54
|
|
|
55
|
+
def ReloadPlugin(self, request, context):
|
|
56
|
+
"""Missing associated documentation comment in .proto file."""
|
|
57
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
58
|
+
context.set_details('Method not implemented!')
|
|
59
|
+
raise NotImplementedError('Method not implemented!')
|
|
60
|
+
|
|
61
|
+
def UnloadPlugin(self, request, context):
|
|
62
|
+
"""Missing associated documentation comment in .proto file."""
|
|
63
|
+
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
|
64
|
+
context.set_details('Method not implemented!')
|
|
65
|
+
raise NotImplementedError('Method not implemented!')
|
|
66
|
+
|
|
45
67
|
|
|
46
68
|
def add_PluginRunnerServicer_to_server(servicer, server):
|
|
47
69
|
rpc_method_handlers = {
|
|
@@ -55,6 +77,16 @@ def add_PluginRunnerServicer_to_server(servicer, server):
|
|
|
55
77
|
request_deserializer=canvas__generated_dot_messages_dot_plugins__pb2.ReloadPluginsRequest.FromString,
|
|
56
78
|
response_serializer=canvas__generated_dot_messages_dot_plugins__pb2.ReloadPluginsResponse.SerializeToString,
|
|
57
79
|
),
|
|
80
|
+
'ReloadPlugin': grpc.unary_stream_rpc_method_handler(
|
|
81
|
+
servicer.ReloadPlugin,
|
|
82
|
+
request_deserializer=canvas__generated_dot_messages_dot_plugins__pb2.ReloadPluginRequest.FromString,
|
|
83
|
+
response_serializer=canvas__generated_dot_messages_dot_plugins__pb2.ReloadPluginResponse.SerializeToString,
|
|
84
|
+
),
|
|
85
|
+
'UnloadPlugin': grpc.unary_stream_rpc_method_handler(
|
|
86
|
+
servicer.UnloadPlugin,
|
|
87
|
+
request_deserializer=canvas__generated_dot_messages_dot_plugins__pb2.UnloadPluginRequest.FromString,
|
|
88
|
+
response_serializer=canvas__generated_dot_messages_dot_plugins__pb2.UnloadPluginResponse.SerializeToString,
|
|
89
|
+
),
|
|
58
90
|
}
|
|
59
91
|
generic_handler = grpc.method_handlers_generic_handler(
|
|
60
92
|
'canvas.PluginRunner', rpc_method_handlers)
|
|
@@ -98,3 +130,37 @@ class PluginRunner(object):
|
|
|
98
130
|
canvas__generated_dot_messages_dot_plugins__pb2.ReloadPluginsResponse.FromString,
|
|
99
131
|
options, channel_credentials,
|
|
100
132
|
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def ReloadPlugin(request,
|
|
136
|
+
target,
|
|
137
|
+
options=(),
|
|
138
|
+
channel_credentials=None,
|
|
139
|
+
call_credentials=None,
|
|
140
|
+
insecure=False,
|
|
141
|
+
compression=None,
|
|
142
|
+
wait_for_ready=None,
|
|
143
|
+
timeout=None,
|
|
144
|
+
metadata=None):
|
|
145
|
+
return grpc.experimental.unary_stream(request, target, '/canvas.PluginRunner/ReloadPlugin',
|
|
146
|
+
canvas__generated_dot_messages_dot_plugins__pb2.ReloadPluginRequest.SerializeToString,
|
|
147
|
+
canvas__generated_dot_messages_dot_plugins__pb2.ReloadPluginResponse.FromString,
|
|
148
|
+
options, channel_credentials,
|
|
149
|
+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def UnloadPlugin(request,
|
|
153
|
+
target,
|
|
154
|
+
options=(),
|
|
155
|
+
channel_credentials=None,
|
|
156
|
+
call_credentials=None,
|
|
157
|
+
insecure=False,
|
|
158
|
+
compression=None,
|
|
159
|
+
wait_for_ready=None,
|
|
160
|
+
timeout=None,
|
|
161
|
+
metadata=None):
|
|
162
|
+
return grpc.experimental.unary_stream(request, target, '/canvas.PluginRunner/UnloadPlugin',
|
|
163
|
+
canvas__generated_dot_messages_dot_plugins__pb2.UnloadPluginRequest.SerializeToString,
|
|
164
|
+
canvas__generated_dot_messages_dot_plugins__pb2.UnloadPluginResponse.FromString,
|
|
165
|
+
options, channel_credentials,
|
|
166
|
+
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
canvas_sdk/caching/client.py
CHANGED
|
@@ -8,7 +8,9 @@ caches: dict[tuple[str, str], Cache] = {}
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def get_cache(
|
|
11
|
-
driver: str = "default",
|
|
11
|
+
driver: str = "default",
|
|
12
|
+
prefix: str = "",
|
|
13
|
+
max_timeout_seconds: int | None = None,
|
|
12
14
|
) -> Cache:
|
|
13
15
|
"""Get the cache client based on the specified driver."""
|
|
14
16
|
try:
|
canvas_sdk/caching/plugins.py
CHANGED
|
@@ -3,18 +3,21 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
5
|
from canvas_sdk.caching.client import get_cache as get_cache_client
|
|
6
|
-
from canvas_sdk.utils.plugins import
|
|
6
|
+
from canvas_sdk.utils.plugins import plugin_context
|
|
7
7
|
from settings import CANVAS_SDK_CACHE_TIMEOUT_SECONDS
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from canvas_sdk.caching.base import Cache
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
@
|
|
13
|
+
@plugin_context
|
|
14
14
|
def get_cache(**kwargs: Any) -> Cache:
|
|
15
15
|
"""Get the cache client for plugins."""
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
return get_cache_client(
|
|
17
|
+
driver="plugins",
|
|
18
|
+
prefix=kwargs["plugin_name"],
|
|
19
|
+
max_timeout_seconds=CANVAS_SDK_CACHE_TIMEOUT_SECONDS,
|
|
20
|
+
)
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
__exports__ = ("get_cache",)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from canvas_sdk.effects import EffectType, _BaseEffect
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PaymentProcessorMetadata(_BaseEffect):
|
|
8
|
+
"""PaymentProcessorInfo effect class."""
|
|
9
|
+
|
|
10
|
+
class Meta:
|
|
11
|
+
effect_type = EffectType.REVENUE__PAYMENT_PROCESSOR__METADATA
|
|
12
|
+
|
|
13
|
+
class PaymentProcessorType(StrEnum):
|
|
14
|
+
"""Enum for payment processor types."""
|
|
15
|
+
|
|
16
|
+
CARD = "card"
|
|
17
|
+
|
|
18
|
+
identifier: str
|
|
19
|
+
type: PaymentProcessorType
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def values(self) -> dict[str, Any]:
|
|
23
|
+
"""Return the values of the PaymentProcessorMetadata."""
|
|
24
|
+
return {
|
|
25
|
+
"identifier": self.identifier,
|
|
26
|
+
"type": self.type.value,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PaymentProcessorForm(_BaseEffect):
|
|
31
|
+
"""PaymentProcessorForm effect class."""
|
|
32
|
+
|
|
33
|
+
class Meta:
|
|
34
|
+
effect_type = EffectType.REVENUE__PAYMENT_PROCESSOR__FORM
|
|
35
|
+
|
|
36
|
+
intent: str
|
|
37
|
+
content: str
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def values(self) -> dict[str, Any]:
|
|
41
|
+
"""Return the values of the PaymentProcessorMetadata."""
|
|
42
|
+
return {
|
|
43
|
+
"intent": self.intent,
|
|
44
|
+
"content": self.content,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CardTransaction(_BaseEffect):
|
|
49
|
+
"""CardTransaction effect class."""
|
|
50
|
+
|
|
51
|
+
class Meta:
|
|
52
|
+
effect_type = EffectType.REVENUE__PAYMENT_PROCESSOR__CREDIT_CARD_TRANSACTION
|
|
53
|
+
|
|
54
|
+
success: bool
|
|
55
|
+
transaction_id: str | None
|
|
56
|
+
error_code: str | None = None
|
|
57
|
+
api_response: dict
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def values(self) -> dict[str, Any]:
|
|
61
|
+
"""Return the values of the CreditCardTransaction."""
|
|
62
|
+
return {
|
|
63
|
+
"success": self.success,
|
|
64
|
+
"transaction_id": self.transaction_id,
|
|
65
|
+
"api_response": self.api_response,
|
|
66
|
+
"error_code": self.error_code,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class PaymentMethod(_BaseEffect):
|
|
71
|
+
"""PaymentMethod effect class."""
|
|
72
|
+
|
|
73
|
+
class Meta:
|
|
74
|
+
effect_type = EffectType.REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHOD
|
|
75
|
+
|
|
76
|
+
payment_method_id: str
|
|
77
|
+
card_holder_name: str | None
|
|
78
|
+
brand: str
|
|
79
|
+
postal_code: str | None = None
|
|
80
|
+
country: str | None = None
|
|
81
|
+
expiration_year: int
|
|
82
|
+
expiration_month: int
|
|
83
|
+
card_last_four_digits: str
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def values(self) -> dict[str, Any]:
|
|
87
|
+
"""Return the values of the PaymentMethod."""
|
|
88
|
+
return {
|
|
89
|
+
"payment_method_id": self.payment_method_id,
|
|
90
|
+
"card_holder_name": self.card_holder_name,
|
|
91
|
+
"brand": self.brand,
|
|
92
|
+
"postal_code": self.postal_code,
|
|
93
|
+
"country": self.country,
|
|
94
|
+
"expiration_year": self.expiration_year,
|
|
95
|
+
"expiration_month": self.expiration_month,
|
|
96
|
+
"card_last_four_digits": self.card_last_four_digits,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class AddPaymentMethodResponse(_BaseEffect):
|
|
101
|
+
"""AddPaymentMethodResponse effect class."""
|
|
102
|
+
|
|
103
|
+
class Meta:
|
|
104
|
+
effect_type = EffectType.REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHOD__ADD_RESPONSE
|
|
105
|
+
|
|
106
|
+
success: bool
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def values(self) -> dict[str, Any]:
|
|
110
|
+
"""Return the values of the AddPaymentMethodResponse."""
|
|
111
|
+
return {"success": self.success}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class RemovePaymentMethodResponse(_BaseEffect):
|
|
115
|
+
"""RemovePaymentMethodResponse effect class."""
|
|
116
|
+
|
|
117
|
+
class Meta:
|
|
118
|
+
effect_type = EffectType.REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHOD__REMOVE_RESPONSE
|
|
119
|
+
|
|
120
|
+
success: bool
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def values(self) -> dict[str, Any]:
|
|
124
|
+
"""Return the values of the RemovePaymentMethodResponse."""
|
|
125
|
+
return {
|
|
126
|
+
"success": self.success,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
__exports__ = (
|
|
131
|
+
"PaymentProcessorMetadata",
|
|
132
|
+
"PaymentProcessorForm",
|
|
133
|
+
"CardTransaction",
|
|
134
|
+
"PaymentMethod",
|
|
135
|
+
"AddPaymentMethodResponse",
|
|
136
|
+
"RemovePaymentMethodResponse",
|
|
137
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__exports__ = ()
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
from abc import ABC
|
|
3
|
+
|
|
4
|
+
from canvas_sdk.effects import Effect
|
|
5
|
+
from canvas_sdk.effects.payment_processor import PaymentProcessorMetadata
|
|
6
|
+
from canvas_sdk.events import EventType
|
|
7
|
+
from canvas_sdk.handlers import BaseHandler
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PaymentProcessor(BaseHandler, ABC):
|
|
11
|
+
"""
|
|
12
|
+
Abstract Base class for payment processors.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
RESPONDS_TO = [
|
|
16
|
+
EventType.Name(EventType.REVENUE__PAYMENT_PROCESSOR__LIST),
|
|
17
|
+
EventType.Name(EventType.REVENUE__PAYMENT_PROCESSOR__SELECTED),
|
|
18
|
+
EventType.Name(EventType.REVENUE__PAYMENT_PROCESSOR__CHARGE),
|
|
19
|
+
EventType.Name(EventType.REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__LIST),
|
|
20
|
+
EventType.Name(EventType.REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__ADD),
|
|
21
|
+
EventType.Name(EventType.REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__REMOVE),
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
TYPE: PaymentProcessorMetadata.PaymentProcessorType
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def identifier(self) -> str:
|
|
28
|
+
"""The application identifier."""
|
|
29
|
+
identifier = f"{self.__class__.__module__}:{self.__class__.__qualname__}"
|
|
30
|
+
|
|
31
|
+
return base64.b64encode(identifier.encode("utf-8")).decode("utf-8")
|
|
32
|
+
|
|
33
|
+
def compute(self) -> list[Effect]:
|
|
34
|
+
"""Compute the effects to be applied."""
|
|
35
|
+
if self.event.type == EventType.REVENUE__PAYMENT_PROCESSOR__LIST and (
|
|
36
|
+
"payment_type" not in self.event.context
|
|
37
|
+
or self.event.context["payment_type"] == self.TYPE
|
|
38
|
+
):
|
|
39
|
+
return [self.metadata().apply()]
|
|
40
|
+
elif self.event.type == EventType.REVENUE__PAYMENT_PROCESSOR__SELECTED:
|
|
41
|
+
effects = self._on_payment_processor_selected()
|
|
42
|
+
return effects
|
|
43
|
+
elif self.event.type == EventType.REVENUE__PAYMENT_PROCESSOR__CHARGE:
|
|
44
|
+
effect = self._charge()
|
|
45
|
+
return [effect] if effect else []
|
|
46
|
+
elif self.event.type == EventType.REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__LIST:
|
|
47
|
+
# This event is used to list payment methods, which may not be applicable for all processors.
|
|
48
|
+
# Subclasses should override this method if they support listing payment methods.
|
|
49
|
+
return self._payment_methods()
|
|
50
|
+
elif self.event.type == EventType.REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__ADD:
|
|
51
|
+
effect = self._add_payment_method()
|
|
52
|
+
return [effect] if effect else []
|
|
53
|
+
elif self.event.type == EventType.REVENUE__PAYMENT_PROCESSOR__PAYMENT_METHODS__REMOVE:
|
|
54
|
+
effect = self._remove_payment_method()
|
|
55
|
+
return [effect] if effect else []
|
|
56
|
+
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
def metadata(self) -> PaymentProcessorMetadata:
|
|
60
|
+
"""Return information about the payment processor."""
|
|
61
|
+
return PaymentProcessorMetadata(identifier=self.identifier, type=self.TYPE)
|
|
62
|
+
|
|
63
|
+
def _on_payment_processor_selected(self) -> list[Effect]:
|
|
64
|
+
"""Handle the event when a payment processor is selected."""
|
|
65
|
+
# This method should be overridden by subclasses to handle specific logic
|
|
66
|
+
# when a payment processor is selected.
|
|
67
|
+
return []
|
|
68
|
+
|
|
69
|
+
def _charge(self) -> Effect | None:
|
|
70
|
+
"""Handle the event when a charge is made."""
|
|
71
|
+
# This method should be overridden by subclasses to handle specific logic
|
|
72
|
+
# when a charge is made.
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
def _payment_methods(self) -> list[Effect]:
|
|
76
|
+
"""List payment methods for the processor."""
|
|
77
|
+
# This method should be overridden by subclasses if they support listing payment methods.
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
def _add_payment_method(self) -> Effect | None:
|
|
81
|
+
"""Handle the event when a payment method is added."""
|
|
82
|
+
# This method should be overridden by subclasses to handle specific logic
|
|
83
|
+
# when a payment method is added.
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
def _remove_payment_method(self) -> Effect | None:
|
|
87
|
+
"""Handle the event when a payment method is removed."""
|
|
88
|
+
# This method should be overridden by subclasses to handle specific logic
|
|
89
|
+
# when a payment method is removed.
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
__exports__ = ("PaymentProcessor",)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
from canvas_sdk.effects import Effect
|
|
6
|
+
from canvas_sdk.effects.payment_processor import (
|
|
7
|
+
AddPaymentMethodResponse,
|
|
8
|
+
CardTransaction,
|
|
9
|
+
PaymentMethod,
|
|
10
|
+
PaymentProcessorForm,
|
|
11
|
+
PaymentProcessorMetadata,
|
|
12
|
+
RemovePaymentMethodResponse,
|
|
13
|
+
)
|
|
14
|
+
from canvas_sdk.handlers.payment_processors.base import PaymentProcessor
|
|
15
|
+
from canvas_sdk.v1.data import Patient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CardPaymentProcessor(PaymentProcessor, ABC):
|
|
19
|
+
"""Base Card Payment Processor Handler."""
|
|
20
|
+
|
|
21
|
+
TYPE = PaymentProcessorMetadata.PaymentProcessorType.CARD
|
|
22
|
+
|
|
23
|
+
class PaymentIntent(StrEnum):
|
|
24
|
+
"""Enum for payment actions."""
|
|
25
|
+
|
|
26
|
+
ADD_CARD = "add_card"
|
|
27
|
+
PAY = "pay"
|
|
28
|
+
|
|
29
|
+
def _on_payment_processor_selected(self) -> list[Effect]:
|
|
30
|
+
"""Handle the event when a payment processor is selected."""
|
|
31
|
+
if self.event.context.get("identifier") == self.identifier:
|
|
32
|
+
intent = self.event.context.get("intent")
|
|
33
|
+
effects = self.on_payment_processor_selected(intent=intent)
|
|
34
|
+
return [effect.apply() for effect in effects]
|
|
35
|
+
|
|
36
|
+
return []
|
|
37
|
+
|
|
38
|
+
def _charge(self) -> Effect | None:
|
|
39
|
+
"""Handle the event when a charge is made."""
|
|
40
|
+
if self.event.context.get("identifier") == self.identifier:
|
|
41
|
+
patient = (
|
|
42
|
+
Patient.objects.get(id=self.event.context.get("patient", {}).get("id"))
|
|
43
|
+
if self.event.context.get("patient")
|
|
44
|
+
else None
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
amount = Decimal(str(self.event.context.get("amount")))
|
|
48
|
+
token = str(self.event.context.get("token"))
|
|
49
|
+
effect = self.charge(amount=amount, token=token, patient=patient)
|
|
50
|
+
|
|
51
|
+
return effect.apply() if effect else None
|
|
52
|
+
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
def _payment_methods(self) -> list[Effect]:
|
|
56
|
+
"""List payment methods for the card payment processor."""
|
|
57
|
+
if self.event.context.get("identifier") == self.identifier:
|
|
58
|
+
patient = (
|
|
59
|
+
Patient.objects.get(id=self.event.context.get("patient", {}).get("id"))
|
|
60
|
+
if self.event.context.get("patient")
|
|
61
|
+
else None
|
|
62
|
+
)
|
|
63
|
+
effects = self.payment_methods(patient=patient)
|
|
64
|
+
return [effect.apply() for effect in effects]
|
|
65
|
+
|
|
66
|
+
return []
|
|
67
|
+
|
|
68
|
+
def _add_payment_method(self) -> Effect | None:
|
|
69
|
+
"""Handle the event when a card is added."""
|
|
70
|
+
if self.event.context.get("identifier") == self.identifier:
|
|
71
|
+
patient = (
|
|
72
|
+
Patient.objects.get(id=self.event.context.get("patient", {}).get("id"))
|
|
73
|
+
if self.event.context.get("patient")
|
|
74
|
+
else None
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if not patient:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
token = str(self.event.context.get("token"))
|
|
81
|
+
effect = self.add_payment_method(token=token, patient=patient)
|
|
82
|
+
return effect.apply() if effect else None
|
|
83
|
+
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
def _remove_payment_method(self) -> Effect | None:
|
|
87
|
+
"""Handle the event when a payment method is removed."""
|
|
88
|
+
if self.event.context.get("identifier") == self.identifier:
|
|
89
|
+
patient = (
|
|
90
|
+
Patient.objects.get(id=self.event.context.get("patient", {}).get("id"))
|
|
91
|
+
if self.event.context.get("patient")
|
|
92
|
+
else None
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
if not patient:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
token = str(self.event.context.get("token"))
|
|
99
|
+
effect = self.remove_payment_method(token=token, patient=patient)
|
|
100
|
+
return effect.apply() if effect else None
|
|
101
|
+
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
def on_payment_processor_selected(self, intent: str | None) -> list[PaymentProcessorForm]:
|
|
105
|
+
"""Handle the event when a payment processor is selected."""
|
|
106
|
+
patient = (
|
|
107
|
+
Patient.objects.get(id=self.event.context.get("patient", {}).get("id"))
|
|
108
|
+
if self.event.context.get("patient")
|
|
109
|
+
else None
|
|
110
|
+
)
|
|
111
|
+
match intent:
|
|
112
|
+
case self.PaymentIntent.ADD_CARD:
|
|
113
|
+
return [self.add_card_form(patient)]
|
|
114
|
+
case self.PaymentIntent.PAY:
|
|
115
|
+
return [self.payment_form(patient)]
|
|
116
|
+
case None:
|
|
117
|
+
return [self.payment_form(patient), self.add_card_form(patient)]
|
|
118
|
+
case _:
|
|
119
|
+
return []
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
def payment_form(self, patient: Patient | None = None) -> PaymentProcessorForm:
|
|
123
|
+
"""Return the payment form for the credit card processor.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
patient (Patient | None): The patient for whom the payment is being processed.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
PaymentProcessorForm: The form for processing the payment.
|
|
130
|
+
"""
|
|
131
|
+
raise NotImplementedError("Subclasses must implement the payment_form method.")
|
|
132
|
+
|
|
133
|
+
@abstractmethod
|
|
134
|
+
def add_card_form(self, patient: Patient | None = None) -> PaymentProcessorForm:
|
|
135
|
+
"""Return the form for adding a card.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
patient (Patient | None): The patient for whom the add is being added.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
PaymentProcessorForm: The form for adding a card.
|
|
142
|
+
"""
|
|
143
|
+
raise NotImplementedError("Subclasses must implement the add_card_form method.")
|
|
144
|
+
|
|
145
|
+
@abstractmethod
|
|
146
|
+
def charge(
|
|
147
|
+
self, amount: Decimal, token: str, patient: Patient | None = None
|
|
148
|
+
) -> CardTransaction:
|
|
149
|
+
"""Charge a credit/debit card using the provided token.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
amount (Decimal): The amount to charge.
|
|
153
|
+
token (str): The token representing the credit card.
|
|
154
|
+
patient (Patient | None): The patient for whom the charge is being made.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
CardTransaction: The result of the card transaction.
|
|
158
|
+
"""
|
|
159
|
+
raise NotImplementedError("Subclasses must implement the charge method.")
|
|
160
|
+
|
|
161
|
+
@abstractmethod
|
|
162
|
+
def payment_methods(self, patient: Patient | None = None) -> list[PaymentMethod]:
|
|
163
|
+
"""List payment methods for the card payment processor.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
patient (Patient | None): The patient for whom the payment methods are being listed.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
list[PaymentMethod]: A list of payment methods available for the card payment processor.
|
|
170
|
+
"""
|
|
171
|
+
raise NotImplementedError("Subclasses must implement the payment_methods method.")
|
|
172
|
+
|
|
173
|
+
@abstractmethod
|
|
174
|
+
def add_payment_method(self, token: str, patient: Patient) -> AddPaymentMethodResponse:
|
|
175
|
+
"""Add a payment method for the card payment processor.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
token (str): The token representing the payment method.
|
|
179
|
+
patient (Patient): The patient for whom the payment method is being added.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
AddPaymentMethodResponse: The response indicating the result of the addition operation.
|
|
183
|
+
"""
|
|
184
|
+
raise NotImplementedError("Subclasses must implement the add_payment_method method.")
|
|
185
|
+
|
|
186
|
+
@abstractmethod
|
|
187
|
+
def remove_payment_method(self, token: str, patient: Patient) -> RemovePaymentMethodResponse:
|
|
188
|
+
"""Remove a payment method for the card payment processor.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
token (str): The token representing the payment method to be removed.
|
|
192
|
+
patient (Patient): The patient for whom the payment method is being removed.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
RemovePaymentMethodResponse: The response indicating the result of the removal operation.
|
|
196
|
+
"""
|
|
197
|
+
raise NotImplementedError("Subclasses must implement the remove_payment_method method.")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
__exports__ = ("CardPaymentProcessor",)
|
|
@@ -7,7 +7,7 @@ from typing import Any, TypedDict
|
|
|
7
7
|
import yaml
|
|
8
8
|
from jsonschema import Draft7Validator, validators
|
|
9
9
|
|
|
10
|
-
from canvas_sdk.utils.plugins import
|
|
10
|
+
from canvas_sdk.utils.plugins import plugin_context
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class Response(TypedDict):
|
|
@@ -75,7 +75,7 @@ def extend_with_defaults(validator_class: type[Draft7Validator]) -> type[Draft7V
|
|
|
75
75
|
ExtendedDraft7Validator = extend_with_defaults(Draft7Validator)
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
@
|
|
78
|
+
@plugin_context
|
|
79
79
|
def from_yaml(questionnaire_name: str, **kwargs: Any) -> QuestionnaireConfig | None:
|
|
80
80
|
"""Load a Questionnaire configuration from a YAML file.
|
|
81
81
|
|
canvas_sdk/templates/utils.py
CHANGED
|
@@ -3,12 +3,14 @@ from typing import Any
|
|
|
3
3
|
|
|
4
4
|
from django.template import Context, Template
|
|
5
5
|
|
|
6
|
-
from canvas_sdk.utils.plugins import
|
|
6
|
+
from canvas_sdk.utils.plugins import plugin_context
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
@
|
|
9
|
+
@plugin_context
|
|
10
10
|
def render_to_string(
|
|
11
|
-
template_name: str,
|
|
11
|
+
template_name: str,
|
|
12
|
+
context: dict[str, Any] | None = None,
|
|
13
|
+
**kwargs: Any,
|
|
12
14
|
) -> str | None:
|
|
13
15
|
"""Load a template and render it with the given context.
|
|
14
16
|
|