|
1 | 1 | # File generated from our OpenAPI spec by Stainless. |
2 | 2 |
|
| 3 | +import hmac |
| 4 | +import json |
| 5 | +import base64 |
| 6 | +import hashlib |
3 | 7 | from typing import Union |
| 8 | +from datetime import datetime, timezone, timedelta |
| 9 | + |
| 10 | +from httpx import URL |
4 | 11 |
|
5 | 12 | from .._types import NOT_GIVEN, Headers, Timeout, NotGiven |
6 | 13 | from .._resource import SyncAPIResource, AsyncAPIResource |
|
12 | 19 | from ..types.card_create_params import CardCreateParams |
13 | 20 | from ..types.card_update_params import CardUpdateParams |
14 | 21 | from ..types.card_reissue_params import CardReissueParams |
| 22 | +from ..types.embed_request_param import * |
15 | 23 | from ..types.card_provision_params import CardProvisionParams |
16 | 24 | from ..types.card_provision_response import * |
| 25 | +from ..types.card_get_embed_url_params import CardGetEmbedURLParams |
| 26 | +from ..types.card_get_embed_html_params import CardGetEmbedHTMLParams |
17 | 27 |
|
18 | 28 | __all__ = ["Cards", "AsyncCards"] |
19 | 29 |
|
@@ -144,6 +154,80 @@ def embed( |
144 | 154 | cast_to=str, |
145 | 155 | ) |
146 | 156 |
|
| 157 | + def get_embed_html( |
| 158 | + self, |
| 159 | + query: CardGetEmbedHTMLParams, |
| 160 | + *, |
| 161 | + headers: Union[Headers, NotGiven] = NOT_GIVEN, |
| 162 | + max_retries: Union[int, NotGiven] = NOT_GIVEN, |
| 163 | + timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, |
| 164 | + ) -> str: |
| 165 | + """ |
| 166 | + Generates and executes an embed request, returning html you can serve to the |
| 167 | + user. |
| 168 | +
|
| 169 | + Be aware that this html contains sensitive data whose presence on your server |
| 170 | + could trigger PCI DSS. |
| 171 | +
|
| 172 | + If your company is not certified PCI compliant, we recommend using |
| 173 | + `get_embed_url()` instead. You would then pass that returned URL to the |
| 174 | + frontend, where you can load it via an iframe. |
| 175 | + """ |
| 176 | + headers = {"Accept": "text/html", **(headers or {})} |
| 177 | + return self._get( |
| 178 | + str(self.get_embed_url(query)), |
| 179 | + cast_to=str, |
| 180 | + options=make_request_options(headers, max_retries, timeout), |
| 181 | + ) |
| 182 | + |
| 183 | + def get_embed_url(self, query: CardGetEmbedURLParams) -> URL: |
| 184 | + """ |
| 185 | + Handling full card PANs and CVV codes requires that you comply with the Payment |
| 186 | + Card Industry Data Security Standards (PCI DSS). Some clients choose to reduce |
| 187 | + their compliance obligations by leveraging our embedded card UI solution |
| 188 | + documented below. |
| 189 | +
|
| 190 | + In this setup, PANs and CVV codes are presented to the end-user via a card UI |
| 191 | + that we provide, optionally styled in the customer's branding using a specified |
| 192 | + css stylesheet. A user's browser makes the request directly to api.lithic.com, |
| 193 | + so card PANs and CVVs never touch the API customer's servers while full card |
| 194 | + data is displayed to their end-users. The response contains an HTML document. |
| 195 | + This means that the url for the request can be inserted straight into the `src` |
| 196 | + attribute of an iframe. |
| 197 | +
|
| 198 | + ```html |
| 199 | + <iframe |
| 200 | + id="card-iframe" |
| 201 | + src="https://sandbox.lithic.com/v1/embed/card?embed_request=eyJjc3MiO...;hmac=r8tx1..." |
| 202 | + allow="clipboard-write" |
| 203 | + class="content" |
| 204 | + ></iframe> |
| 205 | + ``` |
| 206 | +
|
| 207 | + You should compute the request payload on the server side. You can render it (or |
| 208 | + the whole iframe) on the server or make an ajax call from your front end code, |
| 209 | + but **do not ever embed your API key into front end code, as doing so introduces |
| 210 | + a serious security vulnerability**. |
| 211 | + """ |
| 212 | + # Default expiration of 1 minute from now. |
| 213 | + query.setdefault("expiration", (datetime.now(timezone.utc) + timedelta(minutes=1)).isoformat()) |
| 214 | + serialized = json.dumps(query, sort_keys=True, separators=(",", ":")) |
| 215 | + params = { |
| 216 | + "embed_request": base64.b64encode(bytes(serialized, "utf-8")).decode("utf-8"), |
| 217 | + "hmac": base64.b64encode( |
| 218 | + hmac.new( |
| 219 | + key=bytes(self._client.api_key, "utf-8"), |
| 220 | + msg=bytes(serialized, "utf-8"), |
| 221 | + digestmod=hashlib.sha256, |
| 222 | + ).digest() |
| 223 | + ).decode("utf-8"), |
| 224 | + } |
| 225 | + |
| 226 | + # Copied nearly directly from httpx.BaseClient._merge_url |
| 227 | + base_url = self._client.base_url |
| 228 | + raw_path = base_url.raw_path + URL("embed/card").raw_path |
| 229 | + return base_url.copy_with(raw_path=raw_path).copy_merge_params(params) |
| 230 | + |
147 | 231 | def provision( |
148 | 232 | self, |
149 | 233 | card_token: str, |
@@ -317,6 +401,80 @@ async def embed( |
317 | 401 | cast_to=str, |
318 | 402 | ) |
319 | 403 |
|
| 404 | + async def get_embed_html( |
| 405 | + self, |
| 406 | + query: CardGetEmbedHTMLParams, |
| 407 | + *, |
| 408 | + headers: Union[Headers, NotGiven] = NOT_GIVEN, |
| 409 | + max_retries: Union[int, NotGiven] = NOT_GIVEN, |
| 410 | + timeout: Union[float, Timeout, None, NotGiven] = NOT_GIVEN, |
| 411 | + ) -> str: |
| 412 | + """ |
| 413 | + Generates and executes an embed request, returning html you can serve to the |
| 414 | + user. |
| 415 | +
|
| 416 | + Be aware that this html contains sensitive data whose presence on your server |
| 417 | + could trigger PCI DSS. |
| 418 | +
|
| 419 | + If your company is not certified PCI compliant, we recommend using |
| 420 | + `get_embed_url()` instead. You would then pass that returned URL to the |
| 421 | + frontend, where you can load it via an iframe. |
| 422 | + """ |
| 423 | + headers = {"Accept": "text/html", **(headers or {})} |
| 424 | + return await self._get( |
| 425 | + str(self.get_embed_url(query)), |
| 426 | + cast_to=str, |
| 427 | + options=make_request_options(headers, max_retries, timeout), |
| 428 | + ) |
| 429 | + |
| 430 | + def get_embed_url(self, query: CardGetEmbedURLParams) -> URL: |
| 431 | + """ |
| 432 | + Handling full card PANs and CVV codes requires that you comply with the Payment |
| 433 | + Card Industry Data Security Standards (PCI DSS). Some clients choose to reduce |
| 434 | + their compliance obligations by leveraging our embedded card UI solution |
| 435 | + documented below. |
| 436 | +
|
| 437 | + In this setup, PANs and CVV codes are presented to the end-user via a card UI |
| 438 | + that we provide, optionally styled in the customer's branding using a specified |
| 439 | + css stylesheet. A user's browser makes the request directly to api.lithic.com, |
| 440 | + so card PANs and CVVs never touch the API customer's servers while full card |
| 441 | + data is displayed to their end-users. The response contains an HTML document. |
| 442 | + This means that the url for the request can be inserted straight into the `src` |
| 443 | + attribute of an iframe. |
| 444 | +
|
| 445 | + ```html |
| 446 | + <iframe |
| 447 | + id="card-iframe" |
| 448 | + src="https://sandbox.lithic.com/v1/embed/card?embed_request=eyJjc3MiO...;hmac=r8tx1..." |
| 449 | + allow="clipboard-write" |
| 450 | + class="content" |
| 451 | + ></iframe> |
| 452 | + ``` |
| 453 | +
|
| 454 | + You should compute the request payload on the server side. You can render it (or |
| 455 | + the whole iframe) on the server or make an ajax call from your front end code, |
| 456 | + but **do not ever embed your API key into front end code, as doing so introduces |
| 457 | + a serious security vulnerability**. |
| 458 | + """ |
| 459 | + # Default expiration of 1 minute from now. |
| 460 | + query.setdefault("expiration", (datetime.now(timezone.utc) + timedelta(minutes=1)).isoformat()) |
| 461 | + serialized = json.dumps(query, sort_keys=True, separators=(",", ":")) |
| 462 | + params = { |
| 463 | + "embed_request": base64.b64encode(bytes(serialized, "utf-8")).decode("utf-8"), |
| 464 | + "hmac": base64.b64encode( |
| 465 | + hmac.new( |
| 466 | + key=bytes(self._client.api_key, "utf-8"), |
| 467 | + msg=bytes(serialized, "utf-8"), |
| 468 | + digestmod=hashlib.sha256, |
| 469 | + ).digest() |
| 470 | + ).decode("utf-8"), |
| 471 | + } |
| 472 | + |
| 473 | + # Copied nearly directly from httpx.BaseClient._merge_url |
| 474 | + base_url = self._client.base_url |
| 475 | + raw_path = base_url.raw_path + URL("embed/card").raw_path |
| 476 | + return base_url.copy_with(raw_path=raw_path).copy_merge_params(params) |
| 477 | + |
320 | 478 | async def provision( |
321 | 479 | self, |
322 | 480 | card_token: str, |
|
0 commit comments