├── tests ├── __init__.py ├── mocks │ ├── unlink_user.json │ ├── revert_payment_payload.json │ ├── cancel_payment.json │ ├── delete_qrcode.json │ ├── cancel_request_order_response.json │ ├── give_cashback_response.json │ ├── reverse_cashback_response.json │ ├── refund_payment_payload.json │ ├── capture_payment_payload.json │ ├── refund_request_order_payload.json │ ├── reverse_cashback_payload.json │ ├── revert_payment_response.json │ ├── give_cashback_payload.json │ ├── get_user_auth_status.json │ ├── refund_payment_response.json │ ├── refund_details.json │ ├── refund_request_order_response.json │ ├── create_qrcode.json │ ├── check_cashback_detail_response.json │ ├── check_cashback_reversal_details_response.json │ ├── preauth_payload.json │ ├── request_order_payload.json │ ├── create_continues_payment_payload.json │ ├── create_payment_payload.json │ ├── capture_payment_response.json │ ├── get_payment_details.json │ ├── request_order_response.json │ ├── create_qrcode_response.json │ ├── create_continues_payment_response.json │ ├── create_payment_response.json │ ├── get_request_order_response.json │ └── preauth_response.json ├── README.md ├── helpers.py ├── test_create_payment.py ├── test_deleteqrcode.py ├── test_qrcode.py ├── test_give_cashback.py ├── test_refund_payment.py ├── test_capture_payment.py ├── test_refund_details.py ├── test_unlink_user.py ├── test_cancel_payment.py ├── test_refund_request_order.py ├── test_reverse_cashback.py ├── test_revert_payment.py ├── test_get_payment_details.py ├── test_preauth.py ├── test_check_cashback_details.py ├── test_get_payment_qr_details.py ├── test_cancel_request_order.py ├── test_request_order.py ├── test_get_request_order.py ├── test_create_continues_payment.py ├── test_get_user_auth_status.py └── test_check_cashback_reversal_details.py ├── paypayopa ├── constants │ ├── http_status_code.py │ ├── error_code.py │ ├── __init__.py │ ├── url.py │ └── api_list.py ├── __init__.py ├── errors.py ├── resources │ ├── __init__.py │ ├── user.py │ ├── account.py │ ├── base.py │ ├── preauth.py │ ├── code.py │ ├── pending.py │ ├── cashback.py │ └── payment.py └── client.py ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── pull_request_template.md └── workflows │ ├── python-publish.yml │ └── build-ci.yml ├── SECURITY.md ├── setup.py ├── .gitignore ├── .pylintrc ├── contributing.md ├── LICENSE └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /paypayopa/constants/http_status_code.py: -------------------------------------------------------------------------------- 1 | class HTTP_STATUS_CODE(object): 2 | OK = 200 3 | REDIRECT = 300 4 | -------------------------------------------------------------------------------- /tests/mocks/unlink_user.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | } 7 | } -------------------------------------------------------------------------------- /tests/mocks/revert_payment_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "merchantRevertId": "string", 3 | "paymentId": "string", 4 | "requestedAt": 0, 5 | "reason": "string" 6 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /tests/mocks/cancel_payment.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "fake_code", 4 | "message": "string", 5 | "codeId": "fake_codeId" 6 | } 7 | } -------------------------------------------------------------------------------- /tests/mocks/delete_qrcode.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "test_code", 4 | "message": "test message", 5 | "codeId": "test_id" 6 | } 7 | } -------------------------------------------------------------------------------- /tests/mocks/cancel_request_order_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | } 7 | } -------------------------------------------------------------------------------- /tests/mocks/give_cashback_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "REQUEST_ACCEPTED", 4 | "message": "Request accepted", 5 | "codeId": "08100001" 6 | }, 7 | "data": "" 8 | } -------------------------------------------------------------------------------- /tests/mocks/reverse_cashback_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "REQUEST_ACCEPTED", 4 | "message": "Request accepted", 5 | "codeId": "08100001" 6 | }, 7 | "data": "" 8 | } -------------------------------------------------------------------------------- /paypayopa/constants/error_code.py: -------------------------------------------------------------------------------- 1 | class ERROR_CODE(object): 2 | BAD_REQUEST_ERROR = "BAD_REQUEST_ERROR" 3 | GATEWAY_ERROR = "GATEWAY_ERROR" 4 | SERVER_ERROR = "SERVER_ERROR" 5 | UNAUTHORIZED = "UNAUTHORIZED" 6 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Paypay OPA SDK Test Cases 2 | 3 | ## Install pytest 4 | ```sh 5 | $ pip install pytest 6 | ``` 7 | 8 | ## Install unittest 9 | ```sh 10 | $ pip install unittest 11 | ``` 12 | 13 | ## Run pytest 14 | ```sh 15 | $ pytest 16 | ``` -------------------------------------------------------------------------------- /tests/mocks/refund_payment_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "merchantRefundId": "fakeId", 3 | "paymentId": "string", 4 | "amount": { 5 | "amount": 0, 6 | "currency": "JPY" 7 | }, 8 | "requestedAt": 0, 9 | "reason": "string" 10 | } -------------------------------------------------------------------------------- /tests/mocks/capture_payment_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "merchantPaymentId": "string", 3 | "amount": { 4 | "amount": 0, 5 | "currency": "JPY" 6 | }, 7 | "merchantCaptureId": "string", 8 | "requestedAt": 0, 9 | "orderDescription": "string" 10 | } -------------------------------------------------------------------------------- /paypayopa/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import Client 2 | from .resources import Code 3 | from .constants import ERROR_CODE 4 | from .constants import HTTP_STATUS_CODE 5 | 6 | __all__ = [ 7 | 'Client', 8 | 'Code', 9 | 'ERROR_CODE', 10 | 'HTTP_STATUS_CODE', 11 | ] 12 | -------------------------------------------------------------------------------- /paypayopa/constants/__init__.py: -------------------------------------------------------------------------------- 1 | from .http_status_code import HTTP_STATUS_CODE 2 | from .error_code import ERROR_CODE 3 | from .url import URL 4 | from .api_list import API_NAMES 5 | 6 | __all__ = [ 7 | 'HTTP_STATUS_CODE', 8 | 'ERROR_CODE', 9 | 'URL', 10 | 'API_NAMES', 11 | ] 12 | -------------------------------------------------------------------------------- /tests/mocks/refund_request_order_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "merchantRefundId": "fake_merchantRefundId", 3 | "merchantPaymentId": "fake_merchantPaymentId", 4 | "paymentId": "fake_paymentId", 5 | "amount": { 6 | "amount": 1, 7 | "currency": "JPY" 8 | }, 9 | "requestedAt": 0, 10 | "reason": "string" 11 | } -------------------------------------------------------------------------------- /paypayopa/errors.py: -------------------------------------------------------------------------------- 1 | class ServerError(Exception): 2 | def __init__(self, message=None, *args, **kwargs): 3 | super(ServerError, self).__init__(message) 4 | 5 | 6 | class SignatureVerificationError(Exception): 7 | def __init__(self, message=None, *args, **kwargs): 8 | super(SignatureVerificationError, self).__init__(message) 9 | -------------------------------------------------------------------------------- /tests/mocks/reverse_cashback_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "merchantCashbackReversalId": "xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 3 | "merchantCashbackId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 4 | "amount": { 5 | "amount": 100, 6 | "currency": "JPY" 7 | }, 8 | "requestedAt": 1609749559, 9 | "reason": "reversal reason", 10 | "metadata": {} 11 | } -------------------------------------------------------------------------------- /tests/mocks/revert_payment_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | }, 7 | "data": { 8 | "status": "CANCELED", 9 | "acceptedAt": 0, 10 | "paymentId": "string", 11 | "requestedAt": 0, 12 | "reason": "string" 13 | } 14 | } -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | 0.x.x | :white_check_mark: | 8 | 9 | 10 | 11 | ## Reporting a Vulnerability 12 | 13 | Please create an issue or if the issue is sensitive or critical, please reach us to the email address mentioned in the github organisation page 14 | -------------------------------------------------------------------------------- /tests/mocks/give_cashback_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "merchantCashbackId": "xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 3 | "userAuthorizationId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 4 | "amount": { 5 | "amount": 100, 6 | "currency": "JPY" 7 | }, 8 | "requestedAt": 1609749559, 9 | "orderDescription": "order description", 10 | "walletType": "PREPAID", 11 | "expiryDate": "YYYY-MM-DD", 12 | "metadata": "" 13 | } -------------------------------------------------------------------------------- /paypayopa/resources/__init__.py: -------------------------------------------------------------------------------- 1 | from .code import Code 2 | from .payment import Payment 3 | from .account import Account 4 | from .preauth import Preauth 5 | from .pending import Pending 6 | from .user import User 7 | from .cashback import Cashback 8 | 9 | __all__ = [ 10 | 'Code', 11 | 'Payment', 12 | 'Account', 13 | 'Preauth', 14 | 'Pending', 15 | 'User', 16 | 'Cashback' 17 | ] 18 | -------------------------------------------------------------------------------- /tests/mocks/get_user_auth_status.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | }, 7 | "data": { 8 | "userAuthorizationId": "string", 9 | "referenceIds": [], 10 | "status": "ACTIVE", 11 | "scopes": [ 12 | "preauth_capture_native" 13 | ], 14 | "expireAt": 0, 15 | "issuedAt": 0 16 | } 17 | } -------------------------------------------------------------------------------- /tests/mocks/refund_payment_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | }, 7 | "data": { 8 | "status": "CREATED", 9 | "acceptedAt": 0, 10 | "merchantRefundId": "string", 11 | "paymentId": "string", 12 | "amount": {}, 13 | "requestedAt": 0, 14 | "reason": "string", 15 | "assumeMerchant": "string" 16 | } 17 | } -------------------------------------------------------------------------------- /tests/mocks/refund_details.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | }, 7 | "data": { 8 | "status": "CREATED", 9 | "acceptedAt": 0, 10 | "merchantRefundId": "fake_merchant_refundId", 11 | "paymentId": "string", 12 | "amount": {}, 13 | "requestedAt": 0, 14 | "reason": "string", 15 | "assumeMerchant": "string" 16 | } 17 | } -------------------------------------------------------------------------------- /tests/mocks/refund_request_order_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | }, 7 | "data": { 8 | "status": "CREATED", 9 | "acceptedAt": 0, 10 | "merchantRefundId": "string", 11 | "paymentId": "string", 12 | "amount": { 13 | "amount": 0, 14 | "currency": "JPY" 15 | }, 16 | "requestedAt": 0, 17 | "reason": "string" 18 | } 19 | } -------------------------------------------------------------------------------- /paypayopa/constants/url.py: -------------------------------------------------------------------------------- 1 | class URL(object): 2 | SANDBOX_BASE_URL = 'https://stg-api.sandbox.paypay.ne.jp' 3 | PRODUCTION_BASE_URL = 'https://api.paypay.ne.jp' 4 | PERF_BASE_URL = 'https://perf-api.paypay.ne.jp' 5 | RESOLVE = 'https://developer.paypay.ne.jp/develop/resolve' 6 | CODE = "/v2/codes" 7 | PAYMENT = "/v2/payments" 8 | ACCOUNT_LINK = "/v1/qr/sessions" 9 | PENDING_PAYMENT = "/v1/requestOrder" 10 | USER_AUTH = "/v2/user/authorizations" 11 | GIVE_CASHBACK = "/v2/cashback" 12 | REVERSAL_CASHBACK = "/v2/cashback_reversal" 13 | REFUNDS = "/v2/refunds" 14 | 15 | -------------------------------------------------------------------------------- /tests/mocks/create_qrcode.json: -------------------------------------------------------------------------------- 1 | { 2 | "merchantPaymentId": "cb31bcc0-3b6c-46e0-9002-e5c4bb1e3d5f", 3 | "codeType": "ORDER_QR", 4 | "redirectUrl":"http://foobar.com", 5 | "redirectType":"WEB_LINK", 6 | "orderDescription":"Example - Mune Cake shop", 7 | "requestedAt": 0, 8 | "orderItems": [{ 9 | "name": "Moon cake", 10 | "category": "pasteries", 11 | "quantity": "1", 12 | "productId": "67678", 13 | "unitPrice": { 14 | "amount": 1, 15 | "currency": "JPY" 16 | } 17 | }], 18 | "amount": { 19 | "amount": 1, 20 | "currency": "JPY" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/mocks/check_cashback_detail_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "SUCCESS", 4 | "message": "Success", 5 | "codeId": "08100001" 6 | }, 7 | "data": { 8 | "status": "SUCCESS", 9 | "acceptedAt": 1609129521, 10 | "merchantAlias": "282428786090074112", 11 | "amount": { 12 | "amount": 1000, 13 | "currency": "JPY" 14 | }, 15 | "requestedAt": 0, 16 | "metadata": {}, 17 | "cashbackId": "00202680885387092992", 18 | "merchantCashbackId": "00002", 19 | "userAuthorizationId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 20 | "orderDescription": "Cashback descriptions.", 21 | "walletType": "PREPAID" 22 | } 23 | } -------------------------------------------------------------------------------- /tests/mocks/check_cashback_reversal_details_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "SUCCESS", 4 | "message": "Success", 5 | "codeId": "08100001" 6 | }, 7 | "data": { 8 | "status": "SUCCESS", 9 | "acceptedAt": 1609129521, 10 | "merchantAlias": "282428786090074112", 11 | "amount": { 12 | "amount": 1000, 13 | "currency": "JPY" 14 | }, 15 | "requestedAt": 0, 16 | "metadata": {}, 17 | "cashbackId": "00202680885387092992", 18 | "merchantCashbackId": "00002", 19 | "userAuthorizationId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 20 | "orderDescription": "Cashback descriptions.", 21 | "walletType": "PREPAID" 22 | } 23 | } -------------------------------------------------------------------------------- /tests/helpers.py: -------------------------------------------------------------------------------- 1 | import paypayopa 2 | import os 3 | import unittest 4 | import json 5 | 6 | 7 | def mock_file(filename): 8 | if not filename: 9 | return '' 10 | file_dir = os.path.dirname(__file__) 11 | file_path = "{}/mocks/{}.json".format(file_dir, filename) 12 | return json.loads(open(file_path).read()) 13 | 14 | 15 | class ClientTestCase(unittest.TestCase): 16 | def setUp(self): 17 | self.base_url = 'https://stg-api.sandbox.paypay.ne.jp/v2' 18 | self.payment_id = 'fake_payment_id' 19 | self.refund_id = 'fake_refund_id' 20 | self.merchant_id = 'fake_merchant_id' 21 | self.client = paypayopa.Client(auth=('key_id', 'key_secret')) 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /tests/test_create_payment.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestCreatePayment(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestCreatePayment, self).setUp() 11 | 12 | @responses.activate 13 | def test_create_payment(self): 14 | """Test create payment.""" 15 | init = mock_file('create_payment_payload') 16 | result = mock_file('create_payment_response') 17 | url = url = "{}/{}".format(self.base_url, 'payments') 18 | responses.add(responses.POST, url, status=200, body=json.dumps(result), 19 | match_querystring=True) 20 | self.assertEqual(self.client.Payment.create(init), result) 21 | -------------------------------------------------------------------------------- /tests/mocks/preauth_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "merchantPaymentId": "string", 3 | "userAuthorizationId": "string", 4 | "amount": { 5 | "amount": 0, 6 | "currency": "JPY" 7 | }, 8 | "requestedAt": 0, 9 | "expiresAt": 1234567890, 10 | "storeId": "string", 11 | "terminalId": "string", 12 | "orderReceiptNumber": "string", 13 | "orderDescription": "string", 14 | "orderItems": [ 15 | { 16 | "name": "string", 17 | "category": "string", 18 | "quantity": 1, 19 | "productId": "string", 20 | "unitPrice": { 21 | "amount": 0, 22 | "currency": "JPY" 23 | } 24 | } 25 | ], 26 | "metadata": {} 27 | } -------------------------------------------------------------------------------- /tests/test_deleteqrcode.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestDeleteQRCode(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestDeleteQRCode, self).setUp() 11 | self.base_url = '{}/codes'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_qrcode_delete(self): 15 | """Test delete QR code.""" 16 | result = mock_file('delete_qrcode') 17 | url = '{}/{}'.format(self.base_url, 'fake_qr_id') 18 | responses.add(responses.DELETE, url, status=200, body=json.dumps(result), 19 | match_querystring=True, ) 20 | self.assertEqual(self.client.Code.delete_qr_code('fake_qr_id'), result) 21 | -------------------------------------------------------------------------------- /tests/mocks/request_order_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "merchantPaymentId": "string", 3 | "userAuthorizationId": "string", 4 | "amount": { 5 | "amount": 0, 6 | "currency": "JPY" 7 | }, 8 | "requestedAt": 0, 9 | "expiryDate": null, 10 | "storeId": "string", 11 | "terminalId": "string", 12 | "orderReceiptNumber": "string", 13 | "orderDescription": "string", 14 | "orderItems": [ 15 | { 16 | "name": "string", 17 | "category": "string", 18 | "quantity": 1, 19 | "productId": "string", 20 | "unitPrice": { 21 | "amount": 0, 22 | "currency": "JPY" 23 | } 24 | } 25 | ], 26 | "metadata": {} 27 | } -------------------------------------------------------------------------------- /tests/test_qrcode.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestClientQRCode(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestClientQRCode, self).setUp() 11 | self.base_url = '{}/codes'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_order_create(self): 15 | """Test create QR code.""" 16 | init = mock_file('create_qrcode') 17 | result = mock_file('create_qrcode_response') 18 | url = self.base_url 19 | responses.add(responses.POST, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual(self.client.Code.create_qr_code(init), result) 22 | -------------------------------------------------------------------------------- /tests/mocks/create_continues_payment_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "merchantPaymentId": "fake_merchantPaymentId", 3 | "userAuthorizationId": "fake_userAuthorizationId", 4 | "amount": { 5 | "amount": 0, 6 | "currency": "JPY" 7 | }, 8 | "requestedAt": 0, 9 | "storeId": "string", 10 | "terminalId": "string", 11 | "orderReceiptNumber": "string", 12 | "orderDescription": "string", 13 | "orderItems": [ 14 | { 15 | "name": "string", 16 | "category": "string", 17 | "quantity": 1, 18 | "productId": "string", 19 | "unitPrice": { 20 | "amount": 0, 21 | "currency": "JPY" 22 | } 23 | } 24 | ], 25 | "metadata": {} 26 | } -------------------------------------------------------------------------------- /tests/mocks/create_payment_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "merchantPaymentId": "string", 3 | "userAuthorizationId": "string", 4 | "amount": { 5 | "amount": 0, 6 | "currency": "JPY" 7 | }, 8 | "requestedAt": 0, 9 | "storeId": "string", 10 | "terminalId": "string", 11 | "orderReceiptNumber": "string", 12 | "orderDescription": "string", 13 | "orderItems": [ 14 | { 15 | "name": "string", 16 | "category": "string", 17 | "quantity": 1, 18 | "productId": "string", 19 | "unitPrice": { 20 | "amount": 0, 21 | "currency": "JPY" 22 | } 23 | } 24 | ], 25 | "metadata": {}, 26 | "productType": "VIRTUAL_BONUS_INVESTMENT" 27 | } -------------------------------------------------------------------------------- /tests/test_give_cashback.py: -------------------------------------------------------------------------------- 1 | import json 2 | import responses 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestGiveCashback(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestGiveCashback, self).setUp() 11 | self.base_url = '{}/cashback'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_give_cashback(self): 15 | """ Test give cashback""" 16 | init = mock_file('give_cashback_payload') 17 | result = mock_file('give_cashback_response') 18 | url = self.base_url 19 | responses.add(responses.POST, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual(self.client.Cashback.give_cashback(init), result) 22 | -------------------------------------------------------------------------------- /tests/mocks/capture_payment_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | }, 7 | "data": { 8 | "paymentId": "string", 9 | "status": "COMPLETED", 10 | "acceptedAt": 0, 11 | "refunds": {}, 12 | "captures": {}, 13 | "merchantPaymentId": "string", 14 | "userAuthorizationId": "string", 15 | "amount": {}, 16 | "requestedAt": 0, 17 | "expiresAt": null, 18 | "storeId": "string", 19 | "terminalId": "string", 20 | "orderReceiptNumber": "string", 21 | "orderDescription": "string", 22 | "orderItems": [], 23 | "metadata": {}, 24 | "assumeMerchant": "string" 25 | } 26 | } -------------------------------------------------------------------------------- /tests/test_refund_payment.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestRefundPayment(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestRefundPayment, self).setUp() 11 | self.base_url = '{}/refunds/'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_refund_payment(self): 15 | """Test refund payment.""" 16 | init = mock_file('refund_payment_payload') 17 | result = mock_file('refund_payment_response') 18 | url = self.base_url 19 | responses.add(responses.POST, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual(self.client.Payment.refund_payment(init), result) 22 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### All Submissions: 2 | 3 | * [ ] Have you followed the guidelines in our [Contributing](/contributing.md) document? 4 | * [ ] Have you read and signed the automated Contributor's License Agreement? 5 | * [ ] Have you checked to ensure there aren't other open [Pull Requests](../../pulls) for the same update/change? 6 | 7 | 8 | ### New Feature Submissions: 9 | 10 | 1. [ ] Does your submission pass tests? 11 | 2. [ ] Have you lint your code locally prior to submission? 12 | 13 | ### Changes to Core Features: 14 | 15 | * [ ] Have you added an explanation of what your changes do and why you'd like us to include them? 16 | * [ ] Have you written new tests for your core changes, as applicable? 17 | * [ ] Have you successfully ran tests with your changes locally? 18 | -------------------------------------------------------------------------------- /tests/test_capture_payment.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestCapturePayment(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestCapturePayment, self).setUp() 11 | self.base_url = '{}/payments/capture'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_capture_payment(self): 15 | """Test capture payment.""" 16 | init = mock_file('capture_payment_payload') 17 | result = mock_file('capture_payment_response') 18 | url = self.base_url 19 | responses.add(responses.POST, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual(self.client.Payment.capture_payment(init), result) 22 | -------------------------------------------------------------------------------- /tests/test_refund_details.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestRefundDetails(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestRefundDetails, self).setUp() 11 | self.base_url = '{}/refunds/fake_merchant_refundId'.format( 12 | self.base_url) 13 | 14 | @responses.activate 15 | def test_refund_details(self): 16 | """Test refund details.""" 17 | result = mock_file('refund_details') 18 | url = self.base_url 19 | responses.add(responses.GET, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual(self.client.Payment.refund_details( 22 | 'fake_merchant_refundId'), result) 23 | -------------------------------------------------------------------------------- /tests/test_unlink_user.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestUnLinkUser(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestUnLinkUser, self).setUp() 11 | self.base_url = '{}/user/authorizations'.format( 12 | self.base_url) 13 | 14 | @responses.activate 15 | def test_unlink_user(self): 16 | """Test UnLink user.""" 17 | result = mock_file('unlink_user') 18 | url = '{}/{}'.format(self.base_url, 'fake_userId') 19 | responses.add(responses.DELETE, url, status=200, body=json.dumps(result), 20 | match_querystring=True, ) 21 | self.assertEqual( 22 | self.client.User.unlink_user_athorization('fake_userId'), result) 23 | -------------------------------------------------------------------------------- /tests/test_cancel_payment.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestCancelPayment(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestCancelPayment, self).setUp() 11 | self.base_url = '{}/payments/fake_merchant_payment_id'.format( 12 | self.base_url) 13 | 14 | @responses.activate 15 | def test_cancel_payment(self): 16 | """Test cancel payment.""" 17 | result = mock_file('cancel_payment') 18 | url = self.base_url 19 | responses.add(responses.DELETE, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual(self.client.Payment.cancel_payment( 22 | 'fake_merchant_payment_id'), result) 23 | -------------------------------------------------------------------------------- /tests/test_refund_request_order.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestRefundRequestOrder(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestRefundRequestOrder, self).setUp() 11 | self.base_url = '{}/v2/refunds/'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_refund_request_order(self): 15 | """Test refund request order.""" 16 | init = mock_file('refund_request_order_payload') 17 | result = mock_file('refund_request_order_response') 18 | url = "https://stg-api.sandbox.paypay.ne.jp/v2/refunds" 19 | responses.add(responses.POST, url, status=200, body=json.dumps(result)) 20 | self.assertEqual(self.client.Pending.refund_payment(init), result) 21 | -------------------------------------------------------------------------------- /tests/test_reverse_cashback.py: -------------------------------------------------------------------------------- 1 | import json 2 | import responses 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestReversesCashback(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestReversesCashback, self).setUp() 11 | self.base_url = '{}/cashback_reversal'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_reverse_cashback(self): 15 | """ Test reverse cashback """ 16 | init = mock_file('reverse_cashback_payload') 17 | result = mock_file('reverse_cashback_response') 18 | url = self.base_url 19 | responses.add(responses.POST, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual(self.client.Cashback.reverse_cashback(init), result) 22 | -------------------------------------------------------------------------------- /tests/test_revert_payment.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestRevertPayment(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestRevertPayment, self).setUp() 11 | self.base_url = '{}/payments/preauthorize/revert'.format( 12 | self.base_url) 13 | 14 | @responses.activate 15 | def test_revert_payment(self): 16 | """Test revert payment.""" 17 | init = mock_file('revert_payment_payload') 18 | result = mock_file('revert_payment_response') 19 | url = self.base_url 20 | responses.add(responses.POST, url, status=200, body=json.dumps(result), 21 | match_querystring=True) 22 | self.assertEqual(self.client.Payment.revert_payment(init), result) 23 | -------------------------------------------------------------------------------- /tests/mocks/get_payment_details.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "fake_code", 4 | "message": "string", 5 | "codeId": "fake_codeId" 6 | }, 7 | "data": { 8 | "paymentId": "string", 9 | "status": "CREATED", 10 | "acceptedAt": 0, 11 | "refunds": {}, 12 | "captures": {}, 13 | "revert": {}, 14 | "merchantPaymentId": "cb31bcc0-3b6c-46e0-9002-e5c4bb1e3d5f", 15 | "userAuthorizationId": "string", 16 | "amount": {}, 17 | "requestedAt": 0, 18 | "expiresAt": null, 19 | "canceledAt": null, 20 | "storeId": "string", 21 | "terminalId": "string", 22 | "orderReceiptNumber": "string", 23 | "orderDescription": "string", 24 | "orderItems": [], 25 | "metadata": {} 26 | } 27 | } -------------------------------------------------------------------------------- /tests/test_get_payment_details.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestGetPaymentDetails(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestGetPaymentDetails, self).setUp() 11 | self.base_url = '{}/payments/fake_merchant_payment_id'.format( 12 | self.base_url) 13 | 14 | @responses.activate 15 | def test_get_payment_details(self): 16 | """Test get payment details.""" 17 | result = mock_file('get_payment_details') 18 | url = self.base_url 19 | responses.add(responses.GET, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual(self.client.Payment.get_payment_details( 22 | 'fake_merchant_payment_id'), result) 23 | -------------------------------------------------------------------------------- /tests/test_preauth.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestPreAuth(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestPreAuth, self).setUp() 11 | self.base_url = '{}/payments/preauthorize'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_preauth(self): 15 | """Test preauth.""" 16 | init = mock_file('preauth_payload') 17 | result = mock_file('preauth_response') 18 | url = self.base_url 19 | # url = "{}/{}".format(self.base_url, 'preauthorize') 20 | responses.add(responses.POST, url, status=200, body=json.dumps(result), 21 | match_querystring=True) 22 | self.assertEqual( 23 | self.client.Preauth.pre_authorize_create(init), result) 24 | -------------------------------------------------------------------------------- /tests/test_check_cashback_details.py: -------------------------------------------------------------------------------- 1 | import json 2 | import responses 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestCheckCashbackDetail(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestCheckCashbackDetail, self).setUp() 11 | self.base_url = '{}/cashback'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_check_cashback_detail(self): 15 | """ Test check cashback detail """ 16 | init = 'fake_merchant_cashback_id' 17 | result = mock_file('check_cashback_detail_response') 18 | url = '{}/{}'.format(self.base_url, init) 19 | responses.add(responses.GET, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual(self.client.Cashback.check_cashback_detail(init), result) 22 | -------------------------------------------------------------------------------- /tests/test_get_payment_qr_details.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestGetPaymentQRDetails(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestGetPaymentQRDetails, self).setUp() 11 | self.base_url = '{}/codes/payments/fake_merchant_payment_id'.format( 12 | self.base_url) 13 | 14 | @responses.activate 15 | def test_get_payment_qr_details(self): 16 | """Test get payment QR details.""" 17 | result = mock_file('get_payment_details') 18 | url = self.base_url 19 | responses.add(responses.GET, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual(self.client.Code.get_payment_details( 22 | 'fake_merchant_payment_id'), result) 23 | -------------------------------------------------------------------------------- /tests/test_cancel_request_order.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestCancelRequestOrder(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestCancelRequestOrder, self).setUp() 11 | self.base_url = '{}/requestOrder/fakeMerchantId'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_cancel_request_order(self): 15 | """Test cancel request order.""" 16 | result = mock_file('cancel_request_order_response') 17 | url = "https://stg-api.sandbox.paypay.ne.jp/v1/requestOrder/fakeMerchantId" 18 | responses.add(responses.DELETE, url, status=200, body=json.dumps(result), 19 | match_querystring=True, ) 20 | self.assertEqual( 21 | self.client.Pending.cancel_payment('fakeMerchantId'), result) 22 | -------------------------------------------------------------------------------- /tests/test_request_order.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestCreateRequestOrder(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestCreateRequestOrder, self).setUp() 11 | self.base_url = '{}/requestOrder'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_test_create_request_order(self): 15 | """Test request order.""" 16 | init = mock_file('request_order_payload') 17 | result = mock_file('request_order_response') 18 | url = "https://stg-api.sandbox.paypay.ne.jp/v1/requestOrder" 19 | responses.add(responses.POST, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual( 22 | self.client.Pending.create_pending_payment(init), result) 23 | -------------------------------------------------------------------------------- /tests/test_get_request_order.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestGetRequestOrder(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestGetRequestOrder, self).setUp() 11 | self.base_url = '{}/requestOrder/fake_merchant_payment_id'.format( 12 | self.base_url) 13 | 14 | @responses.activate 15 | def test_get_request_order(self): 16 | """Test get request order.""" 17 | result = mock_file('get_request_order_response') 18 | url = "https://stg-api.sandbox.paypay.ne.jp/v1/requestOrder/fake_merchant_payment_id" 19 | responses.add(responses.GET, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual(self.client.Pending.get_payment_details( 22 | 'fake_merchant_payment_id'), result) 23 | -------------------------------------------------------------------------------- /tests/test_create_continues_payment.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestCreateContinuesPayment(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestCreateContinuesPayment, self).setUp() 11 | self.base_url = '{}/psubscription/payments'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_create_continues_payment(self): 15 | """Test create continues payment.""" 16 | init = mock_file('create_continues_payment_payload') 17 | result = mock_file('create_continues_payment_response') 18 | url = "https://stg-api.sandbox.paypay.ne.jp/v1/subscription/payments" 19 | responses.add(responses.POST, url, status=200, body=json.dumps(result), 20 | match_querystring=True) 21 | self.assertEqual( 22 | self.client.Payment.create_continuous_payment(init), result) 23 | -------------------------------------------------------------------------------- /tests/test_get_user_auth_status.py: -------------------------------------------------------------------------------- 1 | import responses 2 | import json 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestGetUserAuthStatus(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestGetUserAuthStatus, self).setUp() 11 | self.base_url = '{}/user/authorizations?userAuthorizationId=fakeid'.format( 12 | self.base_url) 13 | 14 | @responses.activate 15 | def test_get_user_auth_status(self): 16 | """Test user auth status.""" 17 | result = mock_file('get_user_auth_status') 18 | # url = '{}/{}'.format(self.base_url, 'fake_userId') 19 | url = self.base_url 20 | params = { 21 | "fakeid": 'fakeid' 22 | } 23 | responses.add(responses.GET, url, status=200, body=json.dumps(result), 24 | match_querystring=True, ) 25 | self.assertEqual( 26 | self.client.User.get_authorization_status(params), result) 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Environment (please complete the following information):** 27 | If applicable 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | - Device: [e.g. iPhone6] 32 | - OS: [e.g. iOS8.1] 33 | - Browser [e.g. stock browser, safari] 34 | - Version [e.g. 22] 35 | 36 | **Additional context** 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /tests/mocks/request_order_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | }, 7 | "data": { 8 | "merchantPaymentId": "string", 9 | "userAuthorizationId": "string", 10 | "amount": { 11 | "amount": 0, 12 | "currency": "JPY" 13 | }, 14 | "requestedAt": 0, 15 | "expiryDate": null, 16 | "storeId": "string", 17 | "terminalId": "string", 18 | "orderReceiptNumber": "string", 19 | "orderDescription": "string", 20 | "orderItems": [ 21 | { 22 | "name": "string", 23 | "category": "string", 24 | "quantity": 1, 25 | "productId": "string", 26 | "unitPrice": { 27 | "amount": 0, 28 | "currency": "JPY" 29 | } 30 | } 31 | ], 32 | "metadata": {} 33 | } 34 | } -------------------------------------------------------------------------------- /paypayopa/resources/user.py: -------------------------------------------------------------------------------- 1 | from ..resources.base import Resource 2 | from ..constants.url import URL 3 | from ..constants.api_list import API_NAMES 4 | 5 | 6 | class User(Resource): 7 | def __init__(self, client=None): 8 | super(User, self).__init__(client) 9 | User.base_url = URL.USER_AUTH 10 | 11 | def get_authorization_status(self, id, **kwargs): 12 | url = self.base_url 13 | params = { 14 | "userAuthorizationId": id 15 | } 16 | if id is None: 17 | raise ValueError("\x1b[31m MISSING QUERY PARAM" 18 | " \x1b[0m for userAuthorizationId") 19 | return self.fetch(None, url, params, api_id=API_NAMES.GET_USER_AUTH_STATUS, **kwargs) 20 | 21 | def unlink_user_athorization(self, id=None, **kwargs): 22 | if id is None: 23 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 24 | " \x1b[0m for codeId") 25 | url = "{}/{}".format(self.base_url, id) 26 | return self.delete(None, url, api_id=API_NAMES.UNLINK_USER, **kwargs) 27 | -------------------------------------------------------------------------------- /tests/mocks/create_qrcode_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | }, 7 | "data": { 8 | "codeId": "string", 9 | "url": "string", 10 | "deeplink": "string", 11 | "expiryDate": 0, 12 | "merchantPaymentId": "cb31bcc0-3b6c-46e0-9002-e5c4bb1e3d5f", 13 | "amount": { 14 | "amount": 1, 15 | "currency": "JPY" 16 | }, 17 | "orderDescription": "Example - Mune Cake shop", 18 | "orderItems": [ 19 | { 20 | "name": "string", 21 | "category": "pasteries", 22 | "quantity": 1, 23 | "productId": "67678", 24 | "unit_price": { 25 | "amount": 1, 26 | "currency": "JPY" 27 | } 28 | } 29 | ], 30 | "metadata": {}, 31 | "codeType": "ORDER_QR", 32 | "storeInfo": "string", 33 | "storeId": "string", 34 | "terminalId": "string", 35 | "requestedAt": 0, 36 | "redirectUrl": "http://foobar.com", 37 | "redirectType": "WEB_LINK", 38 | "isAuthorization": true, 39 | "authorizationExpiry": null 40 | } 41 | } -------------------------------------------------------------------------------- /paypayopa/resources/account.py: -------------------------------------------------------------------------------- 1 | from .base import Resource 2 | from ..constants.url import URL 3 | from ..constants.api_list import API_NAMES 4 | 5 | 6 | class Account(Resource): 7 | def __init__(self, client=None): 8 | super(Account, self).__init__(client) 9 | self.base_url = URL.ACCOUNT_LINK 10 | 11 | def create_qr_session(self, data={}, **kwargs): 12 | url = self.base_url 13 | if "scopes" not in data: 14 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 15 | "\x1b[0m for scopes") 16 | if "nonce" not in data: 17 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 18 | "\x1b[0m for nonce") 19 | if "redirectUrl" not in data: 20 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 21 | "\x1b[0m for redirectUrl") 22 | if "referenceId" not in data: 23 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 24 | "\x1b[0m for referenceId") 25 | return self.post_url(url, data, api_id=API_NAMES.CREATE_QR_SESSION, **kwargs) 26 | -------------------------------------------------------------------------------- /tests/test_check_cashback_reversal_details.py: -------------------------------------------------------------------------------- 1 | import json 2 | import responses 3 | 4 | from .helpers import mock_file, ClientTestCase 5 | 6 | 7 | class TestCheckCashbackReversalDetails(ClientTestCase): 8 | 9 | def setUp(self): 10 | super(TestCheckCashbackReversalDetails, self).setUp() 11 | self.base_url = '{}/cashback_reversal'.format(self.base_url) 12 | 13 | @responses.activate 14 | def test_check_cashback_reversal_detail(self): 15 | """ Test check cashback reversal detail """ 16 | cashback_reversal_id = 'fake_merchant_cashback_reversal_id' 17 | cashback_id = 'fake_merchant_cashback_id' 18 | result = mock_file('check_cashback_reversal_details_response') 19 | url = '{}/{}/{}'.format(self.base_url, cashback_reversal_id, cashback_id) 20 | responses.add(responses.GET, url, status=200, body=json.dumps(result), 21 | match_querystring=True) 22 | self.assertEqual(self.client.Cashback.check_cashback_reversal_detail(cashback_reversal_id, 23 | cashback_id), 24 | result) 25 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflows will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Pip Upload 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set version variable 18 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 19 | - name: Update version 20 | run: | 21 | sed -i -E "s/version=\\\"0\.0\.0\\\",/version=\\\"${{ env.RELEASE_VERSION }}\\\",/g" ./setup.py 22 | - name: Set up Python 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: '3.8' 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | pip install setuptools wheel twine 30 | - name: Build and publish 31 | env: 32 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 33 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 34 | run: | 35 | python setup.py sdist bdist_wheel 36 | twine upload dist/* 37 | -------------------------------------------------------------------------------- /paypayopa/resources/base.py: -------------------------------------------------------------------------------- 1 | class Resource(object): 2 | 3 | def __init__(self, client=None): 4 | self.client = client 5 | 6 | def all(self, data, **kwargs): 7 | return self.get_url(self.base_url, data, **kwargs) 8 | 9 | def fetch(self, id, url=None, data={}, **kwargs): 10 | if(url): 11 | self.url = url 12 | else: 13 | self.url = "{}/{}".format(self.base_url, id) 14 | return self.get_url(self.url, data, **kwargs) 15 | 16 | def get_url(self, url, data, **kwargs): 17 | return self.client.get(url, data, **kwargs) 18 | 19 | def patch_url(self, url, data, **kwargs): 20 | return self.client.patch(url, data, **kwargs) 21 | 22 | def post_url(self, url, data, **kwargs): 23 | return self.client.post(url, data, **kwargs) 24 | 25 | def put_url(self, url, data, **kwargs): 26 | return self.client.put(url, data, **kwargs) 27 | 28 | def delete_url(self, url, data, **kwargs): 29 | return self.client.delete(url, data, **kwargs) 30 | 31 | def delete(self, id, url=None, data={}, **kwargs): 32 | if(url): 33 | self.url = url 34 | else: 35 | self.url = "{}/{}/".format(self.base_url, id) 36 | return self.delete_url(self.url, data, **kwargs) 37 | -------------------------------------------------------------------------------- /.github/workflows/build-ci.yml: -------------------------------------------------------------------------------- 1 | name: Python SDK CI 2 | 3 | on: [push,pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | env: 9 | CC_TEST_REPORTER_ID: 84299815950f2a6aeca847b60f89cc438aa3fbd0b0f3f92a447a0febcd9140bf 10 | strategy: 11 | matrix: 12 | python-version: [3.5, 3.6, 3.7, 3.8, 3.9] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | 21 | 22 | - name: Install dependencies 23 | run: pip install -e ".[test]" 24 | 25 | - name: Install CI dependencies 26 | run: | 27 | pip install pytest 28 | pip install pytest-cov 29 | pip install coveralls 30 | 31 | - name: Before script 32 | run: | 33 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 34 | chmod +x ./cc-test-reporter 35 | ./cc-test-reporter before-build 36 | 37 | - name: test script 38 | run: pytest --cov=paypayopa/ --cov-report xml 39 | 40 | - name: Coveralls 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | run: coveralls --service=github 44 | 45 | - name: After build 46 | run: ./cc-test-reporter after-build --coverage-input-type coverage.py --exit-code 0 47 | -------------------------------------------------------------------------------- /paypayopa/constants/api_list.py: -------------------------------------------------------------------------------- 1 | class API_NAMES(object): 2 | CANCEL_PAYMENT = "v2_cancelPayment" 3 | CAPTURE_PAYMENT = "v2_captureAuthorizedOrder" 4 | CREATE_PAYMENT = "v2_createPayment" 5 | CREATE_QRCODE = "v2_createDynamicQRCode" 6 | DELETE_QRCODE = "v2_deleteDynamicQRCode" 7 | GET_PAYMENT = "v2_getPaymentDetail" 8 | GET_QR_PAYMENT = "v2_getQRPaymentDetails" 9 | GET_REFUND = "v2_getRefundDetails" 10 | REFUND_PAYMENT = "v2_createRefundPayment" 11 | REVERT_AUTHORIZE = "v2_revertAuthorizedOrder" 12 | PREAUTHORIZE_PAYMENT = "v2_createOrderAndAuthorize" 13 | CREATE_CONTINUOUS_PAYMENT = "v1_createSubscriptionPayment" 14 | CREATE_REQUEST_ORDER = "v1_createRequestOrder" 15 | GET_REQUEST_ORDER = "v1_getRequestOrder" 16 | CANCEL_REQUEST_ORDER = "v1_cancelRequestOrder" 17 | REFUND_REQUEST_ORDER = "v2_createRefundPayment" 18 | GET_SECURE_USER_PROFILE = "v2_getSecureUserProfile" 19 | CHECK_BALANCE = "v2_checkWalletBalance" 20 | GET_USER_AUTH_STATUS = "v2_userAuthStatus" 21 | UNLINK_USER = "v2_unlinkUser" 22 | CREATE_QR_SESSION = "v1_qrSession" 23 | CREATE_CASHBACK_REQUEST = "v2_createCashBackRequest" #GIVE_CASHBACK = 'v2_createRefundPayment' 24 | GET_CASHBACK_DETAILS = "v2_getCashbackDetails" #REVERSAL_CASHBACK = 'v2_reversalCashback' 25 | CREATE_REVERSE_CASHBACK_REQUEST = "v2_createReverseCashBackRequest" 26 | GET_REVERESED_CASHBACK_DETAILS = "v2_getReversedCashBackDetails" 27 | -------------------------------------------------------------------------------- /tests/mocks/create_continues_payment_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | }, 7 | "data": { 8 | "paymentId": "string", 9 | "status": "CREATED", 10 | "acceptedAt": 0, 11 | "refunds": { 12 | "data": [ 13 | { 14 | "status": "CREATED", 15 | "acceptedAt": 0, 16 | "merchantRefundId": "string", 17 | "paymentId": "string", 18 | "amount": {}, 19 | "requestedAt": 0, 20 | "reason": "string" 21 | } 22 | ] 23 | }, 24 | "merchantPaymentId": "string", 25 | "userAuthorizationId": "string", 26 | "amount": { 27 | "amount": 0, 28 | "currency": "JPY" 29 | }, 30 | "requestedAt": 0, 31 | "storeId": "string", 32 | "terminalId": "string", 33 | "orderReceiptNumber": "string", 34 | "orderDescription": "string", 35 | "orderItems": [ 36 | { 37 | "name": "string", 38 | "category": "string", 39 | "quantity": 1, 40 | "productId": "string", 41 | "unitPrice": { 42 | "amount": 0, 43 | "currency": "JPY" 44 | } 45 | } 46 | ], 47 | "metadata": {} 48 | } 49 | } -------------------------------------------------------------------------------- /tests/mocks/create_payment_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | }, 7 | "data": { 8 | "paymentId": "string", 9 | "status": "CREATED", 10 | "acceptedAt": 0, 11 | "refunds": { 12 | "data": [ 13 | { 14 | "status": "CREATED", 15 | "acceptedAt": 0, 16 | "merchantRefundId": "string", 17 | "paymentId": "string", 18 | "amount": { 19 | "amount": 0, 20 | "currency": "JPY" 21 | }, 22 | "requestedAt": 0, 23 | "reason": "string" 24 | } 25 | ] 26 | }, 27 | "merchantPaymentId": "string", 28 | "userAuthorizationId": "string", 29 | "amount": { 30 | "amount": 0, 31 | "currency": "JPY" 32 | }, 33 | "requestedAt": 0, 34 | "storeId": "string", 35 | "terminalId": "string", 36 | "orderReceiptNumber": "string", 37 | "orderDescription": "string", 38 | "orderItems": [ 39 | { 40 | "name": "string", 41 | "category": "string", 42 | "quantity": 1, 43 | "productId": "string", 44 | "unitPrice": {} 45 | } 46 | ], 47 | "metadata": {} 48 | } 49 | } -------------------------------------------------------------------------------- /tests/mocks/get_request_order_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | }, 7 | "data": { 8 | "paymentId": "string", 9 | "status": "CREATED", 10 | "acceptedAt": 0, 11 | "refunds": { 12 | "data": [ 13 | { 14 | "status": "CREATED", 15 | "acceptedAt": 0, 16 | "merchantRefundId": "string", 17 | "paymentId": "string", 18 | "amount": { 19 | "amount": 0, 20 | "currency": "JPY" 21 | }, 22 | "requestedAt": 0, 23 | "reason": "string" 24 | } 25 | ] 26 | }, 27 | "merchantPaymentId": "string", 28 | "userAuthorizationId": "string", 29 | "amount": { 30 | "amount": 0, 31 | "currency": "JPY" 32 | }, 33 | "requestedAt": 0, 34 | "expiryDate": null, 35 | "storeId": "string", 36 | "terminalId": "string", 37 | "orderReceiptNumber": "string", 38 | "orderDescription": "string", 39 | "orderItems": [ 40 | { 41 | "name": "string", 42 | "category": "string", 43 | "quantity": 1, 44 | "productId": "string", 45 | "unitPrice": { 46 | "amount": 0, 47 | "currency": "JPY" 48 | } 49 | } 50 | ], 51 | "metadata": {} 52 | } 53 | } -------------------------------------------------------------------------------- /paypayopa/resources/preauth.py: -------------------------------------------------------------------------------- 1 | from .base import Resource 2 | from ..constants.url import URL 3 | from ..constants.api_list import API_NAMES 4 | import datetime 5 | 6 | 7 | class Preauth(Resource): 8 | def __init__(self, client=None): 9 | super(Preauth, self).__init__(client) 10 | self.base_url = URL.PAYMENT 11 | 12 | def pre_authorize_create(self, data={}, **kwargs): 13 | url = "{}/{}".format(self.base_url, 'preauthorize') 14 | if "requestedAt" not in data: 15 | data['requestedAt'] = int(datetime.datetime.now().timestamp()) 16 | if "merchantPaymentId" not in data: 17 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 18 | "\x1b[0m for merchantPaymentId") 19 | if "userAuthorizationId" not in data: 20 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 21 | "\x1b[0m for merchantPaymentId") 22 | if "amount" not in data["amount"]: 23 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 24 | "\x1b[0m for amount") 25 | if "expiresAt" in data and type(data["expiresAt"]) != int: 26 | raise ValueError("\x1b[31m expiresAt should be of " 27 | "type integer (EPOCH) \x1b[0m") 28 | if type(data["amount"]["amount"]) != int: 29 | raise ValueError("\x1b[31m Amount should be of type integer" 30 | " \x1b[0m") 31 | if "currency" not in data["amount"]: 32 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 33 | " \x1b[0m for currency") 34 | return self.post_url(url, data, api_id=API_NAMES.PREAUTHORIZE_PAYMENT, **kwargs) 35 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | from os import path 4 | 5 | def fetch_long_description(): 6 | """Loads the `long_description` from README.md.""" 7 | this_directory = path.abspath(path.dirname(__file__)) 8 | try: 9 | with open(path.join(this_directory, 'README.md'), encoding='utf-8') as readme_file: 10 | return readme_file.read() 11 | except FileNotFoundError: 12 | return 'See https://github.com/paypay/paypayopa-sdk-python' 13 | 14 | setup( 15 | name="paypayopa", 16 | version="0.0.0", 17 | description="PayPay OPA SDK", 18 | long_description=fetch_long_description(), 19 | long_description_content_type='text/markdown', 20 | author="Team PayPay", 21 | author_email="opensource@paypay-corp.co.jp", 22 | license="Apache 2.0", 23 | project_urls={ 24 | 'Documentation': 'https://github.com/paypay/paypayopa-sdk-python/README.md', 25 | 'Source': 'https://github.com/paypay/paypayopa-sdk-python', 26 | }, 27 | install_requires=["requests", "pyjwt"], 28 | extras_require={ 29 | 'test': ['responses'], 30 | }, 31 | include_package_data=True, 32 | package_dir={'paypayopa': 'paypayopa', 33 | 'paypayopa.resources': 'paypayopa/resources', 34 | 'paypayopa.constants': 'paypayopa/constants'}, 35 | packages=['paypayopa', 'paypayopa.resources', 'paypayopa.constants'], 36 | keywords='paypay payment gateway japan', 37 | classifiers=[ 38 | "Intended Audience :: Developers", 39 | "License :: OSI Approved :: Apache Software License", 40 | 41 | # List of supported Python versions 42 | # Make sure that this is reflected in .travis.yml as well 43 | 'Programming Language :: Python :: 3.5', 44 | 'Programming Language :: Python :: 3.6', 45 | 'Programming Language :: Python :: 3.7', 46 | 'Programming Language :: Python :: 3.8', 47 | 48 | "Topic :: Software Development :: Libraries :: Python Modules", 49 | ] 50 | ) 51 | -------------------------------------------------------------------------------- /tests/mocks/preauth_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "resultInfo": { 3 | "code": "string", 4 | "message": "string", 5 | "codeId": "string" 6 | }, 7 | "data": { 8 | "paymentId": "string", 9 | "status": "AUTHORIZED", 10 | "acceptedAt": 0, 11 | "refunds": { 12 | "data": [ 13 | { 14 | "status": "CREATED", 15 | "acceptedAt": 0, 16 | "merchantRefundId": "string", 17 | "paymentId": "string", 18 | "amount": { 19 | "amount": 0, 20 | "currency": "JPY" 21 | }, 22 | "requestedAt": 0, 23 | "reason": "string" 24 | } 25 | ] 26 | }, 27 | "captures": { 28 | "data": [ 29 | { 30 | "acceptedAt": 0, 31 | "merchantCaptureId": "string", 32 | "amount": { 33 | "amount": 0, 34 | "currency": "JPY" 35 | }, 36 | "orderDescription": "string", 37 | "requestedAt": 0, 38 | "expiresAt": null, 39 | "status": "USER_REQUESTED" 40 | } 41 | ] 42 | }, 43 | "revert": { 44 | "acceptedAt": 0, 45 | "merchantRevertId": "string", 46 | "requestedAt": 0, 47 | "reason": "string" 48 | }, 49 | "merchantPaymentId": "string", 50 | "userAuthorizationId": "string", 51 | "amount": { 52 | "amount": 0, 53 | "currency": "JPY" 54 | }, 55 | "requestedAt": 0, 56 | "expiresAt": null, 57 | "storeId": "string", 58 | "terminalId": "string", 59 | "orderReceiptNumber": "string", 60 | "orderDescription": "string", 61 | "orderItems": [ 62 | { 63 | "name": "string", 64 | "category": "string", 65 | "quantity": 1, 66 | "productId": "string", 67 | "unitPrice": { 68 | "amount": 0, 69 | "currency": "JPY" 70 | } 71 | } 72 | ], 73 | "metadata": {} 74 | } 75 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # .DS_Store banished! 30 | .DS_Store 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | 137 | # pytype static type analyzer 138 | .pytype/ 139 | 140 | # Cython debug symbols 141 | cython_debug/ 142 | 143 | # idea editor file 144 | .idea/ 145 | 146 | #example.py file 147 | example.py 148 | sdk_dev.py -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | 7 | [MESSAGES CONTROL] 8 | 9 | # Enable the message, report, category or checker with the given id(s). You can 10 | # either give multiple identifier separated by comma (,) or put this option 11 | # multiple time. See also the "--disable" option for examples. 12 | enable=indexing-exception,old-raise-syntax 13 | # Disable the message, report, category or checker with the given id(s). You 14 | # can either give multiple identifiers separated by comma (,) or put this 15 | # option multiple times (only on the command line, not in the configuration 16 | # file where it should appear only once).You can also use "--disable=all" to 17 | # disable everything first and then reenable specific checks. For example, if 18 | # you want to run only the similarities checker, you can use "--disable=all 19 | # --enable=similarities". If you want to run only the classes checker, but have 20 | # no Warning level messages displayed, use"--disable=all --enable=classes 21 | # --disable=W" 22 | disable=design,similarities,no-self-use,attribute-defined-outside-init,locally-disabled,star-args,pointless-except,bad-option-value,global-statement,fixme,suppressed-message,useless-suppression,locally-enabled,no-member,no-name-in-module,import-error,unsubscriptable-object,unbalanced-tuple-unpacking,undefined-variable,not-context-manager 23 | 24 | 25 | [REPORTS] 26 | 27 | # Set the output format. Available formats are text, parseable, colorized, msvs 28 | # (visual studio) and html. You can also give a reporter class, eg 29 | # mypackage.mymodule.MyReporterClass. 30 | output-format=text 31 | 32 | # Put messages in a separate file for each module / package specified on the 33 | # command line instead of printing them on stdout. Reports (if any) will be 34 | # written in a file name "pylint_global.[txt|html]". This option is deprecated 35 | # and it will be removed in Pylint 2.0. 36 | files-output=no 37 | 38 | # Tells whether to display a full report or only the messages 39 | reports=yes 40 | 41 | # Python expression which should return a note less than 10 (10 is the highest 42 | # note). You have access to the variables errors warning, statement which 43 | # respectively contain the number of errors / warnings messages and the total 44 | # number of statements analyzed. This is used by the global evaluation report 45 | # (RP0004). 46 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 47 | 48 | # Template used to display messages. This is a python new-style format string 49 | # used to format the message information. See doc for all details 50 | #msg-template= 51 | 52 | 53 | [LOGGING] 54 | 55 | # Logging modules to check that the string format arguments are in logging 56 | # function parameter format 57 | logging-modules=logging 58 | 59 | 60 | [EXCEPTIONS] 61 | 62 | # Exceptions that will emit a warning when being caught. Defaults to 63 | # "Exception" 64 | overgeneral-exceptions=Exception -------------------------------------------------------------------------------- /paypayopa/resources/code.py: -------------------------------------------------------------------------------- 1 | from ..resources.base import Resource 2 | from ..constants.url import URL 3 | from ..constants.api_list import API_NAMES 4 | import datetime 5 | 6 | 7 | class Code(Resource): 8 | def __init__(self, client=None): 9 | super(Code, self).__init__(client) 10 | Code.base_url = URL.CODE 11 | 12 | def create_qr_code(self, data=None, **kwargs): 13 | if data is None: 14 | data = {} 15 | url = self.base_url 16 | if "requestedAt" not in data: 17 | data["requestedAt"] = int(datetime.datetime.now().timestamp()) 18 | if "merchantPaymentId" not in data: 19 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 20 | "\x1b[0m for merchantPaymentId") 21 | if "amount" not in data["amount"]: 22 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 23 | "\x1b[0m for amount") 24 | if type(data["amount"]["amount"]) != int: 25 | raise ValueError("\x1b[31m Amount should be of type integer" 26 | " \x1b[0m") 27 | if "currency" not in data["amount"]: 28 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 29 | " \x1b[0m for currency") 30 | if "orderItems" in data: 31 | for item in data["orderItems"]: 32 | if "name" not in item: 33 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 34 | " \x1b[0m for orderItem Name") 35 | if "quantity" not in item: 36 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 37 | " \x1b[0m for orderItem quantity") 38 | if "amount" not in item["unitPrice"]: 39 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 40 | " \x1b[0m for orderItem.amount.unitPrice") 41 | if "currency" not in item["unitPrice"]: 42 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 43 | " \x1b[0m for orderItem.amount.currency") 44 | return self.post_url(url, data, api_id=API_NAMES.CREATE_QRCODE, **kwargs) 45 | 46 | def get_payment_details(self, id, **kwargs): 47 | url = "{}/{}/{}".format(self.base_url, 'payments', id) 48 | if id is None: 49 | raise ValueError("\x1b[31m MISSING REQUESTS PARAMS" 50 | " \x1b[0m for merchantPaymentId") 51 | return self.fetch(None, url, None, api_id=API_NAMES.GET_QR_PAYMENT, **kwargs) 52 | 53 | def delete_qr_code(self, id=None, **kwargs): 54 | if id is None: 55 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 56 | " \x1b[0m for codeId") 57 | url = "{}/{}".format(self.base_url, id) 58 | return self.delete(None, url, api_id=API_NAMES.DELETE_QRCODE, **kwargs) 59 | -------------------------------------------------------------------------------- /paypayopa/resources/pending.py: -------------------------------------------------------------------------------- 1 | from .base import Resource 2 | from ..constants.url import URL 3 | from ..constants.api_list import API_NAMES 4 | import datetime 5 | 6 | 7 | class Pending(Resource): 8 | def __init__(self, client=None): 9 | super(Pending, self).__init__(client) 10 | self.base_url = URL.PENDING_PAYMENT 11 | 12 | def create_pending_payment(self, data={}, **kwargs): 13 | url = self.base_url 14 | if "requestedAt" not in data: 15 | data['requestedAt'] = int(datetime.datetime.now().timestamp()) 16 | if "merchantPaymentId" not in data: 17 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 18 | "\x1b[0m for merchantPaymentId") 19 | if "userAuthorizationId" not in data: 20 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 21 | "\x1b[0m for userAuthorizationId") 22 | if "amount" not in data["amount"]: 23 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 24 | "\x1b[0m for amount") 25 | if type(data["amount"]["amount"]) != int: 26 | raise ValueError("\x1b[31m Amount should be of type integer" 27 | " \x1b[0m") 28 | if "currency" not in data["amount"]: 29 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 30 | " \x1b[0m for currency") 31 | return self.post_url(url, data, api_id=API_NAMES.CREATE_REQUEST_ORDER, **kwargs) 32 | 33 | def get_payment_details(self, id, **kwargs): 34 | url = "{}/{}".format(self.base_url, id) 35 | if id is None: 36 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 37 | " \x1b[0m for merchantPaymentId") 38 | return self.fetch(None, url, None, api_id=API_NAMES.GET_REQUEST_ORDER, **kwargs) 39 | 40 | def cancel_payment(self, id, **kwargs): 41 | url = "{}/{}".format(self.base_url, id) 42 | if id is None: 43 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 44 | " \x1b[0m for merchantPaymentId") 45 | return self.delete(None, url, None, api_id=API_NAMES.CANCEL_REQUEST_ORDER, **kwargs) 46 | 47 | def refund_payment(self, data={}, **kwargs): 48 | url = "{}".format(URL.REFUNDS) 49 | if "merchantRefundId" not in data: 50 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 51 | "\x1b[0m for merchantRefundId") 52 | if "requestedAt" not in data: 53 | data['requestedAt'] = int(datetime.datetime.now().timestamp()) 54 | if "paymentId" not in data: 55 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 56 | "\x1b[0m for paymentId") 57 | if "amount" not in data["amount"]: 58 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 59 | "\x1b[0m for amount") 60 | if type(data["amount"]["amount"]) != int: 61 | raise ValueError("\x1b[31m Amount should be of type integer" 62 | " \x1b[0m") 63 | if "currency" not in data["amount"]: 64 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 65 | " \x1b[0m for currency") 66 | return self.post_url(url, data, api_id=API_NAMES.REFUND_REQUEST_ORDER, **kwargs) 67 | 68 | def refund_details(self, id=None, **kwargs): 69 | if id is None: 70 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 71 | " \x1b[0m for merchantRefundId") 72 | url = "{}/{}".format('/v2/refunds', id) 73 | return self.fetch(None, url, None, api_id=API_NAMES.GET_REFUND, **kwargs) 74 | -------------------------------------------------------------------------------- /paypayopa/resources/cashback.py: -------------------------------------------------------------------------------- 1 | from ..resources.base import Resource 2 | from ..constants.url import URL 3 | from ..constants.api_list import API_NAMES 4 | 5 | 6 | class Cashback(Resource): 7 | def __init__(self, client=None): 8 | super(Cashback, self).__init__(client) 9 | self.give_base_url = URL.GIVE_CASHBACK 10 | self.reverse_base_url = URL.REVERSAL_CASHBACK 11 | 12 | def give_cashback(self, data=None, **kwargs): 13 | if data is None: 14 | data = {} 15 | url = "{}".format(self.give_base_url) 16 | if "merchantCashbackId" not in data: 17 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 18 | "\x1b[0m for merchantCashbackId") 19 | if "userAuthorizationId" not in data: 20 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 21 | "\x1b[0m for userAuthorizationId") 22 | if "amount" not in data["amount"]: 23 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 24 | "\x1b[0m for amount amount") 25 | if "currency" not in data["amount"]: 26 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 27 | "\x1b[0m for amount currency") 28 | if "requestedAt" not in data: 29 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 30 | "\x1b[0m for requestedAt") 31 | if "walletType" not in data: 32 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 33 | "\x1b[0m for walletType") 34 | return self.post_url(url, data, api_id=API_NAMES.CREATE_CASHBACK_REQUEST, **kwargs) 35 | 36 | def check_cashback_detail(self, merchant_cashback_id, **kwargs): 37 | if merchant_cashback_id is None: 38 | raise ValueError("\x1b[31m MISSING merchantCashbackId") 39 | url = "{}/{}".format(self.give_base_url, merchant_cashback_id) 40 | return self.get_url(url=url, data={}, api_id=API_NAMES.GET_CASHBACK_DETAILS, **kwargs) 41 | 42 | def reverse_cashback(self, data=None, **kwargs): 43 | if data is None: 44 | data = {} 45 | url = "{}".format(self.reverse_base_url) 46 | if "merchantCashbackReversalId" not in data: 47 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 48 | "\x1b[0m for merchantCashbackReversalId") 49 | if "merchantCashbackId" not in data: 50 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 51 | "\x1b[0m for merchantCashbackId") 52 | if "amount" not in data["amount"]: 53 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 54 | "\x1b[0m for amount amount") 55 | if "currency" not in data["amount"]: 56 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 57 | "\x1b[0m for amount currency") 58 | if "requestedAt" not in data: 59 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 60 | "\x1b[0m for requestedAt") 61 | return self.post_url(url, data, api_id=API_NAMES.CREATE_REVERSE_CASHBACK_REQUEST, **kwargs) 62 | 63 | def check_cashback_reversal_detail(self, merchant_cashback_reversal_id=None, merchant_cashback_id=None, **kwargs): 64 | url = "{}/{}/{}".format(self.reverse_base_url, merchant_cashback_reversal_id, merchant_cashback_id) 65 | if merchant_cashback_reversal_id is None: 66 | raise ValueError("\x1b[31m MISSING merchantCashbackReversalId") 67 | if merchant_cashback_id is None: 68 | raise ValueError("\x1b[31m MISSING merchantCashbackId") 69 | return self.get_url(url=url, data={}, api_id=API_NAMES.GET_REVERESED_CASHBACK_DETAILS, **kwargs) 70 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any build files, examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of one other developers, all automated checks pass, Contributor's License Agreement signed, or if you 17 | do not have permission to do that, you may request the repo owner to merge it for you. 18 | 19 | # Contributor Covenant Code of Conduct 20 | 21 | ## Our Pledge 22 | 23 | We as members, contributors, and leaders pledge to make participation in our 24 | community a harassment-free experience for everyone, regardless of age, body 25 | size, visible or invisible disability, ethnicity, sex characteristics, gender 26 | identity and expression, level of experience, education, socio-economic status, 27 | nationality, personal appearance, race, religion, or sexual identity 28 | and orientation. 29 | 30 | We pledge to act and interact in ways that contribute to an open, welcoming, 31 | diverse, inclusive, and healthy community. 32 | 33 | ## Our Standards 34 | 35 | Examples of behavior that contributes to a positive environment for our 36 | community include: 37 | 38 | * Demonstrating empathy and kindness toward other people 39 | * Being respectful of differing opinions, viewpoints, and experiences 40 | * Giving and gracefully accepting constructive feedback 41 | * Accepting responsibility and apologizing to those affected by our mistakes, 42 | and learning from the experience 43 | * Focusing on what is best not just for us as individuals, but for the 44 | overall community 45 | 46 | Examples of unacceptable behavior include: 47 | 48 | * The use of sexualized language or imagery, and sexual attention or 49 | advances of any kind 50 | * Trolling, insulting or derogatory comments, and personal or political attacks 51 | * Public or private harassment 52 | * Publishing others' private information, such as a physical or email 53 | address, without their explicit permission 54 | * Other conduct which could reasonably be considered inappropriate in a 55 | professional setting 56 | 57 | ## Enforcement Responsibilities 58 | 59 | Community leaders are responsible for clarifying and enforcing our standards of 60 | acceptable behavior and will take appropriate and fair corrective action in 61 | response to any behavior that they deem inappropriate, threatening, offensive, 62 | or harmful. 63 | 64 | Community leaders have the right and responsibility to remove, edit, or reject 65 | comments, commits, code, wiki edits, issues, and other contributions that are 66 | not aligned to this Code of Conduct, and will communicate reasons for moderation 67 | decisions when appropriate. 68 | 69 | ## Scope 70 | 71 | This Code of Conduct applies within all community spaces, and also applies when 72 | an individual is officially representing the community in public spaces. 73 | Examples of representing our community include using an official e-mail address, 74 | posting via an official social media account, or acting as an appointed 75 | representative at an online or offline event. 76 | 77 | ## Enforcement 78 | 79 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 80 | reported to the community leaders responsible for enforcement at 81 | [INSERT CONTACT METHOD]. 82 | All complaints will be reviewed and investigated promptly and fairly. 83 | 84 | All community leaders are obligated to respect the privacy and security of the 85 | reporter of any incident. 86 | 87 | ## Enforcement Guidelines 88 | 89 | Community leaders will follow these Community Impact Guidelines in determining 90 | the consequences for any action they deem in violation of this Code of Conduct: 91 | 92 | ### 1. Correction 93 | 94 | **Community Impact**: Use of inappropriate language or other behavior deemed 95 | unprofessional or unwelcome in the community. 96 | 97 | **Consequence**: A private, written warning from community leaders, providing 98 | clarity around the nature of the violation and an explanation of why the 99 | behavior was inappropriate. A public apology may be requested. 100 | 101 | ### 2. Warning 102 | 103 | **Community Impact**: A violation through a single incident or series 104 | of actions. 105 | 106 | **Consequence**: A warning with consequences for continued behavior. No 107 | interaction with the people involved, including unsolicited interaction with 108 | those enforcing the Code of Conduct, for a specified period of time. This 109 | includes avoiding interactions in community spaces as well as external channels 110 | like social media. Violating these terms may lead to a temporary or 111 | permanent ban. 112 | 113 | ### 3. Temporary Ban 114 | 115 | **Community Impact**: A serious violation of community standards, including 116 | sustained inappropriate behavior. 117 | 118 | **Consequence**: A temporary ban from any sort of interaction or public 119 | communication with the community for a specified period of time. No public or 120 | private interaction with the people involved, including unsolicited interaction 121 | with those enforcing the Code of Conduct, is allowed during this period. 122 | Violating these terms may lead to a permanent ban. 123 | 124 | ### 4. Permanent Ban 125 | 126 | **Community Impact**: Demonstrating a pattern of violation of community 127 | standards, including sustained inappropriate behavior, harassment of an 128 | individual, or aggression toward or disparagement of classes of individuals. 129 | 130 | **Consequence**: A permanent ban from any sort of public interaction within 131 | the community. 132 | 133 | ## Attribution 134 | 135 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 136 | version 2.0, available at 137 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 138 | 139 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 140 | enforcement ladder](https://github.com/mozilla/diversity). 141 | 142 | [homepage]: https://www.contributor-covenant.org 143 | 144 | For answers to common questions about this code of conduct, see the FAQ at 145 | https://www.contributor-covenant.org/faq. Translations are available at 146 | https://www.contributor-covenant.org/translations. 147 | -------------------------------------------------------------------------------- /paypayopa/resources/payment.py: -------------------------------------------------------------------------------- 1 | from .base import Resource 2 | from ..constants.url import URL 3 | from ..constants.api_list import API_NAMES 4 | import datetime 5 | 6 | 7 | class Payment(Resource): 8 | def __init__(self, client=None): 9 | super(Payment, self).__init__(client) 10 | self.base_url = URL.PAYMENT 11 | 12 | def create(self, data={}, **kwargs): 13 | url = self.base_url 14 | if "requestedAt" not in data: 15 | data['requestedAt'] = int(datetime.datetime.now().timestamp()) 16 | if "merchantPaymentId" not in data: 17 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 18 | "\x1b[0m for merchantPaymentId") 19 | if "amount" not in data["amount"]: 20 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 21 | "\x1b[0m for amount") 22 | if type(data["amount"]["amount"]) != int: 23 | raise ValueError("\x1b[31m Amount should be of type integer" 24 | " \x1b[0m") 25 | if "currency" not in data["amount"]: 26 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 27 | " \x1b[0m for currency") 28 | return self.post_url(url, data, api_id=API_NAMES.CREATE_PAYMENT, **kwargs) 29 | 30 | def get_payment_details(self, id, **kwargs): 31 | url = "{}/{}".format(self.base_url, id) 32 | if id is None: 33 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 34 | " \x1b[0m for merchantPaymentId") 35 | return self.fetch(None, url, None, api_id=API_NAMES.GET_PAYMENT, **kwargs) 36 | 37 | def cancel_payment(self, id, **kwargs): 38 | url = "{}/{}".format(self.base_url, id) 39 | if id is None: 40 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 41 | " \x1b[0m for merchantPaymentId") 42 | return self.delete(None, url, None, api_id=API_NAMES.CANCEL_PAYMENT, **kwargs) 43 | 44 | def refund_payment(self, data={}, **kwargs): 45 | url = "{}/".format('/v2/refunds') 46 | if "requestedAt" not in data: 47 | data['requestedAt'] = int(datetime.datetime.now().timestamp()) 48 | if "merchantRefundId" not in data: 49 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 50 | "\x1b[0m for merchantRefundId") 51 | if "paymentId" not in data: 52 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 53 | "\x1b[0m for paymentId") 54 | if "amount" not in data["amount"]: 55 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 56 | "\x1b[0m for amount") 57 | if type(data["amount"]["amount"]) != int: 58 | raise ValueError("\x1b[31m Amount should be of type integer" 59 | " \x1b[0m") 60 | if "currency" not in data["amount"]: 61 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 62 | " \x1b[0m for currency") 63 | return self.post_url(url, data, api_id=API_NAMES.REFUND_PAYMENT, **kwargs) 64 | 65 | def refund_details(self, id=None, **kwargs): 66 | if id is None: 67 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 68 | " \x1b[0m for merchantRefundId") 69 | url = "{}/{}".format('/v2/refunds', id) 70 | return self.fetch(None, url, None, api_id=API_NAMES.GET_REFUND, **kwargs) 71 | 72 | def capture_payment(self, data=None, **kwargs): 73 | if data is None: 74 | data = {} 75 | url = "{}/{}".format('/v2/payments', 'capture') 76 | if "requestedAt" not in data: 77 | data['requestedAt'] = int(datetime.datetime.now().timestamp()) 78 | if "merchantPaymentId" not in data: 79 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 80 | " \x1b[0m for merchantPaymentId") 81 | if "merchantCaptureId" not in data: 82 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 83 | " \x1b[0m for merchantPaymentId") 84 | if "orderDescription" not in data: 85 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 86 | "\x1b[0m for merchantPaymentId") 87 | if "amount" not in data["amount"]: 88 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 89 | "\x1b[0m for amount") 90 | if type(data["amount"]["amount"]) != int: 91 | raise ValueError("\x1b[31m Amount should be of type integer" 92 | " \x1b[0m") 93 | if "currency" not in data["amount"]: 94 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 95 | "\x1b[0m for currency") 96 | return self.post_url(url, data, api_id=API_NAMES.CAPTURE_PAYMENT, **kwargs) 97 | 98 | def create_continuous_payment(self, data=None, **kwargs): 99 | if data is None: 100 | data = {} 101 | url = "{}/{}".format('/v1/subscription', 'payments') 102 | if "requestedAt" not in data: 103 | data['requestedAt'] = int(datetime.datetime.now().timestamp()) 104 | if "merchantPaymentId" not in data: 105 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 106 | " \x1b[0m for merchantPaymentId") 107 | if "userAuthorizationId" not in data: 108 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 109 | " \x1b[0m for userAuthorizationId") 110 | if "amount" not in data["amount"]: 111 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 112 | "\x1b[0m for amount") 113 | if type(data["amount"]["amount"]) != int: 114 | raise ValueError("\x1b[31m Amount should be of type integer" 115 | " \x1b[0m") 116 | if "currency" not in data["amount"]: 117 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS " 118 | "\x1b[0m for currency") 119 | return self.post_url(url, data, api_id=API_NAMES.CREATE_CONTINUOUS_PAYMENT, **kwargs) 120 | 121 | def revert_payment(self, data=None, **kwargs): 122 | if data is None: 123 | data = {} 124 | url = "{}/{}/{}".format('/v2/payments', 'preauthorize', 'revert') 125 | if "requestedAt" not in data: 126 | data['requestedAt'] = int(datetime.datetime.now().timestamp()) 127 | if "merchantRevertId" not in data: 128 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 129 | " \x1b[0m for merchantPaymentId") 130 | if "paymentId" not in data: 131 | raise ValueError("\x1b[31m MISSING REQUEST PARAMS" 132 | " \x1b[0m for merchantPaymentId") 133 | return self.post_url(url, data, api_id=API_NAMES.REVERT_AUTHORIZE, **kwargs) 134 | -------------------------------------------------------------------------------- /paypayopa/client.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import hashlib 3 | import base64 4 | import json 5 | 6 | import jwt 7 | import requests 8 | import uuid 9 | import datetime 10 | 11 | import pkg_resources 12 | from pkg_resources import DistributionNotFound 13 | 14 | from types import ModuleType 15 | from .constants import URL, HTTP_STATUS_CODE 16 | 17 | from . import resources 18 | 19 | 20 | def capitalize_camel_case(string): 21 | return "".join(map(str.capitalize, string.split('_'))) 22 | 23 | 24 | # Create a dict of resource classes 25 | RESOURCE_CLASSES = {} 26 | for name, module in resources.__dict__.items(): 27 | if isinstance(module, ModuleType) and \ 28 | capitalize_camel_case(name) in module.__dict__: 29 | RESOURCE_CLASSES[capitalize_camel_case(name)] = module.__dict__[capitalize_camel_case(name)] 30 | 31 | 32 | class Client: 33 | """PayPay client class""" 34 | DEFAULTS = { 35 | 'sandbox_base_url': URL.SANDBOX_BASE_URL, 36 | 'production_base_url': URL.PRODUCTION_BASE_URL, 37 | 'perf_mode_base_url': URL.PERF_BASE_URL 38 | } 39 | 40 | def __init__(self, 41 | session=None, 42 | auth=None, 43 | production_mode=False, 44 | **options): 45 | """ 46 | Initialize a Client object with session, 47 | optional auth handler, and options 48 | """ 49 | self.session = session or requests.Session() 50 | self.auth = auth 51 | self.production_mode = production_mode 52 | self.perf_mode = options.get('perf_mode') 53 | self.assume_merchant = "" 54 | 55 | self.base_url = self._set_base_url(**options) 56 | # intializes each resource 57 | # injecting this client object into the constructor 58 | for name, Klass in RESOURCE_CLASSES.items(): 59 | setattr(self, name, Klass(self)) 60 | 61 | def get_version(self): 62 | version = "" 63 | try: 64 | version = pkg_resources.require("paypayopa")[0].version 65 | except DistributionNotFound: 66 | print('DistributionNotFound') 67 | return version 68 | 69 | 70 | def _set_base_url(self, **options): 71 | if self.production_mode is False: 72 | base_url = self.DEFAULTS['sandbox_base_url'] 73 | if self.production_mode is True: 74 | base_url = self.DEFAULTS['production_base_url'] 75 | if self.perf_mode is True: 76 | base_url = self.DEFAULTS['perf_mode_base_url'] 77 | if 'base_url' in options: 78 | base_url = options['base_url'] 79 | del (options['base_url']) 80 | return base_url 81 | 82 | def set_assume_merchant(self, merchant): 83 | if (merchant): 84 | self.assume_merchant = merchant 85 | 86 | def encode_jwt(self, secret=str, scope="direct_debit", 87 | redirect_url=None, 88 | reference_id=str(uuid.uuid4())[:8], 89 | device_id="", phone_number=""): 90 | jwt_data = { 91 | "iss": 'merchant', 92 | "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=5), 93 | "scope": scope, 94 | "nonce": str(uuid.uuid4())[:8], 95 | "redirectUrl": redirect_url, 96 | "referenceId": reference_id, 97 | "deviceId": device_id, 98 | "phoneNumber": phone_number 99 | } 100 | encoded = jwt.encode(jwt_data, 101 | base64.b64decode(secret), 102 | algorithm='HS256') 103 | return encoded 104 | 105 | def decode_jwt(self, secret, token): 106 | try: 107 | ca = jwt.decode(token, base64.b64decode(secret), algorithm='HS256') 108 | return ca.get('userAuthorizationId'), ca.get('referenceId') 109 | except Exception as e: 110 | print("JWT Signature verification failed: ", e) 111 | 112 | def auth_header(self, api_key, api_secret, 113 | method, resource, content_type="empty", 114 | request_body=None): 115 | auth_type = 'hmac OPA-Auth' 116 | nonce = str(uuid.uuid4())[:8] 117 | timestamp = str(int(datetime.datetime.now().timestamp())) 118 | body_hash = "empty" 119 | if request_body is not None: 120 | hashed_body = hashlib.md5() 121 | hashed_body.update(content_type.encode("utf-8")) 122 | hashed_body.update(request_body.encode("utf-8")) 123 | body_hash = base64.b64encode(hashed_body.digest()) 124 | if body_hash != "empty": 125 | body_hash = body_hash.decode() 126 | signature_list = "\n".join([resource, 127 | method, 128 | nonce, 129 | timestamp, 130 | content_type, 131 | body_hash]) 132 | hmac_data = hmac.new(api_secret.encode("utf-8"), 133 | signature_list.encode("utf-8"), 134 | digestmod=hashlib.sha256) 135 | hmac_base64 = base64.b64encode(hmac_data.digest()) 136 | header_list = [api_key, 137 | hmac_base64.decode("utf-8"), 138 | nonce, timestamp, 139 | body_hash] 140 | header = ":".join(header_list) 141 | return "{}:{}".format(auth_type, header) 142 | 143 | def request(self, method, path, auth_header, **options): 144 | """ 145 | Dispatches a request to the PayPay HTTP API 146 | """ 147 | api_name = options['api_id'] 148 | del options['api_id'] 149 | url = "{}{}".format(self.base_url, path) 150 | response = getattr(self.session, method)(url, headers={ 151 | 'Authorization': auth_header, 152 | 'Content-Type': 'application/json;charset=UTF-8', 153 | 'X-ASSUME-MERCHANT': self.assume_merchant 154 | }, **options) 155 | if ((response.status_code >= HTTP_STATUS_CODE.OK) and 156 | (response.status_code < HTTP_STATUS_CODE.REDIRECT)): 157 | return response.json() 158 | else: 159 | json_response = response.json() 160 | resolve_url = "{}?api_name={}&code={}&code_id={}".format( 161 | URL.RESOLVE, 162 | api_name, 163 | json_response['resultInfo']['code'], 164 | json_response['resultInfo']['codeId']) 165 | print("This link should help you to troubleshoot the error: " + resolve_url) 166 | return json_response 167 | 168 | def get(self, path, params, **options): 169 | """ 170 | Parses GET request options and dispatches a request 171 | """ 172 | method = "GET" 173 | data, auth_header = self._update_request(None, path, method) 174 | return self.request("get", 175 | path, 176 | params=params, 177 | auth_header=auth_header, 178 | **options) 179 | 180 | def post(self, path, data, **options): 181 | """ 182 | Parses POST request options and dispatches a request 183 | """ 184 | method = "POST" 185 | data, auth_header = self._update_request(data, path, method) 186 | return self.request("post", 187 | path, 188 | data=data, 189 | auth_header=auth_header, 190 | **options) 191 | 192 | def patch(self, path, data, **options): 193 | """ 194 | Parses PATCH request options and dispatches a request 195 | """ 196 | method = "PATCH" 197 | data, auth_header = self._update_request(data, path, method) 198 | return self.request("patch", 199 | path, 200 | auth_header=auth_header, 201 | **options) 202 | 203 | def delete(self, path, data, **options): 204 | """ 205 | Parses DELETE request options and dispatches a request 206 | """ 207 | method = "DELETE" 208 | data, auth_header = self._update_request(data, path, method) 209 | return self.request("delete", 210 | path, 211 | data=data, 212 | auth_header=auth_header, 213 | **options) 214 | 215 | def put(self, path, data, **options): 216 | """ 217 | Parses PUT request options and dispatches a request 218 | """ 219 | method = "PUT" 220 | data, auth_header = self._update_request(data, path, method) 221 | return self.request("put", 222 | path, 223 | data=data, 224 | auth_header=auth_header, 225 | **options) 226 | 227 | def _update_request(self, data, path, method): 228 | """ 229 | Updates The resource data and header options 230 | """ 231 | _data = None 232 | content_type = "empty" 233 | if data is not None: 234 | _data = json.dumps(data) 235 | content_type = "application/json;charset=UTF-8" 236 | uri_path = path 237 | _auth_header = self.auth_header( 238 | self.auth[0], 239 | self.auth[1], 240 | method, 241 | uri_path, 242 | content_type, 243 | _data) 244 | return _data, _auth_header 245 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paypay OPA SDK - Python 2 | 3 | [![License](https://img.shields.io/:license-apache2.0-red.svg)](https://opensource.org/licenses/Apache-2.0) 4 | [![PyPI Version](https://img.shields.io/pypi/v/paypayopa.svg)](https://pypi.python.org/pypi/paypayopa) 5 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fpaypay%2Fpaypayopa-sdk-python.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fpaypay%2Fpaypayopa-sdk-python?ref=badge_shield) 6 | [![Build Status](https://travis-ci.org/paypay/paypayopa-sdk-python.svg?branch=master)](https://travis-ci.org/paypay/paypayopa-sdk-python) 7 | [![Coverage Status](https://coveralls.io/repos/github/paypay/paypayopa-sdk-python/badge.svg?branch=feature/testcases)](https://coveralls.io/github/paypay/paypayopa-sdk-python?branch=master) 8 | [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/paypay/paypayopa-sdk-python.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/paypay/paypayopa-sdk-python/context:python) 9 | [![Black Duck Security Risk](https://copilot.blackducksoftware.com/github/repos/paypay/paypayopa-sdk-python/branches/master/badge-risk.svg)](https://copilot.blackducksoftware.com/github/repos/paypay/paypayopa-sdk-python/branches/master) 10 | [![Maintainability](https://api.codeclimate.com/v1/badges/9165d052bfa9e688ae3f/maintainability)](https://codeclimate.com/github/paypay/paypayopa-sdk-python/maintainability) 11 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=paypay_paypayopa-sdk-python&metric=alert_status)](https://sonarcloud.io/dashboard?id=paypay_paypayopa-sdk-python) 12 | [![PyPI - Downloads](https://img.shields.io/pypi/dm/paypayopa)](https://pypi.python.org/pypi/paypayopa) 13 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/a86509528ec944ad9cf86fe2e31a6b27)](https://app.codacy.com/gh/paypay/paypayopa-sdk-python?utm_source=github.com&utm_medium=referral&utm_content=paypay/paypayopa-sdk-python&utm_campaign=Badge_Grade_Settings) 14 | [![BCH compliance](https://bettercodehub.com/edge/badge/paypay/paypayopa-sdk-python?branch=master)](https://bettercodehub.com/) 15 | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4222/badge)](https://bestpractices.coreinfrastructure.org/projects/4222) 16 | 17 | So you are a developer and want to start accepting payments using PayPay. PayPay's Payment SDK is the simplest way to achieve the integration. With PayPay's Payment SDK, you can build a custom Payment checkout process to suit your unique business needs and branding guidelines. 18 | 19 | # When to use QR Code Payments 20 | QR Code flow is recommended normally in the following scenarios 21 | - Payment to happen on a Tablet 22 | - Payments on Vending Machines 23 | - Payment to happen on a TV Screen 24 | - Printing a QR Code for Bill Payment 25 | 26 | ## Understanding the Payment Flow 27 | Following diagram defines the flow for Dynamic QR Code. 28 | ![](https://www.paypay.ne.jp/opa/doc/v1.0/imgs/dynamicqrcode-sequence.png) 29 | 30 | We recommend that the merchant implements a Polling of the Get payment Details API with a 4-5 second interval in order to know the status of the transaction. 31 | 32 | ## Let's get Started 33 | Once you have understood the payment flow, before we start the integration make sure you have: 34 | 35 | - [Registered](https://developer.paypay.ne.jp/) for a PayPay developer/merchant Account 36 | - Get the API key and secret from the Developer Panel. 37 | - Use the sandbox API Keys to test out the integration 38 | 39 | ### Install pip package 40 | ```sh 41 | $ pip install paypayopa 42 | ``` 43 | 44 | ## Getting Started 45 | You need to setup your key and secret using the following: 46 | 47 | To work in production mode you need to specify your production API_KEY & API_SECRET along with a production_mode True boolean flag 48 | ```py 49 | import paypayopa 50 | 51 | client = paypayopa.Client(auth=(API_KEY, API_SECRET), 52 | production_mode=True) 53 | 54 | client.set_assume_merchant("MERCHANT_ID") 55 | ``` 56 | or 57 | 58 | 59 | To work in sandbox mode you need to specify your sandbox API_KEY & API_SECRET keys along with a False boolean flag or you could just omit the production_mode flag since it defaults to False if not specified 60 | ```py 61 | import paypayopa 62 | 63 | client = paypayopa.Client(auth=(API_KEY, API_SECRET), 64 | production_mode=False) 65 | ``` 66 | 67 | After setting up the client instance you can get the current PayPay SDK version using the following: 68 | 69 | ```py 70 | print(client.get_version()) 71 | ``` 72 | 73 | ### Create a QR Code 74 | In order to receive payments using this flow, first of all you will need to create a QR Code. Following are the important parameters that you can provide for this method: 75 | 76 | | Field | Required |Type | Description | 77 | |---|---|---|---| 78 | |merchantPaymentId | Yes |string <= 64 characters |The unique payment transaction id provided by merchant | 79 | | amount |Yes |integer <= 11 characters |Amount the user has to Pay | 80 | |codeType | Yes |string <= 64 characters |Please pass the fixed value "ORDER_QR"| 81 | |orderDescription |No |string <= 255 characters|Description of the Order, [Click here](https://docs.google.com/presentation/d/1_S4syfMkLDplMVib7ai-L-3oHoIBRmuT6jrCoiANvqQ/edit?usp=sharing) to check how it will show up | 82 | |isAuthorization |No |boolean|By default it will be false, please set true if the amount will be captured later (preauth and capture payment) | 83 | 84 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/dynamicqrcode#operation/createQRCode) 85 | 86 | ```py 87 | request = { 88 | "merchantPaymentId": "cb31bcc0-3b6c-46e0-9002-e5c4bb1e3d5f", 89 | "codeType": "ORDER_QR", 90 | "redirectUrl": "http://foobar.com", 91 | "redirectType":"WEB_LINK", 92 | "orderDescription":"Example - Mune Cake shop", 93 | "orderItems": [{ 94 | "name": "Moon cake", 95 | "category": "pasteries", 96 | "quantity": 1, 97 | "productId": "67678", 98 | "unitPrice": { 99 | "amount": 1, 100 | "currency": "JPY", 101 | }, 102 | }], 103 | "amount": { 104 | "amount": 1, 105 | "currency": "JPY", 106 | }, 107 | } 108 | 109 | client.Code.create_qr_code(request) 110 | ``` 111 | 112 | Did you get a **HTTP 201** response, if yes then you are all set for the next step. 113 | 114 |
115 | 116 | ### Get Payment Details 117 | 118 | Now that you have created a Code, the next step is to implement polling to get Payment Details. We recommend a 4-5 second interval between requests. Following are the important parameters that you can provide for this method: 119 | 120 | | Field | Required |Type | Description | 121 | |---|---|---|---| 122 | |merchantPaymentId | Yes |string <= 64 characters |The unique payment transaction id provided by merchant | 123 | 124 | ### Fetch a particular QR CODE payment details 125 | ```py 126 | client.Payment.get_payment_details("") 127 | ``` 128 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/jp/v1.0/dynamicqrcode#operation/getPaymentDetails) 129 | On successful payment, the status in the response will change to **COMPLETED** 130 | In case of a Preauth for Payment, the status in the response will change to **AUTHORIZED** 131 | 132 |
133 | 134 | ### Delete a QRCode 135 | So you want to delete a Code that you have already generated. Following can be possible reasons to use this API: 136 | - User has cancelled the order 137 | - Ensuring race conditions don't come up in the form user has scanned the QR Code and not made the payment and in the meantime the order expires at your end 138 | 139 | Following are the important parameters that you can provide for this method: 140 | 141 | | Field | Required |Type | Description | 142 | |---|---|---|---| 143 | |codeId | Yes |string |This is given as a response in the Create a QR Code method | 144 | 145 | ```py 146 | client.Code.delete_qr_code("") 147 | ``` 148 | 149 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/jp/v1.0/dynamicqrcode#operation/deleteQRCode) 150 | 151 |
152 | 153 | ### Cancel a payment 154 | So you want to cancel a Payment. In most cases this should not be needed for payment happening in this flow, however following can be a case when this might be needed. 155 | 156 | - Polling for Get Payment Details timeout, and you are uncertain of the status 157 | 158 | Note: The Cancel API can be used until 00:14:59 AM the day after the Payment has happened. For 00:15 AM or later, please call the refund API to refund the payment. 159 | 160 | Following are the important parameters that you can provide for this method: 161 | 162 | | Field | Required |Type | Description | 163 | |---|---|---|---| 164 | |merchantPaymentId | Yes |string <= 64 characters |The unique payment transaction id provided by merchant | 165 | 166 | 167 | ```py 168 | client.Payment.cancel_payment("") 169 | ``` 170 | 171 |
172 | 173 | ### Refund a payment 174 | 175 | So the user has decided to return the goods they have purchased and needs to be given a refund. Following are the important parameters that you can provide for this method: 176 | 177 | | Field | Required |Type | Description | 178 | |---|---|---|---| 179 | |merchantRefundId | Yes |string <= 64 characters |The unique refund transaction id provided by merchant | 180 | |paymentId | Yes |string <= 64 characters |The payment transaction id provided by PayPay | 181 | |amount | Yes |integer <= 11 characters |The amount to be refunded | 182 | |reason | No |integer <= 11 characters |The reason for refund | 183 | 184 | ```py 185 | refund_payment_details = { 186 | "assumeMerchant": "cb31bcc0-3b6c-46e0-9002-e5c4bb1e3d5f", 187 | "merchantRefundId": "3b6c-46e0-9002-e5c4bb1e3d5f", 188 | "paymentId": "456787656", 189 | "amount": { 190 | "amount": 1, 191 | "currency": "JPY" 192 | }, 193 | "reason": "reason for refund" 194 | } 195 | 196 | client.Payment.refund_payment(refund_payment_details) 197 | ``` 198 | 199 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/dynamicqrcode#operation/refundPayment). **Please note that currently we only support 1 refund per order.** 200 | 201 |
202 | 203 | ### Capture a payment authorization 204 | 205 | So you are implementing a PreAuth and Capture, and hence want to capture the payment later. In this case, please ensure you have passed *isAuthorization* as *true* in create a code method. Following are the important parameters that you can provide for this method: 206 | 207 | | Field | Required |Type | Description | 208 | |---|---|---|---| 209 | |merchantPaymentId | Yes |string <= 64 characters |The unique payment transaction id provided by merchant | 210 | |merchantCaptureId | Yes |string <= 64 characters |The unique capture transaction id provided by merchant | 211 | | amount |Yes |integer <= 11 characters |Amount to be captured | 212 | |orderDescription |Yes |string <= 255 characters|Description of the Capture for the user| 213 | 214 | ```py 215 | request_payload = { 216 | "merchantPaymentId": "merchant_payment_id", 217 | "amount": { 218 | "amount": 1, 219 | "currency": "JPY" 220 | }, 221 | "merchantCaptureId": "31bcc0-3b6c-46e0-9002", 222 | "orderDescription": "Example - Mune Cake shop", 223 | } 224 | 225 | client.Payment.capture_payment(request_payload) 226 | ``` 227 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/dynamicqrcode#operation/capturePaymentAuth). 228 | 229 |
230 | 231 | ### Revert a payment authorization 232 | So the order has cancelled the order while the payment was still Authorized, please use the revert a payment authorization method to refund back to the user. Following are the important parameters that you can provide for this method: 233 | 234 | | Field | Required |Type | Description | 235 | |---|---|---|---| 236 | |merchantRevertId | Yes |string <= 64 characters |The unique revert transaction id provided by merchant | 237 | |paymentId | Yes |string <= 64 characters |The payment transaction id provided by PayPay | 238 | |reason |No |string <= 255 characters|Reason for reverting the payment authorization| 239 | 240 | ```py 241 | request_payload = { 242 | "merchantRevertId": "cb31bcc0-3b6c-46e0-9002-e5c4bb1e3d5f", 243 | "paymentId": "1585537300", 244 | "reason": "reason for refund", 245 | } 246 | 247 | client.Payment.revert_payment(request_payload) 248 | ``` 249 | For List of params refer to the API guide : 250 | https://www.paypay.ne.jp/opa/doc/v1.0/dynamicqrcode#operation/revertAuth 251 | 252 |
253 | 254 | ### Fetch refund status and details 255 | So you want to confirm the status of the refund, maybe because the request for the refund timed out when you were processing the same. Following are the important parameters that you can provide for this method: 256 | 257 | | Field | Required |Type | Description | 258 | |---|---|---|---| 259 | |merchantRefundId | Yes |string <= 64 characters |The unique refund transaction id provided by merchant | 260 | 261 | 262 | ```py 263 | client.Payment.refund_status("") 264 | ``` 265 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/dynamicqrcode#operation/getRefundDetails). 266 | 267 |
268 | 269 | # When to use Native Payments 270 | Native Payments is recommended normally in the following scenarios 271 | - If you want provide your customers with the easiest possible checkout 272 | - You have the security in place to ensure our mutual customers money is safe (We have a strict evaluation procedure to enforce the same) 273 | 274 | ## Integrating Native Integration 275 | 276 | ### Acquire User Authorization 277 | First of all you need to acquire a user Authorization. Following diagram defines the flow to acquire a user authorization. 278 | 279 | ![](https://www.paypay.ne.jp/opa/doc/v1.0/imgs/authorization-sequence.png) 280 | 281 | In order to acquire an authorization you need to create a JWT Token - 282 | 283 | |cliam| required | type| description| 284 | |---|---|---|---| 285 | |iss| yes| string| the merchant name| 286 | |exp| yes| number| The expiration date of the authorization page URL. Set with epoch time stamp (seconds). 287 | |scope| yes| string| direct_debit| 288 | |nonce| yes| string| will be sent back with response for the client side validation| 289 | |redirectUrl| yes| string| The callback endpoint provided by client. Must be HTTPS, and its domain should be in the allowed authorization callback domains| 290 | |referenceId| yes| string| The id used to identify the user in merchant system. It will be stored in the PayPay db for reconciliation purpose| 291 | |deviceId| no| string| The user mobile phone device id. If it is provided, we can use it to verify the user and skip the SMS verification, so as to provide more fluent UX| 292 | |phoneNumber| no| string| The user mobile phone number| 293 | 294 | ```py 295 | # Helper function to create a JWT Token for requesting user Authorization 296 | client.encode_jwt(API_SECRET, 297 | redirectUrl = "https://example.com", 298 | deviceId = "qwertyuiopoiuytre54567", 299 | phoneNumber = 90999999999 ) 300 | ``` 301 | 302 | Once the user has granted authorization, we will return the UserAuthorizationID as a part of the JWT Token in response/ webhook 303 | 304 | ```py 305 | # Retrieving userAuthorizationId from response JWT 306 | client.decode_jwt(API_SECRET, token) 307 | ``` 308 | 309 | ### Unlink a user from the client 310 | 311 | | Field | Required |Type | Description | 312 | |---|---|---|---| 313 | |userAuthorizationId|yes|string <= 64 characters|The PayPay user reference id returned by the user authorization flow| 314 | 315 | ```py 316 | # Calling the method to unlink a Payment 317 | response = client.User.unlink_user_athorization('userAuthorizationId') 318 | # Printing if the method call was SUCCESS 319 | print(response["resultInfo"]["code"]) 320 | ``` 321 | 322 | Did you get **SUCCESS** in the print statement above, if yes then the API execution has happened correctly. 323 | 324 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/direct_debit#operation/unlinkUser). 325 | 326 |
327 | 328 | 329 | 330 | ### Create a payment 331 | In order to take a payment, you will need to send a request to us with the following parameters: 332 | 333 | | Field | Required |Type | Description | 334 | |---|---|---|---| 335 | |merchantPaymentId | Yes |string <= 64 characters |The unique payment transaction id provided by merchant | 336 | |userAuthorizationId | Yes |string <= 64 characters |The PayPay user reference id returned by the user authorization flow | 337 | | amount |Yes |integer <= 11 characters |Amount the user has to Pay | 338 | |orderDescription |No |string <= 255 characters|Description of the Order, [Click here](https://docs.google.com/presentation/d/1_S4syfMkLDplMVib7ai-L-3oHoIBRmuT6jrCoiANvqQ/edit#slide=id.g6feeaf7a3d_1_0) to check how it will show up | 339 | 340 | ```py 341 | # Creating the payload to create a Payment, additional parameters can be added basis the API Documentation 342 | request = { 343 | "merchantPaymentId": "my_payment_id", 344 | "userAuthorizationId": "my_user_authorization_id", 345 | "amount": {"amount": 1, "currency": "JPY"}, 346 | "orderDescription": "Mune's Favourite Cake", 347 | } 348 | # Calling the method to create a payment 349 | response = client.Payment.create(request) 350 | # Printing if the method call was SUCCESS, this does not mean the payment was a success 351 | print(response["resultInfo"]["code"]) 352 | ``` 353 | 354 | Did you get **SUCCESS** in the print statement above, if yes then the API execution has happened correctly. 355 | 356 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/direct_debit#operation/createPayment) 357 | 358 |
359 | 360 | ### Get Payment Details 361 | Now that you have created a payment, in case the payment request timeout, you can call get payment details method to know the payment status. Following are the important parameters that you can provide for this method: 362 | 363 | | Field | Required |Type | Description | 364 | |---|---|---|---| 365 | |merchantPaymentId | Yes |string <= 64 characters |The unique payment transaction id provided by merchant | 366 | 367 | ```py 368 | # Calling the method to get payment details 369 | response = client.Payment.get_payment_details("") 370 | # Printing if the method call was SUCCESS, this does not mean the payment was a success 371 | print(response["resultInfo"]["code"]) 372 | # Printing if the transaction status for the code has COMPLETED/ AUTHORIZED 373 | print(response["data"]["status"]) 374 | ``` 375 | 376 | Did you get **SUCCESS** in the print statement above, if yes then the API execution has happen correctly. 377 | 378 | On successful payment, the status in response["data"]["status"] will be **COMPLETED** 379 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/webcashier#operation/getPaymentDetails) 380 | 381 |
382 | 383 | ### Cancel a payment 384 | So you want to cancel a Payment. In most cases this should not be needed for payment happening in this flow, however following can be a case when this might be needed. 385 | - Initial create payment timeout and you want to cancel the Payment 386 | - Get Payment Details timeout, and you are uncertain of the status 387 | 388 | Note: The Cancel API can be used until 00:14:59 AM the day after the Payment has happened. For 00:15 AM or later, please call the refund API to refund the payment. 389 | 390 | Following are the important parameters that you can provide for this method: 391 | 392 | | Field | Required |Type | Description | 393 | |---|---|---|---| 394 | |merchantPaymentId | Yes |string <= 64 characters |The unique payment transaction id provided by merchant | 395 | 396 | ```py 397 | # Calling the method to cancel a Payment 398 | response = client.Payment.cancel_payment("") 399 | # Printing if the method call was SUCCESS 400 | print(response["resultInfo"]["code"]) 401 | ``` 402 | 403 | Did you get **SUCCESS** in the print statement above, if yes then the API execution has happend correctly. 404 | 405 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/direct_debit#operation/cancelPayment) 406 | 407 |
408 | 409 | ### Refund a payment 410 | So the user has decided to return the goods they have purchased and needs to be giveb a refund. Following are the important parameters that you can provide for this method: 411 | 412 | | Field | Required |Type | Description | 413 | |---|---|---|---| 414 | |merchantRefundId | Yes |string <= 64 characters |The unique refund transaction id provided by merchant | 415 | |paymentId | Yes |string <= 64 characters |The payment transaction id provided by PayPay | 416 | |amount | Yes |integer <= 11 characters |The amount to be refunded | 417 | |reason | No |integer <= 11 characters |The reason for refund | 418 | 419 | ```py 420 | # Creating the payload to refund a Payment, additional parameters can be added based on the API Documentation 421 | request = { 422 | "merchantRefundId": "merchant_refund_id", 423 | "paymentId": "paypay_payment_id", 424 | "amount": 1, 425 | "reason": "reason for refund", 426 | } 427 | # Calling the method to refund a Payment 428 | response = client.Payment.refund_payment(request) 429 | # Printing if the method call was SUCCESS 430 | print(response["resultInfo"]["code"]) 431 | ``` 432 | 433 | Did you get **SUCCESS** in the print statement above, if yes then the API execution has happened correctly. 434 | 435 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/direct_debit#operation/refundPayment). **Please note that currently we only support 1 refund per order.** 436 | 437 |
438 | 439 | ### Fetch refund status and details 440 | 441 | So you want to confirm the status of the refund, maybe because the request for the refund timed out when you were processing the same. Following are the important parameters that you can provide for this method: 442 | 443 | | Field | Required |Type | Description | 444 | |---|---|---|---| 445 | |merchantRefundId | Yes |string <= 64 characters |The unique refund transaction id provided by merchant | 446 | 447 | ```py 448 | # Calling the method to get Refund Details 449 | response = client.Payment.refund_details("") 450 | # Printing if the method call was SUCCESS 451 | print(response["resultInfo"]["code"]) 452 | ``` 453 | Did you get **SUCCESS** in the print statement above, if yes then the API execution has happen correctly. 454 | 455 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/direct_debit#operation/getRefundDetails). 456 | 457 |
458 | 459 | ### Acquire User Authorization 460 | 461 | So you want to confirm the status of the refund, maybe because the request for the refund timed out when you were processing the same. Following are the important parameters that you can provide for this method: 462 | 463 | | Claim | Required |Type | Description | 464 | |---|---|---|---| 465 | | scopes | Yes | Array of string | Items Enum: 'direct_debit' 'cashback' 'get_balance' 'quick_pay' 'continuous_payments' 'merchant_topup' 'pending_payments' 'user_notification' 'user_topup' 'user_profile' 'preauth_capture_native' 'preauth_capture_transaction' 'push_notification' 'notification_center_ob' 'notification_center_ab' 'notification_center_tl' Scopes of the user authorization | 466 | | nonce | Yes | string | Random generated string | 467 | | redirectType | No | string | Default: 'WEB_LINK' Enum: 'APP_DEEP_LINK' 'WEB_LINK' Parameter to decide whether to redirect to merchant app or merchant web application | 468 | | redirectUrl | Yes | string | The callback endpoint provided by client. For 'WEB_LINK' it must be HTTPS, and its domain should be in the allowed authorization callback domains | 469 | | referenceId | Yes | string | The id used to identify the user in merchant system. It will be stored in the PayPay db for reconsilliation purpose | 470 | | phoneNumber | No | string | The user mobile phone number | 471 | | deviceId | No | string | The user mobile phone device id. If it is provided, we can use it to verify the user and skip the SMS verification, so as to provide more fluent UX | 472 | | userAgent | No | string | The User agent of the web browser. When redirectType is 'WEB_LINK' this parameter is provided, on mobile devices PayPay tries to open the browser that the merchant website is using. | 473 | 474 | ```py 475 | payload = { 476 | "scopes": [ 477 | "direct_debit" 478 | ], 479 | "nonce": "rtyuhghj7989", 480 | "redirectType": "WEB_LINK", 481 | "redirectUrl": "www.example.com", 482 | "referenceId": "uioiugf789", 483 | "phoneNumber": "90999999999", 484 | "deviceId": "qwertyuiopoiuytre54567", 485 | "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36" 486 | } 487 | 488 | client.Account.create_qr_session(payload) 489 | ``` 490 | 491 | Once the user has granted authorization, we will return the UserAuthorizationID as a part of the JWT Token in response/ webhook 492 | 493 | 494 | ```py 495 | # Retrieving userAuthorizationId from response JWT 496 | client.decode_jwt(API_SECRET, token) 497 | ``` 498 | 499 | Did you get **SUCCESS** in the print statement above, if yes then the API execution has happen correctly. 500 | 501 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/preauth_capture#operation/createAuth). 502 | 503 |
504 | 505 | ### Create a Payment Authorization 506 | 507 | In order to create a payment authorization, you will need to send a request to us with the following parameters: 508 | 509 | | Claim | Required |Type | Description | 510 | |---|---|---|---| 511 | | merchantPaymentId | Yes | string <= 64 characters | The unique payment transaction id provided by merchant | 512 | | userAuthorizationId | Yes | string <= 64 characters | The PayPay user reference id returned by the user authorization flow | 513 | | amount | Yes | integer <= 11 characters | Amount the user has to Pay | 514 | | requestedAt | Yes | integer | Epoch timestamp in seconds | 515 | | orderDescription | Yes | string <= 255 characters | Description of the Capture for the user | 516 | 517 | ```py 518 | # Creating the payload for a payment authorization request, additional parameters can be added basis the API Documentation 519 | request_payload = { 520 | "merchantPaymentId": "merchant_payment_id", 521 | "userAuthorizationId": "user_authorization_id", 522 | "amount": { 523 | "amount": 26.00, 524 | "currency": "JPY" 525 | }, 526 | "requestedAt": 5353454354, 527 | "orderReceiptNumber": "435435435", 528 | "orderDescription": "Mune's Favourite Cake", 529 | } 530 | 531 | # Calling the method to create a payment authorization 532 | client.Preauth.pre_authorize_create(request_payload) 533 | ``` 534 | 535 |
536 | 537 | ### Creating a Continuous Payment authorization 538 | 539 | In order to acquire an authorization you need to create a JWT Token - 540 | 541 | | Claim | Required |Type | Description | 542 | |---|---|---|---| 543 | | scopes | Yes | Array of string | Items Enum: 'direct_debit' 'cashback' 'get_balance' 'quick_pay' 'continuous_payments' 'merchant_topup' 'pending_payments' 'user_notification' 'user_topup' 'user_profile' 'preauth_capture_native' 'preauth_capture_transaction' 'push_notification' 'notification_center_ob' 'notification_center_ab' 'notification_center_tl' Scopes of the user authorization | 544 | | nonce | Yes | string | Random generated string | 545 | | redirectType | No | string | Default: 'WEB_LINK' Enum: 'APP_DEEP_LINK' 'WEB_LINK' Parameter to decide whether to redirect to merchant app or merchant web application | 546 | | redirectUrl | Yes | string | The callback endpoint provided by client. For 'WEB_LINK' it must be HTTPS, and its domain should be in the allowed authorization callback domains | 547 | | referenceId | Yes | string | The id used to identify the user in merchant system. It will be stored in the PayPay db for reconsilliation purpose | 548 | | phoneNumber | No | string | The user mobile phone number | 549 | | deviceId | No | string | The user mobile phone device id. If it is provided, we can use it to verify the user and skip the SMS verification, so as to provide more fluent UX | 550 | | userAgent | No | string | The User agent of the web browser. When redirectType is 'WEB_LINK' this parameter is provided, on mobile devices PayPay tries to open the browser that the merchant website is using. | 551 | 552 | ```py 553 | # Creating the payload for a payment authorization request, additional parameters can be added basis the API Documentation 554 | request_payload = { 555 | "merchantPaymentId": "merchant_payment_id", 556 | "userAuthorizationId": "my_user_authorization_id", 557 | "orderDescription": "Mune's Favourite Cake", 558 | "amount": { 559 | "amount": 1, 560 | "currency": "JPY" 561 | } 562 | } 563 | 564 | # Calling the method to create a continuous payment authorization 565 | client.Payment.create_continuous_payment(request_payload) 566 | 567 | # Printing if the method call was SUCCESS, this does not mean the payment was a success 568 | print(response["resultInfo"]["code"]) 569 | ``` 570 | 571 |
572 | 573 | ### Creating a Pending Payment authorization 574 | 575 | In order to acquire an authorization you need to create a JWT Token - 576 | 577 | | Claim | Required |Type | Description | 578 | |---|---|---|---| 579 | | merchantPaymentId | Yes | string <= 64 characters | The unique payment transaction id provided by merchant | 580 | | userAuthorizationId | Yes | string <= 64 characters | The PayPay user reference id returned by the user authorization flow | 581 | | amount | Yes | integer <= 11 characters | Amount the user has to Pay | 582 | | requestedAt | Yes | integer | Epoch timestamp in seconds | 583 | | orderDescription | Yes | string <= 255 characters | Description of the Capture for the user | 584 | 585 | ```py 586 | # Creating the payload for a payment authorization request, additional parameters can be added basis the API Documentation 587 | request_payload = { 588 | "merchantPaymentId": "merchant_payment_id", 589 | "userAuthorizationId": "my_user_authorization_id", 590 | "amount": { 591 | "amount": 1, 592 | "currency": "JPY" 593 | }, 594 | "requestedAt": 1632123456, 595 | "expiryDate": None, 596 | "storeId": "001", 597 | "terminalId": "0042", 598 | "orderReceiptNumber": "0878", 599 | "orderDescription": "Example - Mune Cake shop", 600 | "orderItems": [{ 601 | "name": "Moon cake", 602 | "category": "pasteries", 603 | "quantity": 1, 604 | "productId": "67678", 605 | "unitPrice": { 606 | "amount": 1, 607 | "currency": "JPY" 608 | } 609 | }], 610 | "metadata": {} 611 | } 612 | 613 | # Calling the method to create a continuous payment authorization 614 | client.Pending.create_pending_payment(request_payload) 615 | 616 | # Printing if the method call was SUCCESS, this does not mean the payment was a success 617 | print(response["resultInfo"]["code"]) 618 | ``` 619 | 620 | Did you get **SUCCESS** in the print statement above, if yes then the API execution has happen correctly. 621 | 622 | On successful payment, the status in response["data"]["status"] will be **COMPLETED** 623 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/pending_payments#operation/createPayment) 624 | 625 |
626 | 627 | ### Get Pending Payment Details 628 | Now that you have created a pending payment, in case the payment request timeout, you can call get payment details method to know the payment status. Following are the important parameters that you can provide for this method: 629 | 630 | | Field | Required |Type | Description | 631 | |---|---|---|---| 632 | |merchantPaymentId | Yes |string <= 64 characters |The unique payment transaction id provided by merchant | 633 | 634 | ```py 635 | # Calling the method to get payment details 636 | response = client.Pending.get_payment_details("") 637 | # Printing if the method call was SUCCESS, this does not mean the payment was a success 638 | print(response["resultInfo"]["code"]) 639 | # Printing if the transaction status for the code has COMPLETED/ AUTHORIZED 640 | print(response["data"]["status"]) 641 | ``` 642 | 643 | Did you get **SUCCESS** in the print statement above, if yes then the API execution has happen correctly. 644 | 645 | On successful payment, the status in response["data"]["status"] will be **COMPLETED** 646 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/pending_payments#operation/getPaymentDetails) 647 | 648 |
649 | 650 | ### Cancel Pending Payment Details 651 | 652 | | Field | Required |Type | Description | 653 | |---|---|---|---| 654 | |merchantPaymentId | Yes |string <= 64 characters |The unique payment transaction id provided by merchant | 655 | 656 | ```py 657 | # Calling the method to cancel pending payment 658 | response = client.Pending.cancel_payment("") 659 | # Printing if the method call was SUCCESS, this does not mean the payment was a success 660 | print(response["resultInfo"]["code"]) 661 | # Printing if the transaction status for the code has COMPLETED/ AUTHORIZED 662 | print(response["data"]["status"]) 663 | ``` 664 | 665 | Did you get **SUCCESS** in the print statement above, if yes then the API execution has happen correctly. 666 | 667 | On successful payment, the status in response["data"]["status"] will be **COMPLETED** 668 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/pending_payments#operation/cancelPendingOrder) 669 | 670 |
671 | 672 | ### Refund a Pending Payment 673 | So the user has decided to return the goods they have purchased and needs to be given a refund. Following are the important parameters that you can provide for this method: 674 | 675 | | Field | Required |Type | Description | 676 | |---|---|---|---| 677 | |merchantRefundId | Yes |string <= 64 characters |The unique refund transaction id provided by merchant | 678 | |paymentId | Yes |string <= 64 characters |The payment transaction id provided by PayPay | 679 | |amount | Yes |integer <= 11 characters |The amount to be refunded | 680 | |reason | No |integer <= 11 characters |The reason for refund | 681 | 682 | ```py 683 | payload = { 684 | "assumeMerchant": "cb31bcc0-3b6c-46e0-9002-e5c4bb1e3d5f", 685 | "merchantRefundId": "3b6c-46e0-9002-e5c4bb1e3d5f", 686 | "paymentId": "456787656", 687 | "amount": { 688 | "amount": 1, 689 | "currency": "JPY" 690 | }, 691 | "reason": "reason for refund" 692 | } 693 | 694 | client.Pending.refund_payment(refund_payment_details) 695 | ``` 696 | 697 | On successful payment, the status in response["data"]["status"] will be **COMPLETED** 698 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/pending_payments#operation/refundPayment) 699 | 700 |
701 | 702 | ### Cashback 703 | 704 | Paypay Cashback API can give users some balance. 705 | If you use Cashback API, you should authorize paypay user to use **create_qr_session** sdk. 706 | 707 | #### Give Cashback 708 | 709 | | Field | Required |Type | Description | 710 | |---|---|---|---| 711 | |merchantCashbackId | Yes |string <= 64 characters |The unique cashback transaction id provided by merchant | 712 | |userAuthorizationId | Yes |string <= 64 characters |The paypay user reference id provided by PayPay | 713 | |amount | Yes |integer <= 11 characters |The amount to cashback | 714 | |currency | Yes |string |"JPY" | 715 | |requestedAt | Yes |integer |Epoch timestamp in seconds | 716 | |orderDescription | No |string <= 255 characters |Description of the order | 717 | |walletType | No |string |Wallet type | 718 | |expiryDate | No |date |The date on which the Cashback Expires | 719 | |metadata | No |string |Extra information the merchant want to add | 720 | 721 | ```python 722 | payload = { 723 | "merchantCashbackId": "ab31bcc0-3b6c-46e0-9002-e5c4bb1e3d5f", 724 | "userAuthorizationId": "3b6c-a7c9-9002-e5c4bb1e3d5f", 725 | "amount": { 726 | "amount": 100, 727 | "currency": "JPY" 728 | }, 729 | "requestedAt": 1609749559, 730 | "orderDescription": "order description", 731 | "walletType": "PREPAID", 732 | "expiryDate": None, 733 | "metadata": "" 734 | } 735 | client.Cashback.give_cashback(payload) 736 | ``` 737 | #### Check Cashback Details 738 | 739 | | Field | Required |Type | Description | 740 | |---|---|---|---| 741 | |merchantCashbackId | Yes |string <= 64 characters |The unique cashback transaction id provided by merchant | 742 | 743 | ```python 744 | merchant_cashback_id = "ab31bcc0-3b6c-46e0-9002-e5c4bb1e3d5f" 745 | client.Cashback.check_give_cashback(merchant_cashback_id) 746 | ``` 747 | 748 | #### Reverse Cashback 749 | 750 | | Field | Required |Type | Description | 751 | |---|---|---|---| 752 | |merchantCashbackReversalId | Yes |string <= 64 characters |The unique reversal cashback transaction id provided by merchant | 753 | |merchantCashbackId | Yes |string <= 64 characters |The unique cashback transaction id provided by merchant | 754 | |amount | Yes |integer <= 11 characters |The amount to cashback | 755 | |currency | Yes |string |"JPY" | 756 | |reason | No |string <= 255 characters |Reason for reversing the cashback | 757 | |metadata | No |string |Extra information the merchant want to add | 758 | 759 | ```python 760 | payload = { 761 | "merchantCashbackReversalId": "e031bcc0-3b6c-9a7d-9002-e5c4cc1e3d5f", 762 | "merchantCashbackId": "ab31bcc0-3b6c-46e0-9002-e5c4bb1e3d5f", 763 | "amount": { 764 | "amount": 100, 765 | "currency": "JPY" 766 | }, 767 | "requestedAt": 1609749559, 768 | "reason": "reversal reason", 769 | "metadata": {} 770 | } 771 | client.Cashback.reverse_cashback(payload) 772 | ``` 773 | 774 | #### Check Cashback Reversal Details 775 | 776 | | Field | Required |Type | Description | 777 | |---|---|---|---| 778 | |merchantCashbackReversalId | Yes |string <= 64 characters |The unique reversal cashback transaction id provided by merchant | 779 | |merchantCashbackId | Yes |string <= 64 characters |The unique cashback transaction id provided by merchant | 780 | 781 | ```python 782 | merchant_cashback_reversal_id = "e031bcc0-3b6c-9a7d-9002-e5c4cc1e3d5f" 783 | merchant_cashback_id = "ab31bcc0-3b6c-46e0-9002-e5c4bb1e3d5f" 784 | client.Cashback.check_reversal_cashback(merchant_cashback_reversal_id, merchant_cashback_id) 785 | ``` 786 | 787 | 788 | ### Get refund details of a Pending payment 789 | 790 | So the user has decided to return the goods they have purchased and needs to be given a refund. Following are the important parameters that you can provide for this method: 791 | 792 | | Field | Required |Type | Description | 793 | |---|---|---|---| 794 | |merchantPaymentId | Yes |string <= 64 characters |The unique payment transaction id provided by merchant | 795 | 796 | ```py 797 | # Calling the method to cancel pending payment 798 | response = client.Pending.refund_details("") 799 | # Printing if the method call was SUCCESS, this does not mean the payment was a success 800 | print(response["resultInfo"]["code"]) 801 | # Printing if the transaction status for the code has COMPLETED/ AUTHORIZED 802 | print(response["data"]["status"]) 803 | ``` 804 | 805 | Did you get **SUCCESS** in the print statement above, if yes then the API execution has happen correctly. 806 | 807 | On successful payment, the status in response["data"]["status"] will be **COMPLETED** 808 | For details of all the request and response parameters , check our [API Documentation guide](https://www.paypay.ne.jp/opa/doc/v1.0/pending_payments#operation/getRefundDetails) 809 | 810 | 811 | 812 | ### Error Handling 813 | PayPay uses HTTP response status codes and error code to indicate the success or failure of the requests. With this information, you can decide what error handling strategy to use. In general, PayPay returns the following http status codes. 814 | 815 | #### HTTP 2xx 816 | **200** 817 | Everything works as expected. 818 | 819 | **201** 820 | The requested resource(e.g. dynamic QR code) was created. 821 | 822 | **202** 823 | Means the request is received, and will be processed sometime later. 824 | 825 | #### HTTP 4xx 826 | **400** 827 | This status code indicates an error because the information provided in request is not able to be processed. The following OPA error code may be returned. 828 | 829 | - INVALID_PARAMS 830 | The information provided by the request contains invalid data. E.g. unsupported currency. 831 | 832 | - UNACCEPTABLE_OP 833 | The requested operation is not able to be processed due to the current condition. E.g. the transaction limit exceeded. 834 | 835 | - NO_SUFFICIENT_FUND 836 | There is no sufficient fund for the transaction. 837 | 838 | **401** 839 | This status code indicates an authorization error. The following OPA error code may be returned. 840 | 841 | - UNAUTHORIZED 842 | No valid api key and secret provided. 843 | 844 | - OP_OUT_OF_SCOPE 845 | The operation is not permitted. 846 | 847 | **404** 848 | This status code indicates that the requested resource is not existing in the system. 849 | 850 | **429** 851 | This status code indicates that the client sent too many requests in a specific period of time, and hit the rate limits. You should slow down the request sending or contact us to rise your limit. 852 | 853 | #### HTTP 5xx 854 | **500** 855 | 856 | This status code indicates that something went wrong on the PayPay side. A few OPA error code could be returned. 857 | 858 | - TRANSACTION_FAILED 859 | This code means the transaction is failed on the PayPay side. You can create new transactions for the same purpose with reasonable backoff time. 860 | 861 | - INTERNAL_SERVER_ERROR 862 | This code means that something goes wrong, but we don't know exactly if the transaction has happened or not. It should be treated as unknown payment status. 863 | 864 | **502,503,504** 865 | Treated as unknown payment status. 866 | 867 | #### Timeout 868 | The recommended timeout setting is specified in each API. The most important one is for the payment creation api, where the read timeout should not be less than 30 seconds. When timeout happens, it should be treated as unknown payment status. 869 | 870 | #### Handle unknown payment status 871 | There are two ways to react with this situation: 872 | - Use the query api to query the transaction status. If the original transaction was failed or not found in PayPay, you can start a new transaction for the same purpose. 873 | - Or, you can cancel the transaction, if the cancel api is provided. After the cancellation is accepted, you can start a new transaction for the same purpose. 874 | 875 | 876 | ### Response code list 877 | **Common response code** 878 | 879 | | Status | CodeId |Code | Message | 880 | |---|---|---|---| 881 | |200| 08100001| SUCCESS| Success| 882 | |202| 08100001| REQUEST_ACCEPTED| Request accepted| 883 | |400| 08100006| INVALID_REQUEST_PARAMS| Invalid request params| 884 | |401| 08100023| OP_OUT_OF_SCOPE| The operation is not permitted| 885 | |400| 08100024| MISSING_REQUEST_PARAMS| | 886 | |401| 08100016| UNAUTHORIZED| Unauthorized request| 887 | |404| 08100007| OPA_CLIENT_NOT_FOUND| OPA Client not found| 888 | |429| 08100998| RATE_LIMIT| Too many requests| 889 | |500| 08100026| SERVICE_ERROR| | 890 | |500| 08101000| INTERNAL_SERVER_ERROR| Something went wrong on PayPay service side| 891 | |503| 08100999| MAINTENANCE_MODE| Sorry, we are down for scheduled maintenance| 892 | 893 | **Create a QRCode** 894 | 895 | |Status |CodeId |Code |Message| 896 | |---|---|---|---| 897 | |400| 01652073| DUPLICATE_DYNAMIC_QR_REQUEST| Duplicate Dynamic QR request error| 898 | |400| 00400060| PRE_AUTH_CAPTURE_UNSUPPORTED_MERCHANT| Merchant do not support| Pre-Auth-Capture 899 | |400| 00400061| PRE_AUTH_CAPTURE_INVALID_EXPIRY_DATE| Provided Expiry Date is above the allowed limit of Max allowed expiry days| 900 | |400| 01650000| DYNAMIC_QR_BAD_REQUEST| Dynamic QR bad request error| 901 | 902 | **Get payment details** 903 | 904 | |Status |CodeId |Code |Message| 905 | |---|---|---|---| 906 | |400| 01652075| DYNAMIC_QR_PAYMENT_NOT_FOUND| Dynamic QR payment not found| 907 | |400| 01650000| DYNAMIC_QR_BAD_REQUEST| Dynamic QR bad request error| 908 | 909 | **Delete a QRCode** 910 | 911 | |Status |CodeId |Code |Message| 912 | |---|---|---|---| 913 | |400 |01652074 |DYNAMIC_QR_ALREADY_PAID| Dynamic QR already paid| 914 | |400 |01650000 |DYNAMIC_QR_BAD_REQUEST| Dynamic QR bad request error| 915 | |404 |01652072 |DYNAMIC_QR_NOT_FOUND| Dynamic qr code not found| 916 | 917 | **Cancel a Payment** 918 | 919 | |Status |CodeId |Code |Message| 920 | |---|---|---|---| 921 | |400 |00200044 |ORDER_NOT_REVERSIBLE| Order cannot be reversed| 922 | |500 |00200034 |INTERNAL_SERVER_ERROR| Request timed out| 923 | 924 | **Refund a payment** 925 | 926 | |Status |CodeId |Code |Message| 927 | |---|---|---|---| 928 | |400 |00200004 |INVALID_PARAMS |Invalid parameters received| 929 | |400 |00200013 |UNACCEPTABLE_OP |Order cannot be refunded| 930 | |400 |00200014 |UNACCEPTABLE_OP |Multiple refund not allowed| 931 | |400 |00200015 |INVALID_PARAMS |Invalid refund amount| 932 | |400 |01103027 |CANCELED_USER |Canceled user| 933 | |404 |00200001 |RESOURCE_NOT_FOUND |Order not found| 934 | |500 |00200002 |TRANSACTION_FAILED |Transaction failed| 935 | |500 |00200003 |TRANSACTION_FAILED |Transaction failed| 936 | |500 |00800017 |TRANSACTION_FAILED |Balance exceeded| 937 | |500 |00200034 |INTERNAL_SERVER_ERROR |Request timed out| 938 | 939 | **Fetch refund status and details** 940 | 941 | |Status |CodeId |Code |Message| 942 | |---|---|---|---| 943 | |404 |00200018| NO_SUCH_REFUND_ORDER |Refund not found| 944 | |500 |00200034| INTERNAL_SERVER_ERROR |Request timed out| 945 | 946 | **Capture a payment authorization** 947 | 948 | |Status |CodeId |Code |Message| 949 | |---|---|---|---| 950 | |202 |08300103 |USER_CONFIRMATION_REQUIRED |User confirmation required as requested amount is above allowed limit| 951 | |400 |00400035 |UNACCEPTABLE_OP |Total transaction limit exceeds merchant limit| 952 | |400 |00200039 |ALREADY_CAPTURED |Cannot capture already captured acquiring order| 953 | |400 |01103027 |CANCELED_USER |Canceled user| 954 | |400 |00400062 |HIGHER_CAPTURE_NOT_PERMITTED |Merchant not allowed to capture higher amount| 955 | |400 |00200043 |ORDER_EXPIRED |Order cannot be captured or updated as it has already expired| 956 | |400 |00200035 |ORDER_NOT_CAPTURABLE |Order is not capturable| 957 | |400 |00200038 |REAUTHORIZATION_IN_PROGRESS |Order is being reauthorized| 958 | |400 |00400064 |TOO_CLOSE_TO_EXPIRY |Order cannot be reauthorized as request is too close to expiry time| 959 | 960 | **Revert a payment authorization** 961 | 962 | |Status |CodeId |Code |Message| 963 | |---|---|---|---| 964 | |400 |00200042|ORDER_NOT_CANCELABLE |Order is not cancelable| 965 | 966 | 967 | ## License 968 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fpaypay%2Fpaypayopa-sdk-python.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fpaypay%2Fpaypayopa-sdk-python?ref=badge_large) 969 | --------------------------------------------------------------------------------