bpay 0.1.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.
- bpay/__init__.py +5 -0
- bpay/client.py +157 -0
- bpay/config/__init__.py +0 -0
- bpay/config/environments.py +6 -0
- bpay/config.py +0 -0
- bpay/exceptions.py +52 -0
- bpay/providers/__init__.py +0 -0
- bpay/providers/base.py +15 -0
- bpay/providers/bkash/__init__.py +0 -0
- bpay/providers/bkash/agreement.py +96 -0
- bpay/providers/bkash/auth.py +87 -0
- bpay/providers/bkash/callbacks.py +21 -0
- bpay/providers/bkash/constants.py +17 -0
- bpay/providers/bkash/payment.py +103 -0
- bpay/providers/bkash/payment_callbacks.py +22 -0
- bpay/providers/bkash/provider.py +117 -0
- bpay/providers/bkash/schemas.py +15 -0
- bpay/providers/bkash/verification.py +84 -0
- bpay/providers/nagad/__init__.py +0 -0
- bpay/providers/registry.py +5 -0
- bpay/providers/sslcommerz/__init__.py +0 -0
- bpay/schemas/__init__.py +0 -0
- bpay/schemas/agreement.py +15 -0
- bpay/schemas/callback.py +10 -0
- bpay/schemas/payment.py +20 -0
- bpay/schemas/payment_callback.py +9 -0
- bpay/schemas/verification.py +12 -0
- bpay/security/__init__.py +0 -0
- bpay/transports/__init__.py +0 -0
- bpay/transports/http.py +15 -0
- bpay/types.py +21 -0
- bpay/webhooks/__init__.py +0 -0
- bpay-0.1.0.dist-info/METADATA +293 -0
- bpay-0.1.0.dist-info/RECORD +36 -0
- bpay-0.1.0.dist-info/WHEEL +4 -0
- bpay-0.1.0.dist-info/licenses/LICENSE +21 -0
bpay/__init__.py
ADDED
bpay/client.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from bpay.providers.bkash.callbacks import (
|
|
4
|
+
parse_agreement_callback,
|
|
5
|
+
)
|
|
6
|
+
from bpay.providers.bkash.payment_callbacks import (
|
|
7
|
+
parse_payment_callback,
|
|
8
|
+
)
|
|
9
|
+
from bpay.providers.registry import (
|
|
10
|
+
PROVIDERS,
|
|
11
|
+
)
|
|
12
|
+
from bpay.schemas.agreement import (
|
|
13
|
+
AgreementResponse,
|
|
14
|
+
CreateAgreementRequest,
|
|
15
|
+
)
|
|
16
|
+
from bpay.schemas.callback import (
|
|
17
|
+
AgreementCallback,
|
|
18
|
+
)
|
|
19
|
+
from bpay.schemas.payment import (
|
|
20
|
+
CreatePaymentRequest,
|
|
21
|
+
PaymentResponse,
|
|
22
|
+
)
|
|
23
|
+
from bpay.schemas.payment_callback import (
|
|
24
|
+
PaymentCallback,
|
|
25
|
+
)
|
|
26
|
+
from bpay.schemas.verification import (
|
|
27
|
+
PaymentVerificationResponse,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BPay:
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
provider: str,
|
|
35
|
+
**credentials: Any,
|
|
36
|
+
) -> None:
|
|
37
|
+
provider_class = (
|
|
38
|
+
PROVIDERS.get(provider)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if provider_class is None:
|
|
42
|
+
supported = ", ".join(
|
|
43
|
+
PROVIDERS.keys()
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"Unsupported provider: "
|
|
48
|
+
f"{provider}. "
|
|
49
|
+
f"Supported providers: "
|
|
50
|
+
f"{supported}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
self.provider_name = (
|
|
54
|
+
provider
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
self.provider = (
|
|
58
|
+
provider_class(
|
|
59
|
+
**credentials
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
async def create_agreement(
|
|
64
|
+
self,
|
|
65
|
+
payload: CreateAgreementRequest,
|
|
66
|
+
) -> AgreementResponse:
|
|
67
|
+
if not hasattr(
|
|
68
|
+
self.provider,
|
|
69
|
+
"create_agreement",
|
|
70
|
+
):
|
|
71
|
+
raise NotImplementedError(
|
|
72
|
+
f"{self.provider_name} "
|
|
73
|
+
"does not support "
|
|
74
|
+
"agreement creation"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return await (
|
|
78
|
+
self.provider.create_agreement(
|
|
79
|
+
payload
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
async def create_payment(
|
|
84
|
+
self,
|
|
85
|
+
payload: CreatePaymentRequest,
|
|
86
|
+
) -> PaymentResponse:
|
|
87
|
+
if not hasattr(
|
|
88
|
+
self.provider,
|
|
89
|
+
"create_payment",
|
|
90
|
+
):
|
|
91
|
+
raise NotImplementedError(
|
|
92
|
+
f"{self.provider_name} "
|
|
93
|
+
"does not support "
|
|
94
|
+
"payment creation"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return await (
|
|
98
|
+
self.provider.create_payment(
|
|
99
|
+
payload
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def parse_agreement_callback(
|
|
104
|
+
self,
|
|
105
|
+
params: dict[str, str],
|
|
106
|
+
) -> AgreementCallback:
|
|
107
|
+
if (
|
|
108
|
+
self.provider_name
|
|
109
|
+
!= "bkash"
|
|
110
|
+
):
|
|
111
|
+
raise NotImplementedError(
|
|
112
|
+
f"{self.provider_name} "
|
|
113
|
+
"does not support "
|
|
114
|
+
"agreement callbacks yet"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
parse_agreement_callback(
|
|
119
|
+
params
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
def parse_payment_callback(
|
|
124
|
+
self,
|
|
125
|
+
params: dict[str, str],
|
|
126
|
+
) -> PaymentCallback:
|
|
127
|
+
if self.provider_name != "bkash":
|
|
128
|
+
raise NotImplementedError(
|
|
129
|
+
f"{self.provider_name} "
|
|
130
|
+
"does not support "
|
|
131
|
+
"payment callbacks yet"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return parse_payment_callback(
|
|
135
|
+
params
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
async def verify_payment(
|
|
139
|
+
self,
|
|
140
|
+
payment_id: str,
|
|
141
|
+
) -> PaymentVerificationResponse:
|
|
142
|
+
if not hasattr(
|
|
143
|
+
self.provider,
|
|
144
|
+
"verify_payment",
|
|
145
|
+
):
|
|
146
|
+
raise NotImplementedError(
|
|
147
|
+
f"{self.provider_name} "
|
|
148
|
+
"does not support "
|
|
149
|
+
"payment verification"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return await (
|
|
153
|
+
self.provider.verify_payment(
|
|
154
|
+
payment_id
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
bpay/config/__init__.py
ADDED
|
File without changes
|
bpay/config.py
ADDED
|
File without changes
|
bpay/exceptions.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
class BPayError(Exception):
|
|
2
|
+
"""Base exception for bpay."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AuthenticationError(BPayError):
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
provider: str,
|
|
9
|
+
message: str,
|
|
10
|
+
provider_code: str | None = None,
|
|
11
|
+
) -> None:
|
|
12
|
+
self.provider = provider
|
|
13
|
+
self.provider_code = provider_code
|
|
14
|
+
self.message = message
|
|
15
|
+
|
|
16
|
+
super().__init__(self.__str__())
|
|
17
|
+
|
|
18
|
+
def __str__(self) -> str:
|
|
19
|
+
lines = [
|
|
20
|
+
f"[{self.provider} Authentication Error]",
|
|
21
|
+
f"Message: {self.message}",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
if self.provider_code:
|
|
25
|
+
lines.append(
|
|
26
|
+
f"Code: {self.provider_code}"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
return "\n".join(lines)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AgreementError(
|
|
33
|
+
BPayError
|
|
34
|
+
):
|
|
35
|
+
"""Agreement workflow failed."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ProviderAPIError(
|
|
39
|
+
BPayError
|
|
40
|
+
):
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
message: str,
|
|
44
|
+
provider_code: (
|
|
45
|
+
str | None
|
|
46
|
+
) = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
self.provider_code = (
|
|
49
|
+
provider_code
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
super().__init__(message)
|
|
File without changes
|
bpay/providers/base.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from bpay.schemas.payment import (
|
|
4
|
+
CreatePaymentRequest,
|
|
5
|
+
PaymentResponse,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseProvider(ABC):
|
|
10
|
+
@abstractmethod
|
|
11
|
+
async def create_payment(
|
|
12
|
+
self,
|
|
13
|
+
payload: CreatePaymentRequest,
|
|
14
|
+
) -> PaymentResponse:
|
|
15
|
+
pass
|
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from bpay.exceptions import AgreementError
|
|
6
|
+
from bpay.providers.bkash.auth import (
|
|
7
|
+
BkashAuth,
|
|
8
|
+
)
|
|
9
|
+
from bpay.providers.bkash.constants import (
|
|
10
|
+
BKASH_AGREEMENT_STATUS_MAP,
|
|
11
|
+
)
|
|
12
|
+
from bpay.schemas.agreement import (
|
|
13
|
+
AgreementResponse,
|
|
14
|
+
CreateAgreementRequest,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BkashAgreement:
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
auth: BkashAuth,
|
|
22
|
+
base_url: str,
|
|
23
|
+
) -> None:
|
|
24
|
+
self.auth = auth
|
|
25
|
+
self.base_url = base_url
|
|
26
|
+
|
|
27
|
+
async def create(
|
|
28
|
+
self,
|
|
29
|
+
payload: CreateAgreementRequest,
|
|
30
|
+
) -> AgreementResponse:
|
|
31
|
+
token = await self.auth.get_token()
|
|
32
|
+
|
|
33
|
+
url = (
|
|
34
|
+
f"{self.base_url}"
|
|
35
|
+
"/tokenized/checkout/create"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
headers = {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
"Authorization": (
|
|
41
|
+
f"Bearer {token}"
|
|
42
|
+
),
|
|
43
|
+
"X-APP-Key": (
|
|
44
|
+
self.auth.credentials.app_key
|
|
45
|
+
),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
request_body = {
|
|
49
|
+
"mode": "0000",
|
|
50
|
+
"payerReference": (
|
|
51
|
+
payload.customer_phone
|
|
52
|
+
),
|
|
53
|
+
"callbackURL": (
|
|
54
|
+
payload.callback_url
|
|
55
|
+
),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async with httpx.AsyncClient(
|
|
59
|
+
timeout=30
|
|
60
|
+
) as client:
|
|
61
|
+
response = await client.post(
|
|
62
|
+
url,
|
|
63
|
+
json=request_body,
|
|
64
|
+
headers=headers,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
response.raise_for_status()
|
|
68
|
+
|
|
69
|
+
data: dict[str, Any] = (
|
|
70
|
+
response.json()
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if data.get("statusCode") != "0000":
|
|
74
|
+
raise AgreementError(
|
|
75
|
+
f"Agreement creation failed: "
|
|
76
|
+
f"{data}"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return AgreementResponse(
|
|
80
|
+
agreement_id="",
|
|
81
|
+
payment_id=str(
|
|
82
|
+
data["paymentID"]
|
|
83
|
+
),
|
|
84
|
+
checkout_url=str(
|
|
85
|
+
data["bkashURL"]
|
|
86
|
+
),
|
|
87
|
+
status=(
|
|
88
|
+
BKASH_AGREEMENT_STATUS_MAP[
|
|
89
|
+
str(
|
|
90
|
+
data[
|
|
91
|
+
"agreementStatus"
|
|
92
|
+
]
|
|
93
|
+
)
|
|
94
|
+
]
|
|
95
|
+
),
|
|
96
|
+
)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from datetime import UTC, datetime, timedelta
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from bpay.exceptions import AuthenticationError
|
|
7
|
+
from bpay.providers.bkash.schemas import (
|
|
8
|
+
BkashCredentials,
|
|
9
|
+
BkashTokenResponse,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BkashAuth:
|
|
14
|
+
BASE_URL = "https://tokenized.sandbox.bka.sh/v1.2.0-beta"
|
|
15
|
+
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
credentials: BkashCredentials,
|
|
19
|
+
base_url: str
|
|
20
|
+
) -> None:
|
|
21
|
+
self.credentials = credentials
|
|
22
|
+
self.base_url = base_url
|
|
23
|
+
|
|
24
|
+
self._token: BkashTokenResponse | None = None
|
|
25
|
+
self._expires_at: datetime | None = None
|
|
26
|
+
|
|
27
|
+
async def authenticate(
|
|
28
|
+
self,
|
|
29
|
+
) -> BkashTokenResponse:
|
|
30
|
+
url = f"{self.base_url}/tokenized/checkout/token/grant"
|
|
31
|
+
|
|
32
|
+
headers = {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
"username": self.credentials.username,
|
|
35
|
+
"password": self.credentials.password,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
payload = {
|
|
39
|
+
"app_key": self.credentials.app_key,
|
|
40
|
+
"app_secret": self.credentials.app_secret,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async with httpx.AsyncClient(timeout=30) as client:
|
|
44
|
+
response = await client.post(
|
|
45
|
+
url,
|
|
46
|
+
json=payload,
|
|
47
|
+
headers=headers,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
response.raise_for_status()
|
|
51
|
+
|
|
52
|
+
data: dict[str, Any] = response.json()
|
|
53
|
+
|
|
54
|
+
if data.get("statusCode") != "0000":
|
|
55
|
+
raise AuthenticationError(
|
|
56
|
+
provider="bKash",
|
|
57
|
+
message=data.get(
|
|
58
|
+
"statusMessage",
|
|
59
|
+
"Authentication failed",
|
|
60
|
+
),
|
|
61
|
+
provider_code=data.get(
|
|
62
|
+
"statusCode"
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return BkashTokenResponse(
|
|
67
|
+
id_token=str(data["id_token"]),
|
|
68
|
+
refresh_token=str(data["refresh_token"]),
|
|
69
|
+
token_type=str(data["token_type"]),
|
|
70
|
+
expires_in=int(data["expires_in"]),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
async def get_token(self) -> str:
|
|
74
|
+
if (
|
|
75
|
+
self._token is not None
|
|
76
|
+
and self._expires_at is not None
|
|
77
|
+
and datetime.now(UTC) < self._expires_at
|
|
78
|
+
):
|
|
79
|
+
return self._token.id_token
|
|
80
|
+
|
|
81
|
+
token = await self.authenticate()
|
|
82
|
+
|
|
83
|
+
self._token = token
|
|
84
|
+
|
|
85
|
+
self._expires_at = datetime.now(UTC) + timedelta(seconds=token.expires_in - 60)
|
|
86
|
+
|
|
87
|
+
return token.id_token
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from bpay.schemas.callback import (
|
|
2
|
+
AgreementCallback,
|
|
3
|
+
)
|
|
4
|
+
from bpay.types import AgreementStatus
|
|
5
|
+
|
|
6
|
+
STATUS_MAP = {
|
|
7
|
+
"success": AgreementStatus.ACTIVE,
|
|
8
|
+
"failure": AgreementStatus.FAILED,
|
|
9
|
+
"cancel": AgreementStatus.CANCELLED,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_agreement_callback(
|
|
14
|
+
params: dict[str, str],
|
|
15
|
+
) -> AgreementCallback:
|
|
16
|
+
return AgreementCallback(
|
|
17
|
+
payment_id=params["paymentID"],
|
|
18
|
+
status=STATUS_MAP[params["status"]],
|
|
19
|
+
signature=params["signature"],
|
|
20
|
+
agreement_id=params.get("agreementID"),
|
|
21
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from bpay.types import AgreementStatus
|
|
2
|
+
|
|
3
|
+
BKASH_AGREEMENT_STATUS_MAP = {
|
|
4
|
+
"Initiated": AgreementStatus.INITIATED,
|
|
5
|
+
"Completed": AgreementStatus.ACTIVE,
|
|
6
|
+
"Cancelled": AgreementStatus.CANCELLED,
|
|
7
|
+
"Failure": AgreementStatus.FAILED,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
BKASH_BASE_URLS = {
|
|
11
|
+
"sandbox": (
|
|
12
|
+
"https://tokenized.sandbox.bka.sh/v1.2.0-beta"
|
|
13
|
+
),
|
|
14
|
+
"production": (
|
|
15
|
+
"https://tokenized.pay.bka.sh/v1.2.0-beta"
|
|
16
|
+
),
|
|
17
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from bpay.exceptions import (
|
|
6
|
+
ProviderAPIError,
|
|
7
|
+
)
|
|
8
|
+
from bpay.providers.bkash.auth import (
|
|
9
|
+
BkashAuth,
|
|
10
|
+
)
|
|
11
|
+
from bpay.schemas.payment import (
|
|
12
|
+
CreatePaymentRequest,
|
|
13
|
+
PaymentResponse,
|
|
14
|
+
)
|
|
15
|
+
from bpay.types import PaymentStatus
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BkashPayment:
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
auth: BkashAuth,
|
|
22
|
+
base_url: str,
|
|
23
|
+
) -> None:
|
|
24
|
+
self.auth = auth
|
|
25
|
+
self.base_url = base_url
|
|
26
|
+
|
|
27
|
+
async def create(
|
|
28
|
+
self,
|
|
29
|
+
payload: CreatePaymentRequest,
|
|
30
|
+
) -> PaymentResponse:
|
|
31
|
+
token = await self.auth.get_token()
|
|
32
|
+
|
|
33
|
+
url = (
|
|
34
|
+
f"{self.base_url}"
|
|
35
|
+
"/tokenized/checkout/create"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
headers = {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
"Authorization": (
|
|
41
|
+
f"Bearer {token}"
|
|
42
|
+
),
|
|
43
|
+
"X-APP-Key": (
|
|
44
|
+
self.auth.credentials.app_key
|
|
45
|
+
),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
request_body = {
|
|
49
|
+
"mode": "0011",
|
|
50
|
+
"callbackURL": (
|
|
51
|
+
payload.callback_url
|
|
52
|
+
),
|
|
53
|
+
"amount": str(
|
|
54
|
+
payload.amount
|
|
55
|
+
),
|
|
56
|
+
"currency": (
|
|
57
|
+
payload.currency
|
|
58
|
+
),
|
|
59
|
+
"intent": (
|
|
60
|
+
payload.intent
|
|
61
|
+
),
|
|
62
|
+
"merchantInvoiceNumber": (
|
|
63
|
+
payload.merchant_invoice_number
|
|
64
|
+
),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if payload.payer_reference:
|
|
68
|
+
request_body[
|
|
69
|
+
"payerReference"
|
|
70
|
+
] = (
|
|
71
|
+
payload.payer_reference
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
async with httpx.AsyncClient(
|
|
75
|
+
timeout=30
|
|
76
|
+
) as client:
|
|
77
|
+
response = await client.post(
|
|
78
|
+
url,
|
|
79
|
+
json=request_body,
|
|
80
|
+
headers=headers,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
response.raise_for_status()
|
|
84
|
+
|
|
85
|
+
data: dict[str, Any] = (
|
|
86
|
+
response.json()
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if data.get("statusCode") != "0000":
|
|
90
|
+
raise ProviderAPIError(
|
|
91
|
+
f"Payment creation failed: "
|
|
92
|
+
f"{data}"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return PaymentResponse(
|
|
96
|
+
payment_id=str(
|
|
97
|
+
data["paymentID"]
|
|
98
|
+
),
|
|
99
|
+
checkout_url=str(
|
|
100
|
+
data["bkashURL"]
|
|
101
|
+
),
|
|
102
|
+
status=PaymentStatus.PENDING,
|
|
103
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from bpay.schemas.payment_callback import (
|
|
2
|
+
PaymentCallback,
|
|
3
|
+
)
|
|
4
|
+
from bpay.types import PaymentStatus
|
|
5
|
+
|
|
6
|
+
STATUS_MAP = {
|
|
7
|
+
"success": PaymentStatus.SUCCESS,
|
|
8
|
+
"failure": PaymentStatus.FAILED,
|
|
9
|
+
"cancel": PaymentStatus.CANCELLED,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_payment_callback(
|
|
14
|
+
params: dict[str, str],
|
|
15
|
+
) -> PaymentCallback:
|
|
16
|
+
return PaymentCallback(
|
|
17
|
+
payment_id=params["paymentID"],
|
|
18
|
+
status=STATUS_MAP[
|
|
19
|
+
params["status"]
|
|
20
|
+
],
|
|
21
|
+
signature=params["signature"],
|
|
22
|
+
)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from bpay.config.environments import (
|
|
2
|
+
Environment,
|
|
3
|
+
)
|
|
4
|
+
from bpay.providers.bkash.agreement import (
|
|
5
|
+
BkashAgreement,
|
|
6
|
+
)
|
|
7
|
+
from bpay.providers.bkash.auth import (
|
|
8
|
+
BkashAuth,
|
|
9
|
+
)
|
|
10
|
+
from bpay.providers.bkash.constants import (
|
|
11
|
+
BKASH_BASE_URLS,
|
|
12
|
+
)
|
|
13
|
+
from bpay.providers.bkash.payment import (
|
|
14
|
+
BkashPayment,
|
|
15
|
+
)
|
|
16
|
+
from bpay.providers.bkash.schemas import (
|
|
17
|
+
BkashCredentials,
|
|
18
|
+
)
|
|
19
|
+
from bpay.providers.bkash.verification import (
|
|
20
|
+
BkashVerification,
|
|
21
|
+
)
|
|
22
|
+
from bpay.schemas.agreement import (
|
|
23
|
+
AgreementResponse,
|
|
24
|
+
CreateAgreementRequest,
|
|
25
|
+
)
|
|
26
|
+
from bpay.schemas.payment import (
|
|
27
|
+
CreatePaymentRequest,
|
|
28
|
+
PaymentResponse,
|
|
29
|
+
)
|
|
30
|
+
from bpay.schemas.verification import (
|
|
31
|
+
PaymentVerificationResponse,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class BkashProvider:
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
username: str,
|
|
39
|
+
password: str,
|
|
40
|
+
app_key: str,
|
|
41
|
+
app_secret: str,
|
|
42
|
+
environment: str = "sandbox",
|
|
43
|
+
) -> None:
|
|
44
|
+
credentials = (
|
|
45
|
+
BkashCredentials(
|
|
46
|
+
username=username,
|
|
47
|
+
password=password,
|
|
48
|
+
app_key=app_key,
|
|
49
|
+
app_secret=app_secret,
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
environment_enum = (
|
|
54
|
+
Environment(environment)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
base_url = (
|
|
58
|
+
BKASH_BASE_URLS[
|
|
59
|
+
environment_enum.value
|
|
60
|
+
]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
auth = BkashAuth(
|
|
64
|
+
credentials=credentials,
|
|
65
|
+
base_url=base_url,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
self.agreement_service = (
|
|
69
|
+
BkashAgreement(
|
|
70
|
+
auth=auth,
|
|
71
|
+
base_url=base_url,
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
self.payment_service = (
|
|
76
|
+
BkashPayment(
|
|
77
|
+
auth=auth,
|
|
78
|
+
base_url=base_url,
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self.verification_service = (
|
|
83
|
+
BkashVerification(
|
|
84
|
+
auth=auth,
|
|
85
|
+
base_url=base_url,
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
async def create_agreement(
|
|
90
|
+
self,
|
|
91
|
+
payload: CreateAgreementRequest,
|
|
92
|
+
) -> AgreementResponse:
|
|
93
|
+
return await (
|
|
94
|
+
self.agreement_service.create(
|
|
95
|
+
payload
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
async def create_payment(
|
|
100
|
+
self,
|
|
101
|
+
payload: CreatePaymentRequest,
|
|
102
|
+
) -> PaymentResponse:
|
|
103
|
+
return await (
|
|
104
|
+
self.payment_service.create(
|
|
105
|
+
payload
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
async def verify_payment(
|
|
110
|
+
self,
|
|
111
|
+
payment_id: str,
|
|
112
|
+
) -> PaymentVerificationResponse:
|
|
113
|
+
return await (
|
|
114
|
+
self.verification_service.verify_payment(
|
|
115
|
+
payment_id
|
|
116
|
+
)
|
|
117
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BkashCredentials(BaseModel):
|
|
5
|
+
username: str
|
|
6
|
+
password: str
|
|
7
|
+
app_key: str
|
|
8
|
+
app_secret: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BkashTokenResponse(BaseModel):
|
|
12
|
+
id_token: str
|
|
13
|
+
refresh_token: str
|
|
14
|
+
token_type: str
|
|
15
|
+
expires_in: int
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
from bpay.exceptions import (
|
|
6
|
+
ProviderAPIError,
|
|
7
|
+
)
|
|
8
|
+
from bpay.providers.bkash.auth import (
|
|
9
|
+
BkashAuth,
|
|
10
|
+
)
|
|
11
|
+
from bpay.schemas.verification import (
|
|
12
|
+
PaymentVerificationResponse,
|
|
13
|
+
)
|
|
14
|
+
from bpay.types import PaymentStatus
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BkashVerification:
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
auth: BkashAuth,
|
|
21
|
+
base_url: str,
|
|
22
|
+
) -> None:
|
|
23
|
+
self.auth = auth
|
|
24
|
+
self.base_url = base_url
|
|
25
|
+
|
|
26
|
+
async def verify_payment(
|
|
27
|
+
self,
|
|
28
|
+
payment_id: str,
|
|
29
|
+
) -> PaymentVerificationResponse:
|
|
30
|
+
token = await self.auth.get_token()
|
|
31
|
+
|
|
32
|
+
url = (
|
|
33
|
+
f"{self.base_url}"
|
|
34
|
+
"/tokenized/checkout/payment/status"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
headers = {
|
|
38
|
+
"Authorization": (
|
|
39
|
+
f"Bearer {token}"
|
|
40
|
+
),
|
|
41
|
+
"X-APP-Key": (
|
|
42
|
+
self.auth.credentials.app_key
|
|
43
|
+
),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
payload = {
|
|
47
|
+
"paymentID": payment_id
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async with httpx.AsyncClient(
|
|
51
|
+
timeout=30
|
|
52
|
+
) as client:
|
|
53
|
+
response = await client.post(
|
|
54
|
+
url,
|
|
55
|
+
json=payload,
|
|
56
|
+
headers=headers,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
response.raise_for_status()
|
|
60
|
+
|
|
61
|
+
data: dict[str, Any] = (
|
|
62
|
+
response.json()
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if data.get("statusCode") != "0000":
|
|
66
|
+
raise ProviderAPIError(
|
|
67
|
+
f"Payment verification failed: "
|
|
68
|
+
f"{data}"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
PaymentVerificationResponse(
|
|
73
|
+
payment_id=str(
|
|
74
|
+
data["paymentID"]
|
|
75
|
+
),
|
|
76
|
+
trx_id=data.get("trxID"),
|
|
77
|
+
amount=float(
|
|
78
|
+
data.get("amount", 0)
|
|
79
|
+
),
|
|
80
|
+
status=(
|
|
81
|
+
PaymentStatus.SUCCESS
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
)
|
|
File without changes
|
|
File without changes
|
bpay/schemas/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from bpay.types import AgreementStatus
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CreateAgreementRequest(BaseModel):
|
|
7
|
+
customer_phone: str
|
|
8
|
+
callback_url: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AgreementResponse(BaseModel):
|
|
12
|
+
agreement_id: str
|
|
13
|
+
payment_id: str
|
|
14
|
+
checkout_url: str
|
|
15
|
+
status: AgreementStatus
|
bpay/schemas/callback.py
ADDED
bpay/schemas/payment.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from bpay.types import PaymentStatus
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CreatePaymentRequest(BaseModel):
|
|
7
|
+
amount: float
|
|
8
|
+
callback_url: str
|
|
9
|
+
merchant_invoice_number: str
|
|
10
|
+
|
|
11
|
+
currency: str = "BDT"
|
|
12
|
+
intent: str = "sale"
|
|
13
|
+
|
|
14
|
+
payer_reference: str | None = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PaymentResponse(BaseModel):
|
|
18
|
+
payment_id: str
|
|
19
|
+
checkout_url: str
|
|
20
|
+
status: PaymentStatus
|
|
File without changes
|
|
File without changes
|
bpay/transports/http.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class HttpTransport:
|
|
7
|
+
def __init__(self) -> None:
|
|
8
|
+
self.client = httpx.AsyncClient(timeout=30)
|
|
9
|
+
|
|
10
|
+
async def post(
|
|
11
|
+
self,
|
|
12
|
+
url: str,
|
|
13
|
+
**kwargs: Any,
|
|
14
|
+
) -> httpx.Response:
|
|
15
|
+
return await self.client.post(url, **kwargs)
|
bpay/types.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PaymentStatus(StrEnum):
|
|
5
|
+
PENDING = "pending"
|
|
6
|
+
SUCCESS = "success"
|
|
7
|
+
FAILED = "failed"
|
|
8
|
+
CANCELLED = "cancelled"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RefundStatus(StrEnum):
|
|
12
|
+
PENDING = "pending"
|
|
13
|
+
SUCCESS = "success"
|
|
14
|
+
FAILED = "failed"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgreementStatus(StrEnum):
|
|
18
|
+
INITIATED = "initiated"
|
|
19
|
+
ACTIVE = "active"
|
|
20
|
+
FAILED = "failed"
|
|
21
|
+
CANCELLED = "cancelled"
|
|
File without changes
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bpay
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unified Python SDK for Bangladeshi payment gateways
|
|
5
|
+
Project-URL: Homepage, https://github.com/thatsayon/bpay-python
|
|
6
|
+
Project-URL: Repository, https://github.com/thatsayon/bpay-python
|
|
7
|
+
Project-URL: Issues, https://github.com/thatsayon/bpay-python/issues
|
|
8
|
+
Author: Ayon
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: bangladesh,bkash,nagad,payments,sslcommerz
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.13
|
|
21
|
+
Requires-Dist: httpx>=0.28.1
|
|
22
|
+
Requires-Dist: pydantic>=2.13.4
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# bpay
|
|
26
|
+
|
|
27
|
+
Unified Python SDK for Bangladeshi payment gateways.
|
|
28
|
+
|
|
29
|
+
`bpay` provides a clean, typed, async-first developer experience for integrating Bangladeshi payment providers like bKash, Nagad, and SSLCommerz.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- Async-first architecture
|
|
36
|
+
- Typed request & response models
|
|
37
|
+
- Sandbox & production environment support
|
|
38
|
+
- Hosted checkout workflows
|
|
39
|
+
- Agreement/tokenized checkout support
|
|
40
|
+
- Payment callback parsing
|
|
41
|
+
- Payment verification support
|
|
42
|
+
- Provider abstraction layer
|
|
43
|
+
- Modern Python packaging with `uv`
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Supported Providers
|
|
48
|
+
|
|
49
|
+
| Provider | Status |
|
|
50
|
+
|---|---|
|
|
51
|
+
| bKash | In Progress |
|
|
52
|
+
| Nagad | Planned |
|
|
53
|
+
| SSLCommerz | Planned |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install bpay
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Quick Start
|
|
66
|
+
|
|
67
|
+
### Create a One-Time Payment
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import asyncio
|
|
71
|
+
|
|
72
|
+
from bpay import BPay
|
|
73
|
+
from bpay.config.environments import (
|
|
74
|
+
Environment,
|
|
75
|
+
)
|
|
76
|
+
from bpay.schemas.payment import (
|
|
77
|
+
CreatePaymentRequest,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def main() -> None:
|
|
82
|
+
client = BPay(
|
|
83
|
+
provider="bkash",
|
|
84
|
+
environment=Environment.SANDBOX,
|
|
85
|
+
username="YOUR_USERNAME",
|
|
86
|
+
password="YOUR_PASSWORD",
|
|
87
|
+
app_key="YOUR_APP_KEY",
|
|
88
|
+
app_secret="YOUR_APP_SECRET",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
payment = await client.create_payment(
|
|
92
|
+
CreatePaymentRequest(
|
|
93
|
+
amount=100,
|
|
94
|
+
merchant_invoice_number="INV-1001",
|
|
95
|
+
callback_url=(
|
|
96
|
+
"https://yourdomain.com/callback"
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
print(payment.checkout_url)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
asyncio.run(main())
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Agreement / Tokenized Checkout
|
|
111
|
+
|
|
112
|
+
Agreements allow merchants to create reusable customer payment authorizations for recurring or saved payments.
|
|
113
|
+
|
|
114
|
+
### Create Agreement
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import asyncio
|
|
118
|
+
|
|
119
|
+
from bpay import BPay
|
|
120
|
+
from bpay.config.environments import (
|
|
121
|
+
Environment,
|
|
122
|
+
)
|
|
123
|
+
from bpay.schemas.agreement import (
|
|
124
|
+
CreateAgreementRequest,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
async def main() -> None:
|
|
129
|
+
client = BPay(
|
|
130
|
+
provider="bkash",
|
|
131
|
+
environment=Environment.SANDBOX,
|
|
132
|
+
username="YOUR_USERNAME",
|
|
133
|
+
password="YOUR_PASSWORD",
|
|
134
|
+
app_key="YOUR_APP_KEY",
|
|
135
|
+
app_secret="YOUR_APP_SECRET",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
agreement = await (
|
|
139
|
+
client.create_agreement(
|
|
140
|
+
CreateAgreementRequest(
|
|
141
|
+
customer_phone="017XXXXXXXX",
|
|
142
|
+
callback_url=(
|
|
143
|
+
"https://yourdomain.com/callback"
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
print(agreement.checkout_url)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
asyncio.run(main())
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Callback Parsing
|
|
159
|
+
|
|
160
|
+
### Payment Callback
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from bpay import BPay
|
|
164
|
+
|
|
165
|
+
client = BPay(...)
|
|
166
|
+
|
|
167
|
+
callback = client.parse_payment_callback(
|
|
168
|
+
{
|
|
169
|
+
"paymentID": "PAY123",
|
|
170
|
+
"status": "success",
|
|
171
|
+
"signature": "abc123",
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
print(callback.status)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### Agreement Callback
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from bpay import BPay
|
|
184
|
+
|
|
185
|
+
client = BPay(...)
|
|
186
|
+
|
|
187
|
+
callback = (
|
|
188
|
+
client.parse_agreement_callback(
|
|
189
|
+
{
|
|
190
|
+
"paymentID": "PAY123",
|
|
191
|
+
"agreementID": "AGR123",
|
|
192
|
+
"status": "success",
|
|
193
|
+
"signature": "abc123",
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
print(callback.status)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Payment Verification
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
verification = await client.verify_payment(
|
|
207
|
+
payment_id="PAY123"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
print(verification.status)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Environments
|
|
216
|
+
|
|
217
|
+
`bpay` supports both sandbox and production environments.
|
|
218
|
+
|
|
219
|
+
### Sandbox
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from bpay.config.environments import (
|
|
223
|
+
Environment,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
Environment.SANDBOX
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Production
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from bpay.config.environments import (
|
|
233
|
+
Environment,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
Environment.PRODUCTION
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Development
|
|
242
|
+
|
|
243
|
+
### Setup
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
uv sync
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Run Checks
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
make check
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Run Tests
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
uv run pytest
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Project Structure
|
|
264
|
+
|
|
265
|
+
```text
|
|
266
|
+
src/bpay/
|
|
267
|
+
├── client.py
|
|
268
|
+
├── exceptions.py
|
|
269
|
+
├── config/
|
|
270
|
+
├── providers/
|
|
271
|
+
├── schemas/
|
|
272
|
+
├── transports/
|
|
273
|
+
└── types.py
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Roadmap
|
|
279
|
+
|
|
280
|
+
- Full bKash recurring payment workflow
|
|
281
|
+
- Nagad integration
|
|
282
|
+
- SSLCommerz integration
|
|
283
|
+
- Refund support
|
|
284
|
+
- Webhook signature verification
|
|
285
|
+
- Automatic retry handling
|
|
286
|
+
- Sync client support
|
|
287
|
+
- Logging middleware
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## License
|
|
292
|
+
|
|
293
|
+
MIT License
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
bpay/__init__.py,sha256=L4D37QQavc_fb7ub3UdD6nN1V7ke3utYojGV8CGbOe8,72
|
|
2
|
+
bpay/client.py,sha256=yYUpNvyrfLdPUdqrSDxMBmdUoCiyTnw2z7ohn5dUrDQ,3591
|
|
3
|
+
bpay/config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
bpay/exceptions.py,sha256=eNN79RSgY42tU-RJE4s69ZiR-m7fq1ZJYDhiutoJ_BU,1049
|
|
5
|
+
bpay/types.py,sha256=KblekKPgCT0VW4CpUviw2PvHNDfeP2XX5XTQlCBuT-k,390
|
|
6
|
+
bpay/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
bpay/config/environments.py,sha256=uhIAYD0o9y49_D2SmXhH_oy1TPJV83RHwOejbtvipW0,109
|
|
8
|
+
bpay/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
bpay/providers/base.py,sha256=q2uS8o5o8o6_Jymdc3kf2w1AAr-SGKaNbuLOEeGUNAM,290
|
|
10
|
+
bpay/providers/registry.py,sha256=ArWomtK-7tiLE5i-Mx4M2lhv9dTYSM4SbHfiP51ElDs,101
|
|
11
|
+
bpay/providers/bkash/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
bpay/providers/bkash/agreement.py,sha256=iMYt4lHv7NmqNo7lwAZ3abqjH70K_9waFZ1EJszFdsg,2191
|
|
13
|
+
bpay/providers/bkash/auth.py,sha256=an9grQMvNs-2qM5S33i2LabSGK_ZRSvLNzJfxVhjeCI,2356
|
|
14
|
+
bpay/providers/bkash/callbacks.py,sha256=bFCn-adHn4LdduWCkEEdmKWr_4IToO4wicQXerK4mjs,529
|
|
15
|
+
bpay/providers/bkash/constants.py,sha256=d0Ig-J4aEdIN5dq9FQuQuWBYuiM8IeZIA-1ZVkcBVdg,421
|
|
16
|
+
bpay/providers/bkash/payment.py,sha256=C8UH8dmDcy2FLtUKVLs-isky4kepZjbY-Qa_t5ySDPk,2313
|
|
17
|
+
bpay/providers/bkash/payment_callbacks.py,sha256=1zw-ZWYsfBAWtPE3j7YE17baaYV9NEaj2_0tVlPRIkE,496
|
|
18
|
+
bpay/providers/bkash/provider.py,sha256=x_sBk7k4d7tQ6-OY4PHcUbYqpx_u1U1YCgUe2nkTfAo,2559
|
|
19
|
+
bpay/providers/bkash/schemas.py,sha256=_1uj-nyhYEGQPoNXk19I76TfsuT041RExrxBjN6ELGo,261
|
|
20
|
+
bpay/providers/bkash/verification.py,sha256=LcTcCOcVDB2Kt4nVVjNwuXyNWxvty7BCJNY4HB_eOqI,1860
|
|
21
|
+
bpay/providers/nagad/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
bpay/providers/sslcommerz/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
bpay/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
bpay/schemas/agreement.py,sha256=ChuUhHTAiKwomyPWjFdaPEB8Z-WYh3wQqv4erOdVElY,290
|
|
25
|
+
bpay/schemas/callback.py,sha256=Iwn8tnOGBU0_kabeMjI0iYAuur7XrOavox7A4fXiXo8,212
|
|
26
|
+
bpay/schemas/payment.py,sha256=rc1ko6RLZADFslktdPjDf_V3ZKjU_xl70De1ft2rC2Q,379
|
|
27
|
+
bpay/schemas/payment_callback.py,sha256=FGUAHb-xAC-MZqGoDHxaxfKfn62bfTYI3kL9MmFpW9o,170
|
|
28
|
+
bpay/schemas/verification.py,sha256=fwmQUH2hNbQG00JXnLFIE8Cqa-sgGWkK_fdgLIOr5eM,231
|
|
29
|
+
bpay/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
+
bpay/transports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
bpay/transports/http.py,sha256=39dppdUBvlS59auFh5S16_zBLpTvSQRw_H6YfcjMl4A,298
|
|
32
|
+
bpay/webhooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
+
bpay-0.1.0.dist-info/METADATA,sha256=JCMUSLo2LVG5_W0Wv_Qcu2su9S4iYraWchXm89eb9ko,5035
|
|
34
|
+
bpay-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
35
|
+
bpay-0.1.0.dist-info/licenses/LICENSE,sha256=Tci2KBJuPGYHHw0o2VmR8mwmB4145ZIZ6S_Mp21z57o,1061
|
|
36
|
+
bpay-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ayon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|