agentref 1.0.4__tar.gz → 2.0.0__tar.gz
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.
- {agentref-1.0.4 → agentref-2.0.0}/PKG-INFO +1 -1
- {agentref-1.0.4 → agentref-2.0.0}/agentref/resources/merchant.py +12 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/resources/payout_info.py +8 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/resources/programs.py +18 -6
- {agentref-1.0.4 → agentref-2.0.0}/agentref/types/models.py +60 -19
- {agentref-1.0.4 → agentref-2.0.0}/pyproject.toml +1 -1
- {agentref-1.0.4 → agentref-2.0.0}/tests/test_async.py +22 -6
- {agentref-1.0.4 → agentref-2.0.0}/tests/test_errors.py +53 -3
- agentref-2.0.0/tests/test_programs.py +384 -0
- agentref-1.0.4/tests/test_programs.py +0 -215
- {agentref-1.0.4 → agentref-2.0.0}/.github/workflows/ci.yml +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/.github/workflows/publish.yml +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/.gitignore +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/CHANGELOG.md +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/README.md +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/__init__.py +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/_http.py +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/client.py +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/errors.py +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/resources/__init__.py +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/resources/affiliates.py +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/resources/billing.py +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/resources/conversions.py +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/resources/flags.py +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/resources/notifications.py +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/resources/payouts.py +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/agentref/types/__init__.py +0 -0
- {agentref-1.0.4 → agentref-2.0.0}/tests/test_http.py +0 -0
|
@@ -23,6 +23,9 @@ class MerchantResource:
|
|
|
23
23
|
timezone: Optional[str] = None,
|
|
24
24
|
default_cookie_duration: Optional[int] = None,
|
|
25
25
|
default_payout_threshold: Optional[int] = None,
|
|
26
|
+
tracking_requires_consent: Optional[bool] = None,
|
|
27
|
+
tracking_param_aliases: Optional[list[str]] = None,
|
|
28
|
+
tracking_legacy_metadata_fallback_enabled: Optional[bool] = None,
|
|
26
29
|
) -> Merchant:
|
|
27
30
|
payload = UpdateMerchantParams(
|
|
28
31
|
company_name=company_name,
|
|
@@ -31,6 +34,9 @@ class MerchantResource:
|
|
|
31
34
|
timezone=timezone,
|
|
32
35
|
default_cookie_duration=default_cookie_duration,
|
|
33
36
|
default_payout_threshold=default_payout_threshold,
|
|
37
|
+
tracking_requires_consent=tracking_requires_consent,
|
|
38
|
+
tracking_param_aliases=tracking_param_aliases,
|
|
39
|
+
tracking_legacy_metadata_fallback_enabled=tracking_legacy_metadata_fallback_enabled,
|
|
34
40
|
).model_dump(by_alias=True, exclude_none=True)
|
|
35
41
|
envelope = self._http.request("PATCH", "/merchant", json=payload)
|
|
36
42
|
return Merchant.model_validate(envelope["data"])
|
|
@@ -61,6 +67,9 @@ class AsyncMerchantResource:
|
|
|
61
67
|
timezone: Optional[str] = None,
|
|
62
68
|
default_cookie_duration: Optional[int] = None,
|
|
63
69
|
default_payout_threshold: Optional[int] = None,
|
|
70
|
+
tracking_requires_consent: Optional[bool] = None,
|
|
71
|
+
tracking_param_aliases: Optional[list[str]] = None,
|
|
72
|
+
tracking_legacy_metadata_fallback_enabled: Optional[bool] = None,
|
|
64
73
|
) -> Merchant:
|
|
65
74
|
payload = UpdateMerchantParams(
|
|
66
75
|
company_name=company_name,
|
|
@@ -69,6 +78,9 @@ class AsyncMerchantResource:
|
|
|
69
78
|
timezone=timezone,
|
|
70
79
|
default_cookie_duration=default_cookie_duration,
|
|
71
80
|
default_payout_threshold=default_payout_threshold,
|
|
81
|
+
tracking_requires_consent=tracking_requires_consent,
|
|
82
|
+
tracking_param_aliases=tracking_param_aliases,
|
|
83
|
+
tracking_legacy_metadata_fallback_enabled=tracking_legacy_metadata_fallback_enabled,
|
|
72
84
|
).model_dump(by_alias=True, exclude_none=True)
|
|
73
85
|
envelope = await self._http.request("PATCH", "/merchant", json=payload)
|
|
74
86
|
return Merchant.model_validate(envelope["data"])
|
|
@@ -19,7 +19,9 @@ class PayoutInfoResource:
|
|
|
19
19
|
*,
|
|
20
20
|
payout_method: Optional[str] = None,
|
|
21
21
|
paypal_email: Optional[str] = None,
|
|
22
|
+
bank_account_holder: Optional[str] = None,
|
|
22
23
|
bank_iban: Optional[str] = None,
|
|
24
|
+
bank_bic: Optional[str] = None,
|
|
23
25
|
first_name: Optional[str] = None,
|
|
24
26
|
last_name: Optional[str] = None,
|
|
25
27
|
address_line1: Optional[str] = None,
|
|
@@ -32,7 +34,9 @@ class PayoutInfoResource:
|
|
|
32
34
|
payload = UpdatePayoutInfoParams(
|
|
33
35
|
payout_method=payout_method,
|
|
34
36
|
paypal_email=paypal_email,
|
|
37
|
+
bank_account_holder=bank_account_holder,
|
|
35
38
|
bank_iban=bank_iban,
|
|
39
|
+
bank_bic=bank_bic,
|
|
36
40
|
first_name=first_name,
|
|
37
41
|
last_name=last_name,
|
|
38
42
|
address_line1=address_line1,
|
|
@@ -59,7 +63,9 @@ class AsyncPayoutInfoResource:
|
|
|
59
63
|
*,
|
|
60
64
|
payout_method: Optional[str] = None,
|
|
61
65
|
paypal_email: Optional[str] = None,
|
|
66
|
+
bank_account_holder: Optional[str] = None,
|
|
62
67
|
bank_iban: Optional[str] = None,
|
|
68
|
+
bank_bic: Optional[str] = None,
|
|
63
69
|
first_name: Optional[str] = None,
|
|
64
70
|
last_name: Optional[str] = None,
|
|
65
71
|
address_line1: Optional[str] = None,
|
|
@@ -72,7 +78,9 @@ class AsyncPayoutInfoResource:
|
|
|
72
78
|
payload = UpdatePayoutInfoParams(
|
|
73
79
|
payout_method=payout_method,
|
|
74
80
|
paypal_email=paypal_email,
|
|
81
|
+
bank_account_holder=bank_account_holder,
|
|
75
82
|
bank_iban=bank_iban,
|
|
83
|
+
bank_bic=bank_bic,
|
|
76
84
|
first_name=first_name,
|
|
77
85
|
last_name=last_name,
|
|
78
86
|
address_line1=address_line1,
|
|
@@ -68,6 +68,8 @@ class ProgramsResource:
|
|
|
68
68
|
description: Optional[str] = None,
|
|
69
69
|
landing_page_url: Optional[str] = None,
|
|
70
70
|
commission_limit_months: Optional[int] = None,
|
|
71
|
+
portal_slug: Optional[str] = None,
|
|
72
|
+
currency: Optional[str] = None,
|
|
71
73
|
idempotency_key: Optional[str] = None,
|
|
72
74
|
) -> Program:
|
|
73
75
|
body: Dict[str, Any] = {
|
|
@@ -80,6 +82,8 @@ class ProgramsResource:
|
|
|
80
82
|
"description": description,
|
|
81
83
|
"landingPageUrl": landing_page_url,
|
|
82
84
|
"commissionLimitMonths": commission_limit_months,
|
|
85
|
+
"portalSlug": portal_slug,
|
|
86
|
+
"currency": currency,
|
|
83
87
|
}
|
|
84
88
|
envelope = self._http.request(
|
|
85
89
|
"POST",
|
|
@@ -102,8 +106,9 @@ class ProgramsResource:
|
|
|
102
106
|
description: Optional[str] = None,
|
|
103
107
|
landing_page_url: Optional[str] = None,
|
|
104
108
|
status: Optional[Literal["active", "paused", "archived"]] = None,
|
|
105
|
-
is_public: Optional[bool] = None,
|
|
106
109
|
commission_limit_months: Optional[int] = None,
|
|
110
|
+
portal_slug: Optional[str] = None,
|
|
111
|
+
currency: Optional[str] = None,
|
|
107
112
|
) -> Program:
|
|
108
113
|
body: Dict[str, Any] = {
|
|
109
114
|
"name": name,
|
|
@@ -115,8 +120,9 @@ class ProgramsResource:
|
|
|
115
120
|
"description": description,
|
|
116
121
|
"landingPageUrl": landing_page_url,
|
|
117
122
|
"status": status,
|
|
118
|
-
"isPublic": is_public,
|
|
119
123
|
"commissionLimitMonths": commission_limit_months,
|
|
124
|
+
"portalSlug": portal_slug,
|
|
125
|
+
"currency": currency,
|
|
120
126
|
}
|
|
121
127
|
envelope = self._http.request("PATCH", f"/programs/{id}", json={k: v for k, v in body.items() if v is not None})
|
|
122
128
|
return Program.model_validate(envelope["data"])
|
|
@@ -227,7 +233,7 @@ class ProgramsResource:
|
|
|
227
233
|
self,
|
|
228
234
|
id: str,
|
|
229
235
|
*,
|
|
230
|
-
status: Optional[Literal["private", "public"]] = None,
|
|
236
|
+
status: Optional[Literal["private", "pending", "public"]] = None,
|
|
231
237
|
category: Optional[str] = None,
|
|
232
238
|
description: Optional[str] = None,
|
|
233
239
|
logo_url: Optional[str] = None,
|
|
@@ -297,6 +303,8 @@ class AsyncProgramsResource:
|
|
|
297
303
|
description: Optional[str] = None,
|
|
298
304
|
landing_page_url: Optional[str] = None,
|
|
299
305
|
commission_limit_months: Optional[int] = None,
|
|
306
|
+
portal_slug: Optional[str] = None,
|
|
307
|
+
currency: Optional[str] = None,
|
|
300
308
|
idempotency_key: Optional[str] = None,
|
|
301
309
|
) -> Program:
|
|
302
310
|
body: Dict[str, Any] = {
|
|
@@ -309,6 +317,8 @@ class AsyncProgramsResource:
|
|
|
309
317
|
"description": description,
|
|
310
318
|
"landingPageUrl": landing_page_url,
|
|
311
319
|
"commissionLimitMonths": commission_limit_months,
|
|
320
|
+
"portalSlug": portal_slug,
|
|
321
|
+
"currency": currency,
|
|
312
322
|
}
|
|
313
323
|
envelope = await self._http.request(
|
|
314
324
|
"POST",
|
|
@@ -331,8 +341,9 @@ class AsyncProgramsResource:
|
|
|
331
341
|
description: Optional[str] = None,
|
|
332
342
|
landing_page_url: Optional[str] = None,
|
|
333
343
|
status: Optional[Literal["active", "paused", "archived"]] = None,
|
|
334
|
-
is_public: Optional[bool] = None,
|
|
335
344
|
commission_limit_months: Optional[int] = None,
|
|
345
|
+
portal_slug: Optional[str] = None,
|
|
346
|
+
currency: Optional[str] = None,
|
|
336
347
|
) -> Program:
|
|
337
348
|
body: Dict[str, Any] = {
|
|
338
349
|
"name": name,
|
|
@@ -344,8 +355,9 @@ class AsyncProgramsResource:
|
|
|
344
355
|
"description": description,
|
|
345
356
|
"landingPageUrl": landing_page_url,
|
|
346
357
|
"status": status,
|
|
347
|
-
"isPublic": is_public,
|
|
348
358
|
"commissionLimitMonths": commission_limit_months,
|
|
359
|
+
"portalSlug": portal_slug,
|
|
360
|
+
"currency": currency,
|
|
349
361
|
}
|
|
350
362
|
envelope = await self._http.request("PATCH", f"/programs/{id}", json={k: v for k, v in body.items() if v is not None})
|
|
351
363
|
return Program.model_validate(envelope["data"])
|
|
@@ -456,7 +468,7 @@ class AsyncProgramsResource:
|
|
|
456
468
|
self,
|
|
457
469
|
id: str,
|
|
458
470
|
*,
|
|
459
|
-
status: Optional[Literal["private", "public"]] = None,
|
|
471
|
+
status: Optional[Literal["private", "pending", "public"]] = None,
|
|
460
472
|
category: Optional[str] = None,
|
|
461
473
|
description: Optional[str] = None,
|
|
462
474
|
logo_url: Optional[str] = None,
|
|
@@ -32,18 +32,26 @@ class Program(BaseModel):
|
|
|
32
32
|
model_config = _API_CONFIG
|
|
33
33
|
|
|
34
34
|
id: str
|
|
35
|
+
merchant_id: str
|
|
35
36
|
name: str
|
|
37
|
+
description: Optional[str] = None
|
|
38
|
+
slug: str
|
|
39
|
+
landing_page_url: Optional[str] = None
|
|
40
|
+
portal_slug: Optional[str] = None
|
|
41
|
+
status: str
|
|
42
|
+
marketplace_status: str
|
|
43
|
+
marketplace_category: Optional[str] = None
|
|
44
|
+
marketplace_description: Optional[str] = None
|
|
45
|
+
marketplace_logo_url: Optional[str] = None
|
|
36
46
|
commission_type: str
|
|
37
47
|
commission_percent: float
|
|
38
48
|
commission_limit_months: Optional[int] = None
|
|
49
|
+
commission_hold_days: int
|
|
39
50
|
cookie_duration: int
|
|
40
51
|
payout_threshold: int
|
|
52
|
+
currency: str
|
|
41
53
|
auto_approve_affiliates: bool
|
|
42
|
-
|
|
43
|
-
landing_page_url: Optional[str] = None
|
|
44
|
-
status: str
|
|
45
|
-
is_public: bool
|
|
46
|
-
merchant_id: str
|
|
54
|
+
terms_url: Optional[str] = None
|
|
47
55
|
created_at: str
|
|
48
56
|
updated_at: str
|
|
49
57
|
|
|
@@ -51,17 +59,21 @@ class Program(BaseModel):
|
|
|
51
59
|
class ProgramStats(BaseModel):
|
|
52
60
|
model_config = _API_CONFIG
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
program_id: str
|
|
63
|
+
program_name: str
|
|
64
|
+
status: str
|
|
65
|
+
total_revenue: float
|
|
66
|
+
total_conversions: int
|
|
67
|
+
total_commissions: float
|
|
68
|
+
pending_commissions: float
|
|
69
|
+
active_affiliates: int
|
|
70
|
+
conversions_by_status: Dict[str, int]
|
|
59
71
|
|
|
60
72
|
|
|
61
73
|
class UpdateProgramMarketplaceParams(BaseModel):
|
|
62
74
|
model_config = _API_CONFIG
|
|
63
75
|
|
|
64
|
-
status: Optional[Literal["private", "public"]] = None
|
|
76
|
+
status: Optional[Literal["private", "pending", "public"]] = None
|
|
65
77
|
category: Optional[str] = None
|
|
66
78
|
description: Optional[str] = None
|
|
67
79
|
logo_url: Optional[str] = None
|
|
@@ -209,13 +221,32 @@ class Merchant(BaseModel):
|
|
|
209
221
|
model_config = _API_CONFIG
|
|
210
222
|
|
|
211
223
|
id: str
|
|
212
|
-
|
|
213
|
-
company_name:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
224
|
+
user_id: str
|
|
225
|
+
company_name: str
|
|
226
|
+
website: Optional[str] = None
|
|
227
|
+
logo_url: Optional[str] = None
|
|
228
|
+
stripe_account_id: Optional[str] = None
|
|
229
|
+
stripe_connected_at: Optional[str] = None
|
|
230
|
+
billing_tier: str
|
|
231
|
+
stripe_customer_id: Optional[str] = None
|
|
232
|
+
stripe_subscription_id: Optional[str] = None
|
|
233
|
+
payment_status: str
|
|
234
|
+
last_payment_failed_at: Optional[str] = None
|
|
235
|
+
default_cookie_duration: int
|
|
236
|
+
default_payout_threshold: int
|
|
237
|
+
timezone: str
|
|
238
|
+
tracking_requires_consent: bool
|
|
239
|
+
tracking_param_aliases: List[str]
|
|
240
|
+
tracking_legacy_metadata_fallback_enabled: bool
|
|
241
|
+
state: str
|
|
242
|
+
verified_domain: Optional[str] = None
|
|
243
|
+
domain_verification_token: Optional[str] = None
|
|
244
|
+
domain_verified_at: Optional[str] = None
|
|
245
|
+
notification_preferences: Optional[Dict[str, bool]] = None
|
|
246
|
+
onboarding_completed: bool
|
|
247
|
+
onboarding_step: int
|
|
218
248
|
created_at: str
|
|
249
|
+
updated_at: str
|
|
219
250
|
|
|
220
251
|
|
|
221
252
|
class UpdateMerchantParams(BaseModel):
|
|
@@ -227,14 +258,20 @@ class UpdateMerchantParams(BaseModel):
|
|
|
227
258
|
timezone: Optional[str] = None
|
|
228
259
|
default_cookie_duration: Optional[int] = None
|
|
229
260
|
default_payout_threshold: Optional[int] = None
|
|
261
|
+
tracking_requires_consent: Optional[bool] = None
|
|
262
|
+
tracking_param_aliases: Optional[List[str]] = None
|
|
263
|
+
tracking_legacy_metadata_fallback_enabled: Optional[bool] = None
|
|
230
264
|
|
|
231
265
|
|
|
232
266
|
class MerchantDomainStatus(BaseModel):
|
|
233
267
|
model_config = _API_CONFIG
|
|
234
268
|
|
|
269
|
+
status: str
|
|
235
270
|
domain: Optional[str] = None
|
|
236
|
-
verified: bool
|
|
237
271
|
txt_record: Optional[str] = None
|
|
272
|
+
verified_at: Optional[str] = None
|
|
273
|
+
tracking_mode: str
|
|
274
|
+
advanced_tracking_enabled: bool
|
|
238
275
|
|
|
239
276
|
|
|
240
277
|
class StripeConnectSession(BaseModel):
|
|
@@ -268,7 +305,9 @@ class PayoutInfo(BaseModel):
|
|
|
268
305
|
|
|
269
306
|
payout_method: Optional[str] = None
|
|
270
307
|
paypal_email: Optional[str] = None
|
|
308
|
+
bank_account_holder: Optional[str] = None
|
|
271
309
|
bank_iban: Optional[str] = None
|
|
310
|
+
bank_bic: Optional[str] = None
|
|
272
311
|
first_name: Optional[str] = None
|
|
273
312
|
last_name: Optional[str] = None
|
|
274
313
|
address_line1: Optional[str] = None
|
|
@@ -284,7 +323,9 @@ class UpdatePayoutInfoParams(BaseModel):
|
|
|
284
323
|
|
|
285
324
|
payout_method: Optional[str] = None
|
|
286
325
|
paypal_email: Optional[str] = None
|
|
326
|
+
bank_account_holder: Optional[str] = None
|
|
287
327
|
bank_iban: Optional[str] = None
|
|
328
|
+
bank_bic: Optional[str] = None
|
|
288
329
|
first_name: Optional[str] = None
|
|
289
330
|
last_name: Optional[str] = None
|
|
290
331
|
address_line1: Optional[str] = None
|
|
@@ -302,7 +343,7 @@ class NotificationPreferences(BaseModel):
|
|
|
302
343
|
new_conversion: bool = True
|
|
303
344
|
commission_approved: bool = True
|
|
304
345
|
payout_processed: bool = True
|
|
305
|
-
weekly_digest: bool =
|
|
346
|
+
weekly_digest: bool = False
|
|
306
347
|
|
|
307
348
|
|
|
308
349
|
class UpdateNotificationPreferencesParams(BaseModel):
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentref"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.0"
|
|
8
8
|
description = "Official Python SDK for the AgentRef Affiliate API"
|
|
9
9
|
authors = [{ name = "AgentRef", email = "hi@agentref.dev" }]
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -40,18 +40,26 @@ async def test_async_list_all_stops_on_has_more_false() -> None:
|
|
|
40
40
|
"data": [
|
|
41
41
|
{
|
|
42
42
|
"id": "prog_1",
|
|
43
|
+
"merchantId": "merch_1",
|
|
43
44
|
"name": "Program",
|
|
44
45
|
"description": None,
|
|
46
|
+
"slug": "program",
|
|
45
47
|
"landingPageUrl": None,
|
|
48
|
+
"portalSlug": "program",
|
|
49
|
+
"status": "active",
|
|
50
|
+
"marketplaceStatus": "public",
|
|
51
|
+
"marketplaceCategory": None,
|
|
52
|
+
"marketplaceDescription": None,
|
|
53
|
+
"marketplaceLogoUrl": None,
|
|
46
54
|
"commissionType": "one_time",
|
|
47
55
|
"commissionPercent": 20,
|
|
48
56
|
"commissionLimitMonths": None,
|
|
57
|
+
"commissionHoldDays": 30,
|
|
49
58
|
"cookieDuration": 30,
|
|
50
59
|
"payoutThreshold": 5000,
|
|
60
|
+
"currency": "USD",
|
|
51
61
|
"autoApproveAffiliates": True,
|
|
52
|
-
"
|
|
53
|
-
"isPublic": True,
|
|
54
|
-
"merchantId": "merch_1",
|
|
62
|
+
"termsUrl": None,
|
|
55
63
|
"createdAt": "2026-01-01T00:00:00Z",
|
|
56
64
|
"updatedAt": "2026-01-01T00:00:00Z",
|
|
57
65
|
}
|
|
@@ -71,18 +79,26 @@ async def test_async_list_all_stops_on_has_more_false() -> None:
|
|
|
71
79
|
"data": [
|
|
72
80
|
{
|
|
73
81
|
"id": "prog_2",
|
|
82
|
+
"merchantId": "merch_1",
|
|
74
83
|
"name": "Program",
|
|
75
84
|
"description": None,
|
|
85
|
+
"slug": "program",
|
|
76
86
|
"landingPageUrl": None,
|
|
87
|
+
"portalSlug": "program",
|
|
88
|
+
"status": "active",
|
|
89
|
+
"marketplaceStatus": "public",
|
|
90
|
+
"marketplaceCategory": None,
|
|
91
|
+
"marketplaceDescription": None,
|
|
92
|
+
"marketplaceLogoUrl": None,
|
|
77
93
|
"commissionType": "one_time",
|
|
78
94
|
"commissionPercent": 20,
|
|
79
95
|
"commissionLimitMonths": None,
|
|
96
|
+
"commissionHoldDays": 30,
|
|
80
97
|
"cookieDuration": 30,
|
|
81
98
|
"payoutThreshold": 5000,
|
|
99
|
+
"currency": "USD",
|
|
82
100
|
"autoApproveAffiliates": True,
|
|
83
|
-
"
|
|
84
|
-
"isPublic": True,
|
|
85
|
-
"merchantId": "merch_1",
|
|
101
|
+
"termsUrl": None,
|
|
86
102
|
"createdAt": "2026-01-01T00:00:00Z",
|
|
87
103
|
"updatedAt": "2026-01-01T00:00:00Z",
|
|
88
104
|
}
|
|
@@ -38,15 +38,26 @@ def test_pydantic_models_parse_camel_case() -> None:
|
|
|
38
38
|
program = Program.model_validate(
|
|
39
39
|
{
|
|
40
40
|
"id": "p1",
|
|
41
|
+
"merchantId": "m1",
|
|
41
42
|
"name": "Test",
|
|
43
|
+
"description": None,
|
|
44
|
+
"slug": "test",
|
|
45
|
+
"landingPageUrl": None,
|
|
46
|
+
"portalSlug": "test",
|
|
47
|
+
"status": "active",
|
|
48
|
+
"marketplaceStatus": "public",
|
|
49
|
+
"marketplaceCategory": None,
|
|
50
|
+
"marketplaceDescription": None,
|
|
51
|
+
"marketplaceLogoUrl": None,
|
|
42
52
|
"commissionType": "one_time",
|
|
43
53
|
"commissionPercent": 20.0,
|
|
54
|
+
"commissionLimitMonths": None,
|
|
55
|
+
"commissionHoldDays": 30,
|
|
44
56
|
"cookieDuration": 30,
|
|
45
57
|
"payoutThreshold": 5000,
|
|
58
|
+
"currency": "USD",
|
|
46
59
|
"autoApproveAffiliates": True,
|
|
47
|
-
"
|
|
48
|
-
"isPublic": True,
|
|
49
|
-
"merchantId": "m1",
|
|
60
|
+
"termsUrl": None,
|
|
50
61
|
"createdAt": "2026-01-01T00:00:00Z",
|
|
51
62
|
"updatedAt": "2026-01-01T00:00:00Z",
|
|
52
63
|
}
|
|
@@ -58,6 +69,45 @@ def test_pydantic_models_parse_camel_case() -> None:
|
|
|
58
69
|
assert program.auto_approve_affiliates is True
|
|
59
70
|
|
|
60
71
|
|
|
72
|
+
def test_merchant_model_parses_current_rest_shape() -> None:
|
|
73
|
+
from agentref.types.models import Merchant
|
|
74
|
+
|
|
75
|
+
merchant = Merchant.model_validate(
|
|
76
|
+
{
|
|
77
|
+
"id": "merch_1",
|
|
78
|
+
"userId": "user_1",
|
|
79
|
+
"companyName": "AgentRef Inc",
|
|
80
|
+
"website": "https://agentref.dev",
|
|
81
|
+
"logoUrl": None,
|
|
82
|
+
"stripeAccountId": None,
|
|
83
|
+
"stripeConnectedAt": None,
|
|
84
|
+
"billingTier": "free",
|
|
85
|
+
"stripeCustomerId": None,
|
|
86
|
+
"stripeSubscriptionId": None,
|
|
87
|
+
"paymentStatus": "active",
|
|
88
|
+
"lastPaymentFailedAt": None,
|
|
89
|
+
"defaultCookieDuration": 30,
|
|
90
|
+
"defaultPayoutThreshold": 5000,
|
|
91
|
+
"timezone": "UTC",
|
|
92
|
+
"trackingRequiresConsent": True,
|
|
93
|
+
"trackingParamAliases": ["ref"],
|
|
94
|
+
"trackingLegacyMetadataFallbackEnabled": True,
|
|
95
|
+
"state": "verified",
|
|
96
|
+
"verifiedDomain": "agentref.dev",
|
|
97
|
+
"domainVerificationToken": None,
|
|
98
|
+
"domainVerifiedAt": "2026-01-01T00:00:00Z",
|
|
99
|
+
"notificationPreferences": {"newAffiliate": True},
|
|
100
|
+
"onboardingCompleted": True,
|
|
101
|
+
"onboardingStep": 4,
|
|
102
|
+
"createdAt": "2026-01-01T00:00:00Z",
|
|
103
|
+
"updatedAt": "2026-01-02T00:00:00Z",
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
assert merchant.state == "verified"
|
|
108
|
+
assert merchant.verified_domain == "agentref.dev"
|
|
109
|
+
|
|
110
|
+
|
|
61
111
|
def test_pagination_meta_parses_camel_case() -> None:
|
|
62
112
|
from agentref.types.models import PaginationMeta
|
|
63
113
|
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import respx
|
|
7
|
+
|
|
8
|
+
from agentref import AgentRef
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _mock_program() -> dict:
|
|
12
|
+
return {
|
|
13
|
+
"id": "prog_1",
|
|
14
|
+
"merchantId": "merch_1",
|
|
15
|
+
"name": "Program",
|
|
16
|
+
"description": None,
|
|
17
|
+
"slug": "program",
|
|
18
|
+
"landingPageUrl": None,
|
|
19
|
+
"portalSlug": "program",
|
|
20
|
+
"status": "active",
|
|
21
|
+
"marketplaceStatus": "public",
|
|
22
|
+
"marketplaceCategory": None,
|
|
23
|
+
"marketplaceDescription": None,
|
|
24
|
+
"marketplaceLogoUrl": None,
|
|
25
|
+
"commissionType": "one_time",
|
|
26
|
+
"commissionPercent": 20,
|
|
27
|
+
"commissionLimitMonths": None,
|
|
28
|
+
"commissionHoldDays": 30,
|
|
29
|
+
"cookieDuration": 30,
|
|
30
|
+
"payoutThreshold": 5000,
|
|
31
|
+
"currency": "USD",
|
|
32
|
+
"autoApproveAffiliates": True,
|
|
33
|
+
"termsUrl": None,
|
|
34
|
+
"createdAt": "2026-01-01T00:00:00Z",
|
|
35
|
+
"updatedAt": "2026-01-01T00:00:00Z",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_create_uses_real_field_names() -> None:
|
|
40
|
+
client = AgentRef(api_key="ak_live_test")
|
|
41
|
+
|
|
42
|
+
with respx.mock:
|
|
43
|
+
route = respx.post("https://www.agentref.dev/api/v1/programs").mock(
|
|
44
|
+
return_value=httpx.Response(201, json={"data": _mock_program(), "meta": {"requestId": "r"}})
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
client.programs.create(
|
|
48
|
+
name="Test",
|
|
49
|
+
commission_type="one_time",
|
|
50
|
+
commission_percent=20,
|
|
51
|
+
cookie_duration=30,
|
|
52
|
+
portal_slug="test-program",
|
|
53
|
+
currency="EUR",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
body = json.loads(route.calls[0].request.content)
|
|
57
|
+
|
|
58
|
+
assert "commissionType" in body
|
|
59
|
+
assert "commissionPercent" in body
|
|
60
|
+
assert "cookieDuration" in body
|
|
61
|
+
assert body["portalSlug"] == "test-program"
|
|
62
|
+
assert body["currency"] == "EUR"
|
|
63
|
+
assert "commissionRate" not in body
|
|
64
|
+
assert "cookieDays" not in body
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_list_all_stops_on_has_more_false() -> None:
|
|
68
|
+
client = AgentRef(api_key="ak_live_test")
|
|
69
|
+
|
|
70
|
+
with respx.mock:
|
|
71
|
+
respx.get("https://www.agentref.dev/api/v1/programs").mock(
|
|
72
|
+
side_effect=[
|
|
73
|
+
httpx.Response(
|
|
74
|
+
200,
|
|
75
|
+
json={
|
|
76
|
+
"data": [_mock_program()],
|
|
77
|
+
"meta": {"total": 2, "page": 1, "pageSize": 1, "hasMore": True, "requestId": "r1"},
|
|
78
|
+
},
|
|
79
|
+
),
|
|
80
|
+
httpx.Response(
|
|
81
|
+
200,
|
|
82
|
+
json={
|
|
83
|
+
"data": [{**_mock_program(), "id": "prog_2"}],
|
|
84
|
+
"meta": {"total": 2, "page": 2, "pageSize": 1, "hasMore": False, "requestId": "r2"},
|
|
85
|
+
},
|
|
86
|
+
),
|
|
87
|
+
]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
all_programs = list(client.programs.list_all(page_size=1))
|
|
91
|
+
|
|
92
|
+
assert len(all_programs) == 2
|
|
93
|
+
assert all_programs[0].id == "prog_1"
|
|
94
|
+
assert all_programs[1].id == "prog_2"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_flags_resolve_sends_block_affiliate_true() -> None:
|
|
98
|
+
client = AgentRef(api_key="ak_live_test")
|
|
99
|
+
|
|
100
|
+
with respx.mock:
|
|
101
|
+
route = respx.post("https://www.agentref.dev/api/v1/flags/flag_1/resolve").mock(
|
|
102
|
+
return_value=httpx.Response(
|
|
103
|
+
200,
|
|
104
|
+
json={
|
|
105
|
+
"data": {
|
|
106
|
+
"id": "flag_1",
|
|
107
|
+
"affiliateId": "aff_1",
|
|
108
|
+
"type": "manual_review",
|
|
109
|
+
"status": "confirmed",
|
|
110
|
+
"details": {"source": "test"},
|
|
111
|
+
"note": "confirmed fraud",
|
|
112
|
+
"createdAt": "2026-01-01T00:00:00Z",
|
|
113
|
+
"resolvedAt": "2026-01-01T01:00:00Z",
|
|
114
|
+
},
|
|
115
|
+
"meta": {"requestId": "r"},
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
client.flags.resolve(
|
|
121
|
+
"flag_1",
|
|
122
|
+
status="confirmed",
|
|
123
|
+
note="confirmed fraud",
|
|
124
|
+
block_affiliate=True,
|
|
125
|
+
idempotency_key="idem-flag-1",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
body = json.loads(route.calls[0].request.content)
|
|
129
|
+
|
|
130
|
+
assert body["status"] == "confirmed"
|
|
131
|
+
assert body["blockAffiliate"] is True
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_list_invites_returns_typed_invites() -> None:
|
|
135
|
+
client = AgentRef(api_key="ak_live_test")
|
|
136
|
+
|
|
137
|
+
with respx.mock:
|
|
138
|
+
respx.get("https://www.agentref.dev/api/v1/programs/prog_1/invites").mock(
|
|
139
|
+
return_value=httpx.Response(
|
|
140
|
+
200,
|
|
141
|
+
json={
|
|
142
|
+
"data": [
|
|
143
|
+
{
|
|
144
|
+
"token": "tok_1",
|
|
145
|
+
"email": "affiliate@example.com",
|
|
146
|
+
"programId": "prog_1",
|
|
147
|
+
"expiresAt": "2026-12-01T00:00:00Z",
|
|
148
|
+
"createdAt": "2026-01-01T00:00:00Z",
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
"meta": {"requestId": "r"},
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
invites = client.programs.list_invites("prog_1")
|
|
156
|
+
assert invites[0].token == "tok_1"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_update_marketplace_uses_camel_case_payload() -> None:
|
|
160
|
+
client = AgentRef(api_key="ak_live_test")
|
|
161
|
+
|
|
162
|
+
with respx.mock:
|
|
163
|
+
route = respx.patch("https://www.agentref.dev/api/v1/programs/prog_1/marketplace").mock(
|
|
164
|
+
return_value=httpx.Response(200, json={"data": {"status": "pending"}, "meta": {"requestId": "r"}})
|
|
165
|
+
)
|
|
166
|
+
client.programs.update_marketplace("prog_1", status="pending", logo_url="https://cdn.example.com/logo.png")
|
|
167
|
+
body = json.loads(route.calls[0].request.content)
|
|
168
|
+
assert body["status"] == "pending"
|
|
169
|
+
assert body["logoUrl"] == "https://cdn.example.com/logo.png"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_payouts_create_sends_idempotency_and_body() -> None:
|
|
173
|
+
client = AgentRef(api_key="ak_live_test")
|
|
174
|
+
|
|
175
|
+
with respx.mock:
|
|
176
|
+
route = respx.post("https://www.agentref.dev/api/v1/payouts").mock(
|
|
177
|
+
return_value=httpx.Response(201, json={"data": {"id": "pay_1"}, "meta": {"requestId": "r"}})
|
|
178
|
+
)
|
|
179
|
+
client.payouts.create(
|
|
180
|
+
affiliate_id="aff_1",
|
|
181
|
+
program_id="prog_1",
|
|
182
|
+
method="paypal",
|
|
183
|
+
idempotency_key="idem-payout-1",
|
|
184
|
+
)
|
|
185
|
+
body = json.loads(route.calls[0].request.content)
|
|
186
|
+
idempotency = route.calls[0].request.headers.get("idempotency-key")
|
|
187
|
+
assert body["affiliateId"] == "aff_1"
|
|
188
|
+
assert body["programId"] == "prog_1"
|
|
189
|
+
assert body["method"] == "paypal"
|
|
190
|
+
assert idempotency == "idem-payout-1"
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_merchant_update_and_connect_stripe() -> None:
|
|
194
|
+
client = AgentRef(api_key="ak_live_test")
|
|
195
|
+
|
|
196
|
+
with respx.mock:
|
|
197
|
+
update_route = respx.patch("https://www.agentref.dev/api/v1/merchant").mock(
|
|
198
|
+
return_value=httpx.Response(
|
|
199
|
+
200,
|
|
200
|
+
json={
|
|
201
|
+
"data": {
|
|
202
|
+
"id": "merch_1",
|
|
203
|
+
"userId": "user_1",
|
|
204
|
+
"companyName": "AgentRef Inc",
|
|
205
|
+
"website": "https://agentref.dev",
|
|
206
|
+
"logoUrl": None,
|
|
207
|
+
"stripeAccountId": None,
|
|
208
|
+
"stripeConnectedAt": None,
|
|
209
|
+
"billingTier": "free",
|
|
210
|
+
"stripeCustomerId": None,
|
|
211
|
+
"stripeSubscriptionId": None,
|
|
212
|
+
"paymentStatus": "active",
|
|
213
|
+
"lastPaymentFailedAt": None,
|
|
214
|
+
"defaultCookieDuration": 30,
|
|
215
|
+
"defaultPayoutThreshold": 5000,
|
|
216
|
+
"timezone": "UTC",
|
|
217
|
+
"trackingRequiresConsent": True,
|
|
218
|
+
"trackingParamAliases": ["ref", "partner"],
|
|
219
|
+
"trackingLegacyMetadataFallbackEnabled": True,
|
|
220
|
+
"state": "verified",
|
|
221
|
+
"verifiedDomain": "agentref.dev",
|
|
222
|
+
"domainVerificationToken": None,
|
|
223
|
+
"domainVerifiedAt": "2026-01-01T00:00:00Z",
|
|
224
|
+
"notificationPreferences": {"newAffiliate": True},
|
|
225
|
+
"onboardingCompleted": True,
|
|
226
|
+
"onboardingStep": 4,
|
|
227
|
+
"createdAt": "2026-01-01T00:00:00Z",
|
|
228
|
+
"updatedAt": "2026-01-02T00:00:00Z",
|
|
229
|
+
},
|
|
230
|
+
"meta": {"requestId": "r"},
|
|
231
|
+
},
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
connect_route = respx.post("https://www.agentref.dev/api/v1/merchant/connect-stripe").mock(
|
|
235
|
+
return_value=httpx.Response(200, json={"data": {"url": "https://connect.stripe.com/x"}, "meta": {"requestId": "r"}})
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
merchant = client.merchant.update(
|
|
239
|
+
company_name="AgentRef Inc",
|
|
240
|
+
tracking_requires_consent=True,
|
|
241
|
+
tracking_param_aliases=["ref", "partner"],
|
|
242
|
+
)
|
|
243
|
+
connect = client.merchant.connect_stripe()
|
|
244
|
+
update_body = json.loads(update_route.calls[0].request.content)
|
|
245
|
+
connect_method = connect_route.calls[0].request.method
|
|
246
|
+
|
|
247
|
+
assert merchant.company_name == "AgentRef Inc"
|
|
248
|
+
assert update_body["companyName"] == "AgentRef Inc"
|
|
249
|
+
assert update_body["trackingRequiresConsent"] is True
|
|
250
|
+
assert update_body["trackingParamAliases"] == ["ref", "partner"]
|
|
251
|
+
assert merchant.state == "verified"
|
|
252
|
+
assert merchant.verified_domain == "agentref.dev"
|
|
253
|
+
assert connect.url.startswith("https://connect.stripe.com")
|
|
254
|
+
assert connect_method == "POST"
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def test_merchant_domain_status_uses_current_contract() -> None:
|
|
258
|
+
client = AgentRef(api_key="ak_live_test")
|
|
259
|
+
|
|
260
|
+
with respx.mock:
|
|
261
|
+
respx.get("https://www.agentref.dev/api/v1/merchant/domain-status").mock(
|
|
262
|
+
return_value=httpx.Response(
|
|
263
|
+
200,
|
|
264
|
+
json={
|
|
265
|
+
"data": {
|
|
266
|
+
"status": "verified",
|
|
267
|
+
"domain": "agentref.dev",
|
|
268
|
+
"txtRecord": None,
|
|
269
|
+
"verifiedAt": "2026-01-01T00:00:00Z",
|
|
270
|
+
"trackingMode": "advanced",
|
|
271
|
+
"advancedTrackingEnabled": True,
|
|
272
|
+
},
|
|
273
|
+
"meta": {"requestId": "r"},
|
|
274
|
+
},
|
|
275
|
+
)
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
status = client.merchant.domain_status()
|
|
279
|
+
|
|
280
|
+
assert status.status == "verified"
|
|
281
|
+
assert status.tracking_mode == "advanced"
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def test_program_stats_uses_current_contract() -> None:
|
|
285
|
+
client = AgentRef(api_key="ak_live_test")
|
|
286
|
+
|
|
287
|
+
with respx.mock:
|
|
288
|
+
respx.get("https://www.agentref.dev/api/v1/programs/prog_1/stats").mock(
|
|
289
|
+
return_value=httpx.Response(
|
|
290
|
+
200,
|
|
291
|
+
json={
|
|
292
|
+
"data": {
|
|
293
|
+
"programId": "prog_1",
|
|
294
|
+
"programName": "Growth Program",
|
|
295
|
+
"status": "active",
|
|
296
|
+
"totalRevenue": 25000,
|
|
297
|
+
"totalConversions": 12,
|
|
298
|
+
"totalCommissions": 5000,
|
|
299
|
+
"pendingCommissions": 1200,
|
|
300
|
+
"activeAffiliates": 3,
|
|
301
|
+
"conversionsByStatus": {
|
|
302
|
+
"pending": 1,
|
|
303
|
+
"approved": 10,
|
|
304
|
+
"rejected": 1,
|
|
305
|
+
"refunded": 0,
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
"meta": {"requestId": "r"},
|
|
309
|
+
},
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
stats = client.programs.stats("prog_1")
|
|
314
|
+
|
|
315
|
+
assert stats.program_id == "prog_1"
|
|
316
|
+
assert stats.conversions_by_status["approved"] == 10
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def test_payout_info_supports_bank_fields() -> None:
|
|
320
|
+
client = AgentRef(api_key="ak_live_test")
|
|
321
|
+
|
|
322
|
+
with respx.mock:
|
|
323
|
+
get_route = respx.get("https://www.agentref.dev/api/v1/me/payout-info").mock(
|
|
324
|
+
return_value=httpx.Response(
|
|
325
|
+
200,
|
|
326
|
+
json={
|
|
327
|
+
"data": {
|
|
328
|
+
"payoutMethod": "bank_transfer",
|
|
329
|
+
"paypalEmail": None,
|
|
330
|
+
"bankAccountHolder": "Jane Doe",
|
|
331
|
+
"bankIban": "****1234",
|
|
332
|
+
"bankBic": "COBADEFFXXX",
|
|
333
|
+
"firstName": "Jane",
|
|
334
|
+
"lastName": "Doe",
|
|
335
|
+
"addressLine1": "Main Street 1",
|
|
336
|
+
"addressLine2": None,
|
|
337
|
+
"city": "Berlin",
|
|
338
|
+
"state": None,
|
|
339
|
+
"postalCode": "10115",
|
|
340
|
+
"vatId": "DE123",
|
|
341
|
+
},
|
|
342
|
+
"meta": {"requestId": "r"},
|
|
343
|
+
},
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
update_route = respx.patch("https://www.agentref.dev/api/v1/me/payout-info").mock(
|
|
347
|
+
return_value=httpx.Response(
|
|
348
|
+
200,
|
|
349
|
+
json={
|
|
350
|
+
"data": {
|
|
351
|
+
"payoutMethod": "bank_transfer",
|
|
352
|
+
"paypalEmail": None,
|
|
353
|
+
"bankAccountHolder": "Jane Doe",
|
|
354
|
+
"bankIban": "****1234",
|
|
355
|
+
"bankBic": "COBADEFFXXX",
|
|
356
|
+
"firstName": "Jane",
|
|
357
|
+
"lastName": "Doe",
|
|
358
|
+
"addressLine1": "Main Street 1",
|
|
359
|
+
"addressLine2": None,
|
|
360
|
+
"city": "Berlin",
|
|
361
|
+
"state": None,
|
|
362
|
+
"postalCode": "10115",
|
|
363
|
+
"vatId": "DE123",
|
|
364
|
+
},
|
|
365
|
+
"meta": {"requestId": "r"},
|
|
366
|
+
},
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
payout_info = client.payout_info.get()
|
|
371
|
+
client.payout_info.update(
|
|
372
|
+
payout_method="bank_transfer",
|
|
373
|
+
bank_account_holder="Jane Doe",
|
|
374
|
+
bank_iban="DE89370400440532013000",
|
|
375
|
+
bank_bic="COBADEFFXXX",
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
update_body = json.loads(update_route.calls[0].request.content)
|
|
379
|
+
|
|
380
|
+
assert get_route.called
|
|
381
|
+
assert payout_info.bank_account_holder == "Jane Doe"
|
|
382
|
+
assert payout_info.bank_bic == "COBADEFFXXX"
|
|
383
|
+
assert update_body["bankAccountHolder"] == "Jane Doe"
|
|
384
|
+
assert update_body["bankBic"] == "COBADEFFXXX"
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
|
|
5
|
-
import httpx
|
|
6
|
-
import respx
|
|
7
|
-
|
|
8
|
-
from agentref import AgentRef
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _mock_program() -> dict:
|
|
12
|
-
return {
|
|
13
|
-
"id": "prog_1",
|
|
14
|
-
"name": "Program",
|
|
15
|
-
"description": None,
|
|
16
|
-
"landingPageUrl": None,
|
|
17
|
-
"commissionType": "one_time",
|
|
18
|
-
"commissionPercent": 20,
|
|
19
|
-
"commissionLimitMonths": None,
|
|
20
|
-
"cookieDuration": 30,
|
|
21
|
-
"payoutThreshold": 5000,
|
|
22
|
-
"autoApproveAffiliates": True,
|
|
23
|
-
"status": "active",
|
|
24
|
-
"isPublic": True,
|
|
25
|
-
"merchantId": "merch_1",
|
|
26
|
-
"createdAt": "2026-01-01T00:00:00Z",
|
|
27
|
-
"updatedAt": "2026-01-01T00:00:00Z",
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_create_uses_real_field_names() -> None:
|
|
32
|
-
client = AgentRef(api_key="ak_live_test")
|
|
33
|
-
|
|
34
|
-
with respx.mock:
|
|
35
|
-
route = respx.post("https://www.agentref.dev/api/v1/programs").mock(
|
|
36
|
-
return_value=httpx.Response(201, json={"data": _mock_program(), "meta": {"requestId": "r"}})
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
client.programs.create(
|
|
40
|
-
name="Test",
|
|
41
|
-
commission_type="one_time",
|
|
42
|
-
commission_percent=20,
|
|
43
|
-
cookie_duration=30,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
body = json.loads(route.calls[0].request.content)
|
|
47
|
-
|
|
48
|
-
assert "commissionType" in body
|
|
49
|
-
assert "commissionPercent" in body
|
|
50
|
-
assert "cookieDuration" in body
|
|
51
|
-
assert "commissionRate" not in body
|
|
52
|
-
assert "cookieDays" not in body
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def test_list_all_stops_on_has_more_false() -> None:
|
|
56
|
-
client = AgentRef(api_key="ak_live_test")
|
|
57
|
-
|
|
58
|
-
with respx.mock:
|
|
59
|
-
respx.get("https://www.agentref.dev/api/v1/programs").mock(
|
|
60
|
-
side_effect=[
|
|
61
|
-
httpx.Response(
|
|
62
|
-
200,
|
|
63
|
-
json={
|
|
64
|
-
"data": [_mock_program()],
|
|
65
|
-
"meta": {"total": 2, "page": 1, "pageSize": 1, "hasMore": True, "requestId": "r1"},
|
|
66
|
-
},
|
|
67
|
-
),
|
|
68
|
-
httpx.Response(
|
|
69
|
-
200,
|
|
70
|
-
json={
|
|
71
|
-
"data": [{**_mock_program(), "id": "prog_2"}],
|
|
72
|
-
"meta": {"total": 2, "page": 2, "pageSize": 1, "hasMore": False, "requestId": "r2"},
|
|
73
|
-
},
|
|
74
|
-
),
|
|
75
|
-
]
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
all_programs = list(client.programs.list_all(page_size=1))
|
|
79
|
-
|
|
80
|
-
assert len(all_programs) == 2
|
|
81
|
-
assert all_programs[0].id == "prog_1"
|
|
82
|
-
assert all_programs[1].id == "prog_2"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def test_flags_resolve_sends_block_affiliate_true() -> None:
|
|
86
|
-
client = AgentRef(api_key="ak_live_test")
|
|
87
|
-
|
|
88
|
-
with respx.mock:
|
|
89
|
-
route = respx.post("https://www.agentref.dev/api/v1/flags/flag_1/resolve").mock(
|
|
90
|
-
return_value=httpx.Response(
|
|
91
|
-
200,
|
|
92
|
-
json={
|
|
93
|
-
"data": {
|
|
94
|
-
"id": "flag_1",
|
|
95
|
-
"affiliateId": "aff_1",
|
|
96
|
-
"type": "manual_review",
|
|
97
|
-
"status": "confirmed",
|
|
98
|
-
"details": {"source": "test"},
|
|
99
|
-
"note": "confirmed fraud",
|
|
100
|
-
"createdAt": "2026-01-01T00:00:00Z",
|
|
101
|
-
"resolvedAt": "2026-01-01T01:00:00Z",
|
|
102
|
-
},
|
|
103
|
-
"meta": {"requestId": "r"},
|
|
104
|
-
},
|
|
105
|
-
)
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
client.flags.resolve(
|
|
109
|
-
"flag_1",
|
|
110
|
-
status="confirmed",
|
|
111
|
-
note="confirmed fraud",
|
|
112
|
-
block_affiliate=True,
|
|
113
|
-
idempotency_key="idem-flag-1",
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
body = json.loads(route.calls[0].request.content)
|
|
117
|
-
|
|
118
|
-
assert body["status"] == "confirmed"
|
|
119
|
-
assert body["blockAffiliate"] is True
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def test_list_invites_returns_typed_invites() -> None:
|
|
123
|
-
client = AgentRef(api_key="ak_live_test")
|
|
124
|
-
|
|
125
|
-
with respx.mock:
|
|
126
|
-
respx.get("https://www.agentref.dev/api/v1/programs/prog_1/invites").mock(
|
|
127
|
-
return_value=httpx.Response(
|
|
128
|
-
200,
|
|
129
|
-
json={
|
|
130
|
-
"data": [
|
|
131
|
-
{
|
|
132
|
-
"token": "tok_1",
|
|
133
|
-
"email": "affiliate@example.com",
|
|
134
|
-
"programId": "prog_1",
|
|
135
|
-
"expiresAt": "2026-12-01T00:00:00Z",
|
|
136
|
-
"createdAt": "2026-01-01T00:00:00Z",
|
|
137
|
-
}
|
|
138
|
-
],
|
|
139
|
-
"meta": {"requestId": "r"},
|
|
140
|
-
},
|
|
141
|
-
)
|
|
142
|
-
)
|
|
143
|
-
invites = client.programs.list_invites("prog_1")
|
|
144
|
-
assert invites[0].token == "tok_1"
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def test_update_marketplace_uses_camel_case_payload() -> None:
|
|
148
|
-
client = AgentRef(api_key="ak_live_test")
|
|
149
|
-
|
|
150
|
-
with respx.mock:
|
|
151
|
-
route = respx.patch("https://www.agentref.dev/api/v1/programs/prog_1/marketplace").mock(
|
|
152
|
-
return_value=httpx.Response(200, json={"data": {"status": "public"}, "meta": {"requestId": "r"}})
|
|
153
|
-
)
|
|
154
|
-
client.programs.update_marketplace("prog_1", status="public", logo_url="https://cdn.example.com/logo.png")
|
|
155
|
-
body = json.loads(route.calls[0].request.content)
|
|
156
|
-
assert body["status"] == "public"
|
|
157
|
-
assert body["logoUrl"] == "https://cdn.example.com/logo.png"
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def test_payouts_create_sends_idempotency_and_body() -> None:
|
|
161
|
-
client = AgentRef(api_key="ak_live_test")
|
|
162
|
-
|
|
163
|
-
with respx.mock:
|
|
164
|
-
route = respx.post("https://www.agentref.dev/api/v1/payouts").mock(
|
|
165
|
-
return_value=httpx.Response(201, json={"data": {"id": "pay_1"}, "meta": {"requestId": "r"}})
|
|
166
|
-
)
|
|
167
|
-
client.payouts.create(
|
|
168
|
-
affiliate_id="aff_1",
|
|
169
|
-
program_id="prog_1",
|
|
170
|
-
method="paypal",
|
|
171
|
-
idempotency_key="idem-payout-1",
|
|
172
|
-
)
|
|
173
|
-
body = json.loads(route.calls[0].request.content)
|
|
174
|
-
idempotency = route.calls[0].request.headers.get("idempotency-key")
|
|
175
|
-
assert body["affiliateId"] == "aff_1"
|
|
176
|
-
assert body["programId"] == "prog_1"
|
|
177
|
-
assert body["method"] == "paypal"
|
|
178
|
-
assert idempotency == "idem-payout-1"
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def test_merchant_update_and_connect_stripe() -> None:
|
|
182
|
-
client = AgentRef(api_key="ak_live_test")
|
|
183
|
-
|
|
184
|
-
with respx.mock:
|
|
185
|
-
update_route = respx.patch("https://www.agentref.dev/api/v1/merchant").mock(
|
|
186
|
-
return_value=httpx.Response(
|
|
187
|
-
200,
|
|
188
|
-
json={
|
|
189
|
-
"data": {
|
|
190
|
-
"id": "merch_1",
|
|
191
|
-
"email": "merchant@example.com",
|
|
192
|
-
"companyName": "AgentRef Inc",
|
|
193
|
-
"domain": None,
|
|
194
|
-
"domainVerified": False,
|
|
195
|
-
"trustLevel": "standard",
|
|
196
|
-
"stripeConnected": False,
|
|
197
|
-
"createdAt": "2026-01-01T00:00:00Z",
|
|
198
|
-
},
|
|
199
|
-
"meta": {"requestId": "r"},
|
|
200
|
-
},
|
|
201
|
-
)
|
|
202
|
-
)
|
|
203
|
-
connect_route = respx.post("https://www.agentref.dev/api/v1/merchant/connect-stripe").mock(
|
|
204
|
-
return_value=httpx.Response(200, json={"data": {"url": "https://connect.stripe.com/x"}, "meta": {"requestId": "r"}})
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
merchant = client.merchant.update(company_name="AgentRef Inc")
|
|
208
|
-
connect = client.merchant.connect_stripe()
|
|
209
|
-
update_body = json.loads(update_route.calls[0].request.content)
|
|
210
|
-
connect_method = connect_route.calls[0].request.method
|
|
211
|
-
|
|
212
|
-
assert merchant.company_name == "AgentRef Inc"
|
|
213
|
-
assert update_body["companyName"] == "AgentRef Inc"
|
|
214
|
-
assert connect.url.startswith("https://connect.stripe.com")
|
|
215
|
-
assert connect_method == "POST"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|