bw-essentials-core 0.1.17__py3-none-any.whl → 0.1.33__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.
@@ -21,6 +21,7 @@ class Services(Enum):
21
21
  PORTFOLIO_CATALOGUE = "Portfolio_Catalogue"
22
22
  PORTFOLIO_CONTENT = "Portfolio_Content"
23
23
  JOB_SCHEDULER = "Job_Scheduler"
24
+ COMPLIANCE = "Compliance"
24
25
 
25
26
  class PortfolioStatus(Enum):
26
27
  """
@@ -132,7 +132,7 @@ class EmailClient:
132
132
  cc_addresses: Union[str, List[str], None],
133
133
  subject: str,
134
134
  body: str,
135
- attachment_path: str,
135
+ attachment_path: Optional[Union[str, List[str]]],
136
136
  is_html: bool = True,
137
137
  ):
138
138
  """
@@ -143,7 +143,7 @@ class EmailClient:
143
143
  cc_addresses (Union[str, List[str], None]): One or more CC addresses (optional).
144
144
  subject (str): Email subject line.
145
145
  body (str): Email body content.
146
- attachment_path (str): Full path to the file to attach.
146
+ attachment_path (Optional[Union[str, List[str]]]): File path(s) for attachments.
147
147
  is_html (bool): If True, body is interpreted as HTML; otherwise, plain text.
148
148
  """
149
149
  logger.info(
@@ -158,7 +158,7 @@ class EmailClient:
158
158
  cc_addresses: Union[str, List[str], None],
159
159
  subject: str,
160
160
  body: str,
161
- attachment_path: Optional[str],
161
+ attachment_path: Optional[Union[str, List[str]]],
162
162
  is_html: bool,
163
163
  ):
164
164
  """
@@ -169,7 +169,7 @@ class EmailClient:
169
169
  cc_addresses (Union[str, List[str], None]): CC addresses.
170
170
  subject (str): Subject of the email.
171
171
  body (str): Email body content.
172
- attachment_path (Optional[str]): File path for the attachment, if any.
172
+ attachment_path (Optional[Union[str, List[str]]]): File path(s) for attachments.
173
173
  is_html (bool): True if the body is HTML-formatted.
174
174
  """
175
175
  msg = MIMEMultipart()
@@ -180,23 +180,36 @@ class EmailClient:
180
180
 
181
181
  msg.attach(MIMEText(body, 'html' if is_html else 'plain'))
182
182
  logger.debug("Email headers and body constructed")
183
+ attachment_paths = []
183
184
 
184
185
  if attachment_path:
185
- if not os.path.exists(attachment_path):
186
- logger.warning("Attachment file not found: %s", attachment_path)
186
+ if isinstance(attachment_path, list):
187
+ attachment_paths = attachment_path
187
188
  else:
188
- try:
189
- with open(attachment_path, 'rb') as attachment:
190
- part = MIMEBase('application', 'octet-stream')
191
- part.set_payload(attachment.read())
192
- encoders.encode_base64(part)
193
- part.add_header('Content-Disposition', 'attachment',
194
- filename=os.path.basename(attachment_path))
195
- msg.attach(part)
196
- logger.info("Attachment added: %s", os.path.basename(attachment_path))
197
- except Exception as e:
198
- logger.exception("Failed to read attachment file")
199
- raise e
189
+ attachment_paths = [attachment_path]
190
+
191
+ for path in attachment_paths:
192
+ if not path:
193
+ continue
194
+ if not os.path.exists(path):
195
+ logger.warning("Attachment file not found: %s", path)
196
+ continue
197
+
198
+ try:
199
+ with open(path, 'rb') as attachment:
200
+ part = MIMEBase('application', 'octet-stream')
201
+ part.set_payload(attachment.read())
202
+ encoders.encode_base64(part)
203
+ part.add_header(
204
+ 'Content-Disposition',
205
+ 'attachment',
206
+ filename=os.path.basename(path)
207
+ )
208
+ msg.attach(part)
209
+ logger.info("Attachment added: %s", os.path.basename(path))
210
+ except Exception as e:
211
+ logger.exception("Failed to read attachment file: %s", path)
212
+ raise e
200
213
 
201
214
  try:
202
215
  with smtplib.SMTP(self.smtp_host, self.smtp_port) as server:
@@ -15,6 +15,7 @@ import logging
15
15
  import os
16
16
  import sys
17
17
  import time
18
+ import httpx
18
19
  from importlib.util import spec_from_file_location, module_from_spec
19
20
  from typing import Optional, Dict, Any
20
21
 
@@ -195,6 +196,41 @@ class ApiClient:
195
196
  """
196
197
  return self._request("get", url, endpoint, params=params)
197
198
 
199
+ async def _async_get(self, url: str, endpoint: str, headers: dict | None = None, params: dict | None = None):
200
+ """
201
+ Async GET request, aligned with the sync _request() style.
202
+ """
203
+ headers = self._update_headers(headers or {})
204
+ params = params or {}
205
+ formatted_url = f"{url.rstrip('/')}/{endpoint.lstrip('/')}"
206
+
207
+ logger.info(f"GET {formatted_url} | Headers: {headers} | Params: {params}")
208
+
209
+ start = time.time()
210
+ try:
211
+ async with httpx.AsyncClient() as client:
212
+ response = await client.get(formatted_url, headers=headers, params=params)
213
+
214
+ elapsed_time_ms = (time.time() - start) * 1000
215
+
216
+ # parse JSON safely
217
+ try:
218
+ json_data = response.json()
219
+ except ValueError:
220
+ logger.error(f"Non-JSON response from {formatted_url}")
221
+ json_data = None
222
+
223
+ self._log_response("GET", formatted_url, response.status_code, elapsed_time_ms, json_data)
224
+
225
+ response.raise_for_status()
226
+ return json_data
227
+
228
+ except Exception as exc:
229
+ elapsed_time_ms = (time.time() - start) * 1000
230
+ logger.error(f"GET {formatted_url} failed after {elapsed_time_ms:.2f}ms")
231
+ logger.exception(exc)
232
+ raise
233
+
198
234
  def _post(
199
235
  self,
200
236
  url: str,
@@ -30,6 +30,7 @@ APIs supported:
30
30
  - Acknowledge surveillance orders
31
31
  """
32
32
  import logging
33
+ from typing import Optional
33
34
 
34
35
  from bw_essentials.constants.services import Services
35
36
  from bw_essentials.services.api_client import ApiClient
@@ -139,22 +140,26 @@ class Broker(ApiClient):
139
140
  logger.info(f"{response =}")
140
141
  return response["data"]
141
142
 
142
- def get_holdings(self, broker_name: str, user_id: str) -> list:
143
+ def get_holdings(self, broker_name: str, user_id: str, product_type: Optional[str] = None) -> dict:
143
144
  """
144
145
  Fetch user holdings from the broker service.
145
146
 
146
147
  Args:
147
148
  broker_name (str): Broker name.
148
149
  user_id (str): User ID.
150
+ product_type (str, optional): Product type.
149
151
 
150
152
  Returns:
151
- list: List of user holdings.
153
+ dict: Dict of user holdings.
152
154
  """
153
155
  logger.info(f"In - get_holdings {user_id =}")
156
+ params = {"user_id": user_id}
157
+ if product_type:
158
+ params['product_type'] = product_type
154
159
  response = self._get(
155
160
  url=self.base_url,
156
161
  endpoint=self.urls["holdings"].format(broker_name),
157
- params={"user_id": user_id}
162
+ params=params
158
163
  )
159
164
  logger.info(f"{response =}")
160
165
  return response["data"]["holdings"]
@@ -0,0 +1,78 @@
1
+ import logging
2
+ from bw_essentials.constants.services import Services
3
+ from bw_essentials.services.api_client import ApiClient
4
+
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class Compliance(ApiClient):
10
+ """
11
+ Compliance service integration class.
12
+
13
+ This class facilitates communication with the compliance service.
14
+ """
15
+
16
+ def __init__(self, user, tenant_id: str = None):
17
+ """
18
+ Initialize the Compliance service wrapper.
19
+
20
+ :param user: The user initiating the request.
21
+ """
22
+ super().__init__(user)
23
+ self.base_url = self.get_base_url(Services.COMPLIANCE.value)
24
+ self.name = Services.COMPLIANCE.value
25
+ self.tenant_id = tenant_id
26
+ self.urls = {
27
+ "kra_data": "kra/data"
28
+ }
29
+ logger.info("Compliance service initialized.")
30
+
31
+ def _headers(self):
32
+ """
33
+ Construct headers for compliance service requests.
34
+
35
+ :return: Dictionary of headers.
36
+ """
37
+ return {
38
+ 'Content-Type': 'application/json',
39
+ **({'X-Tenant-ID': self.tenant_id} if self.tenant_id else {})
40
+ }
41
+
42
+ def get_kra_data(self, payload):
43
+ """
44
+ Fetches KRA details by calling API.
45
+
46
+ This method prepares the request headers, constructs the API endpoint,
47
+ sends a POST request with the provided payload, and returns the
48
+ parsed KRA data received from the compliance service.
49
+
50
+
51
+ Args:
52
+ payload (dict):
53
+ Dictionary containing the required fields for KRA lookup.
54
+ Example:
55
+ {
56
+ "pan": "ABCDE1234F",
57
+ "dob": "1985-05-12"
58
+ }
59
+
60
+ Returns:
61
+ dict:
62
+ Parsed KRA data dictionary returned by the Compliance Service.
63
+ Returns an empty dictionary if `data` is missing from the response.
64
+
65
+ Example:
66
+ {
67
+ "kra_status": "KYC_REGISTERED",
68
+ "name": "John Doe",
69
+ "dob": "1985-05-12",
70
+ ...
71
+ }
72
+ """
73
+ logger.info(f"In get_kra_data {payload =}")
74
+ url = f"{self.base_url}"
75
+ self.headers = self._headers()
76
+ kra_data = self._post(url=url, endpoint=self.urls.get('kra_data'), json=payload)
77
+ logger.info(f"Out get_kra_data {kra_data =}")
78
+ return kra_data.get('data', {})
@@ -76,14 +76,14 @@ class MarketPricer(ApiClient):
76
76
  Retrieves live market prices for a list of securities on a specific exchange.
77
77
 
78
78
  Args:
79
- securities (str): A list of security symbols for which live prices are requested.
79
+ securities (list): List of security symbols for which live prices are requested.
80
80
  exchange (str): The exchange on which the securities are traded.
81
81
  Returns:
82
- list: A list of live market price data for the specified securities.
82
+ dict: A dictionary containing the live market price data for the specified securities.
83
83
 
84
84
  Example:
85
85
  market_pricer = MarketPricer(user)
86
- securities = "TCS,RELIANCE"
86
+ securities = ["TCS", "RELIANCE"]
87
87
  exchange = "NSE"
88
88
  live_prices = market_pricer.get_live_prices(securities, exchange)
89
89
 
@@ -96,20 +96,21 @@ class MarketPricer(ApiClient):
96
96
 
97
97
  API Response:
98
98
  {
99
- "data": [
100
- {
101
- "symbol": "TCS",
102
- "price": 150.25,
103
- "timestamp": "2023-10-04T10:30:00Z",
104
- "exchange": "NSE"
105
- },
106
- {
107
- "symbol": "RELIANCE",
108
- "price": 2750.75,
109
- "timestamp": "2023-10-04T10:30:00Z",
110
- "exchange": "NSE"
111
- }
112
- ]
99
+ "data": {
100
+ "TCS": {
101
+ "security": "TCS",
102
+ "exchange": "NSE",
103
+ "price": 3207.8,
104
+ "timestamp": "2026-01-09 16:00:00"
105
+ },
106
+ "RELIANCE": {
107
+ "security": "RELIANCE",
108
+ "exchange": "NSE",
109
+ "price": 1475.3,
110
+ "timestamp": "2026-01-09 16:00:00"
111
+ }
112
+ },
113
+ "success": true
113
114
  }
114
115
  """
115
116
  logger.info(f"In - get_live_prices {securities =}, {exchange =}")
@@ -300,3 +301,56 @@ class MarketPricer(ApiClient):
300
301
  data = response.get("data")
301
302
  logger.info("Out - get_index_performance_by_dates success")
302
303
  return data
304
+
305
+ def get_bulk_option_eod_prices(
306
+ self,
307
+ symbols,
308
+ from_date,
309
+ to_date,
310
+ exchange=None,
311
+ underlying_symbol=None,
312
+ option_type=None,
313
+ expiry_date=None,
314
+ ):
315
+ """
316
+ Fetch bulk EOD OHLCV data for multiple option symbols over a date range.
317
+
318
+ Args:
319
+ symbols (list[str] | str): Option symbols as list or comma-separated string.
320
+ from_date (str): Start date in YYYY-MM-DD format.
321
+ to_date (str): End date in YYYY-MM-DD format.
322
+ exchange (str, optional): Exchange filter.
323
+ underlying_symbol (str, optional): Underlying instrument filter.
324
+ option_type (str, optional): CE or PE.
325
+ expiry_date (str, optional): Option expiry date.
326
+
327
+ Returns:
328
+ dict: Dictionary keyed by symbol containing EOD data.
329
+ """
330
+ logger.info(f"In - get_bulk_option_eod_prices | {symbols=}, {from_date=}, {to_date=}")
331
+
332
+ if not all([symbols, from_date, to_date]):
333
+ raise ValueError("symbols, from_date and to_date are required")
334
+
335
+ symbols = ",".join(symbols) if isinstance(symbols, list) else symbols
336
+
337
+ params = {"symbols": symbols, "from_date": from_date, "to_date": to_date}
338
+
339
+ for key, value in {
340
+ "exchange": exchange,
341
+ "underlying_symbol": underlying_symbol,
342
+ "option_type": option_type,
343
+ "expiry_date": expiry_date,
344
+ }.items():
345
+ if value is not None:
346
+ params[key] = value
347
+
348
+ response = self._get(
349
+ url=self.base_url,
350
+ endpoint="options/bulk/eod",
351
+ params=params,
352
+ )
353
+
354
+ logger.info(f"Out - get_bulk_option_eod_prices | symbols_count={len(symbols.split(','))}")
355
+ return response.get("data")
356
+
@@ -299,3 +299,52 @@ class MasterData(ApiClient):
299
299
  response = self._get(url=self.base_url, endpoint=self.urls["broker_partner_mapping_details"].format(broker_partner, broker))
300
300
  logger.info(f"Broker partner mapping {response =}")
301
301
  return response.get('data', {})
302
+
303
+ async def get_broker_data_async(self, symbols, broker, username=None):
304
+ """
305
+ Asynchronously retrieve broker-specific data for specified symbols.
306
+
307
+ This function fetches broker-specific details such as margin and leverage
308
+ for a list of provided trading symbols from the broker master service.
309
+
310
+ Args:
311
+ username (str) [Optional]: The user_id for whom data is requested.
312
+ symbols (str): A comma-separated string of trading symbols.
313
+ broker (str): The broker identifier for which data is requested.
314
+
315
+ Returns:
316
+ list: A list of dictionaries containing broker data for each symbol
317
+ or an empty list if no data is available.
318
+ """
319
+ logger.info(f"In - get_broker_data_async {broker =}")
320
+ broker_data_response = await self._async_get(
321
+ url=f"{self.base_url}",
322
+ endpoint = self.urls["broker_details"].format(broker),
323
+ params={"symbols": symbols, "user_id": username},
324
+ headers={}
325
+ )
326
+ return broker_data_response.get('data', [])
327
+
328
+ def get_broker_data_all(self, symbols, broker, username=None):
329
+ """
330
+ Retrieve broker-specific data for specified symbols.
331
+
332
+ This function fetches broker-specific details such as margin and leverage
333
+ for a list of provided trading symbols from the broker master service.
334
+
335
+ Args:
336
+ username (str) [Optional]: The user_id for whom data is requested.
337
+ symbols (str): A comma-separated string of trading symbols.
338
+ broker (str): The broker identifier for which data is requested.
339
+
340
+ Returns:
341
+ list: A dictionary containing broker data for the first symbol or
342
+ an empty dictionary if no data is available.
343
+ """
344
+ logger.info(f"In - get_broker_data {broker =}")
345
+ broker_data_response = self._get(
346
+ url=f"{self.base_url}",
347
+ endpoint=self.urls['broker_details'].format(broker),
348
+ params={"symbols": symbols, "user_id": username},
349
+ )
350
+ return broker_data_response.get('data', [])
@@ -33,7 +33,8 @@ class ModelPortfolioReporting(ApiClient):
33
33
  self.object = None
34
34
  self.urls = {
35
35
  "portfolio": "portfolio",
36
- "portfolio_performance": "portfolio/%s/performance"
36
+ "portfolio_performance": "portfolio/%s/performance",
37
+ "index_performance": "index/%s/performance/"
37
38
  }
38
39
  self.name = Services.MODEL_PORTFOLIO.value
39
40
  self.base_url = self.get_base_url(Services.MODEL_PORTFOLIO.value)
@@ -79,3 +80,31 @@ class ModelPortfolioReporting(ApiClient):
79
80
  )
80
81
  logger.info(f"Received response from get_portfolio_performance_by_dates: {performance}")
81
82
  return performance.get("data")
83
+
84
+ def get_index_performance_by_dates(
85
+ self,
86
+ index: str,
87
+ start_date: str,
88
+ end_date: str
89
+ ) -> Optional[Dict[str, Any]]:
90
+ """
91
+ Fetch Index performance for a given date range.
92
+
93
+ Args:
94
+ index (str): The index identifier.
95
+ start_date (str): The start date for performance metrics (format: YYYY-MM-DD).
96
+ end_date (str): The end date for performance metrics (format: YYYY-MM-DD).
97
+
98
+ Returns:
99
+ dict | None: The performance data within the date range.
100
+ """
101
+ logger.info(f"In - get_index_performance_by_dates with index={index}, start_date={start_date}, end_date={end_date}")
102
+ endpoint = self.urls['index_performance'] % index
103
+ params = {"start_date": start_date, "end_date": end_date}
104
+ performance = self._get(
105
+ url=self.base_url,
106
+ endpoint=endpoint,
107
+ params=params
108
+ )
109
+ logger.info(f"Received response from get_index_performance_by_dates: {performance}")
110
+ return performance.get("data")
@@ -32,7 +32,7 @@ class NotificationService(ApiClient):
32
32
  "email": "email"
33
33
  }
34
34
 
35
- def _whatsapp(self, title, template, platform, params, to, user_id) -> None:
35
+ def _whatsapp(self, title, template, platform, params, to, user_id) -> dict:
36
36
  """
37
37
  Sends a WhatsApp notification.
38
38
 
@@ -46,7 +46,7 @@ class NotificationService(ApiClient):
46
46
  - params: Parameters for the notification message.
47
47
 
48
48
  Returns:
49
- None
49
+ returns the response data from the notification service.
50
50
  """
51
51
  logger.info(f"In - whatsapp {user_id =}, {to =}, "
52
52
  f"{title =}, {template =}, {platform =}, {params =}")
@@ -66,8 +66,9 @@ class NotificationService(ApiClient):
66
66
  self.set_headers(headers)
67
67
  resp_data = self._post(url=self.base_url, endpoint=self.urls.get('whatsapp'), data=payload)
68
68
  logger.info(f"Whatsapp response {resp_data =}")
69
+ return resp_data
69
70
 
70
- def send_whatsapp(self, template, title, params, to, user_id) -> None:
71
+ def send_whatsapp(self, template, title, params, to, user_id) -> dict:
71
72
  """
72
73
 
73
74
  Args:
@@ -78,17 +79,18 @@ class NotificationService(ApiClient):
78
79
  user_id (str): The ID of the user receiving the notification.
79
80
 
80
81
  Returns:
81
- None
82
+ Returns the response of calling function
82
83
  """
83
84
  logger.info(f"In - send_whatsapp_notification {user_id =} {title = } {params = } {to = }")
84
- self._whatsapp(title=title,
85
+ response = self._whatsapp(title=title,
85
86
  template=template,
86
87
  platform=self._get_env_var(NotificationService.PLATFORM),
87
88
  params=params,
88
89
  to=to,
89
90
  user_id=user_id)
91
+ return response
90
92
 
91
- def _email(self, title: str, content: str, platform: str, to: str, user_id: str) -> None:
93
+ def _email(self, title: str, content: str, platform: str, to: str, user_id: str) -> dict:
92
94
  """
93
95
  Sends an email notification using the internal notification service.
94
96
 
@@ -100,7 +102,7 @@ class NotificationService(ApiClient):
100
102
  user_id (str): The ID of the user for tracking or logging purposes.
101
103
 
102
104
  Returns:
103
- None
105
+ returns the response data from the notification service.
104
106
 
105
107
  """
106
108
  logger.info(f"In - email {user_id =}, {to =}, {title =}, {platform =}, {content =}")
@@ -119,8 +121,9 @@ class NotificationService(ApiClient):
119
121
  self.set_headers(headers)
120
122
  resp_data = self._post(url=self.base_url, endpoint=self.urls.get('email'), json=payload)
121
123
  logger.info(f"Email response {resp_data =}")
124
+ return resp_data
122
125
 
123
- def send_email(self, title: str, content: str, to: str, user_id: str) -> None:
126
+ def send_email(self, title: str, content: str, to: str, user_id: str) -> dict:
124
127
  """
125
128
  Sends an email notification to the specified recipient.
126
129
 
@@ -131,13 +134,14 @@ class NotificationService(ApiClient):
131
134
  user_id (str): The ID of the user associated with the notification.
132
135
 
133
136
  Returns:
134
- None
137
+ return the response of calling function
135
138
  """
136
139
  logger.info(f"In - send_email {user_id =}, {title =}, {to =}")
137
- self._email(
138
- title=title,
139
- content=content,
140
- platform=self._get_env_var(NotificationService.PLATFORM),
141
- to=to,
142
- user_id=user_id
140
+ response = self._email(
141
+ title=title,
142
+ content=content,
143
+ platform=self._get_env_var(NotificationService.PLATFORM),
144
+ to=to,
145
+ user_id=user_id
143
146
  )
147
+ return response
@@ -11,6 +11,8 @@ class Payment(ApiClient):
11
11
  super().__init__(service_user)
12
12
  self.urls = {
13
13
  "get_subscription": "user-subscription",
14
+ "update_otp_facilitator_code": "order",
15
+ "update_subscription_facilitator_code": "user-subscription-update-data"
14
16
  }
15
17
  self.name = Services.PAYMENT.value
16
18
  self.base_url = self.get_base_url(Services.PAYMENT.value)
@@ -36,4 +38,47 @@ class Payment(ApiClient):
36
38
  url = f"{self.base_url}"
37
39
  subscription = self._get(url=url, endpoint=self.urls.get('get_subscription'), params={'userId': user_id})
38
40
  logger.info(f"Successfully fetched subscription data. {subscription =}")
39
- return subscription.get('data')
41
+ return subscription.get('data')
42
+
43
+ def update_otp_facilitator_code(self, payment_reference_id: str, analyst_id: str, payload: dict):
44
+ """
45
+ Updates the facilitator code on the Payment Service for a given payment_reference_id.
46
+
47
+ Args:
48
+ payment_reference_id (str): The payment reference ID (order ID)
49
+ analyst_id (str): Analyst UUID
50
+ payload (dict): Body containing facilitatorCode
51
+
52
+ Returns:
53
+ dict: Response data
54
+ """
55
+ logger.info(f"In - update_facilitator_code payment_reference_id={payment_reference_id}, "
56
+ f""f"analyst_id={analyst_id}, payload={payload}")
57
+ response = self._put(
58
+ url=f"{self.base_url}",
59
+ endpoint=f"{self.urls.get('update_otp_facilitator_code')}/{payment_reference_id}",
60
+ json=payload,
61
+ params={"analyst": analyst_id},
62
+ )
63
+ logger.info(f"Out - update_facilitator_code response={response}")
64
+ return response
65
+
66
+ def update_subscription_facilitator_code(self, payment_reference_id: str, payload: dict):
67
+ """
68
+ Updates the facilitator code on the Payment Service for a given payment_reference_id.
69
+ Args:
70
+ payment_reference_id (str): The payment reference ID (subscription ID)
71
+ payload (dict): Body containing facilitatorCode and userId
72
+
73
+ Returns:
74
+ dict: Response data
75
+ """
76
+ logger.info(f"In - update_subscription_facilitator_code payment_reference_id={payment_reference_id}, "
77
+ f"payload={payload}")
78
+ response = self._put(
79
+ url=f"{self.base_url}",
80
+ endpoint=f"{self.urls.get('update_subscription_facilitator_code')}/{payment_reference_id}",
81
+ json=payload
82
+ )
83
+ logger.info(f"Out - update_subscription_facilitator_code response={response}")
84
+ return response