├── MANIFEST.in ├── PKG-INFO ├── README.md ├── README.rst ├── click.egg-info ├── PKG-INFO ├── SOURCES.txt ├── dependency_links.txt └── top_level.txt ├── click ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py ├── urls.py ├── utils.py └── views.py ├── setup.cfg └── setup.py /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | recursive-include click/* -------------------------------------------------------------------------------- /PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.1 2 | Name: click 3 | Version: 0.1 4 | Summary: Click services 5 | Home-page: click.uz 6 | Author: Click developers 7 | Author-email: info@click.com 8 | License: BSD License 9 | Description: ===== 10 | CLICK 11 | ===== 12 | 13 | Click is a simple Django application to working with click-api and conduct to Web-based. 14 | Click module has integrated to "django-payments" module as variant. 15 | You can find more information about "django-payments" by https://django-payments.readthedocs.io link. 16 | 17 | 18 | Detailed documentation is in the https://docs.click.uz url. 19 | 20 | Quick start 21 | ----------- 22 | 1. Installing 23 | $ pip install django-payments 24 | $ pip install click.tgz 25 | 26 | 2. Add "payments" and "click" to your INSTALLED_APPS setting like this:: 27 | 28 | INSTALLED_APPS = [ 29 | ... 30 | 'payments', 31 | 'click', 32 | ... 33 | ] 34 | 35 | 3. Add the variables to your settings.py like this:: 36 | PAYMENT_HOST = ':' 37 | PAYMENT_USES_SSL = False # set the True value if you are using the SSL 38 | PAYMENT_MODEL = '' # payment model format like this :: '.' 39 | # add "click" to your variants 40 | PAYMENT_VARIANTS = { 41 | ... 42 | 'click' : ('click.ClickProvider', { 43 | 'merchant_id' : 1111, 44 | 'merchant_service_id' : 11111, 45 | 'merchant_user_id' : 11111, 46 | 'secret_key' : 'AAAAAA' 47 | }) 48 | ... 49 | } 50 | 51 | 4. Include the click and payments URLconf in your project urls.py like this:: 52 | 53 | path('payments/', include('payments.urls')), 54 | path('payments/', include('click.urls')) 55 | 56 | 5. Create your payment model to models.py like this:: 57 | from payments.models import BasePayment 58 | class Payment(BasePayment): 59 | pass 60 | 61 | 6. Add the model to your admin.py like this:: 62 | from django.contrib import admin 63 | 64 | # Register your models here. 65 | from .models import Payment 66 | 67 | class PaymentAdmin(admin.ModelAdmin): 68 | pass 69 | 70 | admin.site.register(Payment, PaymentAdmin) 71 | 72 | 7. Run `python manage.py migrate` to create the payment and your another models. 73 | 74 | 8. Start the development server and visit http://127.0.0.1:8000/admin/ 75 | to create a payment. 76 | 77 | 9. Click payment services: 78 | service urls as pattern : payments/process/click/service/ 79 | service types: 80 | 1) create_invoice 81 | 2) check_invoice 82 | 3) create_card_token 83 | 4) verify_card_token 84 | 5) payment_with_token 85 | 5) delete_card_token 86 | 87 | 10. The "prepare" and "complate" urls as pattern: 88 | prepare : payments/process/click/prepare 89 | complate : payments/process/click/complate 90 | 91 | 11. Example code at "example/" directory. 92 | Platform: UNKNOWN 93 | Classifier: Environment :: Web Environment 94 | Classifier: Framework :: Django 95 | Classifier: Framework :: Django :: 3.5 96 | Classifier: Intended Audience :: Developers 97 | Classifier: License :: OSI Approved :: BSD License 98 | Classifier: Operating System :: OS Independent 99 | Classifier: Programming Language :: Python 100 | Classifier: Programming Language :: Python :: 3.5 101 | Classifier: Programming Language :: Python :: 3.6 102 | Classifier: Topic :: Internet :: WWW/HTTP 103 | Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This module allows you to integrate payment acceptance using `"CLICK"` payment system into `Python Django` web applications. 2 | Click-API module is integrated to `django-payments` module as payment provider. 3 | Detailed documentation is available here __https://docs.click.uz__. 4 | 5 | #### Installing 6 | ``` 7 | $ pip install django-payments 8 | $ pip install click.tar.gz 9 | ``` 10 | #### Add `"payments"` and `"click"` to your `INSTALLED_APPS` setting like this:: 11 | 12 | ```python 13 | INSTALLED_APPS = [ 14 | ... 15 | 'payments', 16 | 'click', 17 | ... 18 | ] 19 | ``` 20 | #### Add the variables to your `settings.py` like this:: 21 | ```python 22 | PAYMENT_HOST = ':' 23 | PAYMENT_USES_SSL = False # set the True value if you are using the SSL 24 | PAYMENT_MODEL = '' 25 | # payment model format like this :: '.' 26 | # add "click" to your variants 27 | PAYMENT_VARIANTS = { 28 | ... 29 | 'click' : ('click.ClickProvider', { 30 | 'merchant_id' : 1111, 31 | 'merchant_service_id' : 11111, 32 | 'merchant_user_id' : 11111, 33 | 'secret_key' : 'AAAAAA' 34 | }) 35 | ... 36 | } 37 | ``` 38 | 39 | #### Include the click and payments `URLconf` in your project `urls.py` like this:: 40 | ```python 41 | path('payments/', include('payments.urls')) 42 | path('payments/', include('click.urls')) 43 | ``` 44 | 45 | #### Create your payment model to `models.py` like this:: 46 | ```python 47 | from payments.models import BasePayment 48 | class Payment(BasePayment): 49 | pass 50 | ``` 51 | 52 | #### Add the model to your `admin.py` like this:: 53 | ```python 54 | from django.contrib import admin 55 | from .models import Payment 56 | 57 | class PaymentAdmin(admin.ModelAdmin): 58 | pass 59 | 60 | admin.site.register(Payment, PaymentAdmin) 61 | ``` 62 | #### Run `python manage.py migrate` to create the payment and your other models. 63 | #### Start the development server and visit `http://127.0.0.1:8000/admin/` to create a payment. 64 | #### Click service urls as pattern : `payments/process/click/service/` : 65 | #### Service types 66 | ``` 67 | 1) create_invoice 68 | 2) check_invoice 69 | 3) create_card_token 70 | 4) verify_card_token 71 | 5) payment_with_token 72 | 5) delete_card_token 73 | ``` 74 | #### The `"prepare"` and `"complete"` urls as pattern: 75 | ``` 76 | prepare : payments/process/click/prepare 77 | complate : payments/process/click/complete 78 | ``` 79 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | CLICK 3 | ===== 4 | 5 | Click is a simple Django application to working with click-api and conduct to Web-based. 6 | Click module has integrated to "django-payments" module as variant. 7 | You can find more information about "django-payments" by https://django-payments.readthedocs.io link. 8 | 9 | 10 | Detailed documentation is in the https://docs.click.uz url. 11 | 12 | Quick start 13 | ----------- 14 | 1. Installing 15 | $ pip install django-payments 16 | $ pip install click.tgz 17 | 18 | 2. Add "payments" and "click" to your INSTALLED_APPS setting like this:: 19 | 20 | INSTALLED_APPS = [ 21 | ... 22 | 'payments', 23 | 'click', 24 | ... 25 | ] 26 | 27 | 3. Add the variables to your settings.py like this:: 28 | PAYMENT_HOST = ':' 29 | PAYMENT_USES_SSL = False # set the True value if you are using the SSL 30 | PAYMENT_MODEL = '' # payment model format like this :: '.' 31 | # add "click" to your variants 32 | PAYMENT_VARIANTS = { 33 | ... 34 | 'click' : ('click.ClickProvider', { 35 | 'merchant_id' : 1111, 36 | 'merchant_service_id' : 11111, 37 | 'merchant_user_id' : 11111, 38 | 'secret_key' : 'AAAAAA' 39 | }) 40 | ... 41 | } 42 | 43 | 4. Include the click and payments URLconf in your project urls.py like this:: 44 | 45 | path('payments/', include('payments.urls')), 46 | path('payments/', include('click.urls')) 47 | 48 | 5. Create your payment model to models.py like this:: 49 | from payments.models import BasePayment 50 | class Payment(BasePayment): 51 | pass 52 | 53 | 6. Add the model to your admin.py like this:: 54 | from django.contrib import admin 55 | 56 | # Register your models here. 57 | from .models import Payment 58 | 59 | class PaymentAdmin(admin.ModelAdmin): 60 | pass 61 | 62 | admin.site.register(Payment, PaymentAdmin) 63 | 64 | 7. Run `python manage.py migrate` to create the payment and your another models. 65 | 66 | 8. Start the development server and visit http://127.0.0.1:8000/admin/ 67 | to create a payment. 68 | 69 | 9. Click payment services: 70 | service urls as pattern : payments/process/click/service/ 71 | service types: 72 | 1) create_invoice 73 | 2) check_invoice 74 | 3) create_card_token 75 | 4) verify_card_token 76 | 5) payment_with_token 77 | 5) delete_card_token 78 | 79 | 10. The "prepare" and "complate" urls as pattern: 80 | prepare : payments/process/click/prepare 81 | complate : payments/process/click/complate 82 | 83 | 11. Example code at "example/" directory. -------------------------------------------------------------------------------- /click.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.1 2 | Name: click 3 | Version: 0.1 4 | Summary: Click services 5 | Home-page: click.uz 6 | Author: Click developers 7 | Author-email: info@click.com 8 | License: BSD License 9 | Description: ===== 10 | CLICK 11 | ===== 12 | 13 | Click is a simple Django application to working with click-api and conduct to Web-based. 14 | Click module has integrated to "django-payments" module as variant. 15 | You can find more information about "django-payments" by https://django-payments.readthedocs.io link. 16 | 17 | 18 | Detailed documentation is in the https://docs.click.uz url. 19 | 20 | Quick start 21 | ----------- 22 | 1. Installing 23 | $ pip install django-payments 24 | $ pip install click.tgz 25 | 26 | 2. Add "payments" and "click" to your INSTALLED_APPS setting like this:: 27 | 28 | INSTALLED_APPS = [ 29 | ... 30 | 'payments', 31 | 'click', 32 | ... 33 | ] 34 | 35 | 3. Add the variables to your settings.py like this:: 36 | PAYMENT_HOST = ':' 37 | PAYMENT_USES_SSL = False # set the True value if you are using the SSL 38 | PAYMENT_MODEL = '' # payment model format like this :: '.' 39 | # add "click" to your variants 40 | PAYMENT_VARIANTS = { 41 | ... 42 | 'click' : ('click.ClickProvider', { 43 | 'merchant_id' : 1111, 44 | 'merchant_service_id' : 11111, 45 | 'merchant_user_id' : 11111, 46 | 'secret_key' : 'AAAAAA' 47 | }) 48 | ... 49 | } 50 | 51 | 4. Include the click and payments URLconf in your project urls.py like this:: 52 | 53 | path('payments/', include('payments.urls')), 54 | path('payments/', include('click.urls')) 55 | 56 | 5. Create your payment model to models.py like this:: 57 | from payments.models import BasePayment 58 | class Payment(BasePayment): 59 | pass 60 | 61 | 6. Add the model to your admin.py like this:: 62 | from django.contrib import admin 63 | 64 | # Register your models here. 65 | from .models import Payment 66 | 67 | class PaymentAdmin(admin.ModelAdmin): 68 | pass 69 | 70 | admin.site.register(Payment, PaymentAdmin) 71 | 72 | 7. Run `python manage.py migrate` to create the payment and your another models. 73 | 74 | 8. Start the development server and visit http://127.0.0.1:8000/admin/ 75 | to create a payment. 76 | 77 | 9. Click payment services: 78 | service urls as pattern : payments/process/click/service/ 79 | service types: 80 | 1) create_invoice 81 | 2) check_invoice 82 | 3) create_card_token 83 | 4) verify_card_token 84 | 5) payment_with_token 85 | 5) delete_card_token 86 | 87 | 10. The "prepare" and "complate" urls as pattern: 88 | prepare : payments/process/click/prepare 89 | complate : payments/process/click/complate 90 | 91 | 11. Example code at "example/" directory. 92 | Platform: UNKNOWN 93 | Classifier: Environment :: Web Environment 94 | Classifier: Framework :: Django 95 | Classifier: Framework :: Django :: 3.5 96 | Classifier: Intended Audience :: Developers 97 | Classifier: License :: OSI Approved :: BSD License 98 | Classifier: Operating System :: OS Independent 99 | Classifier: Programming Language :: Python 100 | Classifier: Programming Language :: Python :: 3.5 101 | Classifier: Programming Language :: Python :: 3.6 102 | Classifier: Topic :: Internet :: WWW/HTTP 103 | Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content 104 | -------------------------------------------------------------------------------- /click.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | MANIFEST.in 2 | README.rst 3 | setup.cfg 4 | setup.py 5 | click/__init__.py 6 | click/admin.py 7 | click/apps.py 8 | click/forms.py 9 | click/models.py 10 | click/tests.py 11 | click/urls.py 12 | click/utils.py 13 | click/views.py 14 | click.egg-info/PKG-INFO 15 | click.egg-info/SOURCES.txt 16 | click.egg-info/dependency_links.txt 17 | click.egg-info/top_level.txt 18 | click/migrations/__init__.py -------------------------------------------------------------------------------- /click.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /click.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | click 2 | -------------------------------------------------------------------------------- /click/__init__.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | import time 3 | import logging 4 | 5 | from django.http import HttpResponseForbidden 6 | from django.shortcuts import redirect 7 | from django.utils import timezone 8 | import requests, json, hashlib 9 | from requests.exceptions import HTTPError 10 | 11 | from .forms import PaymentButtonForm, PaymentPhoneNumberForm, PaymentCardNumberForm 12 | from payments import PaymentError, PaymentStatus, RedirectNeeded, get_payment_model 13 | from payments.core import BasicProvider 14 | from payments.core import provider_factory 15 | from django.shortcuts import get_object_or_404 16 | 17 | # Get an instance of a logger 18 | logger = logging.getLogger(__name__) 19 | 20 | CENTS = Decimal('0.01') 21 | 22 | class ApiHelper: 23 | endpoint = 'https://api.click.uz/v1/merchant' 24 | 25 | def __init__(self, provider, payment, data, **kwargs): 26 | self.provider = provider 27 | self.payment = payment 28 | self.data = data 29 | self.timestaps = int(time.time()) 30 | self.token = hashlib.sha1('{timestaps}{secret_key}'.format( 31 | timestaps = self.timestaps, secret_key = self.provider.secret_key 32 | ).encode('utf-8') 33 | ).hexdigest() 34 | 35 | def get_extra_data(self): 36 | extra_data = {} 37 | try: 38 | extra_data = json.loads(self.payment.extra_data) 39 | except Exception as e: 40 | pass 41 | return extra_data 42 | 43 | def save_extra_data(self, extra_data): 44 | self.payment.extra_data = json.dumps(extra_data) 45 | self.payment.save() 46 | 47 | def post(self, url, data): 48 | response = requests.post(self.endpoint + url, json = data, headers = { 49 | 'Content-Type' : 'application/json', 50 | 'Accept' : 'application/json', 51 | 'Auth' : '{}:{}:{}'.format(self.provider.merchant_user_id, self.token, self.timestaps) 52 | }) 53 | return response 54 | 55 | def get(self, url): 56 | response = requests.get(self.endpoint + url, headers = { 57 | 'Content-Type' : 'application/json', 58 | 'Accept' : 'application/json', 59 | 'Auth' : '{}:{}:{}'.format(self.provider.merchant_user_id, self.token, self.timestaps) 60 | }) 61 | return response 62 | 63 | def check_invoice(self): 64 | self.invoice_id = self.data['invoice_id'] 65 | check_invoice = self.get('/invoice/status/{service_id}/{invoice_id}'.format( 66 | service_id = self.provider.merchant_service_id, 67 | invoice_id = self.invoice_id 68 | )) 69 | if check_invoice.status_code == 200: 70 | _json = check_invoice.json() 71 | if _json['status'] > 0: 72 | self.payment.change_status(PaymentStatus.CONFIRMED) 73 | elif _json['status'] == -99: 74 | self.payment.change_status(PaymentStatus.REJECTED) 75 | elif _json['status'] < 0: 76 | self.payment.change_status(PaymentStatus.ERROR) 77 | self.payment.message = json.dumps(_json) 78 | self.payment.save() 79 | return _json 80 | else: 81 | return { 82 | 'error' : -1 * check_invoice.status_code, 83 | 'error_note' : 'Http request error [{}]'.format(check_invoice.status_code), 84 | 'status' : -1 * check_invoice.status_code, 85 | 'staus_note' : 'Http request error [{}]'.format(check_invoice.status_code) 86 | } 87 | 88 | def create_invoice(self): 89 | if self.payment.status == PaymentStatus.INPUT: 90 | invoice = self.post('/invoice/create', { 91 | 'service_id' : self.provider.merchant_service_id, 92 | 'amount' : float(self.payment.total), 93 | 'phone_number' : self.data['phone_number'], 94 | 'merchant_trans_id' : self.payment.transactions_id 95 | }) 96 | if invoice.status_code == 200: 97 | _json = invoice.json() 98 | extra_data = self.get_extra_data() 99 | extra_data['payment'] = { 100 | 'type' : 'phone_number', 101 | 'phone_number' : self.data['phone_number'], 102 | 'invoice' : _json 103 | } 104 | self.save_extra_data(extra_data) 105 | if _json['error_code'] == 0: 106 | self.payment.change_status(PaymentStatus.WAITING) 107 | else: 108 | self.payment.change_status(PaymentStatus.ERROR) 109 | self.payment.message = json.dumps(_json) 110 | self.payment.save() 111 | return _json 112 | else: 113 | return { 114 | 'error' : -1 * invoice.status_code, 115 | 'error_note' : 'Http request error [{}]'.format(invoice.status_code) 116 | } 117 | else: 118 | return { 119 | 'error' : -5001, 120 | 'error_note' : 'Payment could not found' 121 | } 122 | 123 | def create_card_token(self): 124 | if self.payment.status == PaymentStatus.INPUT: 125 | data = { 126 | 'service_id' : self.provider.merchant_service_id, 127 | 'card_number' : self.data['card_number'], 128 | 'expire_date' : self.data['expire_date'], 129 | 'temporary' : self.data['temporary'] 130 | } 131 | response = self.post('/card_token/request', data = data) 132 | if response.status_code == 200: 133 | _json = response.json() 134 | 135 | extra_data = self.get_extra_data() 136 | extra_data['payment'] = { 137 | 'type' : 'card_number', 138 | 'card_number' : self.data['card_number'], 139 | 'temporary' : self.data['temporary'], 140 | 'card_token' : _json 141 | } 142 | if _json['error_code'] == 0: 143 | self.payment.change_status(PaymentStatus.WAITING) 144 | else: 145 | self.payment.change_status(PaymentStatus.ERROR) 146 | self.payment.message = json.dumps(_json) 147 | self.payment.save() 148 | return _json 149 | return { 150 | 'error' : -1 * response.status_code, 151 | 'error_note' : 'Http request error [{}]'.format(response.status_code) 152 | } 153 | else: 154 | return { 155 | 'error' : 5001, 156 | 'error_note' : 'Payment could not found' 157 | } 158 | 159 | def verify_card_token(self): 160 | if self.payment.status != PaymentStatus.CONFIRMED: 161 | data = { 162 | 'service_id' : self.provider.merchant_service_id, 163 | 'card_token' : self.data['card_token'], 164 | 'sms_code' : self.data['sms_code'] 165 | } 166 | response = self.post('/card_token/payment', data) 167 | if response.status_code == 200: 168 | _json = response.json() 169 | if _json['error_code'] == 0: 170 | self.payment.change_status(PaymentStatus.CONFIRMED) 171 | else: 172 | self.payment.change_status(PaymentStatus.ERROR) 173 | self.payment.message = json.dumps(_json) 174 | self.payment.save() 175 | return _json 176 | else: 177 | return { 178 | 'error' : -1 * response.status_code, 179 | 'error_note' : 'Http request error [{}]'.format(response.status_code) 180 | } 181 | else: 182 | return { 183 | 'error' : -5002, 184 | 'error_note' : 'Payment confirmed' 185 | } 186 | 187 | def payment_with_token(self): 188 | if self.payment.status != PaymentStatus.CONFIRMED: 189 | data = { 190 | "service_id": self.provider.merchant_service_id, 191 | "card_token": self.data['card_token'], 192 | "amount": self.payment.total, 193 | "merchant_trans_id": self.payment.transactions_id 194 | } 195 | if response.status_code == 200: 196 | _json = response.json() 197 | if _json['error_code'] == 0: 198 | self.payment.change_status(PaymentStatus.CONFIRMED) 199 | else: 200 | self.payment.change_status(PaymentStatus.ERROR) 201 | self.payment.message = json.dumps(_json) 202 | self.payment.save() 203 | return _json 204 | else: 205 | return { 206 | 'error' : -1 * response.status_code, 207 | 'error_note' : 'Http request error [{}]'.format(response.status_code) 208 | } 209 | else: 210 | return { 211 | 'error' : -5002, 212 | 'error_note' : 'Payment confirmed' 213 | } 214 | def delete_card_token(self): 215 | data = { 216 | 'service_id' : self.provider.merchant_service_id, 217 | 'card_token' : self.data['card_token'] 218 | } 219 | response = requests.delete(self.endpoint + '/card_token/{service_id}/{card_token}'.format(**data)) 220 | if response.status_code == 200: 221 | return response.json() 222 | return { 223 | 'error' : -1 * response.status_code, 224 | 'error_note' : 'Http request error [{}]'.format(response.status_code) 225 | } 226 | 227 | class Services(ApiHelper): 228 | def __init__(self, data, service_type): 229 | self.data = data 230 | self.service_type = service_type 231 | self.payment_id = self.data.get('payment_id', None) 232 | self.provider = provider_factory('click') 233 | self.payment = get_object_or_404(get_payment_model(), id = self.payment_id) 234 | super(Services, self).__init__(self.provider, self.payment, self.data) 235 | 236 | def api(self): 237 | if self.service_type == 'create_invoice': 238 | return self.create_invoice() 239 | if self.service_type == 'check_invoice': 240 | return self.check_invoice() 241 | if self.service_type == 'create_card_token': 242 | return self.create_card_token() 243 | if self.service_type == 'verify_card_token': 244 | return self.verify_card_token() 245 | if self.service_type == 'payment_with_token': 246 | return self.payment_with_token() 247 | if self.service_type == 'delete_card_token': 248 | return self.delete_card_token() 249 | return { 250 | 'error' : -1000, 251 | 'error_note' : 'Service type could detect' 252 | } 253 | 254 | class ClickProvider(BasicProvider): 255 | ''' 256 | click.uz payment provider 257 | ''' 258 | def __init__(self, merchant_id, merchant_service_id, merchant_user_id, secret_key, **kwargs): 259 | super(ClickProvider, self).__init__(**kwargs) 260 | self.merchant_id = merchant_id 261 | self.merchant_service_id = merchant_service_id 262 | self.merchant_user_id = merchant_user_id 263 | self.secret_key = secret_key 264 | 265 | def get_form(self, payment, data = None): 266 | if payment.status == PaymentStatus.WAITING: 267 | payment.change_status(PaymentStatus.INPUT) 268 | form = [ 269 | PaymentPhoneNumberForm(provider = self, payment = payment), 270 | PaymentCardNumberForm(provider = self, payment = payment), 271 | PaymentButtonForm(provider = self, payment = payment) 272 | ] 273 | return form 274 | -------------------------------------------------------------------------------- /click/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /click/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DjangoPaymentsClickConfig(AppConfig): 5 | name = 'django_payments_click' 6 | -------------------------------------------------------------------------------- /click/forms.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from requests.exceptions import HTTPError 4 | 5 | from django.utils.translation import ugettext_lazy as _ 6 | from django import forms 7 | from payments.forms import CreditCardPaymentFormWithName, PaymentForm 8 | from payments.fields import CreditCardNumberField 9 | from payments.core import get_credit_card_issuer 10 | from payments import PaymentStatus 11 | 12 | from time import strftime 13 | import hashlib, json 14 | 15 | class PaymentButtonForm(PaymentForm): 16 | def __init__(self, provider = None, payment = None): 17 | self.sign_time = strftime('%Y-%m-%d') 18 | self.provider = provider 19 | self.payment = payment 20 | 21 | self.hidden_inputs = { 22 | 'MERCHANT_TRANS_AMOUNT' : self.payment.total, 23 | 'MERCHANT_ID' : self.provider.merchant_id, 24 | 'MERCHANT_USER_ID' : self.provider.merchant_user_id, 25 | 'MERCHANT_SERVICE_ID' : self.provider.merchant_service_id, 26 | 'MERCHANT_TRANS_ID' : self.payment.transaction_id, 27 | 'MERCHANT_TRANS_NOTE' : self.payment.description, 28 | 'MERCHANT_USER_EMAIL' : self.payment.billing_email, 29 | 'SIGN_TIME' : self.sign_time, 30 | 'SIGN_STRING' : self.sign_string(), 31 | 'RETURN_URL' : self.return_url() 32 | } 33 | super(PaymentButtonForm, self).__init__( 34 | action = 'https://my.click.uz/pay/', 35 | provider = provider, 36 | payment = payment 37 | ) 38 | for key, val in self.hidden_inputs.items(): 39 | widget = forms.widgets.HiddenInput() 40 | self.fields[key] = forms.CharField(initial=val, widget=widget) 41 | 42 | 43 | def return_url(self): 44 | try: 45 | extra_data = json.loads(self.payment.extra_data) 46 | links = extra_data.get("links", {}) 47 | if "return" in links: 48 | return links["return"] 49 | except Exception as e: 50 | pass 51 | return "/" 52 | 53 | 54 | def sign_string(self): 55 | encoder = hashlib.md5() 56 | string = '{sign_time}{secret_key}{merchant_service_id}{merchant_trans_id}{amount}'.format( 57 | sign_time = self.sign_time, 58 | secret_key = self.provider.secret_key, 59 | merchant_service_id = self.provider.merchant_service_id, 60 | merchant_trans_id = self.payment.transaction_id, 61 | amount = self.payment.total 62 | ) 63 | encoder.update(string.encode('utf-8')) 64 | return encoder.hexdigest() 65 | 66 | class PaymentPhoneNumberForm(PaymentForm): 67 | def __init__(self, provider = None, payment = None): 68 | super(PaymentPhoneNumberForm, self).__init__( 69 | action = '/payments/process/click/{payment_id}/create'.format(payment_id = payment.id), 70 | provider = provider, 71 | payment = payment 72 | ) 73 | self.provider = provider 74 | self.payment = payment 75 | self.fields['phone_number'] = forms.CharField( 76 | widget = forms.TextInput(attrs = {'class':'form-control', 'placeholder' : '998MMNNNNNNN'}) 77 | ) 78 | 79 | class PaymentCardNumberForm(PaymentForm): 80 | def __init__(self, provider = None, payment = None): 81 | super(PaymentCardNumberForm, self).__init__( 82 | action = '/payments/process/click/{payment_id}/create'.format(payment_id = payment.id), 83 | provider = provider, 84 | payment = payment 85 | ) 86 | self.provider = provider 87 | self.payment = payment 88 | self.fields['card_number'] = forms.CharField( 89 | max_length = 16, 90 | widget = forms.TextInput(attrs = {'class':'form-control', 'placeholder' : '8600AAAABBBBCCCCDDDD'}) 91 | ) 92 | self.fields['expire_date'] = forms.CharField( 93 | max_length = 4, 94 | widget = forms.TextInput(attrs = {'class':'form-control', 'placeholder' : 'MMYY'}) 95 | ) 96 | self.fields['temporary'] = forms.ChoiceField( 97 | choices = ( 98 | (0, 'NO'), 99 | (1, 'YES') 100 | ), 101 | widget = forms.Select(attrs = {'class' : 'form-control'}) 102 | ) 103 | self.fields['sms_code'] = forms.CharField( 104 | max_length = 5, 105 | widget = forms.widgets.TextInput(attrs = {'class':'form-control'}), 106 | required = None 107 | ) -------------------------------------------------------------------------------- /click/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/click-llc/click-integration-django/1af01d441da4d5418fdd1242659cee04e589fc1d/click/migrations/__init__.py -------------------------------------------------------------------------------- /click/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | -------------------------------------------------------------------------------- /click/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /click/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('process/click/prepare', views.prepare, name = 'prepare'), 6 | path('process/click/complete', views.complete, name = 'complete'), 7 | path('process/click/service/', views.service, name = 'service') 8 | ] -------------------------------------------------------------------------------- /click/utils.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from django.utils.translation import ugettext_lazy as _ 3 | from payments import PaymentStatus 4 | from payments import get_payment_model 5 | from django.shortcuts import get_object_or_404 6 | from django.http import JsonResponse 7 | from django.conf import settings 8 | import hashlib 9 | 10 | def isset(data, columns): 11 | for column in columns: 12 | if data.get(column, None): 13 | return False 14 | return True 15 | 16 | def order_load(payment_id): 17 | if int(payment_id) > 1000000000: 18 | return None 19 | payment = get_object_or_404(get_payment_model(), id = int(payment_id)) 20 | return payment 21 | 22 | def click_secret_key(): 23 | PAYMENT_VARIANTS = settings.PAYMENT_VARIANTS 24 | _click = PAYMENT_VARIANTS['click'] 25 | secret_key = _click[1]['secret_key'] 26 | return secret_key 27 | 28 | def click_webhook_errors(request): 29 | click_trans_id = request.POST.get('click_trans_id', None) 30 | service_id = request.POST.get('service_id', None) 31 | click_paydoc_id = request.POST.get('click_paydoc_id', None) 32 | order_id = request.POST.get('merchant_trans_id', None) 33 | amount = request.POST.get('amount', None) 34 | action = request.POST.get('action', None) 35 | error = request.POST.get('error', None) 36 | error_note = request.POST.get('error_note', None) 37 | sign_time = request.POST.get('sign_time', None) 38 | sign_string = request.POST.get('sign_string', None) 39 | merchant_prepare_id = request.POST.get('merchant_prepare_id', None) if action != None and action == '1' else '' 40 | 41 | if isset(request.POST, ['click_trans_id', 'service_id', 'click_paydoc_id', 'amount', 'action', 'error', 'error_note', 'sign_time', 'sign_string']) or ( 42 | action == '1' and isset(request.POST, ['merchant_prepare_id'])): 43 | return { 44 | 'error' : '-8', 45 | 'error_note' : _('Error in request from click') 46 | } 47 | 48 | signString = '{}{}{}{}{}{}{}{}'.format( 49 | click_trans_id, service_id, click_secret_key(), order_id, merchant_prepare_id, amount, action, sign_time 50 | ) 51 | encoder = hashlib.md5(signString.encode('utf-8')) 52 | signString = encoder.hexdigest() 53 | if signString != sign_string: 54 | return { 55 | 'error' : '-1', 56 | 'error_note' : _('SIGN CHECK FAILED!') 57 | } 58 | 59 | if action not in ['0', '1']: 60 | return { 61 | 'error' : '-3', 62 | 'error_note' : _('Action not found') 63 | } 64 | 65 | order = order_load(order_id) 66 | if not order: 67 | return { 68 | 'error' : '-5', 69 | 'error_note' : _('User does not exist') 70 | } 71 | 72 | if abs(float(amount) - float(order.total) > 0.01): 73 | return { 74 | 'error' : '-2', 75 | 'error_note' : _('Incorrect parameter amount') 76 | } 77 | 78 | if order.status == PaymentStatus.CONFIRMED: 79 | return { 80 | 'error' : '-4', 81 | 'error_note' : _('Already paid') 82 | } 83 | 84 | 85 | 86 | if action == '1': 87 | if order_id != merchant_prepare_id: 88 | return { 89 | 'error' : '-6', 90 | 'error_note' : _('Transaction not found') 91 | } 92 | 93 | if order.status == PaymentStatus.REJECTED or int(error) < 0: 94 | return { 95 | 'error' : '-9', 96 | 'error_note' : _('Transaction cancelled') 97 | } 98 | 99 | return { 100 | 'error' : '0', 101 | 'error_note' : 'Success' 102 | } 103 | 104 | def prepare(request): 105 | order_id = request.POST.get('merchant_trans_id', None) 106 | result = click_webhook_errors(request) 107 | order = order_load(order_id) 108 | if result['error'] == '0': 109 | order.status = PaymentStatus.WAITING 110 | order.save() 111 | result['click_trans_id'] = request.POST.get('click_trans_id', None) 112 | result['merchant_trans_id'] = request.POST.get('merchant_trans_id', None) 113 | result['merchant_prepare_id'] = request.POST.get('merchant_trans_id', None) 114 | result['merchant_confirm_id'] = request.POST.get('merchant_trans_id', None) 115 | 116 | return JsonResponse(result) 117 | 118 | def complete(request): 119 | order_id = request.POST.get('merchant_trans_id', None) 120 | order = order_load(order_id) 121 | result = click_webhook_errors(request) 122 | if request.POST.get('error', None) != None and int(request.POST.get('error', None)) < 0: 123 | order.status = PaymentStatus.REJECTED 124 | order.save() 125 | if result['error'] == '0': 126 | order.status = PaymentStatus.CONFIRMED 127 | order.save() 128 | result['click_trans_id'] = request.POST.get('click_trans_id', None) 129 | result['merchant_trans_id'] = request.POST.get('merchant_trans_id', None) 130 | result['merchant_prepare_id'] = request.POST.get('merchant_prepare_id', None) 131 | result['merchant_confirm_id'] = request.POST.get('merchant_prepare_id', None) 132 | return JsonResponse(result) -------------------------------------------------------------------------------- /click/views.py: -------------------------------------------------------------------------------- 1 | from django.http import JsonResponse 2 | from django.views.decorators.csrf import csrf_exempt 3 | from . import utils 4 | from . import Services 5 | 6 | @csrf_exempt 7 | def prepare(request): 8 | return utils.prepare(request) 9 | 10 | @csrf_exempt 11 | def complete(request): 12 | return utils.complete(request) 13 | 14 | @csrf_exempt 15 | def service(request, service_type): 16 | service = Services(request.POST, service_type) 17 | return JsonResponse(service.api()) -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [egg_info] 2 | tag_build = 3 | tag_date = 0 4 | 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import find_packages, setup 3 | 4 | with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: 5 | README = readme.read() 6 | 7 | # allow setup.py to be run from any path 8 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 9 | 10 | setup( 11 | name='click', 12 | version='0.1', 13 | packages=find_packages(), 14 | include_package_data=True, 15 | license='BSD License', # example license 16 | description='Click services', 17 | long_description=README, 18 | url='click.uz', 19 | author='Firdavs Beknazarov', 20 | author_email='tensor2flow@gmail.com', 21 | classifiers=[ 22 | 'Environment :: Web Environment', 23 | 'Framework :: Django', 24 | 'Framework :: Django :: 3.5', 25 | 'Intended Audience :: Developers', 26 | 'License :: OSI Approved :: BSD License', 27 | 'Operating System :: OS Independent', 28 | 'Programming Language :: Python', 29 | 'Programming Language :: Python :: 3.5', 30 | 'Programming Language :: Python :: 3.6', 31 | 'Topic :: Internet :: WWW/HTTP', 32 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 33 | ], 34 | ) --------------------------------------------------------------------------------