Payment Credentials API¶
The Payment Credentials API allows users to securely store and manage their payment provider API credentials for Kopokopo and InstaSend. Private keys are encrypted using the project's encryption key for enhanced security.
Features¶
- Secure Storage: Private keys are encrypted using Fernet encryption
- Multiple Providers: Support for Kopokopo and InstaSend with more to be added
- Environment Support: Sandbox and Live environments
- Verification: Hash-based verification of private keys
- Status Management: Activate/deactivate credentials as needed
Authentication¶
All endpoints require JWT authentication. Include the JWT token in the Authorization header:
Authorization: Bearer <your_jwt_token>
Endpoints¶
1. List/Create Payment Credentials¶
GET /payments/credentials/¶
List all payment credentials for the authenticated user.
Response:
[
{
"id": 1,
"provider": "kopokopo",
"provider_display": "KopoKopo",
"environment": "sandbox",
"is_active": true,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"is_live": false,
"is_sandbox": true
}
]
POST /payments/credentials/¶
Create new payment credentials.
Request Body:
{
"provider": "kopokopo",
"api_key": "pk_test_kopokopo_1234567890abcdef",
"private_key": "sk_test_kopokopo_0987654321fedcba",
"environment": "sandbox"
}
Response:
{
"id": 1,
"provider": "kopokopo",
"provider_display": "KopoKopo",
"environment": "sandbox",
"is_active": true,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"is_live": false,
"is_sandbox": true
}
2. Manage Specific Credentials¶
GET /payments/credentials/{id}/¶
Retrieve specific payment credentials.
PUT /payments/credentials/{id}/¶
Update payment credentials (excluding private key).
Request Body:
{
"api_key": "pk_test_kopokopo_new_key_12345",
"environment": "live"
}
DELETE /payments/credentials/{id}/¶
Delete payment credentials.
Response:
{
"message": "Payment credentials for KopoKopo have been successfully deleted"
}
3. Private Key Management¶
POST /payments/credentials/{id}/update-private-key/¶
Update the private key for existing credentials.
Request Body:
{
"private_key": "sk_test_kopokopo_new_private_key_12345"
}
Response:
{
"message": "Private key updated successfully",
"credentials": {
"id": 1,
"provider": "kopokopo",
"provider_display": "KopoKopo",
"environment": "sandbox",
"is_active": true,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:35:00Z",
"is_live": false,
"is_sandbox": true
}
}
GET /payments/credentials/{id}/get-private-key/¶
Retrieve the decrypted private key for API usage.
Response:
{
"credentials_id": 1,
"provider": "kopokopo",
"provider_display": "KopoKopo",
"private_key": "sk_test_kopokopo_0987654321fedcba",
"message": "Private key retrieved successfully"
}
4. Verification and Status¶
POST /payments/credentials/{id}/verify/¶
Verify credentials by checking the private key.
Request Body:
{
"private_key": "sk_test_kopokopo_0987654321fedcba"
}
Response (Valid):
{
"credentials_id": 1,
"provider": "kopokopo",
"provider_display": "KopoKopo",
"is_valid": true,
"message": "Credentials verified successfully"
}
Response (Invalid):
{
"credentials_id": 1,
"provider": "kopokopo",
"provider_display": "KopoKopo",
"is_valid": false,
"message": "Invalid private key"
}
POST /payments/credentials/{id}/toggle-status/¶
Toggle the active status of credentials.
Response:
{
"message": "Payment credentials for KopoKopo have been deactivated",
"credentials": {
"id": 1,
"provider": "kopokopo",
"provider_display": "KopoKopo",
"environment": "sandbox",
"is_active": false,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:40:00Z",
"is_live": false,
"is_sandbox": true
}
}
5. Provider-Specific Queries¶
GET /payments/credentials/provider/{provider}/¶
Get credentials for a specific provider.
Response:
{
"id": 1,
"provider": "kopokopo",
"provider_display": "KopoKopo",
"environment": "sandbox",
"is_active": true,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"is_live": false,
"is_sandbox": true
}
Code Examples¶
Create Payment Credentials¶
# Login to get JWT token
TOKEN=$(curl -s -X POST http://localhost:8000/users/login/ \
-H "Content-Type: application/json" \
-d '{"username": "your_username", "password": "your_password"}' \
| jq -r '.access_token')
# Create Kopokopo credentials
curl -X POST http://localhost:8000/payments/credentials/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"provider": "kopokopo",
"api_key": "pk_test_kopokopo_1234567890abcdef",
"private_key": "sk_test_kopokopo_0987654321fedcba",
"environment": "sandbox"
}'
import requests
# Login to get JWT token
login_response = requests.post('http://localhost:8000/users/login/', {
'username': 'your_username',
'password': 'your_password'
})
access_token = login_response.json()['access_token']
headers = {'Authorization': f'Bearer {access_token}'}
# Create Kopokopo credentials
credentials_data = {
'provider': 'kopokopo',
'api_key': 'pk_test_kopokopo_1234567890abcdef',
'private_key': 'sk_test_kopokopo_0987654321fedcba',
'environment': 'sandbox'
}
response = requests.post(
'http://localhost:8000/payments/credentials/',
json=credentials_data,
headers=headers
)
if response.status_code == 201:
credentials_id = response.json()['id']
print(f"Credentials created with ID: {credentials_id}")
// Login to get JWT token
const loginResponse = await fetch('http://localhost:8000/users/login/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: 'your_username',
password: 'your_password'
})
});
const { access_token } = await loginResponse.json();
const headers = {
'Authorization': `Bearer ${access_token}`,
'Content-Type': 'application/json'
};
// Create InstaSend credentials
const credentialsData = {
provider: 'instasend',
api_key: 'pk_test_instasend_1234567890abcdef',
private_key: 'sk_test_instasend_0987654321fedcba',
environment: 'sandbox'
};
const response = await fetch('http://localhost:8000/payments/credentials/', {
method: 'POST',
headers,
body: JSON.stringify(credentialsData)
});
if (response.ok) {
const credentials = await response.json();
console.log(`Credentials created with ID: ${credentials.id}`);
}
List Payment Credentials¶
curl -X GET http://localhost:8000/payments/credentials/ \
-H "Authorization: Bearer $TOKEN"
import requests
headers = {
"Authorization": "Bearer <your_jwt_token>"
}
response = requests.get(
"http://localhost:8000/payments/credentials/",
headers=headers
)
credentials = response.json()
print(f"Found {len(credentials)} payment credentials")
const response = await fetch('http://localhost:8000/payments/credentials/', {
headers: {
'Authorization': 'Bearer <your_jwt_token>'
}
});
const credentials = await response.json();
console.log(`Found ${credentials.length} payment credentials`);
Get Credentials by Provider¶
curl -X GET http://localhost:8000/payments/credentials/provider/kopokopo/ \
-H "Authorization: Bearer $TOKEN"
import requests
headers = {
"Authorization": "Bearer <your_jwt_token>"
}
response = requests.get(
"http://localhost:8000/payments/credentials/provider/kopokopo/",
headers=headers
)
credentials = response.json()
print(f"Provider: {credentials['provider_display']}")
const response = await fetch('http://localhost:8000/payments/credentials/provider/kopokopo/', {
headers: {
'Authorization': 'Bearer <your_jwt_token>'
}
});
const credentials = await response.json();
console.log(`Provider: ${credentials.provider_display}`);
Update Private Key¶
curl -X POST http://localhost:8000/payments/credentials/1/update-private-key/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"private_key": "sk_test_kopokopo_new_private_key_12345"
}'
import requests
headers = {
"Authorization": "Bearer <your_jwt_token>",
"Content-Type": "application/json"
}
data = {
"private_key": "sk_test_kopokopo_new_private_key_12345"
}
response = requests.post(
"http://localhost:8000/payments/credentials/1/update-private-key/",
headers=headers,
json=data
)
result = response.json()
print(f"Update message: {result['message']}")
const response = await fetch('http://localhost:8000/payments/credentials/1/update-private-key/', {
method: 'POST',
headers: {
'Authorization': 'Bearer <your_jwt_token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
private_key: 'sk_test_kopokopo_new_private_key_12345'
})
});
const result = await response.json();
console.log(`Update message: ${result.message}`);
Verify Credentials¶
curl -X POST http://localhost:8000/payments/credentials/1/verify/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"private_key": "sk_test_kopokopo_0987654321fedcba"
}'
import requests
headers = {
"Authorization": "Bearer <your_jwt_token>",
"Content-Type": "application/json"
}
data = {
"private_key": "sk_test_kopokopo_0987654321fedcba"
}
response = requests.post(
"http://localhost:8000/payments/credentials/1/verify/",
headers=headers,
json=data
)
result = response.json()
print(f"Verification result: {result['is_valid']}")
const response = await fetch('http://localhost:8000/payments/credentials/1/verify/', {
method: 'POST',
headers: {
'Authorization': 'Bearer <your_jwt_token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
private_key: 'sk_test_kopokopo_0987654321fedcba'
})
});
const result = await response.json();
console.log(`Verification result: ${result.is_valid}`);
Get Decrypted Private Key¶
curl -X GET http://localhost:8000/payments/credentials/1/get-private-key/ \
-H "Authorization: Bearer $TOKEN"
import requests
headers = {
"Authorization": "Bearer <your_jwt_token>"
}
response = requests.get(
"http://localhost:8000/payments/credentials/1/get-private-key/",
headers=headers
)
result = response.json()
print(f"Private key: {result['private_key']}")
const response = await fetch('http://localhost:8000/payments/credentials/1/get-private-key/', {
headers: {
'Authorization': 'Bearer <your_jwt_token>'
}
});
const result = await response.json();
console.log(`Private key: ${result.private_key}`);
Toggle Credentials Status¶
curl -X POST http://localhost:8000/payments/credentials/1/toggle-status/ \
-H "Authorization: Bearer $TOKEN"
import requests
headers = {
"Authorization": "Bearer <your_jwt_token>"
}
response = requests.post(
"http://localhost:8000/payments/credentials/1/toggle-status/",
headers=headers
)
result = response.json()
print(f"Status message: {result['message']}")
const response = await fetch('http://localhost:8000/payments/credentials/1/toggle-status/', {
method: 'POST',
headers: {
'Authorization': 'Bearer <your_jwt_token>'
}
});
const result = await response.json();
console.log(`Status message: ${result.message}`);
Update Credentials¶
curl -X PUT http://localhost:8000/payments/credentials/1/ \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"api_key": "pk_test_kopokopo_new_key_12345",
"environment": "live"
}'
import requests
headers = {
"Authorization": "Bearer <your_jwt_token>",
"Content-Type": "application/json"
}
data = {
"api_key": "pk_test_kopokopo_new_key_12345",
"environment": "live"
}
response = requests.put(
"http://localhost:8000/payments/credentials/1/",
headers=headers,
json=data
)
result = response.json()
print(f"Updated environment: {result['environment']}")
const response = await fetch('http://localhost:8000/payments/credentials/1/', {
method: 'PUT',
headers: {
'Authorization': 'Bearer <your_jwt_token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
api_key: 'pk_test_kopokopo_new_key_12345',
environment: 'live'
})
});
const result = await response.json();
console.log(`Updated environment: ${result.environment}`);
Delete Credentials¶
curl -X DELETE http://localhost:8000/payments/credentials/1/ \
-H "Authorization: Bearer $TOKEN"
import requests
headers = {
"Authorization": "Bearer <your_jwt_token>"
}
response = requests.delete(
"http://localhost:8000/payments/credentials/1/",
headers=headers
)
result = response.json()
print(f"Delete message: {result['message']}")
const response = await fetch('http://localhost:8000/payments/credentials/1/', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer <your_jwt_token>'
}
});
const result = await response.json();
console.log(`Delete message: ${result.message}`);
Security Features¶
Encryption¶
- Private keys are encrypted using Fernet (symmetric encryption)
- Uses the project's
ENCRYPTION_KEY
from settings - Encryption happens automatically when saving
Verification¶
- Private key hash is stored for verification
- Hash is SHA-256 for security
- Verification doesn't require decryption
Access Control¶
- Users can only access their own credentials
- JWT authentication required for all endpoints
- Admin interface restricted to superusers
Error Handling¶
Common Error Responses¶
400 Bad Request:
{
"error": "Private key must be at least 10 characters long"
}
404 Not Found:
{
"error": "Payment credentials not found or access denied"
}
500 Internal Server Error:
{
"error": "Failed to decrypt private key: Invalid token"
}
Database Schema¶
The PaymentCredentials
model includes:
user
: Foreign key to User modelprovider
: Choice between 'kopokopo' and 'instasend'api_key
: Public API key (stored as plain text)encrypted_private_key
: Encrypted private key (BinaryField)private_key_hash
: SHA-256 hash for verificationenvironment
: 'sandbox' or 'live'is_active
: Boolean status flagcreated_at
andupdated_at
: Timestamps
Testing¶
Use the provided test script to verify functionality:
python test_payment_credentials.py
This script tests all endpoints and demonstrates the complete workflow for managing payment credentials.