Card collections
Charging a customer's debit/credit card involves cardholder PII, so Lenco requires PCI DSS compliance on your side and encrypts the request payload end-to-end with JWE. The SDK handles the encryption mechanics.
The popup widget: an alternative with no PCI DSS scope
This page describes the direct API. With the direct API, you build the card form, and PCI DSS compliance becomes your responsibility. Most integrations do not need this. Lenco also offers a client-side popup widget (LencoPay.getPaid(), loaded from pay.lenco.co). The widget collects the card inside Lenco's own script. Card data never reaches your form or your server. See Accept Payments for the widget. The widget is outside this SDK. Both paths end with the same server-side call: get_by_reference.
The rest of this page describes the direct API. It requires the card extra:
pip install "lenco-py[card]"Flow
- Build the plaintext payload (customer, billing, card details).
- Encrypt it —
client.encryption.encrypt()fetches a fresh RSA public key from Lenco (GET /encryption-key) and produces a JWE compact string (A256GCM content encryption, RSA-OAEP-256 key wrapping). - Submit it with
client.collections.from_card(encrypted_payload=...). - If the returned status is
"3ds-auth-required", redirect the customer toresult.authorization.redirectto complete 3-D Secure. - The final status arrives via webhook — see Handling webhooks.
WARNING
Fetch the key fresh per payload (which is what encrypt() does). Lenco can rotate the key at any time — do not cache it.
Example
result = client.collections.from_card(
encrypted_payload=client.encryption.encrypt({
"email": "customer@example.com",
"reference": "order-9012",
"amount": 13.00,
"currency": "ZMW",
"customer": {"firstName": "Haim", "lastName": "Hasegawa"},
"billing": {
"streetAddress": "1 Independence Ave",
"city": "Lusaka",
"postalCode": "10101",
"country": "ZM",
},
"card": {
"number": "5555555555554444",
"expiryMonth": "12",
"expiryYear": "2030",
"cvv": "123",
},
})
)
if result.collection.status == "3ds-auth-required":
redirect_customer(result.authorization.redirect)Typed payload
encrypt() also accepts a CardCollectionPayload, which catches a typo'd field name (for example, postalCode) at construction time instead of an opaque 400 after encryption:
from lenco.models import (
CardCollectionBilling,
CardCollectionCard,
CardCollectionCustomer,
CardCollectionPayload,
)
payload = CardCollectionPayload(
email="customer@example.com",
reference="order-9012",
amount=13.00,
currency="ZMW",
customer=CardCollectionCustomer(first_name="Haim", last_name="Hasegawa"),
billing=CardCollectionBilling(
street_address="1 Independence Ave",
city="Lusaka",
postal_code="10101",
country="ZM",
),
card=CardCollectionCard(
number="5555555555554444",
expiry_month="12",
expiry_year="2030",
cvv="123",
),
)
result = client.collections.from_card(
encrypted_payload=client.encryption.encrypt(payload)
)A plain dict, as in the example above, still works — pass whichever is convenient.
Payload fields
| Field | Required | Description |
|---|---|---|
email | Yes | Customer email address |
reference | Yes | Your unique reference (case-sensitive) |
amount | Yes | Amount to charge, decimals allowed (for example, 10.75) |
currency | Yes | ISO 3-letter code, for example ZMW, USD |
bearer | No | merchant (default) or customer pays the fee |
customer.firstName | Yes | Customer first name |
customer.lastName | Yes | Customer last name |
billing.streetAddress | Yes | Street address |
billing.city | Yes | City |
billing.state | No | State/province, where applicable |
billing.postalCode | Yes | Postal code |
billing.country | Yes | 2-letter code, for example ZM |
card.number | Yes | Card PAN |
card.expiryMonth | Yes | Expiry month |
card.expiryYear | Yes | Expiry year |
card.cvv | Yes | Card security code |
redirectUrl | No | Customer is redirected here after payment |
The full parameter list and the encryption scheme are documented in Lenco's own docs.
Lower-level API
If you already hold a fresh public key (for example, you batch-encrypt several payloads in one request cycle), use encrypt_payload directly:
from lenco.encryption import encrypt_payload
key = client.encryption.get_key() # JWK dict with kty/n/e/kid
token = encrypt_payload(payload, key) # JWE compact string