├── .gitignore ├── README.md ├── config ├── __init__.py ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py ├── manage.py ├── paymeuz ├── __init__.py ├── admin.py ├── apps.py ├── config.py ├── methods.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── serializers.py ├── status.py ├── tests.py ├── urls.py └── views.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Django # 2 | *.log 3 | *.pot 4 | *.pyc 5 | __pycache__ 6 | db.sqlite3 7 | mains 8 | # Backup files # 9 | *.bak 10 | .env 11 | # If you are using PyCharm # 12 | # User-specific stuff 13 | .idea/**/workspace.xml 14 | .idea/**/tasks.xml 15 | .idea/**/usage.statistics.xml 16 | .idea/**/dictionaries 17 | .idea/**/shelf 18 | 19 | # AWS User-specific 20 | .idea/**/aws.xml 21 | 22 | # Generated files 23 | .idea/**/contentModel.xml 24 | 25 | # Sensitive or high-churn files 26 | .idea/**/dataSources/ 27 | .idea/**/dataSources.ids 28 | .idea/**/dataSources.local.xml 29 | .idea/**/sqlDataSources.xml 30 | .idea/**/dynamic.xml 31 | .idea/**/uiDesigner.xml 32 | .idea/**/dbnavigator.xml 33 | 34 | # Gradle 35 | .idea/**/gradle.xml 36 | .idea/**/libraries 37 | 38 | # File-based project format 39 | *.iws 40 | 41 | # IntelliJ 42 | out/ 43 | 44 | # JIRA plugin 45 | atlassian-ide-plugin.xml 46 | 47 | # Python # 48 | *.py[cod] 49 | *$py.class 50 | 51 | # Distribution / packaging 52 | .Python build/ 53 | develop-eggs/ 54 | dist/ 55 | downloads/ 56 | eggs/ 57 | .eggs/ 58 | lib/ 59 | lib64/ 60 | parts/ 61 | sdist/ 62 | var/ 63 | wheels/ 64 | *.egg-info/ 65 | .installed.cfg 66 | *.egg 67 | *.manifest 68 | *.spec 69 | 70 | # Installer logs 71 | pip-log.txt 72 | pip-delete-this-directory.txt 73 | 74 | # Unit test / coverage reports 75 | htmlcov/ 76 | .tox/ 77 | .coverage 78 | .coverage.* 79 | .cache 80 | .pytest_cache/ 81 | nosetests.xml 82 | coverage.xml 83 | *.cover 84 | .hypothesis/ 85 | 86 | # Jupyter Notebook 87 | .ipynb_checkpoints 88 | 89 | # pyenv 90 | .python-version 91 | 92 | # celery 93 | celerybeat-schedule.* 94 | 95 | # SageMath parsed files 96 | *.sage.py 97 | 98 | # Environments 99 | .env 100 | .venv 101 | env/ 102 | venv/ 103 | ENV/ 104 | env.bak/ 105 | venv.bak/ 106 | 107 | # mkdocs documentation 108 | /site 109 | 110 | # mypy 111 | .mypy_cache/ 112 | 113 | # Sublime Text # 114 | *.tmlanguage.cache 115 | *.tmPreferences.cache 116 | *.stTheme.cache 117 | *.sublime-workspace 118 | *.sublime-project 119 | 120 | # sftp configuration file 121 | sftp-config.json 122 | 123 | # Package control specific files Package 124 | Control.last-run 125 | Control.ca-list 126 | Control.ca-bundle 127 | Control.system-ca-bundle 128 | GitHub.sublime-settings 129 | 130 | # Visual Studio Code # 131 | .vscode/* 132 | !.vscode/settings.json 133 | !.vscode/tasks.json 134 | !.vscode/launch.json 135 | !.vscode/extensions.json 136 | .history 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![dj_logo](https://i.pinimg.com/originals/73/b8/f2/73b8f2cac59ab9fb4078241808fbb507.jpg) 2 | ## Introduction 3 | 4 | This package helps to integrate [payme.uz](http://payme.uz) and your application is built on [django](https://www.djangoproject.com/). 5 | 6 | ### Requirements 7 | 8 | Use the package manager [pip](https://pip.pypa.io/en/stable/) to install. 9 | 10 | ```bash 11 | pip install django 12 | pip install djangorestframework 13 | pip install requests 14 | 15 | or 16 | 17 | pip install -r requirements.txt 18 | ``` 19 | 20 | # supported versions 21 | python 3.7 + 22 | django 2 + 23 | djangorestframework 3.7 + 24 | ```` 25 | 26 | 27 | ## Usage 28 | 29 | ```python 30 | # settings.py 31 | 32 | INSTALLED_APPS = [ 33 | ... 34 | 'rest_framework', 35 | ... 36 | ] 37 | 38 | PAYME_SETTINGS = { 39 | 'DEBUG':True, #True - test mode, False - production mode 40 | 'ID':'Your KASSA_ID', 41 | 'SECRET_KEY':'Your TEST KEY OR PRODUCTIN KEY', 42 | 'ACCOUNTS':{ 43 | 'KEY_1':'order_id', 44 | 'KEY_2':'', 45 | } 46 | } 47 | 48 | 49 | ## Get started 50 | ```bash 51 | python manage.py migrate 52 | python manage.py runserver 53 | ``` 54 | 55 | **Let's try the first create card request:** 56 | ``` 57 | POST HTTP/1.1 58 | Host: http://127.0.0.1:8000/paymeuz/card-create/ 59 | { 60 | "id": 123, 61 | "params": { 62 | "card": { "number": "4444444444444444", "expire": "0420"}, 63 | "amount": 200000, 64 | "save": true 65 | } 66 | } 67 | ``` 68 | 69 | **Response:** 70 | ``` 71 | { 72 | "jsonrpc": "2.0", 73 | "result": { 74 | "sent": true, 75 | "phone": "99890*****66", 76 | "wait": 60000 77 | }, 78 | "token": "5f460c3d4e6d0841074e7457_YgQMNCttjxKTMKcfN0GPSaaKyV4zPnJqRP4iezHfBGJBpfAyjJf0onx5QXIkmChPDdGJrUpXj2EqWFnTicR4W7p1nXFVvKPegirWSYObyNvrcz18IQbbAVXPTOq1cFQQVrfN1tBM3XdQChu3yr1kTokO7vmeGyCyPZzdO0G4SJeKIwsJiJJk8jvGYpYk0csZh0OhTd01sXIu1qQ4H79qN5vIi5U9rpQcwWra9ueCgJqgU4XgWE2OaGjY4G3qpDHr7ezOUg4Ud3M7S8A1CnsubOD0rhUnOdwWhIU6wuNVJX6xNYD5vjRd4W1StByQeEgIFWHTe4md6nCpSKANPUCH7xnfa3UUu2gz9WJ0PDmOoPwdVo53v9OpQ23kta0sUzMJgSJt" 79 | } 80 | ``` 81 | 82 | 83 | **The second verify request:** 84 | ``` 85 | POST HTTP/1.1 86 | Host: http://127.0.0.1:8000/paymeuz/card-verify/ 87 | { 88 | "id": 123, 89 | "params": { 90 | "token": "5f460c3d4e6d0841074e7457_YgQMNCttjxKTMKcfN0GPSaaKyV4zPnJqRP4iezHfBGJBpfAyjJf0onx5QXIkmChPDdGJrUpXj2EqWFnTicR4W7p1nXFVvKPegirWSYObyNvrcz18IQbbAVXPTOq1cFQQVrfN1tBM3XdQChu3yr1kTokO7vmeGyCyPZzdO0G4SJeKIwsJiJJk8jvGYpYk0csZh0OhTd01sXIu1qQ4H79qN5vIi5U9rpQcwWra9ueCgJqgU4XgWE2OaGjY4G3qpDHr7ezOUg4Ud3M7S8A1CnsubOD0rhUnOdwWhIU6wuNVJX6xNYD5vjRd4W1StByQeEgIFWHTe4md6nCpSKANPUCH7xnfa3UUu2gz9WJ0PDmOoPwdVo53v9OpQ23kta0sUzMJgSJt", 91 | "code": "213131" 92 | } 93 | } 94 | ``` 95 | 96 | **Response:** 97 | ``` 98 | { 99 | "jsonrpc": "2.0", 100 | "id": 123, 101 | "result": { 102 | "card": { 103 | "number": "860006******6311", 104 | "expire": "03/99", 105 | "token": "5f460c3d4e6d0841074e7457_YgQMNCttjxKTMKcfN0GPSaaKyV4zPnJqRP4iezHfBGJBpfAyjJf0onx5QXIkmChPDdGJrUpXj2EqWFnTicR4W7p1nXFVvKPegirWSYObyNvrcz18IQbbAVXPTOq1cFQQVrfN1tBM3XdQChu3yr1kTokO7vmeGyCyPZzdO0G4SJeKIwsJiJJk8jvGYpYk0csZh0OhTd01sXIu1qQ4H79qN5vIi5U9rpQcwWra9ueCgJqgU4XgWE2OaGjY4G3qpDHr7ezOUg4Ud3M7S8A1CnsubOD0rhUnOdwWhIU6wuNVJX6xNYD5vjRd4W1StByQeEgIFWHTe4md6nCpSKANPUCH7xnfa3UUu2gz9WJ0PDmOoPwdVo53v9OpQ23kta0sUzMJgSJt", 106 | "recurrent": true, 107 | "verify": true, 108 | "type": "22618" 109 | } 110 | } 111 | } 112 | ``` 113 | 114 | 115 | **The third payment request:** 116 | ``` 117 | POST HTTP/1.1 118 | Host: http://127.0.0.1:8000//paymeuz/payment/ 119 | { 120 | "id": 123, 121 | "params": { "token":"5f460c3d4e6d0841074e7457_YgQMNCttjxKTMKcfN0GPSaaKyV4zPnJqRP4iezHfBGJBpfAyjJf0onx5QXIkmChPDdGJrUpXj2EqWFnTicR4W7p1nXFVvKPegirWSYObyNvrcz18IQbbAVXPTOq1cFQQVrfN1tBM3XdQChu3yr1kTokO7vmeGyCyPZzdO0G4SJeKIwsJiJJk8jvGYpYk0csZh0OhTd01sXIu1qQ4H79qN5vIi5U9rpQcwWra9ueCgJqgU4XgWE2OaGjY4G3qpDHr7ezOUg4Ud3M7S8A1CnsubOD0rhUnOdwWhIU6wuNVJX6xNYD5vjRd4W1StByQeEgIFWHTe4md6nCpSKANPUCH7xnfa3UUu2gz9WJ0PDmOoPwdVo53v9OpQ23kta0sUzMJgSJt", 122 | "amount": 200000, 123 | "account": { 124 | "order_id": 1 125 | } 126 | } 127 | } 128 | ``` 129 | 130 | 131 | **Response:** 132 | ``` 133 | { 134 | "jsonrpc": "2.0", 135 | "id": 123, 136 | "result": { 137 | "receipt": { 138 | "_id": "2e0b1bc1f1eb50d487ba268d", 139 | "create_time": 1481113810044, 140 | "pay_time": 1481113810265, 141 | "cancel_time": 0, 142 | "state": 4, 143 | "type": 1, 144 | "external": false, 145 | "operation": -1, 146 | "category": null, 147 | "error": null, 148 | "description": "", 149 | "detail": null, 150 | "amount": 500000, 151 | "commission": 0, 152 | "account": [ 153 | { 154 | "name": "order_id", 155 | "title": "Код заказа", 156 | "value": "5" 157 | } 158 | ], 159 | "card": { 160 | "number": "444444******4444", 161 | "expire": "0420" 162 | }, 163 | "merchant": { 164 | "_id": "100fe486b33784292111b7dc", 165 | "name": "Online Shop LLC", 166 | "organization": "ЧП «Online Shop»", 167 | "address": "", 168 | "epos": { 169 | "merchantId": "106600000050000", 170 | "terminalId": "20660000" 171 | }, 172 | "date": 1480582278779, 173 | "logo": null, 174 | "type": "Shop", 175 | "terms": null 176 | }, 177 | "meta": null 178 | } 179 | } 180 | } 181 | ``` 182 | 183 | Owner: https://khayitovdev.uz/ 184 | 185 | 186 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khayitov-dev/PaymeUz-Protocol-Subscribe-API/54c8c10ebc40b08b210e803513fafdedde77d119/config/__init__.py -------------------------------------------------------------------------------- /config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for config project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /config/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for config project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.1. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'django-insecure-^707s5=8_184#t!t)6!k*+=mkhrdde(zfo&0z-^$$$_y--o1=w' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 41 | #my apps 42 | 'paymeuz.apps.PaymeuzConfig', 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | 'django.middleware.security.SecurityMiddleware', 47 | 'django.contrib.sessions.middleware.SessionMiddleware', 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.middleware.csrf.CsrfViewMiddleware', 50 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | ] 54 | 55 | ROOT_URLCONF = 'config.urls' 56 | 57 | TEMPLATES = [ 58 | { 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 60 | 'DIRS': [], 61 | 'APP_DIRS': True, 62 | 'OPTIONS': { 63 | 'context_processors': [ 64 | 'django.template.context_processors.debug', 65 | 'django.template.context_processors.request', 66 | 'django.contrib.auth.context_processors.auth', 67 | 'django.contrib.messages.context_processors.messages', 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = 'config.wsgi.application' 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/4.1/ref/settings/#databases 78 | 79 | DATABASES = { 80 | 'default': { 81 | 'ENGINE': 'django.db.backends.sqlite3', 82 | 'NAME': BASE_DIR / 'db.sqlite3', 83 | } 84 | } 85 | 86 | 87 | # Password validation 88 | # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators 89 | 90 | AUTH_PASSWORD_VALIDATORS = [ 91 | { 92 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 93 | }, 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 102 | }, 103 | ] 104 | 105 | 106 | # Internationalization 107 | # https://docs.djangoproject.com/en/4.1/topics/i18n/ 108 | 109 | LANGUAGE_CODE = 'en-us' 110 | 111 | TIME_ZONE = 'UTC' 112 | 113 | USE_I18N = True 114 | 115 | USE_TZ = True 116 | 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/4.1/howto/static-files/ 120 | 121 | STATIC_URL = 'static/' 122 | 123 | # Default primary key field type 124 | # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field 125 | 126 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 127 | 128 | 129 | 130 | 131 | PAYME_SETTINGS = { 132 | 'DEBUG':True, #DEBUG => True yoki False ligiga qarab sizning KEY ingiz bilan ishlaydi. 133 | 'ID':'Your KASSA_ID', # ID => bu sizga payme tomonidan beriladigan kassa ID. 134 | 'SECRET_KEY':'Your TEST KEY OR PRODUCTIN KEY', # SECRET_KEY => bu sizga payme tomonidan beriladigan SECRET_KEY. 135 | 'ACCOUNTS':{ 136 | 'KEY_1':'order_id', # KEY_1 => Order ID uchun. 137 | 'KEY_2':'', 138 | } 139 | } 140 | 141 | """ 142 | 143 | ESLATMA: Sizga 2ta KEY beriladi bittasi test uchun bittasi asosiy production uchun. 144 | Kassa ga pul tushishini yani production da real ni ishlatmoqchi bo'lselar, SECRET_KEY ga asosiy KEY ni qo'ying. 145 | Keyin esa KASSA faol ekanini tekshiring. Agar kassa faol bo'lmasa sizda ishlamasligi mumkin. 146 | 147 | """ -------------------------------------------------------------------------------- /config/urls.py: -------------------------------------------------------------------------------- 1 | """config URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/4.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | urlpatterns = [ 20 | path('admin/', admin.site.urls), 21 | path('paymeuz/',include("paymeuz.urls")), 22 | ] 23 | -------------------------------------------------------------------------------- /config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /paymeuz/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khayitov-dev/PaymeUz-Protocol-Subscribe-API/54c8c10ebc40b08b210e803513fafdedde77d119/paymeuz/__init__.py -------------------------------------------------------------------------------- /paymeuz/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from paymeuz.models import Transaction 3 | from django.contrib import admin 4 | from django.conf import settings 5 | from paymeuz import models 6 | 7 | 8 | 9 | class TransactionModelAdmin(admin.ModelAdmin): 10 | 11 | def get_status(self, obj): 12 | return obj.get_status_display() 13 | get_status.short_description = 'status' 14 | 15 | search_fields = ('request_id',) 16 | list_display = ['trans_id', 'request_id', 'amount', 'account', 'get_status', 'create_time', 'pay_time'] 17 | 18 | 19 | admin.site.register(Transaction, TransactionModelAdmin) 20 | -------------------------------------------------------------------------------- /paymeuz/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PaymeuzConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'paymeuz' 7 | -------------------------------------------------------------------------------- /paymeuz/config.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | DEBUG = settings.PAYME_SETTINGS['DEBUG'] 4 | AUTHORIZATION_RECEIPT = {'X-Auth': '{}:{}'.format(settings.PAYME_SETTINGS['ID'],settings.PAYME_SETTINGS['SECRET_KEY'])} 5 | AUTHORIZATION_CREATE = {'X-Auth': '{}'.format(settings.PAYME_SETTINGS['ID'])} 6 | KEY_1 = settings.PAYME_SETTINGS['ACCOUNTS']['KEY_1'] 7 | KEY_2 = settings.PAYME_SETTINGS['ACCOUNTS'].get('KEY_2', 'order_type') 8 | TEST_URL = 'https://checkout.test.paycom.uz/api/' 9 | PRO_URL = 'https://checkout.paycom.uz/api/' 10 | URL = TEST_URL if DEBUG else PRO_URL -------------------------------------------------------------------------------- /paymeuz/methods.py: -------------------------------------------------------------------------------- 1 | CARD_CREATE = 'cards.create' 2 | CARD_VERIFY = 'cards.verify' 3 | CARD_GET_VERIFY_CODE = 'cards.get_verify_code' 4 | RECEIPTS_CREATE = 'receipts.create' 5 | RECEIPTS_PAY = 'receipts.pay' -------------------------------------------------------------------------------- /paymeuz/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1 on 2022-08-23 14:13 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Transaction', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('trans_id', models.CharField(max_length=255)), 19 | ('request_id', models.IntegerField()), 20 | ('amount', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)), 21 | ('account', models.TextField(blank=True, null=True)), 22 | ('status', models.CharField(choices=[(0, 'processing'), (1, 'paid'), (2, 'failed')], default=0, max_length=10)), 23 | ('create_time', models.DateTimeField(auto_now_add=True)), 24 | ('pay_time', models.DateTimeField(auto_now=True)), 25 | ], 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /paymeuz/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/khayitov-dev/PaymeUz-Protocol-Subscribe-API/54c8c10ebc40b08b210e803513fafdedde77d119/paymeuz/migrations/__init__.py -------------------------------------------------------------------------------- /paymeuz/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Transaction(models.Model): 5 | """ 6 | Payme uchun Transaction model 7 | """ 8 | PROCESS = 0 9 | PAID = 1 10 | FAILED = 2 11 | STATUS = ( 12 | (PROCESS, 'processing'), 13 | (PAID, 'paid'), 14 | (FAILED, 'failed'), 15 | ) 16 | 17 | trans_id = models.CharField(max_length=255) 18 | request_id = models.IntegerField() 19 | amount = models.DecimalField(decimal_places=2, default=0.00, max_digits=10) 20 | account = models.TextField(blank=True, null=True) 21 | status = models.CharField(max_length=10, default=PROCESS, choices=STATUS) 22 | create_time = models.DateTimeField(auto_now_add=True) 23 | pay_time = models.DateTimeField(auto_now=True) 24 | 25 | 26 | def create_transaction(self, trans_id, request_id, amount, account, status): 27 | Transaction.objects.create( 28 | trans_id=trans_id, 29 | request_id=request_id, 30 | amount=amount / 100, 31 | account=account, 32 | status=status 33 | ) 34 | 35 | def update_transaction(self, trans_id, status): 36 | trans = Transaction.objects.get(trans_id=trans_id) 37 | trans.status = status 38 | trans.save() 39 | -------------------------------------------------------------------------------- /paymeuz/serializers.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from rest_framework import serializers 4 | 5 | 6 | class SubscribeSerializer(serializers.Serializer): 7 | id = serializers.IntegerField() 8 | params = serializers.JSONField() -------------------------------------------------------------------------------- /paymeuz/status.py: -------------------------------------------------------------------------------- 1 | ORDER_NOT_FOUND = -31050 2 | TRANSACTION_NOT_FOUND = -31003 3 | UNABLE_TO_PERFORM_OPERATION = -31008 4 | INVALID_AMOUNT = -31001 5 | ORDER_FOUND = 200 6 | 7 | CREATE_TRANSACTION = 1 8 | CLOSE_TRANSACTION = 2 9 | CANCEL_TRANSACTION_CODE = -1 10 | PERFORM_CANCELED_CODE = -2 11 | ORDER_NOT_FOND_MESSAGE = { 12 | "uz": "Buyurtma topilmadi", 13 | "ru": "Заказ не найден", 14 | "en": "Order not fond" 15 | } 16 | TRANSACTION_NOT_FOUND_MESSAGE = { 17 | "uz": "Tranzaksiya topilmadi", 18 | "ru": "Транзакция не найдена", 19 | "en": "Transaction not found" 20 | } 21 | UNABLE_TO_PERFORM_OPERATION_MESSAGE = { 22 | "uz": "Ushbu amalni bajarib bo'lmaydi", 23 | "ru": "Невозможно выполнить данную операцию", 24 | "en": "Unable to perform operation" 25 | } 26 | INVALID_AMOUNT_MESSAGE = { 27 | "uz": "Miqdori notog'ri", 28 | "ru": "Неверная сумма", 29 | "en": "Invalid amount" 30 | } 31 | 32 | AUTH_ERROR = { 33 | "error": { 34 | "code": -32504, 35 | "message": { 36 | "ru": "пользователь не существует", 37 | "uz": "foydalanuvchi mavjud emas", 38 | "en": "user does not exist" 39 | }, 40 | "data": "user does not exist" 41 | } 42 | } -------------------------------------------------------------------------------- /paymeuz/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /paymeuz/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from paymeuz.views import CardCreateApiView, CardVerifyApiView,PaymentApiView 3 | 4 | urlpatterns = [ 5 | path('card-create/', CardCreateApiView.as_view(), name='card_create'), 6 | path('card-verify/', CardVerifyApiView.as_view(), name='card_verify'), 7 | path('payment/', PaymentApiView.as_view(), name='payment'), 8 | ] -------------------------------------------------------------------------------- /paymeuz/views.py: -------------------------------------------------------------------------------- 1 | """ 2 | Siz Karta yaratganizda token beriladi, shu token ni barcha request larda ishlatasiz. 3 | """ 4 | 5 | import requests 6 | 7 | from rest_framework.views import APIView 8 | from rest_framework.response import Response 9 | 10 | from paymeuz.serializers import SubscribeSerializer 11 | from paymeuz.models import Transaction 12 | from paymeuz.config import * 13 | from paymeuz.methods import * 14 | 15 | 16 | 17 | 18 | 19 | class CardCreateApiView(APIView): 20 | """Kartani Avval Ro'yxatdan o'tkazishimiz kerak bo'ladi.""" 21 | def post(self, request): 22 | serializer = SubscribeSerializer(data=request.data, many=False) 23 | serializer.is_valid(raise_exception=True) 24 | result = self.card_create(serializer.validated_data) 25 | 26 | return Response(result) 27 | 28 | def card_create(self, validated_data): 29 | """Kartani yaratib qilib olamiz.""" 30 | data = dict( 31 | id=validated_data['id'], 32 | method=CARD_CREATE, 33 | params=dict( 34 | card=dict( 35 | number=validated_data['params']['card']['number'], 36 | expire=validated_data['params']['card']['expire'], 37 | ), 38 | amount=validated_data['params']['amount'], 39 | save=validated_data['params']['save'] 40 | ) 41 | ) 42 | response = requests.post(URL, json=data, headers=AUTHORIZATION_CREATE) 43 | result = response.json() 44 | if 'error' in result: 45 | return result 46 | 47 | token = result['result']['card']['token'] 48 | result = self.card_get_verify_code(token) 49 | 50 | return result 51 | 52 | def card_get_verify_code(self, token): 53 | """Agar karta mavjud bo'lsa unga code jo'natamiz.""" 54 | data = dict( 55 | method=CARD_GET_VERIFY_CODE, 56 | params=dict( 57 | token=token 58 | ) 59 | ) 60 | response = requests.post(URL, json=data, headers=AUTHORIZATION_CREATE) 61 | result = response.json() 62 | if 'error' in result: 63 | return result 64 | 65 | result.update(token=token) 66 | return result 67 | 68 | 69 | class CardVerifyApiView(APIView): 70 | """Tasdiqlash code ni tekshirish.""" 71 | def post(self, request): 72 | serializer = SubscribeSerializer(data=request.data, many=False) 73 | serializer.is_valid(raise_exception=True) 74 | result = self.card_verify(serializer.validated_data) 75 | 76 | return Response(result) 77 | 78 | def card_verify(self, validated_data): 79 | """Code ni kiritish.""" 80 | data = dict( 81 | id=validated_data['id'], 82 | method=CARD_VERIFY, 83 | params=dict( 84 | token=validated_data['params']['token'], 85 | code=validated_data['params']['code'], 86 | ) 87 | ) 88 | response = requests.post(URL, json=data, headers=AUTHORIZATION_CREATE) 89 | result = response.json() 90 | 91 | return result 92 | 93 | 94 | class PaymentApiView(APIView): 95 | """Karta muvaffaqiyatlik ro'yxatdan o'tgandan keyin, endi Pul to'lasek bo'ladi.""" 96 | def post(self, request): 97 | serializer = SubscribeSerializer(data=request.data, many=False) 98 | serializer.is_valid(raise_exception=True) 99 | token = serializer.validated_data['params']['token'] 100 | result = self.receipts_create(token, serializer.validated_data) 101 | 102 | return Response(result) 103 | 104 | def receipts_create(self, token, validated_data): 105 | """Pul tushishi uchun headers da biz KEY ni berishimiz kerak.""" 106 | key_2 = validated_data['params']['account'][KEY_2] if KEY_2 else None 107 | data = dict( 108 | id=validated_data['id'], 109 | method=RECEIPTS_CREATE, 110 | params=dict( 111 | amount=validated_data['params']['amount'], 112 | account=dict( 113 | KEY_1 = validated_data['params']['account'][KEY_1], 114 | KEY_2 = key_2, 115 | ) 116 | ) 117 | ) 118 | response = requests.post(URL, json=data, headers=AUTHORIZATION_RECEIPT) 119 | result = response.json() 120 | if 'error' in result: 121 | return result 122 | 123 | trans_id = result['result']['receipt']['_id'] 124 | trans = Transaction() 125 | trans.create_transaction( 126 | trans_id=trans_id, 127 | request_id=result['id'], 128 | amount=result['result']['receipt']['amount'], 129 | account=result['result']['receipt']['account'], 130 | status=trans.PROCESS, 131 | ) 132 | result = self.receipts_pay(trans_id, token) 133 | return result 134 | 135 | def receipts_pay(self, trans_id, token): 136 | """Barchasi aniq va pul tolandi.""" 137 | data = dict( 138 | method=RECEIPTS_PAY, 139 | params=dict( 140 | id=trans_id, 141 | token=token, 142 | ) 143 | ) 144 | response = requests.post(URL, json=data, headers=AUTHORIZATION_RECEIPT) 145 | result = response.json() 146 | trans = Transaction() 147 | 148 | if 'error' in result: 149 | trans.update_transaction( 150 | trans_id=trans_id, 151 | status=trans.FAILED, 152 | ) 153 | return result 154 | 155 | trans.update_transaction( 156 | trans_id=result['result']['receipt']['_id'], 157 | status=trans.PAID, 158 | ) 159 | 160 | return result -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==4.1 2 | djangorestframework==3.13.1 3 | requests==2.28.1 4 | --------------------------------------------------------------------------------