swig-developer-sdk 0.6.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.
@@ -0,0 +1,724 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping, Sequence
4
+ from dataclasses import dataclass
5
+ from typing import Literal, TypeAlias
6
+ from urllib.parse import quote, urlencode
7
+
8
+ from .common import Network, require_network, to_proto_network
9
+ from .core import HttpClient
10
+
11
+ RampDirection: TypeAlias = Literal["onramp", "offramp", "transfer", "unspecified"]
12
+ ActiveRampDirection: TypeAlias = Literal["onramp", "offramp", "transfer"]
13
+ RampCustomerType: TypeAlias = Literal["individual", "business", "unspecified"]
14
+ RampTransactionType: TypeAlias = Literal[
15
+ "crypto-purchase",
16
+ "crypto-sell",
17
+ "crypto-purchase-swap",
18
+ "crypto-sell-swap",
19
+ "transfer",
20
+ "unspecified",
21
+ ]
22
+ RampTransactionStatus: TypeAlias = Literal[
23
+ "created",
24
+ "pending",
25
+ "settling",
26
+ "settled",
27
+ "failed",
28
+ "declined",
29
+ "cancelled",
30
+ "refunded",
31
+ "unspecified",
32
+ ]
33
+ ActiveRampTransactionStatus: TypeAlias = Literal[
34
+ "created",
35
+ "pending",
36
+ "settling",
37
+ "settled",
38
+ "failed",
39
+ "declined",
40
+ "cancelled",
41
+ "refunded",
42
+ ]
43
+ RampServiceProvider: TypeAlias = Literal["other", "unspecified"]
44
+ RampPaymentMethodType: TypeAlias = Literal[
45
+ "other",
46
+ "credit-debit-card",
47
+ "ach",
48
+ "bank-transfer",
49
+ "apple-pay",
50
+ "google-pay",
51
+ "pix",
52
+ "unspecified",
53
+ ]
54
+
55
+
56
+ @dataclass(frozen=True, slots=True)
57
+ class RampCustomerContext:
58
+ customer_type: RampCustomerType
59
+ partner_application_id: str | None = None
60
+ swig_user_id: str | None = None
61
+ external_customer_id: str | None = None
62
+ external_business_id: str | None = None
63
+
64
+
65
+ @dataclass(frozen=True, slots=True)
66
+ class RampWalletContext:
67
+ wallet_id: str
68
+ wallet_address: str
69
+ network: Network
70
+
71
+
72
+ @dataclass(frozen=True, slots=True)
73
+ class GetRampOptionsResult:
74
+ country_codes: tuple[str, ...]
75
+ fiat_currency_codes: tuple[str, ...]
76
+ payment_method_types: tuple[RampPaymentMethodType, ...]
77
+ crypto_currency_codes: tuple[str, ...]
78
+
79
+
80
+ @dataclass(frozen=True, slots=True)
81
+ class QuoteRampArgs:
82
+ customer: RampCustomerContext
83
+ wallet: RampWalletContext
84
+ direction: ActiveRampDirection
85
+ source_amount: str
86
+ source_currency_code: str
87
+ destination_currency_code: str
88
+ country_code: str
89
+ subdivision: str | None = None
90
+ payment_method_type: RampPaymentMethodType | None = None
91
+ service_providers: tuple[RampServiceProvider, ...] = ()
92
+
93
+
94
+ @dataclass(frozen=True, slots=True)
95
+ class RampQuote:
96
+ quote_id: str
97
+ direction: RampDirection
98
+ service_provider: RampServiceProvider
99
+ payment_method_type: RampPaymentMethodType
100
+ source_amount: str
101
+ source_currency_code: str
102
+ destination_amount: str
103
+ destination_currency_code: str
104
+ exchange_rate: str
105
+ total_fee: str
106
+ network_fee: str
107
+ transaction_fee: str
108
+ partner_fee: str
109
+ ramp_score: str | None = None
110
+ low_kyc: bool | None = None
111
+
112
+
113
+ @dataclass(frozen=True, slots=True)
114
+ class QuoteRampResult:
115
+ quotes: tuple[RampQuote, ...]
116
+
117
+
118
+ @dataclass(frozen=True, slots=True)
119
+ class CreateRampSessionArgs:
120
+ customer: RampCustomerContext
121
+ wallet: RampWalletContext
122
+ direction: ActiveRampDirection
123
+ selected_quote_id: str
124
+ source_amount: str
125
+ source_currency_code: str
126
+ destination_currency_code: str
127
+ country_code: str
128
+ service_provider: RampServiceProvider
129
+ subdivision: str | None = None
130
+ payment_method_type: RampPaymentMethodType | None = None
131
+ redirect_url: str | None = None
132
+
133
+
134
+ @dataclass(frozen=True, slots=True)
135
+ class CreateRampSessionResult:
136
+ local_session_id: str
137
+ meld_session_id: str
138
+ external_customer_id: str
139
+ external_session_id: str
140
+ launch_url: str
141
+ fallback_launch_url: str | None = None
142
+
143
+
144
+ @dataclass(frozen=True, slots=True)
145
+ class RampTransaction:
146
+ transaction_id: str
147
+ wallet_id: str
148
+ direction: RampDirection
149
+ transaction_type: RampTransactionType
150
+ status: RampTransactionStatus
151
+ service_provider: RampServiceProvider
152
+ source_amount: str
153
+ source_currency_code: str
154
+ destination_currency_code: str
155
+ created_at: str
156
+ updated_at: str
157
+ meld_transaction_id: str | None = None
158
+ meld_session_id: str | None = None
159
+ payment_method_type: RampPaymentMethodType | None = None
160
+ destination_amount: str | None = None
161
+
162
+
163
+ @dataclass(frozen=True, slots=True)
164
+ class GetRampTransactionResult:
165
+ transaction: RampTransaction | None = None
166
+
167
+
168
+ @dataclass(frozen=True, slots=True)
169
+ class ListRampTransactionsResult:
170
+ transactions: tuple[RampTransaction, ...]
171
+
172
+
173
+ class _RampCustomerFactory:
174
+ def direct_swig_user(
175
+ self,
176
+ *,
177
+ swig_user_id: str,
178
+ partner_application_id: str | None = None,
179
+ ) -> RampCustomerContext:
180
+ return RampCustomerContext(
181
+ swig_user_id=_non_empty(swig_user_id, "swig_user_id"),
182
+ partner_application_id=(
183
+ partner_application_id
184
+ if partner_application_id and partner_application_id.strip()
185
+ else None
186
+ ),
187
+ customer_type="individual",
188
+ )
189
+
190
+ def partner_customer(
191
+ self,
192
+ *,
193
+ partner_application_id: str,
194
+ external_customer_id: str,
195
+ ) -> RampCustomerContext:
196
+ return RampCustomerContext(
197
+ partner_application_id=_non_empty(
198
+ partner_application_id, "partner_application_id"
199
+ ),
200
+ external_customer_id=_non_empty(
201
+ external_customer_id, "external_customer_id"
202
+ ),
203
+ customer_type="individual",
204
+ )
205
+
206
+ def partner_business(
207
+ self,
208
+ *,
209
+ partner_application_id: str,
210
+ external_business_id: str,
211
+ ) -> RampCustomerContext:
212
+ return RampCustomerContext(
213
+ partner_application_id=_non_empty(
214
+ partner_application_id, "partner_application_id"
215
+ ),
216
+ external_business_id=_non_empty(
217
+ external_business_id, "external_business_id"
218
+ ),
219
+ customer_type="business",
220
+ )
221
+
222
+
223
+ ramp_customer = _RampCustomerFactory()
224
+
225
+
226
+ class RampClient:
227
+ def __init__(
228
+ self,
229
+ http: HttpClient,
230
+ default_network: Network | None = None,
231
+ ) -> None:
232
+ self._http = http
233
+ self._default_network = default_network
234
+
235
+ async def get_options(
236
+ self,
237
+ *,
238
+ partner_application_id: str | None = None,
239
+ country_code: str | None = None,
240
+ fiat_currency_code: str | None = None,
241
+ ) -> GetRampOptionsResult:
242
+ path = _path_with_query(
243
+ "/wallet/api/ramp/options",
244
+ {
245
+ "partnerApplicationId": partner_application_id,
246
+ "countryCode": country_code,
247
+ "fiatCurrencyCode": fiat_currency_code,
248
+ },
249
+ )
250
+ return normalize_ramp_options(await self._http.get(path))
251
+
252
+ async def quote(self, args: QuoteRampArgs) -> QuoteRampResult:
253
+ return normalize_quote_ramp_result(
254
+ await self._http.post("/wallet/api/ramp/quote", _quote_request(args))
255
+ )
256
+
257
+ async def create_session(
258
+ self,
259
+ args: CreateRampSessionArgs,
260
+ ) -> CreateRampSessionResult:
261
+ request = _quote_request(
262
+ QuoteRampArgs(
263
+ customer=args.customer,
264
+ wallet=args.wallet,
265
+ direction=args.direction,
266
+ source_amount=args.source_amount,
267
+ source_currency_code=args.source_currency_code,
268
+ destination_currency_code=args.destination_currency_code,
269
+ country_code=args.country_code,
270
+ subdivision=args.subdivision,
271
+ payment_method_type=args.payment_method_type,
272
+ )
273
+ )
274
+ request.update(
275
+ {
276
+ "selectedQuoteId": args.selected_quote_id,
277
+ "serviceProvider": _service_provider_to_wire(args.service_provider),
278
+ "redirectUrl": args.redirect_url,
279
+ }
280
+ )
281
+ return normalize_create_ramp_session_result(
282
+ await self._http.post("/wallet/api/ramp/sessions", request)
283
+ )
284
+
285
+ async def get_transaction(
286
+ self,
287
+ *,
288
+ transaction_id: str,
289
+ ) -> GetRampTransactionResult:
290
+ response = await self._http.get(
291
+ f"/wallet/api/ramp/transactions/{quote(transaction_id, safe='')}"
292
+ )
293
+ body = _mapping(response, "Ramp transaction response")
294
+ transaction = body.get("transaction")
295
+ return GetRampTransactionResult(
296
+ transaction=(
297
+ _normalize_ramp_transaction(transaction)
298
+ if transaction is not None
299
+ else None
300
+ )
301
+ )
302
+
303
+ async def list_transactions(
304
+ self,
305
+ *,
306
+ wallet_id: str,
307
+ network: Network | None = None,
308
+ direction: ActiveRampDirection | None = None,
309
+ status: ActiveRampTransactionStatus | None = None,
310
+ limit: int | None = None,
311
+ ) -> ListRampTransactionsResult:
312
+ resolved_network = require_network(network, self._default_network)
313
+ path = _path_with_query(
314
+ f"/wallet/api/ramp/wallets/{quote(wallet_id, safe='')}/transactions",
315
+ {
316
+ "network": to_proto_network(resolved_network),
317
+ "direction": (_direction_to_wire(direction) if direction else None),
318
+ "status": _status_to_wire(status) if status else None,
319
+ "limit": limit,
320
+ },
321
+ )
322
+ body = _mapping(await self._http.get(path), "Ramp transaction response")
323
+ transactions = body.get("transactions", [])
324
+ if not isinstance(transactions, Sequence) or isinstance(
325
+ transactions, (str, bytes)
326
+ ):
327
+ raise ValueError("Ramp transaction response has invalid transactions")
328
+ return ListRampTransactionsResult(
329
+ tuple(_normalize_ramp_transaction(item) for item in transactions)
330
+ )
331
+
332
+
333
+ def normalize_ramp_options(response: object) -> GetRampOptionsResult:
334
+ body = _mapping(response, "Ramp options response")
335
+ return GetRampOptionsResult(
336
+ country_codes=_string_tuple(
337
+ body.get("countryCodes", body.get("country_codes", []))
338
+ ),
339
+ fiat_currency_codes=_string_tuple(
340
+ body.get("fiatCurrencyCodes", body.get("fiat_currency_codes", []))
341
+ ),
342
+ payment_method_types=tuple(
343
+ _normalize_payment_method(value)
344
+ for value in _sequence(
345
+ body.get(
346
+ "paymentMethodTypes",
347
+ body.get("payment_method_types", []),
348
+ ),
349
+ "paymentMethodTypes",
350
+ )
351
+ ),
352
+ crypto_currency_codes=_string_tuple(
353
+ body.get("cryptoCurrencyCodes", body.get("crypto_currency_codes", []))
354
+ ),
355
+ )
356
+
357
+
358
+ def normalize_quote_ramp_result(response: object) -> QuoteRampResult:
359
+ body = _mapping(response, "Ramp quote response")
360
+ return QuoteRampResult(
361
+ tuple(
362
+ _normalize_ramp_quote(item)
363
+ for item in _sequence(body.get("quotes", []), "quotes")
364
+ )
365
+ )
366
+
367
+
368
+ def normalize_create_ramp_session_result(
369
+ response: object,
370
+ ) -> CreateRampSessionResult:
371
+ body = _mapping(response, "Ramp session response")
372
+ return CreateRampSessionResult(
373
+ local_session_id=_required_string(
374
+ _pick(body, "localSessionId", "local_session_id"), "localSessionId"
375
+ ),
376
+ meld_session_id=_required_string(
377
+ _pick(body, "meldSessionId", "meld_session_id"), "meldSessionId"
378
+ ),
379
+ external_customer_id=_required_string(
380
+ _pick(body, "externalCustomerId", "external_customer_id"),
381
+ "externalCustomerId",
382
+ ),
383
+ external_session_id=_required_string(
384
+ _pick(body, "externalSessionId", "external_session_id"),
385
+ "externalSessionId",
386
+ ),
387
+ launch_url=_required_string(
388
+ _pick(body, "launchUrl", "launch_url"), "launchUrl"
389
+ ),
390
+ fallback_launch_url=_optional_string(
391
+ _pick(body, "fallbackLaunchUrl", "fallback_launch_url")
392
+ ),
393
+ )
394
+
395
+
396
+ def _quote_request(args: QuoteRampArgs) -> dict[str, object]:
397
+ return {
398
+ "customer": {
399
+ "partnerApplicationId": args.customer.partner_application_id,
400
+ "swigUserId": args.customer.swig_user_id,
401
+ "externalCustomerId": args.customer.external_customer_id,
402
+ "externalBusinessId": args.customer.external_business_id,
403
+ "customerType": _customer_type_to_wire(args.customer.customer_type),
404
+ },
405
+ "wallet": {
406
+ "walletId": args.wallet.wallet_id,
407
+ "walletAddress": args.wallet.wallet_address,
408
+ "network": to_proto_network(args.wallet.network),
409
+ },
410
+ "direction": _direction_to_wire(args.direction),
411
+ "sourceAmount": args.source_amount,
412
+ "sourceCurrencyCode": args.source_currency_code,
413
+ "destinationCurrencyCode": args.destination_currency_code,
414
+ "countryCode": args.country_code,
415
+ "subdivision": args.subdivision,
416
+ "paymentMethodType": (
417
+ _payment_method_to_wire(args.payment_method_type)
418
+ if args.payment_method_type is not None
419
+ else None
420
+ ),
421
+ "serviceProviders": [
422
+ _service_provider_to_wire(value) for value in args.service_providers
423
+ ],
424
+ }
425
+
426
+
427
+ def _normalize_ramp_quote(value: object) -> RampQuote:
428
+ body = _mapping(value, "Ramp quote")
429
+ low_kyc = _pick(body, "lowKyc", "low_kyc")
430
+ if low_kyc is not None and not isinstance(low_kyc, bool):
431
+ raise ValueError("Ramp response has invalid lowKyc")
432
+ return RampQuote(
433
+ quote_id=_required_string(_pick(body, "quoteId", "quote_id"), "quoteId"),
434
+ direction=_normalize_direction(body.get("direction")),
435
+ service_provider=_normalize_service_provider(
436
+ _pick(body, "serviceProvider", "service_provider")
437
+ ),
438
+ payment_method_type=_normalize_payment_method(
439
+ _pick(body, "paymentMethodType", "payment_method_type")
440
+ ),
441
+ source_amount=_required_string(
442
+ _pick(body, "sourceAmount", "source_amount"), "sourceAmount"
443
+ ),
444
+ source_currency_code=_required_string(
445
+ _pick(body, "sourceCurrencyCode", "source_currency_code"),
446
+ "sourceCurrencyCode",
447
+ ),
448
+ destination_amount=_required_string(
449
+ _pick(body, "destinationAmount", "destination_amount"),
450
+ "destinationAmount",
451
+ ),
452
+ destination_currency_code=_required_string(
453
+ _pick(body, "destinationCurrencyCode", "destination_currency_code"),
454
+ "destinationCurrencyCode",
455
+ ),
456
+ exchange_rate=_required_string(
457
+ _pick(body, "exchangeRate", "exchange_rate"), "exchangeRate"
458
+ ),
459
+ total_fee=_required_string(_pick(body, "totalFee", "total_fee"), "totalFee"),
460
+ network_fee=_required_string(
461
+ _pick(body, "networkFee", "network_fee"), "networkFee"
462
+ ),
463
+ transaction_fee=_required_string(
464
+ _pick(body, "transactionFee", "transaction_fee"),
465
+ "transactionFee",
466
+ ),
467
+ partner_fee=_required_string(
468
+ _pick(body, "partnerFee", "partner_fee"), "partnerFee"
469
+ ),
470
+ ramp_score=_optional_string(_pick(body, "rampScore", "ramp_score")),
471
+ low_kyc=low_kyc,
472
+ )
473
+
474
+
475
+ def _normalize_ramp_transaction(value: object) -> RampTransaction:
476
+ body = _mapping(value, "Ramp transaction")
477
+ payment_method = _pick(body, "paymentMethodType", "payment_method_type")
478
+ return RampTransaction(
479
+ transaction_id=_required_string(
480
+ _pick(body, "transactionId", "transaction_id"), "transactionId"
481
+ ),
482
+ meld_transaction_id=_optional_string(
483
+ _pick(body, "meldTransactionId", "meld_transaction_id")
484
+ ),
485
+ meld_session_id=_optional_string(
486
+ _pick(body, "meldSessionId", "meld_session_id")
487
+ ),
488
+ wallet_id=_required_string(_pick(body, "walletId", "wallet_id"), "walletId"),
489
+ direction=_normalize_direction(body.get("direction")),
490
+ transaction_type=_normalize_transaction_type(
491
+ _pick(body, "transactionType", "transaction_type")
492
+ ),
493
+ status=_normalize_status(body.get("status")),
494
+ service_provider=_normalize_service_provider(
495
+ _pick(body, "serviceProvider", "service_provider")
496
+ ),
497
+ payment_method_type=(
498
+ _normalize_payment_method(payment_method)
499
+ if payment_method is not None
500
+ else None
501
+ ),
502
+ source_amount=_required_string(
503
+ _pick(body, "sourceAmount", "source_amount"), "sourceAmount"
504
+ ),
505
+ source_currency_code=_required_string(
506
+ _pick(body, "sourceCurrencyCode", "source_currency_code"),
507
+ "sourceCurrencyCode",
508
+ ),
509
+ destination_amount=_optional_string(
510
+ _pick(body, "destinationAmount", "destination_amount")
511
+ ),
512
+ destination_currency_code=_required_string(
513
+ _pick(body, "destinationCurrencyCode", "destination_currency_code"),
514
+ "destinationCurrencyCode",
515
+ ),
516
+ created_at=_required_string(
517
+ _pick(body, "createdAt", "created_at"), "createdAt"
518
+ ),
519
+ updated_at=_required_string(
520
+ _pick(body, "updatedAt", "updated_at"), "updatedAt"
521
+ ),
522
+ )
523
+
524
+
525
+ def _direction_to_wire(value: ActiveRampDirection) -> str:
526
+ return {
527
+ "onramp": "RAMP_DIRECTION_ONRAMP",
528
+ "offramp": "RAMP_DIRECTION_OFFRAMP",
529
+ "transfer": "RAMP_DIRECTION_TRANSFER",
530
+ }[value]
531
+
532
+
533
+ def _customer_type_to_wire(value: RampCustomerType) -> str:
534
+ return {
535
+ "individual": "RAMP_CUSTOMER_TYPE_INDIVIDUAL",
536
+ "business": "RAMP_CUSTOMER_TYPE_BUSINESS",
537
+ "unspecified": "RAMP_CUSTOMER_TYPE_UNSPECIFIED",
538
+ }[value]
539
+
540
+
541
+ def _service_provider_to_wire(value: RampServiceProvider) -> str:
542
+ return {
543
+ "other": "RAMP_SERVICE_PROVIDER_OTHER",
544
+ "unspecified": "RAMP_SERVICE_PROVIDER_UNSPECIFIED",
545
+ }[value]
546
+
547
+
548
+ def _payment_method_to_wire(value: RampPaymentMethodType) -> str:
549
+ return {
550
+ "other": "RAMP_PAYMENT_METHOD_TYPE_OTHER",
551
+ "credit-debit-card": "RAMP_PAYMENT_METHOD_TYPE_CREDIT_DEBIT_CARD",
552
+ "ach": "RAMP_PAYMENT_METHOD_TYPE_ACH",
553
+ "bank-transfer": "RAMP_PAYMENT_METHOD_TYPE_BANK_TRANSFER",
554
+ "apple-pay": "RAMP_PAYMENT_METHOD_TYPE_APPLE_PAY",
555
+ "google-pay": "RAMP_PAYMENT_METHOD_TYPE_GOOGLE_PAY",
556
+ "pix": "RAMP_PAYMENT_METHOD_TYPE_PIX",
557
+ "unspecified": "RAMP_PAYMENT_METHOD_TYPE_UNSPECIFIED",
558
+ }[value]
559
+
560
+
561
+ def _status_to_wire(value: ActiveRampTransactionStatus) -> str:
562
+ return f"RAMP_TRANSACTION_STATUS_{value.upper()}"
563
+
564
+
565
+ def _normalize_direction(value: object) -> RampDirection:
566
+ values: dict[object, RampDirection] = {
567
+ "onramp": "onramp",
568
+ "RAMP_DIRECTION_ONRAMP": "onramp",
569
+ 1: "onramp",
570
+ "offramp": "offramp",
571
+ "RAMP_DIRECTION_OFFRAMP": "offramp",
572
+ 2: "offramp",
573
+ "transfer": "transfer",
574
+ "RAMP_DIRECTION_TRANSFER": "transfer",
575
+ 3: "transfer",
576
+ "unspecified": "unspecified",
577
+ "RAMP_DIRECTION_UNSPECIFIED": "unspecified",
578
+ 0: "unspecified",
579
+ None: "unspecified",
580
+ }
581
+ try:
582
+ return values[value]
583
+ except (KeyError, TypeError) as error:
584
+ raise ValueError("Ramp response has invalid direction") from error
585
+
586
+
587
+ def _normalize_service_provider(value: object) -> RampServiceProvider:
588
+ if value in ("other", "RAMP_SERVICE_PROVIDER_OTHER", 1):
589
+ return "other"
590
+ if value in (None, "unspecified", "RAMP_SERVICE_PROVIDER_UNSPECIFIED", 0):
591
+ return "unspecified"
592
+ raise ValueError("Ramp response has invalid serviceProvider")
593
+
594
+
595
+ def _normalize_payment_method(value: object) -> RampPaymentMethodType:
596
+ values: dict[object, RampPaymentMethodType] = {
597
+ "other": "other",
598
+ "RAMP_PAYMENT_METHOD_TYPE_OTHER": "other",
599
+ 1: "other",
600
+ "credit-debit-card": "credit-debit-card",
601
+ "RAMP_PAYMENT_METHOD_TYPE_CREDIT_DEBIT_CARD": "credit-debit-card",
602
+ 2: "credit-debit-card",
603
+ "ach": "ach",
604
+ "RAMP_PAYMENT_METHOD_TYPE_ACH": "ach",
605
+ 3: "ach",
606
+ "bank-transfer": "bank-transfer",
607
+ "RAMP_PAYMENT_METHOD_TYPE_BANK_TRANSFER": "bank-transfer",
608
+ 4: "bank-transfer",
609
+ "apple-pay": "apple-pay",
610
+ "RAMP_PAYMENT_METHOD_TYPE_APPLE_PAY": "apple-pay",
611
+ 5: "apple-pay",
612
+ "google-pay": "google-pay",
613
+ "RAMP_PAYMENT_METHOD_TYPE_GOOGLE_PAY": "google-pay",
614
+ 6: "google-pay",
615
+ "pix": "pix",
616
+ "RAMP_PAYMENT_METHOD_TYPE_PIX": "pix",
617
+ 7: "pix",
618
+ "unspecified": "unspecified",
619
+ "RAMP_PAYMENT_METHOD_TYPE_UNSPECIFIED": "unspecified",
620
+ 0: "unspecified",
621
+ None: "unspecified",
622
+ }
623
+ try:
624
+ return values[value]
625
+ except (KeyError, TypeError) as error:
626
+ raise ValueError("Ramp response has invalid paymentMethodType") from error
627
+
628
+
629
+ def _normalize_transaction_type(value: object) -> RampTransactionType:
630
+ values: tuple[tuple[tuple[object, ...], RampTransactionType], ...] = (
631
+ (
632
+ ("crypto-purchase", "RAMP_TRANSACTION_TYPE_CRYPTO_PURCHASE", 1),
633
+ "crypto-purchase",
634
+ ),
635
+ (("crypto-sell", "RAMP_TRANSACTION_TYPE_CRYPTO_SELL", 2), "crypto-sell"),
636
+ (
637
+ ("crypto-purchase-swap", "RAMP_TRANSACTION_TYPE_CRYPTO_PURCHASE_SWAP", 3),
638
+ "crypto-purchase-swap",
639
+ ),
640
+ (
641
+ ("crypto-sell-swap", "RAMP_TRANSACTION_TYPE_CRYPTO_SELL_SWAP", 4),
642
+ "crypto-sell-swap",
643
+ ),
644
+ (("transfer", "RAMP_TRANSACTION_TYPE_TRANSFER", 5), "transfer"),
645
+ ((None, "unspecified", "RAMP_TRANSACTION_TYPE_UNSPECIFIED", 0), "unspecified"),
646
+ )
647
+ for candidates, normalized in values:
648
+ if value in candidates:
649
+ return normalized
650
+ raise ValueError("Ramp response has invalid transactionType")
651
+
652
+
653
+ def _normalize_status(value: object) -> RampTransactionStatus:
654
+ normalized_values: tuple[RampTransactionStatus, ...] = (
655
+ "created",
656
+ "pending",
657
+ "settling",
658
+ "settled",
659
+ "failed",
660
+ "declined",
661
+ "cancelled",
662
+ "refunded",
663
+ )
664
+ for index, normalized in enumerate(normalized_values, start=1):
665
+ if value in (
666
+ normalized,
667
+ f"RAMP_TRANSACTION_STATUS_{normalized.upper()}",
668
+ index,
669
+ ):
670
+ return normalized
671
+ if value in (None, "unspecified", "RAMP_TRANSACTION_STATUS_UNSPECIFIED", 0):
672
+ return "unspecified"
673
+ raise ValueError("Ramp response has invalid status")
674
+
675
+
676
+ def _path_with_query(path: str, query_values: Mapping[str, object]) -> str:
677
+ query = {
678
+ key: str(value) for key, value in query_values.items() if value is not None
679
+ }
680
+ return f"{path}?{urlencode(query)}" if query else path
681
+
682
+
683
+ def _mapping(value: object, label: str) -> Mapping[str, object]:
684
+ if isinstance(value, Mapping):
685
+ return value
686
+ raise ValueError(f"{label} must be an object")
687
+
688
+
689
+ def _sequence(value: object, field: str) -> Sequence[object]:
690
+ if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
691
+ return value
692
+ raise ValueError(f"Ramp response has invalid {field}")
693
+
694
+
695
+ def _string_tuple(value: object) -> tuple[str, ...]:
696
+ sequence = _sequence(value, "string list")
697
+ if not all(isinstance(item, str) for item in sequence):
698
+ raise ValueError("Ramp response has invalid string list")
699
+ return tuple(item for item in sequence if isinstance(item, str))
700
+
701
+
702
+ def _required_string(value: object, field: str) -> str:
703
+ if isinstance(value, str):
704
+ return value
705
+ if isinstance(value, (int, bool)):
706
+ return str(value)
707
+ raise ValueError(f"Ramp response is missing {field}")
708
+
709
+
710
+ def _optional_string(value: object) -> str | None:
711
+ return value if isinstance(value, str) else None
712
+
713
+
714
+ def _pick(value: Mapping[str, object], *keys: str) -> object:
715
+ for key in keys:
716
+ if key in value:
717
+ return value[key]
718
+ return None
719
+
720
+
721
+ def _non_empty(value: str, field: str) -> str:
722
+ if not value.strip():
723
+ raise ValueError(f"{field} is required")
724
+ return value