diff --git a/docs/az/docs/integrations/epoint/api-reference/client.md b/docs/az/docs/integrations/epoint/api-reference/client.md index 83b8bea..412643b 100644 --- a/docs/az/docs/integrations/epoint/api-reference/client.md +++ b/docs/az/docs/integrations/epoint/api-reference/client.md @@ -39,3 +39,8 @@ - split_pay - split_pay_with_saved_card - split_pay_and_save_card + - create_widget + - create_token_payment + - apple_pay_session + - apple_pay + - google_pay diff --git a/docs/az/docs/integrations/epoint/api-reference/response.md b/docs/az/docs/integrations/epoint/api-reference/response.md index 61bfe79..ebdd091 100644 --- a/docs/az/docs/integrations/epoint/api-reference/response.md +++ b/docs/az/docs/integrations/epoint/api-reference/response.md @@ -16,6 +16,14 @@ ::: integrify.epoint.schemas.response.SplitPayWithSavedCardResponseSchema +## Apple Pay & Google Pay + +::: integrify.epoint.schemas.response.WidgetResponseSchema + +::: integrify.epoint.schemas.response.TokenPaymentResponseSchema + +::: integrify.epoint.schemas.response.TokenPayResponseSchema + ## Extra ::: integrify.epoint.schemas.response.BaseWithCodeSchema diff --git a/docs/az/docs/integrations/epoint/index.md b/docs/az/docs/integrations/epoint/index.md index c4195e8..725c4e4 100644 --- a/docs/az/docs/integrations/epoint/index.md +++ b/docs/az/docs/integrations/epoint/index.md @@ -11,6 +11,11 @@ [Rusca](https://epointbucket.s3.eu-central-1.amazonaws.com/files/instructions/API%20Epoint%20ru.pdf) +Markdown versiyaları (bu saytda): + +- [EPoint API (v1.0.3)](./official/api.md) +- [Apple Pay & Google Pay (JS SDK)](./official/apple-google-pay.md) + ## Sürətli başlanğıc { #quickstart } Ödəniş yaratmaq, müştərini ödəniş səhifəsinə yönləndirmək və statusu yoxlamaq: @@ -53,6 +58,122 @@ Asinxron istifadə üçün `EPointAsyncRequest` import edib, eyni metodları `aw | [`split_pay`][integrify.epoint.client.EPointClientClass.split_pay] | Ödənişi başqa EPoint istifadəçisi ilə bölüb ödəmə | `/api/1/split-request` | :fontawesome-solid-check: | | [`split_pay_with_saved_card`][integrify.epoint.client.EPointClientClass.split_pay_with_saved_card] | Saxlanılmış kartla ödənişi başqa EPoint istifadəçisi ilə bölüb ödəmə | `/api/1/split-execute-pay` | :x: | | [`split_pay_and_save_card`][integrify.epoint.client.EPointClientClass.split_pay_and_save_card] | Ödənişi başqa EPoint istifadəçisi ilə bölüb ödəmə və kartı saxlamaq | `/api/1/split-card-registration-with-pay` | :fontawesome-solid-check: | +| [`create_widget`][integrify.epoint.client.EPointClientClass.create_widget] | Apple Pay/Google Pay widget-i yaratmaq | `/api/1/token/widget` | :x: | +| [`create_token_payment`][integrify.epoint.client.EPointClientClass.create_token_payment] | Apple Pay/Google Pay üçün token ödənişi yaratmaq | `/api/1/token/payment` | :x: | +| [`apple_pay_session`][integrify.epoint.client.EPointClientClass.apple_pay_session] | Apple Pay session-u almaq | `/api/1/token/apple/session` | :x: | +| [`apple_pay`][integrify.epoint.client.EPointClientClass.apple_pay] | Apple Pay ilə ödənişi tamamlamaq | `/api/1/token/apple/pay` | :x: | +| [`google_pay`][integrify.epoint.client.EPointClientClass.google_pay] | Google Pay ilə ödənişi tamamlamaq | `/api/1/token/google/pay` | :x: | + +## Apple Pay & Google Pay { #apple-pay-google-pay } + +Apple Pay və Google Pay-i iki yolla qoşmaq olar: + +| Üsul | Nə vaxt | Backend sorğuları | +| :--- | :------ | :---------------- | +| **Widget** | Ən sadə yol: düymələr EPoint-in səhifəsində, iframe/webview daxilində göstərilir | [`create_widget`][integrify.epoint.client.EPointClientClass.create_widget] | +| **JS SDK** | Düymələr birbaşa sizin səhifənizdə, öz dizaynınızla | [`create_token_payment`][integrify.epoint.client.EPointClientClass.create_token_payment], [`apple_pay_session`][integrify.epoint.client.EPointClientClass.apple_pay_session], [`apple_pay`][integrify.epoint.client.EPointClientClass.apple_pay], [`google_pay`][integrify.epoint.client.EPointClientClass.google_pay] | + +### Widget { #apple-google-pay-widget } + +```python +from integrify.epoint import EPointRequest + +resp = EPointRequest.create_widget(amount=2.5, order_id='12345678', description='Ödəniş') +widget_url = resp.body.widget_url # frontend-ə ötürün +``` + +`widget_url`-i saytda iframe, mobil tətbiqdə isə webview daxilində açın. Ödəniş bitdikdən sonra +widget səhifəyə `message` event-i göndərir: + +```html + + + +``` + +> **Qeyd** +> +> `event.data` brauzerdən gəldiyi üçün ona tam güvənməyin: ödənişin nəticəsini backend-də +> [`get_transaction_status`][integrify.epoint.client.EPointClientClass.get_transaction_status] ilə yoxlayın. + +### JS SDK { #apple-google-pay-sdk } + +Bu üsulda düymələr EPoint-in JS SDK-sı (`epoint-token-pay`) vasitəsilə göstərilir. +Axın belədir: + +1. **Backend:** [`create_token_payment`][integrify.epoint.client.EPointClientClass.create_token_payment] ilə EPoint-də token ödənişi yaradın. +2. **Frontend:** SDK-nı qoşun, düymələri əlavə edin və `initTokenPay`-i 1-ci addımda gələn ödənişlə çağırın. +3. **Apple Pay:** SDK sizin session endpoint-inizə müraciət edir → [`apple_pay_session`][integrify.epoint.client.EPointClientClass.apple_pay_session]. +4. **Ödəniş:** İstifadəçi ödənişi təsdiqlədikdən sonra SDK pay endpoint-inizə `id`, `token` və `billingContact` göndərir → [`apple_pay`][integrify.epoint.client.EPointClientClass.apple_pay] / [`google_pay`][integrify.epoint.client.EPointClientClass.google_pay]. + +Session və pay endpoint-ləri EPoint-dən gələn cavabı **olduğu kimi** (`resp.body`) qaytarmalıdır. + +#### Frontend { #apple-google-pay-frontend } + +```html + + + + + + +``` + +#### Backend (FastAPI nümunəsi) { #apple-google-pay-backend } + +```python +from fastapi import APIRouter, Request +from integrify.epoint import EPointAsyncRequest + +router = APIRouter(prefix='/epoint') + + +@router.post('/apple/session') +async def apple_session(request: Request): + resp = await EPointAsyncRequest.apple_pay_session(origin=request.headers['origin']) + return resp.body + + +@router.post('/apple') +async def apple_pay(request: Request): + body = await request.json() + resp = await EPointAsyncRequest.apple_pay( + payment_id=body['id'], + token=body['token'], + billing_contact=body.get('billingContact'), + ) + return resp.body.model_dump() + + +@router.post('/google') +async def google_pay(request: Request): + body = await request.json() + resp = await EPointAsyncRequest.google_pay( + payment_id=body['id'], + token=body['token'], + billing_contact=body.get('billingContact'), + ) + return resp.body.model_dump() +``` + +> **Qeyd** +> +> `apple_pay`/`google_pay` sorğularının cavabında `redirect_url` gələ bilər (məs., 3DS üçün) — SDK bunu özü idarə edir. +> Uğurlu ödənişdən sonra statusu [`get_transaction_status`][integrify.epoint.client.EPointClientClass.get_transaction_status] ilə də yoxlaya bilərsiniz. ## Callback Sorğusu { #callback-request } diff --git a/docs/az/docs/integrations/epoint/official/api.md b/docs/az/docs/integrations/epoint/official/api.md new file mode 100644 index 0000000..7b15d58 --- /dev/null +++ b/docs/az/docs/integrations/epoint/official/api.md @@ -0,0 +1,894 @@ +# EPoint API (v1.0.3) + +???+ info + Bu səhifə EPoint-in rəsmi API dokumentasiyasının (ingiliscə, v1.0.3) Markdown versiyasıdır. + Orijinal: [API Epoint en.pdf](https://epointbucket.s3.eu-central-1.amazonaws.com/files/instructions/API%20Epoint%20en.pdf). + Orijinalda kod nümunələri şəkil kimi verildiyi üçün, burada onlar parametr cədvəllərinə + əsasən yenidən yazılıb. Uyğunsuzluq olarsa, orijinal PDF əsas götürülür. + +**Epoint.az** — electronic payment platform. Solution for payment on site. + +## Contents + +- [Epoint payment page working principle](#working-principle) +- [Formation of API request](#formation-of-api-request) +- [Callback function processing](#callback-function-processing) +- [Checking payment status](#checking-payment-status) +- [Saving a card to make payments without entering card data](#card-registration) +- [Executing a payment with a saved card](#execute-pay) +- [Saving the card with the first payment made](#card-registration-with-pay) +- [Request for disbursement of funds](#refund-request) +- [Cancel operations](#reverse) +- [Split payment request](#split-request) +- [Executing a split payment with a stored card](#split-execute-pay) +- [Saving the card with the first split payment](#split-card-registration-with-pay) +- [Formation of API preauth request](#pre-auth-request) +- [Apple Pay & Google Pay](#apple-pay-google-pay) +- [Google Pay (integration for mobile applications)](#google-pay-mobile) +- [Wallets](#wallets) +- [Invoices](#invoices) +- [Heartbeat API](#heartbeat) +- [Bank Response Codes](#bank-response-codes) + +--- + +Epoint system provides possibility to connect payment acceptance to your site. To add a payment +button to your application you need to connect the payment service in your personal cabinet on our +website. + +To set up a merchant in our system you will need to provide us with the following information: + +- your website address; +- url of the page of successful payment — `success_url`; +- url of the page for displaying information about unsuccessful payment — `error_url`; +- url to send the result of payment — `result_url`. + +After checking this information, you will be given access keys: `public_key` — merchant ID in our +system and `private_key` — secret API access key. + +## Epoint payment page working principle { #working-principle } + +1. It is necessary to form a request to the Epoint API according to the technical documentation. +2. As a result of executing the request, the client will be redirected to the bank's payment page. +3. The client fills in the card details and confirms the payment. +4. In case of successful payment, the customer will be redirected to `success_url`, or `error_url` otherwise. +5. The result of payment execution with payment details will be sent to the `result_url` specified by you. + +## Formation of API request { #formation-of-api-request } + +To call the Epoint API, you must send the `data` and `signature` parameters using the POST method to +`https://epoint.az/api/1/request` or redirect the user using the POST method to +`https://epoint.az/api/1/checkout`, where: + +- `data` — json string with API parameters encoded with base64 function, `base64_encode(json_string)`, +- `signature` — unique signature for each request, `base64_encode(sha1(private_key + data + private_key, 1))`, +- `base64_encode` — returns a string encoded in MIME base64 format, +- `sha1` — returns a hash of a string of 20 characters (raw binary). + +### Formation of data and signature + +Parameters `json_string` of the api call: + +| Parameter | Required | Type | Description | +| :--------------------- | :------- | :----- | :---------------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `amount` | Required | Number | The amount of the payment. For example: 100, 20.50 | +| `currency` | Required | String | Payment currency. Possible values: `AZN` | +| `language` | Required | String | Page display language. Possible values: `az`, `en`, `ru` | +| `order_id` | Required | String | The unique ID of the transaction in your application. Maximum length 255 characters. | +| `description` | Optional | String | Payment description. No more than 1000 characters. | +| `is_installment` | Optional | Number | Parameter defining the payment type. Possible values: `1` (installment) or `0` (standard) | +| `success_redirect_url` | Optional | String | Redirection link in case of successful payment. | +| `error_redirect_url` | Optional | String | Redirection link in case of unsuccessful payment. | +| `other_attr` | Optional | Array | Additional payment options | + +`json_string` example: + +```json +{ + "public_key": "i000000001", + "amount": "30.75", + "currency": "AZN", + "description": "test payment", + "order_id": "1", + "language": "az" +} +``` + +To form a signature, concatenate `private_key + data + private_key` and apply +`base64_encode(sha1(sgn_string, 1))` to the resulting string: + +```php +$data = base64_encode(json_encode($json_string)); +$signature = base64_encode(sha1($private_key . $data . $private_key, 1)); +``` + +### Sending a request + +A form must be generated to send a request to the Epoint page: + +```html +
+ + + +
+``` + +Or send the received `data` and `signature` to `https://epoint.az/api/1/request`. In this case a +json string will be returned with the value of `status` (`success|error`), `transaction` and +`redirect_url` to which the user should be redirected to enter card data. + +After entering the card data, the user will be redirected to `success_redirect_url` or +`error_redirect_url` depending on the payment status. Along with this, a POST request will be sent to +`result_url` with payment details and transaction status. + +## Callback function processing { #callback-function-processing } + +After the transaction is processed by Epoint service and the payment status is received from the +bank, a POST request with two parameters `data` and `signature` will be sent to your server +(`result_url`). + +To authenticate a request from the Epoint server, you must: + +1. Generate a signature on your server side using the `data` received in the response from Epoint and your `private_key`. +2. The received signature should be compared with the one received from Epoint. If the signatures + match, then you have received a genuine response from the Epoint server unmodified by a third party. + +To decode the `data` value you must execute: + +```php +$result = json_decode(base64_decode($data), true); +``` + +Use the Payment Status API function to get the status of a transaction, which can be done at any time. + +Payment result parameters: + +| Parameter | Description | +| :----------------- | :--------------------------------------------------------------------------------------------------- | +| `order_id` | The unique ID of the transaction in your application. | +| `status` | Operation result: `success` or `failed` | +| `code` | Bank response code | +| `message` | Payment execution status message | +| `transaction` | Epoint service transaction | +| `bank_transaction` | Bank payment transaction | +| `bank_response` | Bank's response, with the result of payment processing | +| `operation_code` | `001` — card registration, `100` — user payment | +| `rrn` | Retrieval Reference Number — a unique transaction identifier. Present only for a successful transaction | +| `card_name` | User name specified on the payment page | +| `card_mask` | User card mask in the format: `123456******1234` | +| `amount` | Payment amount | +| `other_attr` | Additional parameters | + +## Checking payment status { #checking-payment-status } + +To invoke Epoint payment status check, you need to pass the `data` and `signature` parameters by POST +method to `https://epoint.az/api/1/get-status`, where: + +- `data` — json string with API parameters encoded by function base64, `base64_encode(json_string)`, +- `signature` — unique signature for each request, `base64_encode(sha1(private_key + data + private_key, 1))`. + +`json_string` example: + +```json +{ + "public_key": "i000000001", + "transaction": "te000000001" +} +``` + +Response parameters: + +| Parameter | Description | +| :----------------- | :--------------------------------------------------------------------------------------------------- | +| `status` | Payment status | +| `code` | Bank response code | +| `message` | Payment execution status message | +| `transaction` | Epoint service transaction | +| `bank_transaction` | Bank payment transaction | +| `bank_response` | Bank's response, with the result of payment processing | +| `operation_code` | `001` — card registration, `100` — user payment | +| `rrn` | Retrieval Reference Number — a unique transaction identifier. Present only for a successful transaction | +| `card_name` | Username specified on the payment page | +| `card_mask` | User card mask in `123456******1234` format | +| `amount` | Payment amount | +| `other_attr` | Additional parameters | + +Payment statuses: + +- `new` — payment is registered in the Epoint system; +- `success` — successful payment; +- `returned` — the payment has been refunded; +- `error` — an error occurred during payment; +- `server_error` — status check execution error. + +## Saving a card to make payments without entering card data { #card-registration } + +To call Epoint API you need to send `data` and `signature` parameters by POST method to +`https://epoint.az/api/1/card-registration`. + +The `json_string` parameters of the api call: + +| Parameter | Required | Type | Description | +| :--------------------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `language` | Required | String | Page display language. Possible values: `az`, `en`, `ru` | +| `refund` | Optional | Number | Card type: `0` — debit card; `1` — payout card. | +| `description` | Optional | String | Payment description. No more than 1000 characters. | +| `success_redirect_url` | Optional | String | Redirection link in case of successful payment. | +| `error_redirect_url` | Optional | String | Redirection link in case of unsuccessful payment. | + +The request will return a json string with the `status` value (`success|error`) and `redirect_url` to +which the user should be redirected to enter card data, and `card_id` — a unique card identifier +that will be used to make payments. + +The client fills in the card information and confirms the payment. In case of success, the client +will be redirected to `success_url`, or `error_url` otherwise (`success_redirect_url` and +`error_redirect_url`, if specified). + +After the transaction is processed by Epoint service and the payment status is received from the +bank, a POST request with two parameters `data` and `signature` will be sent to the `result_url` +specified by you. Verify and decode it as described in +[Callback function processing](#callback-function-processing). + +Response parameters: + +| Parameter | Description | +| :----------------- | :--------------------------------------------------------------------------------------------------- | +| `status` | Operation result: `success` or `failed` | +| `code` | `000` — successful operation, `500` — error | +| `message` | Operation progress status message | +| `card_id` | The unique card identifier that will be used to make the payment | +| `bank_transaction` | Bank payment transaction | +| `bank_response` | Bank's response, with the result of payment processing | +| `operation_code` | `001` — card registration, `100` — user payment | +| `rrn` | Retrieval Reference Number — a unique transaction identifier. Present only for a successful transaction | +| `card_name` | Username specified on the payment page | +| `card_mask` | User card mask in `123456******1234` format | + +## Executing a payment with a saved card { #execute-pay } + +To make a payment with a stored card you need to send `data` and `signature` parameters by POST +method to `https://epoint.az/api/1/execute-pay`. + +Parameters `json_string` of API call: + +| Parameter | Required | Type | Description | +| :------------ | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `language` | Required | String | Page display language. Possible values: `az`, `en`, `ru` | +| `card_id` | Required | String | The card ID obtained by saving the card. | +| `order_id` | Required | String | Unique transaction ID in your application. The maximum length is 255 characters. | +| `amount` | Required | Number | Amount of payment. For example: 100, 20.50 | +| `currency` | Required | String | Payment currency. Possible values: `AZN` | +| `description` | Optional | String | Description of the payment. Not more than 1000 characters. | + +After processing the transaction by the Epoint service and receiving the payment status from the +bank, a response will be returned with the following parameters: + +| Parameter | Description | +| :----------------- | :--------------------------------------------------------------------------------------------------- | +| `status` | Result of a `success` or `failed` operation | +| `transaction` | Epoint transaction ID | +| `bank_transaction` | Bank payment transaction | +| `bank_response` | Bank response, with the result of payment processing | +| `rrn` | Retrieval Reference Number — unique identifier of the transaction. Present only for successful transaction | +| `card_name` | User name specified on the payment page | +| `card_mask` | User card mask in `123456******1234` format | +| `amount` | Amount of payment | +| `message` | Error message | + +## Saving the card with the first payment made { #card-registration-with-pay } + +If you use this type of payment, you will be paid for the specified amount along with the card +registration. To call the Epoint API, you need to pass the `data` and `signature` parameters by POST +method to `https://epoint.az/api/1/card-registration-with-pay`. + +Parameters for the `json_string` call of the API: + +| Parameter | Required | Type | Description | +| :--------------------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `language` | Required | String | Page display language. Possible values: `az`, `en`, `ru` | +| `order_id` | Required | String | Unique transaction ID in your application. The maximum length is 255 characters. | +| `amount` | Required | Number | Amount of payment. For example: 100, 20.50 | +| `currency` | Required | String | Payment currency. Possible values: `AZN` | +| `description` | Optional | String | Description of the payment. Not more than 1000 characters. | +| `success_redirect_url` | Optional | String | Redirection link in case of successful payment. | +| `error_redirect_url` | Optional | String | Redirection link in case of failed payment. | + +As a result of the request, a json string with the `status` (`success|error`) value, `transaction` +and `redirect_url` to which the user needs to be redirected to enter card data will be returned, and +`card_id` — the unique identifier of the card that will need to be used to make payments. + +The customer fills in the card details and confirms payment. If the payment is successful, the client +will be redirected to `success_url`, or `error_url` otherwise (`success_redirect_url` and +`error_redirect_url` if specified). + +After processing the transaction, a POST request with `data` and `signature` parameters will be sent +to the `result_url` indicated by you. Verify and decode it as described in +[Callback function processing](#callback-function-processing). + +Response parameters: + +| Parameter | Description | +| :----------------- | :--------------------------------------------------------------------------------------------------- | +| `status` | Result of a `success` or `failed` operation | +| `code` | `000` — successful operation | +| `card_id` | Unique identifier of the card you want to use to make the payment | +| `order_id` | Unique identifier of the payment in your application | +| `transaction` | Epoint transaction ID | +| `bank_transaction` | Bank payment transaction | +| `bank_response` | Bank response, with the result of payment processing | +| `operation_code` | `200` — card registration with the first payment | +| `rrn` | Retrieval Reference Number — unique identifier of the transaction. Present only for successful transaction | +| `card_mask` | User card mask in `123456******1234` format | +| `card_name` | Name of the cardholder | +| `amount` | Amount of payment | +| `other_attr` | Advanced options | + +## Request for disbursement of funds { #refund-request } + +To request a payout of funds, you must send a POST request to `https://epoint.az/api/1/refund-request` +with the `data` and `signature` parameters. + +API call `json_string` parameters: + +| Parameter | Required | Type | Description | +| :------------ | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `language` | Required | String | Page display language. Possible values: `az`, `en`, `ru` | +| `card_id` | Required | String | The card ID received by the card saving method. | +| `order_id` | Required | String | Unique transaction ID in your application. The maximum length is 255 characters. | +| `amount` | Required | Number | Amount of payment. For example: 100, 20.50 | +| `currency` | Required | String | Payment currency. Possible values: `AZN` | +| `description` | Optional | String | Description of the payment. Not more than 1000 characters. | + +After processing the transaction by the Epoint service and receiving the payment status from the +bank, a response will be returned with the following parameters: + +| Parameter | Description | +| :----------------- | :--------------------------------------------------------------------------------------------------- | +| `status` | Result of a `success` or `failed` operation | +| `transaction` | Epoint transaction ID | +| `bank_transaction` | Bank payment transaction | +| `bank_response` | Bank response, with the result of payment processing | +| `rrn` | Retrieval Reference Number — unique identifier of the transaction. Present only for successful transaction | +| `card_mask` | User card mask in `123456******1234` format | +| `card_name` | Name of the cardholder | +| `amount` | Amount of payment | +| `message` | Error message | + +## Cancel operations { #reverse } + +To cancel the operation, you must send a POST request to `https://epoint.az/api/1/reverse` with the +`data` and `signature` parameters. + +API call `json_string` parameters: + +| Parameter | Required | Type | Description | +| :------------ | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `language` | Required | String | Page display language. Possible values: `az`, `en`, `ru` | +| `transaction` | Required | String | Epoint transaction ID. | +| `amount` | Optional | Number | Amount of payment. For example: 100, 20.50. You can specify a partial refund. | +| `currency` | Required | String | Payment currency. Possible values: `AZN` | + +After processing, a response will be returned with the following parameters: + +| Parameter | Description | +| :-------- | :------------------------------------------ | +| `status` | Result of a `success` or `failed` operation | +| `message` | Error message | + +## Split payment request { #split-request } + +To create a split payment, you need to pass the `data` and `signature` parameters by POST method to +`https://epoint.az/api/1/split-request`. + +Parameters for the `json_string` call of the API: + +| Parameter | Required | Type | Description | +| :--------------------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `amount` | Required | Number | Amount of payment. For example: 100, 20.50 | +| `split_user` | Required | String | The ID of the second user in the Epoint system. | +| `split_amount` | Required | Number | Payment amount for the second user. For example: 100, 20.50 | +| `currency` | Required | String | Payment currency. Possible values: `AZN` | +| `language` | Required | String | Page display language. Possible values: `az`, `en`, `ru` | +| `order_id` | Required | String | Unique transaction ID in your application. The maximum length is 255 characters. | +| `description` | Optional | String | Description of the payment. Not more than 1000 characters. | +| `success_redirect_url` | Optional | String | Redirection link in case of successful payment. | +| `error_redirect_url` | Optional | String | Redirection link in case of failed payment. | +| `other_attr` | Optional | Array | Additional payment options | + +In this case, a json string will be returned with the `status` (`success|error`) value, `transaction` +and `redirect_url` to which the user must be redirected to enter card data. + +After processing the transaction, a POST request with `data` and `signature` parameters will be sent +to your server (`result_url`). Verify and decode it as described in +[Callback function processing](#callback-function-processing). + +Please note that the amount paid for the second merchant will only appear on their payment list. + +Payment result parameters: + +| Parameter | Description | +| :----------------- | :--------------------------------------------------------------------------------------------------- | +| `order_id` | Unique transaction ID in your application | +| `status` | Result of a `success` or `failed` operation | +| `code` | Bank response code | +| `message` | Payment status message | +| `transaction` | Epoint service transaction | +| `bank_transaction` | Bank payment transaction | +| `bank_response` | Bank response, with the result of payment processing | +| `operation_code` | `001` — card registration, `100` — user payment | +| `rrn` | Retrieval Reference Number — unique identifier of the transaction. Present only for successful transaction | +| `card_name` | User name specified on the payment page | +| `card_mask` | User card mask in `123456******1234` format | +| `amount` | Amount of payment | +| `split_amount` | Payment amount for the second user | +| `other_attr` | Additional parameters | + +## Executing a split payment with a stored card { #split-execute-pay } + +To pay with a saved card, you must send the `data` and `signature` parameters by POST method to +`https://epoint.az/api/1/split-execute-pay`. + +API call `json_string` parameters: + +| Parameter | Required | Type | Description | +| :------------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `language` | Required | String | Page display language. Possible values: `az`, `en`, `ru` | +| `card_id` | Required | String | The card ID received by the card saving method. | +| `order_id` | Required | String | Unique transaction ID in your application. The maximum length is 255 characters. | +| `amount` | Required | Number | Amount of payment. For example: 100, 20.50 | +| `split_user` | Required | String | The ID of the second user in the Epoint system. | +| `split_amount` | Required | Number | Payment amount for the second user. For example: 100, 20.50 | +| `currency` | Required | String | Payment currency. Possible values: `AZN` | +| `description` | Optional | String | Description of the payment. Not more than 1000 characters. | + +After processing, a response will be returned with the following parameters: + +| Parameter | Description | +| :----------------- | :--------------------------------------------------------------------------------------------------- | +| `status` | Result of a `success` or `failed` operation | +| `transaction` | Epoint transaction ID | +| `bank_transaction` | Bank payment transaction | +| `bank_response` | Bank response, with the result of payment processing | +| `rrn` | Retrieval Reference Number — unique identifier of the transaction. Present only for successful transaction | +| `card_mask` | User card mask in `123456******1234` format | +| `card_name` | User name specified on the payment page | +| `amount` | Amount of payment | +| `message` | Error message | +| `split_amount` | Payment amount for the second user | + +## Saving the card with the first split payment { #split-card-registration-with-pay } + +If you use this type of payment, you will be paid for the specified amount along with the card +registration. To call the Epoint API, you need to pass the `data` and `signature` parameters by POST +method to `https://epoint.az/api/1/split-card-registration-with-pay`. + +Parameters for the `json_string` call of the API: + +| Parameter | Required | Type | Description | +| :--------------------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `language` | Required | String | Page display language. Possible values: `az`, `en`, `ru` | +| `order_id` | Required | String | Unique transaction ID in your application. The maximum length is 255 characters. | +| `amount` | Required | Number | Amount of payment. For example: 100, 20.50 | +| `split_user` | Required | String | The ID of the second user in the Epoint system. | +| `split_amount` | Required | Number | Payment amount for the second user. For example: 100, 20.50 | +| `currency` | Required | String | Payment currency. Possible values: `AZN` | +| `description` | Optional | String | Description of the payment. Not more than 1000 characters. | +| `success_redirect_url` | Optional | String | Redirection link in case of successful payment. | +| `error_redirect_url` | Optional | String | Redirection link in case of failed payment. | + +As a result of the request, a json string with the `status` (`success|error`) value, `transaction` +and `redirect_url` to which the user needs to be redirected to enter card data will be returned, and +`card_id` — the unique identifier of the card that will need to be used to make payments. + +The customer fills in the card details and confirms payment. If the payment is successful, the client +will be redirected to `success_url`, or `error_url` otherwise (`success_redirect_url` and +`error_redirect_url` if specified). After processing, a POST request with `data` and `signature` +parameters will be sent to the `result_url` indicated by you. + +Response parameters: + +| Parameter | Description | +| :----------------- | :--------------------------------------------------------------------------------------------------- | +| `status` | Result of a `success` or `failed` operation | +| `code` | `000` — successful operation | +| `card_id` | Unique identifier of the card you want to use to make the payment | +| `order_id` | Unique identifier of the payment in your application | +| `transaction` | Epoint transaction ID | +| `bank_transaction` | Bank payment transaction | +| `bank_response` | Bank response, with the result of payment processing | +| `operation_code` | `200` — card registration with the first payment | +| `rrn` | Retrieval Reference Number — unique identifier of the transaction. Present only for successful transaction | +| `card_mask` | User card mask in `123456******1234` format | +| `card_name` | Name of the cardholder | +| `amount` | Payment amount | +| `split_amount` | Payment amount for the second user | +| `other_attr` | Advanced options | + +## Formation of API preauth request { #pre-auth-request } + +To call the Epoint API, you must send the `data` and `signature` parameters using the POST method to +`https://epoint.az/api/1/pre-auth-request`. `data` and `signature` are formed the same way as in +[Formation of API request](#formation-of-api-request). + +Parameters `json_string` of the api call: + +| Parameter | Required | Type | Description | +| :--------------------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `amount` | Required | Number | The amount of the payment. For example: 100, 20.50 | +| `currency` | Required | String | Payment currency. Possible values: `AZN` | +| `language` | Required | String | Page display language. Possible values: `az`, `en`, `ru` | +| `order_id` | Required | String | The unique ID of the transaction in your application. Maximum length 255 characters. | +| `description` | Optional | String | Payment description. No more than 1000 characters. | +| `success_redirect_url` | Optional | String | Redirection link in case of successful payment. | +| `error_redirect_url` | Optional | String | Redirection link in case of unsuccessful payment. | +| `other_attr` | Optional | Array | Additional payment options | + +### Sending a preauth request + +Send the received `data` and `signature` to `https://epoint.az/api/1/pre-auth-request`. In this case a +json string will be returned with the value of `status` (`success|error`), `transaction` and +`redirect_url` to which user should be redirected to enter card data. + +After entering the card data, the user will be redirected to `success_redirect_url` or +`error_redirect_url` depending on the payment status. Along with this, a POST request will be sent to +`result_url` with payment details and transaction status. + +### Complete preauth request + +After the transaction is processed by Epoint service and the payment status is received from the +bank, you should complete this payment, otherwise it will not be added to your Epoint balance. Until +then, it will be shown on your pending balance in the business panel. + +Everything is the same when you want to complete the preauth request, only the body data differs. +Use the endpoint `https://epoint.az/api/1/pre-auth-complete`. + +Parameters `json_string` of the api call: + +| Parameter | Required | Type | Description | +| :------------ | :------- | :----- | :------------------------------------------------------------------------------------------------------------------ | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `amount` | Required | Number | The amount of the payment. For example: 100, 20.50 | +| `transaction` | Required | String | Transaction id you got from Epoint (see [Callback function processing](#callback-function-processing)), e.g. `te001111111` | + +## Apple Pay & Google Pay { #apple-pay-google-pay } + +- **Google Pay:** for web integration. +- **Apple Pay:** for both web and app integration. + +See also: [Apple Pay & Google Pay (JS SDK)](apple-google-pay.md) — the separate EPoint document for +integrating the buttons directly into your page. + +### Create Widget Url (POST Request) + +You need to pass the `data` and `signature` to `https://epoint.az/api/1/token/widget`. + +| Parameter | Required | Type | Description | +| :------------ | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `amount` | Required | Number | Amount of payment. For example: 100, 20.50 | +| `order_id` | Required | String | Unique transaction ID in your application. The maximum length is 255 characters. | +| `description` | Required | String | Description of the payment. Not more than 1000 characters. | + +``` +data = base64_encode(fields_in_json) +``` + +Fields in json: + +```json +{ + "public_key": "your_public_key_on_epoint", + "amount": 2.50, + "order_id": "order id generated by your system", + "description": "Test payment" +} +``` + +``` +signature_string = private_key + data + private_key +signature = base64_encode(sha1(signature_string)) +``` + +Signature string: + +``` +signature_string = d3hjsl38sd8kdfhbcea0be04eafde9e8e2bad2fb092deyJwdWJsaWNfa2V5IjoiaTAwMDAwMDAwMSIsImFtb3VudCI6IjMwLjc1IiwiY3VycmVuY3kiOiJBWk4iLCJkZXNjcmlwdGlvbiI6InRlc3QgcGF5bWVudCIsIm9yZGVyX2lkIjoiMSJ9d3hjsl38sd8kdfhbcea0be04eafde9e8e2bad2fb092d +``` + +Example in PHP (Laravel): + +```php +$payload = [ + 'public_key' => 'public_key', + 'amount' => 2.50, + 'order_id' => 'order id generated by your system', + 'description' => 'Test payment', +]; + +$data = base64_encode(json_encode($payload)); +$private_key = 'your_private_key'; +$signature = base64_encode(sha1($private_key . $data . $private_key, 1)); + +$request = Http::get("https://epoint.az/api/1/token/widget", [ + 'data' => $data, + 'signature' => $signature +]); + +$response = $request->json(); +``` + +!!! note + The heading says POST, while the PHP example in the original uses `Http::get`. Integrify sends POST. + +Response: + +```json +{ + "status": "success", + "widget_url": "https://epointv1.test/api/1/token/widget/000001" +} +``` + +When payment is finished inside of the iframe or webview you can listen to the iframe message: + +```javascript +window.addEventListener('message', function(event) { + console.log(event.data); // {status: 'success', payment: {...}} +}); +``` + +## Google Pay (integration for mobile applications) { #google-pay-mobile } + +To integrate Google Pay into your mobile application, you must implement a native integration. + +1. Implement native integration following the official Google Pay documentation: + +2. Provide us with screenshots showing: + - the placement of the Google Pay button in your app interface; + - the appearance of the Google Pay button. +3. After reviewing the submitted materials, we will provide you with a Merchant ID to complete the integration. + +## Wallets { #wallets } + +The Wallet API provides the following endpoints: + +1. `https://epoint.az/api/1/wallet/status` — to retrieve the list of wallets +2. `https://epoint.az/api/1/wallet/payment` — to create a payment using a wallet + +### Wallet status + +To retrieve the list of wallets, the `public_key` parameter must be sent to +`https://epoint.az/api/1/wallet/status` using the POST method. + +| Parameter | Required | Type | Description | +| :----------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | + +!!! note + The original contains request submission and dynamic button creation examples (HTML/JS) only as + images; see the original PDF. + +### Wallet payment + +To create a payment using a wallet, the `data` and `signature` POST parameters must be sent to +`https://epoint.az/api/1/wallet/payment`. + +| Parameter | Required | Type | Description | +| :------------ | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `wallet_id` | Required | String | Selected wallet ID. | +| `amount` | Required | Number | Amount of payment. For example: 100, 20.50 | +| `currency` | Required | String | Payment currency. Possible values: `AZN` | +| `order_id` | Required | String | Unique transaction ID in your application. The maximum length is 255 characters. | +| `description` | Optional | String | Description of the payment. Not more than 1000 characters. | +| `language` | Required | String | Page display language. Possible values: `az`, `en`, `ru` | + +## Invoices { #invoices } + +To call the Invoice API, you need to pass the `data` and `signature` parameters using the POST method +to `https://epoint.az/api/1`. The Invoice API has the following endpoints: + +1. `/invoices/create` — creating an invoice +2. `/invoices/update` — updating invoice information +3. `/invoices/view` — viewing invoice information +4. `/invoices/list` — viewing information on all invoices +5. `/invoices/send-sms` — sending an SMS to the invoice recipient +6. `/invoices/send-email` — sending an email to the invoice recipient + +Creating and updating an invoice has an additional optional parameter `invoice_images[]`, which +contains images in jpg, png, jpeg, svg, bmp formats. + +### /invoices/create + +| Parameter | Required | Type | Description | +| :------------------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `sum` | Required | Number | The amount of the payment. For example: 100, 20.50 | +| `display` | Required | Number | Display invoice. Possible values: `1` or `0` | +| `save_as_template` | Required | Number | Save invoice as template. Possible values: `1` or `0` | +| `status_installment` | Optional | Number | Enables installment payments. Possible values: `1` or `0` | +| `name` | Optional | String | Name | +| `description` | Optional | String | Payment description. No more than 1000 characters. | +| `phone` | Optional | String | Phone | +| `email` | Optional | String | Email | +| `inn` | Optional | String | TIN | +| `contract_number` | Optional | String | Contract number | +| `merchant_order_id` | Optional | String | Order ID | +| `period_from` | Required | Date | Invoice start date | +| `period_to` | Required | Date | Invoice end date | + +### /invoices/update + +| Parameter | Required | Type | Description | +| :------------------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `id` | Required | Number | ID of the invoice being updated | +| `sum` | Required | Number | The amount of the payment. For example: 100, 20.50 | +| `display` | Required | Number | Display invoice. Possible values: `1` or `0` | +| `save_as_template` | Required | Number | Save invoice as template. Possible values: `1` or `0` | +| `status_installment` | Optional | Number | Enables installment payments. Possible values: `1` or `0` | +| `name` | Optional | String | Name | +| `description` | Optional | String | Payment description. No more than 1000 characters. | +| `phone` | Optional | String | Phone | +| `email` | Optional | String | Email | +| `inn` | Optional | String | TIN | +| `contract_number` | Optional | String | Contract number | +| `merchant_order_id` | Optional | String | Order ID | +| `period_from` | Required | Date | Invoice start date | +| `period_to` | Required | Date | Invoice end date | + +### /invoices/view + +| Parameter | Required | Type | Description | +| :----------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `id` | Required | Number | Invoice ID | + +### /invoices/list + +| Parameter | Required | Type | Description | +| :----------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `type` | Optional | String | Invoice type — `incoming`, `outgoing`, `static` | +| `order` | Optional | String | Sorting by ascending, descending | + +### /invoices/send-sms + +| Parameter | Required | Type | Description | +| :----------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `id` | Required | Number | Invoice ID | +| `phone` | Required | String | Phone | + +### /invoices/send-email + +| Parameter | Required | Type | Description | +| :----------- | :------- | :----- | :---------------------------------------------------------------------------------- | +| `public_key` | Required | String | The public key is the identifier of the created merchant. For example: `i000000001` | +| `id` | Required | Number | Invoice ID | +| `email` | Required | String | Email | + +## Heartbeat API { #heartbeat } + +To check the service availability, send a GET request to `https://epoint.az/api/heartbeat`. + +This endpoint is designed to verify if the service is operational. A successful response with +`status: "ok"` indicates that the service is running properly. + +## Bank Response Codes { #bank-response-codes } + +| Code | Description | +| :---- | :----------------------------------------------------------------------- | +| `000` | Confirmed | +| `100` | Rejected (general, no comment) | +| `101` | Declined, your card has expired | +| `102` | Rejected, suspected fraud | +| `103` | Rejected, cardholder will contact acquirer | +| `104` | Rejected, restricted card | +| `105` | Rejected, card receiver will contact acquirer security | +| `106` | Rejected, PIN attempts exceeded | +| `107` | Declined, please contact your card issuer | +| `108` | Declined, please refer to card issuer's special terms | +| `109` | Rejected, invalid merchant | +| `110` | Rejected, incorrect amount | +| `111` | Rejected, incorrect card number | +| `112` | Rejected, PIN required | +| `113` | Denied, inappropriate payment | +| `114` | Rejected, no account of the requested type | +| `115` | Denied, requested function is not supported | +| `116` | Declined, insufficient funds | +| `117` | Rejected, incorrect PIN | +| `118` | Rejected, no card data | +| `119` | Rejected, transaction not allowed by cardholder | +| `120` | Rejected, transaction not allowed to terminal | +| `121` | Declined, withdrawal limit exceeded | +| `122` | Rejected, safety violation | +| `123` | Declined, withdrawal limit exceeded | +| `124` | Rejected, violation of the law | +| `125` | Rejected, card not valid | +| `126` | Rejected, invalid PIN block | +| `127` | Rejected, PIN length error | +| `128` | Rejected, PIN key synchronization failed | +| `129` | Rejected, suspected fake card | +| `180` | Rejected, at the request of cardholders | +| `200` | Pick-up (general, no comment) | +| `201` | Pick-up, expired card | +| `202` | Pick-up, suspected fraud | +| `203` | Pick-up, the card receiver will contact the acquirer | +| `204` | Pick-up, restricted card | +| `205` | Pick-up, the cardholder will contact the acquirer's security department | +| `206` | Pick-up, PIN limit exceeded | +| `207` | Pick-up, special conditions | +| `208` | Pick-up, lost card | +| `209` | Pick-up, stolen card | +| `210` | Pick-up, suspected fake card | +| `300` | Status message: file action successful | +| `301` | Status message: file action not supported by recipient | +| `302` | Status message: could not find an entry in the file | +| `303` | Status message: duplicate record, old record replaced | +| `304` | Status message: file write field edit error | +| `305` | Status message: file locked | +| `306` | Status message: file action failed | +| `307` | Status message: file data format error | +| `308` | Status message: duplicate record, new record rejected | +| `309` | Status message: unknown file | +| `400` | Accepted (for cancellation) | +| `499` | Confirmed, no original message data | +| `500` | Status message: agreed, in the balance sheet | +| `501` | Status message: agreed, out of balance | +| `502` | Status message: amount not agreed, amount provided | +| `503` | Status message: amount not available for negotiation | +| `504` | Status message: not agreed, amount provided | +| `600` | Accepted (for administrative information) | +| `601` | Status message: original transaction cannot be tracked | +| `602` | Status message: invalid transaction reference number | +| `603` | Status message: link number/PANs are incompatible | +| `604` | Status message: POS photo not available | +| `605` | Status message: requested item provided | +| `606` | Status message: request failed — required documentation unavailable | +| `680` | The list is ready | +| `681` | The list is not ready | +| `700` | Accepted (for payment collection) | +| `800` | Accepted (for network management) | +| `900` | The recommendation has been taken into account, no financial obligations have been accepted | +| `901` | Recommendations taken into account, financial liability accepted | +| `902` | Rejection reason message: invalid transaction | +| `903` | Status message: re-enter transaction | +| `904` | Rejection reason message: format error | +| `905` | Deviation cause message: acquirer not supported by switch | +| `906` | Reason for deviation report: process reduction | +| `907` | Reason for rejection message: card issuer or switch not in effect | +| `908` | Rejection reason message: unable to find the destination of the routing transaction | +| `909` | Cause of deviation report: system failure | +| `910` | Reason for rejection message: card issuer disabled | +| `911` | Reason for rejection message: card issuer expired | +| `912` | Reason for rejection message: issuer unavailable | +| `913` | Deviation cause message: duplicate transmission | +| `914` | Rejection reason message: failed to track original transaction | +| `915` | Deviation cause message: failure to disable negotiation or checkpoint | +| `916` | Deviation cause message: MAC incorrect | +| `917` | Reject cause message: MAC key synchronization error | +| `918` | Rejection reason message: no binding keys available for use | +| `919` | Reject cause message: encryption key synchronization error | +| `920` | Cause of deviation message: software/hardware security error — try again | +| `921` | Deviation cause message: software/hardware security error — no action | +| `922` | Rejection reason message: incorrect message number sequence | +| `923` | Status message: in-process query | +| `950` | Reason for rejection message: business agreement violation | +| `XXX` | Code to be replaced by card status code or stop list reason code | +| `0Y1` | Confirmed, offline ICC | +| `0Y3` | Confirmed, offline ICC | +| `1Q1` | Rejected due to ICC offline mode | +| `1Z1` | Rejected due to ICC offline mode | +| `1Z3` | Rejected due to ICC offline mode | diff --git a/docs/az/docs/integrations/epoint/official/apple-google-pay.md b/docs/az/docs/integrations/epoint/official/apple-google-pay.md new file mode 100644 index 0000000..870ba62 --- /dev/null +++ b/docs/az/docs/integrations/epoint/official/apple-google-pay.md @@ -0,0 +1,291 @@ +# EPoint — Apple Pay & Google Pay (JS SDK) + +???+ info + Bu səhifə EPoint-in "Epoint.az - Apple Pay & Google Pay" sənədinin Markdown versiyasıdır + (ictimai linki yoxdur). Bu sənəd düymələrin birbaşa sizin səhifənizə, EPoint JS SDK-sı ilə + qoşulmasını izah edir. Daha sadə, iframe/webview əsaslı üsul üçün rəsmi API sənədindəki + [Apple Pay & Google Pay (widget)](api.md#apple-pay-google-pay) bölməsinə baxın. + +## Client + +To add Apple Pay and Google Pay buttons to your web page (site) or app, you can use the code below. + +HTML implementation: + +```html + + + + +``` + +## 1. Create Payment { #create-payment } + +After adding buttons to the view you need to initialize them, but before that you need to create a +tokenized payment on Epoint, so all tokenized operations will be referenced to this payment. You can +get payment information by sending a request to `/api/1/token/payment`. + +You need to add 2 parameters to the request: `data`, `signature`. + +- **data:** base64 encoded form of your json data like below. + Fields: `public_key`, `amount`, `currency`, `language`, `order_id`, `description`. + `data = base64_encode(fields_in_json)` + +Fields in json: + +```json +{ + "public_key": "your_public_key_on_epoint", + "amount": 2.50, + "currency": "AZN", + "language": "az", + "order_id": "order id generated by your system", + "description": "Test payment" +} +``` + +``` +signature_string = private_key + data + private_key +signature = base64_encode(sha1(signature_string)) +``` + +Signature string: + +``` +signature_string = d3hjsl38sd8kdfhbcea0be04eafde9e8e2bad2fb092deyJwdWJsaWNfa2V5IjoiaTAwMDAwMDAwMSIsImFtb3VudCI6IjMwLjc1IiwiY3VycmVuY3kiOiJBWk4iLCJkZXNjcmlwdGlvbiI6InRlc3QgcGF5bWVudCIsIm9yZGVyX2lkIjoiMSJ9d3hjsl38sd8kdfhbcea0be04eafde9e8e2bad2fb092d +``` + +Example in PHP (Laravel): + +```php +$payload = [ + 'public_key' => 'public_key', + 'amount' => 2.50, + 'currency' => 'AZN', + 'language' => 'az', + 'order_id' => 'order id generated by your system', + 'description' => 'Test payment', +]; + +$data = base64_encode(json_encode($payload)); +$private_key = 'your_private_key'; +$signature = base64_encode(sha1($private_key . $data . $private_key, 1)); + +$request = Http::post("https://epoint.az/api/1/token/payment", [ + 'data' => $data, + 'signature' => $signature +]); + +$response = $request->json(); +``` + +Response sample: + +```json +{ + "id": 9998887, + "transaction": "12345", + "rrn": "123", + "short_link": "a1b2", + "bank_order_id": "aaa111", + "total": 2.36, + "card_name": "Name", + "card_mask": "4000********1234", + "description": "test description", + "merchant_order_id": "111222333", + "other_attr": "other attributes", + "created_at": "2024-10-16 12:09:10", + "updated_at": "2024-10-16 15:00:48" +} +``` + +## 2. Initialize Token Payments { #initialize-token-payments } + +To initialize token payments you need to call the `initTokenPay` method with some parameters: + +- **payment**: payment information that you created and got from Epoint +- **endpoints**: you need to implement Apple and Google Pay endpoints (see below) +- **onError**: get any errors that happened while initializing or paying +- **onSuccess**: get success result after payment + +```html + +``` + +## Apple Pay implementation { #apple-pay } + +You need to implement 2 endpoints for the Epoint token pay SDK for Apple Pay: + +- **Session:** Apple Pay has an additional layer for which you must create an endpoint that gets a + session from Epoint and provides it to the Epoint token pay SDK. +- **Pay:** you need to create an endpoint for completing the payment after the session is + successfully created and your user validates the Apple Pay popup (choose card, confirm pay). + +## 3. Apple Pay Session { #apple-pay-session } + +You can get a session by sending a request to `/api/1/token/apple/session`. Create a **POST** +endpoint on your backend and just return the response from Epoint. + +You need to add 2 parameters to the request: `data`, `signature`. + +- **data:** base64 encoded form of your json data like below. + Fields: `public_key`, `origin`. + `data = base64_encode(fields_in_json)` + +Fields in json: + +```json +{ + "public_key": "your_public_key_on_epoint", + "origin": "https://yoursite.az" +} +``` + +``` +signature_string = private_key + data + private_key +signature = base64_encode(sha1(signature_string)) +``` + +Example: + +```php +$payload = [ + 'public_key' => $public_key, + 'origin' => request()->header('origin') +]; + +$data = base64_encode(json_encode($payload)); +$signature = base64_encode(sha1($private_key . $data . $private_key, 1)); + +$request = Http::post("$api_url/token/apple/session", [ + 'data' => $data, + 'signature' => $signature +]); +``` + +## 4. Apple Pay Payment { #apple-pay-payment } + +You can complete the payment by sending a request to `/api/1/token/apple/pay`. Create a **POST** +endpoint on your backend and just return the response from Epoint. + +You need to add 2 parameters to the request: `data`, `signature`. + +- **data:** base64 encoded form of your json data like below. + Fields: `public_key`, `id`, `token`, `billingContact`. + You will just add `public_key` manually; other fields will come from the request (see example). + `data = base64_encode(fields_in_json)` + +Fields in json: + +```json +{ + "public_key": "your_public_key_on_epoint", + "id": "id_from_request", + "token": "token_from_request", + "billingContact": "billingContact_from_request" +} +``` + +``` +signature_string = private_key + data + private_key +signature = base64_encode(sha1(signature_string)) +``` + +Example: + +```php +$payload = [ + 'public_key' => $public_key, + 'id' => $request->get('id'), + 'token' => $request->get('token'), + 'billingContact' => $request->get('billingContact'), +]; +$data = base64_encode(json_encode($payload)); +$signature = base64_encode(sha1($private_key . $data . $private_key, 1)); + +$request = Http::post("$api_url/token/apple/pay", [ + 'data' => $data, + 'signature' => $signature +]); +``` + +## 4.1. Google Pay Payment { #google-pay-payment } + +It is the same as with Apple Pay, just the endpoint changes. + +You can complete the payment by sending a request to `/api/1/token/google/pay`. Create a **POST** +endpoint on your backend and just return the response from Epoint. + +You need to add 2 parameters to the request: `data`, `signature`. + +- **data:** base64 encoded form of your json data like below. + Fields: `public_key`, `id`, `token`, `billingContact`. + You will just add `public_key` manually; other fields will come from the request (see example). + `data = base64_encode(fields_in_json)` + +Fields in json: + +```json +{ + "public_key": "your_public_key_on_epoint", + "id": "id_from_request", + "token": "token_from_request", + "billingContact": "billingContact_from_request" +} +``` + +``` +signature_string = private_key + data + private_key +signature = base64_encode(sha1(signature_string)) +``` + +Example: + +```php +$payload = [ + 'public_key' => $public_key, + 'id' => $request->get('id'), + 'token' => $request->get('token'), + 'billingContact' => $request->get('billingContact'), +]; +$data = base64_encode(json_encode($payload)); +$signature = base64_encode(sha1($private_key . $data . $private_key, 1)); + +$request = Http::post("$api_url/token/google/pay", [ + 'data' => $data, + 'signature' => $signature +]); +``` + +Finish payment response sample: + +```php +{ + 'status' => 'success', + 'message' => 'Payment is finished successfully', + 'result' => {}, // response from psp + 'redirect_url' => '' // optional -> sometimes not exists +} +``` diff --git a/docs/az/mkdocs.yml b/docs/az/mkdocs.yml index 7b97279..550edc9 100644 --- a/docs/az/mkdocs.yml +++ b/docs/az/mkdocs.yml @@ -125,6 +125,9 @@ nav: - Response: integrations/epoint/api-reference/response.md - Callback: integrations/epoint/api-reference/callback.md - integrations/epoint/api-reference/helper-functions.md + - Rəsmi Dokumentasiya: + - "API (v1.0.3)": integrations/epoint/official/api.md + - "Apple Pay & Google Pay (JS SDK)": integrations/epoint/official/apple-google-pay.md - Kapital Bank: - integrations/kapitalbank/index.md - integrations/kapitalbank/env.md diff --git a/packages/epoint/CHANGELOG.md b/packages/epoint/CHANGELOG.md index 36372cc..09643aa 100644 --- a/packages/epoint/CHANGELOG.md +++ b/packages/epoint/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to `integrify-epoint` are documented here. The format is bas on [Keep a Changelog](https://keepachangelog.com/) and this project follows [Semantic Versioning](https://semver.org/). +## [1.3.0] - 2026-09-22 + +### Added + +- Apple Pay & Google Pay widget: `create_widget` (`/api/1/token/widget`), returning a `widget_url` to embed in an iframe/webview. +- Apple Pay & Google Pay via EPoint's JS SDK (token payment API): `create_token_payment`, `apple_pay_session`, `apple_pay`, `google_pay`. +- `WidgetResponseSchema`, `TokenPaymentResponseSchema` and `TokenPayResponseSchema` response schemas. + ## [1.2.0] - 2026-08-11 ### Added @@ -34,6 +42,7 @@ on [Keep a Changelog](https://keepachangelog.com/) and this project follows - Initial release — refactored from the [old library](https://github.com/mmzeynalli/integrify) to the new style. +[1.3.0]: https://github.com/integrify-sdk/integrify-python/compare/epoint-1.2.0...epoint-1.3.0 [1.2.0]: https://github.com/integrify-sdk/integrify-python/compare/epoint-1.1.0...epoint-1.2.0 [1.1.0]: https://github.com/integrify-sdk/integrify-python/compare/epoint-1.0.0...epoint-1.1.0 [1.0.0]: https://github.com/integrify-sdk/integrify-python/releases/tag/epoint-1.0.0 diff --git a/packages/epoint/README.md b/packages/epoint/README.md index 74ecee0..a6f7926 100644 --- a/packages/epoint/README.md +++ b/packages/epoint/README.md @@ -65,6 +65,11 @@ Sorğular uğurlu və ya uğursuz olduqda, spesifik URL-ə yönləndirmək istə | `split_pay` | Ödənişi başqa EPoint istifadəçisi ilə bölüb ödəmə | `/api/1/split-request` | ✅ | | `split_pay_with_saved_card` | Saxlanılmış kartla ödənişi başqa EPoint istifadəçisi ilə bölüb ödəmə | `/api/1/split-execute-pay` | ❌ | | `split_pay_and_save_card` | Ödənişi başqa EPoint istifadəçisi ilə bölüb ödəmə və kartı saxlamaq | `/api/1/split-card-registration-with-pay` | ✅ | +| `create_widget` | Apple Pay/Google Pay widget-i yaratmaq | `/api/1/token/widget` | ❌ | +| `create_token_payment` | Apple Pay/Google Pay üçün token ödənişi yaratmaq | `/api/1/token/payment` | ❌ | +| `apple_pay_session` | Apple Pay session-u almaq | `/api/1/token/apple/session` | ❌ | +| `apple_pay` | Apple Pay ilə ödənişi tamamlamaq | `/api/1/token/apple/pay` | ❌ | +| `google_pay` | Google Pay ilə ödənişi tamamlamaq | `/api/1/token/google/pay` | ❌ | ### Callback Sorğusu diff --git a/packages/epoint/pyproject.toml b/packages/epoint/pyproject.toml index 304e733..20d7c14 100644 --- a/packages/epoint/pyproject.toml +++ b/packages/epoint/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "integrify-epoint" -version = "1.2.0" +version = "1.3.0" description = "Integrify API inteqrasiyalarını rahatlaşdıran sorğular kitabaxanasıdır. Bu kitabxana Epoint inteqrasiyası üçün nəzərdə tutulmuşdur." authors = [{ name = "mmzeynalli", email = "miradil.zeynalli@gmail.com" }] requires-python = ">=3.10" diff --git a/packages/epoint/src/integrify/epoint/client.py b/packages/epoint/src/integrify/epoint/client.py index 1b3cff3..29e2ae7 100644 --- a/packages/epoint/src/integrify/epoint/client.py +++ b/packages/epoint/src/integrify/epoint/client.py @@ -5,7 +5,12 @@ from integrify.api import APIClient, _Async, _Mode, _Sync from integrify.epoint import env from integrify.epoint.handlers import ( + ApplePayPayloadHandler, + ApplePaySessionPayloadHandler, + CreateTokenPaymentPayloadHandler, + CreateWidgetPayloadHandler, GetTransactionStatusPayloadHandler, + GooglePayPayloadHandler, PayAndSaveCardPayloadHandler, PaymentPayloadHandler, PayoutPayloadHandler, @@ -22,7 +27,10 @@ RedirectUrlResponseSchema, RedirectUrlWithCardIdResponseSchema, SplitPayWithSavedCardResponseSchema, + TokenPaymentResponseSchema, + TokenPayResponseSchema, TransactionStatusResponseSchema, + WidgetResponseSchema, ) from integrify.schemas import APIResponse from integrify.utils import UNSET, Unset @@ -73,6 +81,22 @@ def __init__( self.add_url('split_pay_and_save_card', env.API.SPLIT_PAY_AND_SAVE_CARD, verb='POST') self.add_handler('split_pay_and_save_card', SplitPayAndSaveCardPayloadHandler) + # Apple Pay & Google Pay + self.add_url('create_widget', env.API.CREATE_WIDGET, verb='POST') + self.add_handler('create_widget', CreateWidgetPayloadHandler) + + self.add_url('create_token_payment', env.API.CREATE_TOKEN_PAYMENT, verb='POST') + self.add_handler('create_token_payment', CreateTokenPaymentPayloadHandler) + + self.add_url('apple_pay_session', env.API.APPLE_PAY_SESSION, verb='POST') + self.add_handler('apple_pay_session', ApplePaySessionPayloadHandler) + + self.add_url('apple_pay', env.API.APPLE_PAY, verb='POST') + self.add_handler('apple_pay', ApplePayPayloadHandler) + + self.add_url('google_pay', env.API.GOOGLE_PAY, verb='POST') + self.add_handler('google_pay', GooglePayPayloadHandler) + if TYPE_CHECKING: # pylint: disable=missing-function-docstring,unused-argument @@ -497,6 +521,227 @@ def split_pay_and_save_card( ) -> Coroutine[Any, Any, APIResponse[RedirectUrlWithCardIdResponseSchema]]: ... def split_pay_and_save_card(self, *args: Any, **kwds: Any) -> Any: ... + @overload + def create_widget( + self: 'EPointClientClass[_Sync]', + amount: Numeric, + order_id: str, + description: str, + ) -> APIResponse[WidgetResponseSchema]: + """Apple Pay və Google Pay widget-i yaratma sorğusu + + **Endpoint:** */api/1/token/widget* + + Example: + ```python + from integrify.epoint import EPointRequest + + EPointRequest.create_widget(amount=2.5, order_id='12345678', description='Ödəniş') + ``` + + **Cavab formatı**: [`WidgetResponseSchema`][integrify.epoint.schemas.response.WidgetResponseSchema] + + Apple Pay/Google Pay-i qoşmağın ən sadə yolu. Cavabda gələn `widget_url`-i saytınızda + iframe, mobil tətbiqdə isə webview daxilində açın: düymələr və ödəniş axını EPoint + tərəfindən idarə olunur, əlavə backend endpoint-lərinə ehtiyac yoxdur. Ödəniş bitdikdən + sonra widget səhifəyə `message` event-i göndərir (`event.data`: + `{status: 'success', payment: {...}}`). Ödənişin nəticəsini backend-də + [`get_transaction_status`][integrify.epoint.client.EPointClientClass.get_transaction_status] + ilə yoxlamaq tövsiyə olunur. + + Düymələri öz dizaynınızla göstərmək istəyirsinizsə, + [`create_token_payment`][integrify.epoint.client.EPointClientClass.create_token_payment] + ilə başlayan SDK axınından istifadə edin. + + Args: + amount: Ödəniş miqdarı. Numerik dəyər. + order_id: Unikal ID. Maksimal uzunluq: 255 simvol. + description: Ödənişin təsviri. Maksimal uzunluq: 1000 simvol. + """ # noqa: E501 + + @overload + def create_widget( + self: 'EPointClientClass[_Async]', + amount: Numeric, + order_id: str, + description: str, + ) -> Coroutine[Any, Any, APIResponse[WidgetResponseSchema]]: ... + def create_widget(self, *args: Any, **kwds: Any) -> Any: ... + + @overload + def create_token_payment( + self: 'EPointClientClass[_Sync]', + amount: Numeric, + currency: str, + order_id: str, + description: Unset[str] = UNSET, + ) -> APIResponse[TokenPaymentResponseSchema]: + """Apple Pay/Google Pay üçün token ödənişi yaratma sorğusu + + **Endpoint:** */api/1/token/payment* + + Example: + ```python + from integrify.epoint import EPointRequest + + EPointRequest.create_token_payment(amount=2.5, currency='AZN', order_id='12345678', description='Ödəniş') + ``` + + **Cavab formatı**: [`TokenPaymentResponseSchema`][integrify.epoint.schemas.response.TokenPaymentResponseSchema] + + Apple Pay/Google Pay düymələrini işə salmazdan əvvəl, EPoint-də token ödənişi + yaradılmalıdır: bütün sonrakı token əməliyyatları bu ödənişə istinad edir. Cavabda gələn + ödəniş obyektini (ən azı `id` və məbləği) frontend-də `initTokenPay`-ə `payment` + parametri kimi ötürün. Sonra SDK sizin backend-inizdəki + [`apple_pay_session`][integrify.epoint.client.EPointClientClass.apple_pay_session], + [`apple_pay`][integrify.epoint.client.EPointClientClass.apple_pay] və + [`google_pay`][integrify.epoint.client.EPointClientClass.google_pay] sorğularını + çağıran endpoint-lərə müraciət edəcək. + + Args: + amount: Ödəniş miqdarı. Numerik dəyər. + currency: Ödəniş məzənnəsi. Mümkün dəyərlər: AZN + order_id: Unikal ID. Maksimal uzunluq: 255 simvol. + description: Ödənişin təsviri. Məcburi arqument deyil. + """ # noqa: E501 + + @overload + def create_token_payment( + self: 'EPointClientClass[_Async]', + amount: Numeric, + currency: str, + order_id: str, + description: Unset[str] = UNSET, + ) -> Coroutine[Any, Any, APIResponse[TokenPaymentResponseSchema]]: ... + def create_token_payment(self, *args: Any, **kwds: Any) -> Any: ... + + @overload + def apple_pay_session( + self: 'EPointClientClass[_Sync]', + origin: str, + ) -> APIResponse[dict]: + """Apple Pay session-u almaq sorğusu + + **Endpoint:** */api/1/token/apple/session* + + Example: + ```python + from integrify.epoint import EPointRequest + + EPointRequest.apple_pay_session(origin='https://yoursite.az') + ``` + + **Cavab formatı**: `dict` (Apple merchant session obyekti, olduğu kimi) + + Apple Pay-in əlavə təhlükəsizlik qatı var: SDK ödəniş pəncərəsini açmazdan əvvəl + sizin backend-inizdəki session endpoint-inə (məs., `/epoint/apple/session`) POST + sorğusu göndərir. Həmin endpoint bu sorğunu çağırıb, `resp.body`-ni olduğu kimi + geri qaytarmalıdır. + + Args: + origin: Ödəniş səhifəsinin açıldığı saytın origin-i (məs., `https://yoursite.az`). + Adətən daxil olan sorğunun `Origin` header-indən götürülür. + """ # noqa: E501 + + @overload + def apple_pay_session( + self: 'EPointClientClass[_Async]', + origin: str, + ) -> Coroutine[Any, Any, APIResponse[dict]]: ... + def apple_pay_session(self, *args: Any, **kwds: Any) -> Any: ... + + @overload + def apple_pay( + self: 'EPointClientClass[_Sync]', + payment_id: int | str, + token: Any, + billing_contact: Any = None, + ) -> APIResponse[TokenPayResponseSchema]: + """Apple Pay ilə ödənişi tamamlama sorğusu + + **Endpoint:** */api/1/token/apple/pay* + + Example: + ```python + from integrify.epoint import EPointRequest + + # body: SDK-nın sizin `/epoint/apple` endpoint-inə göndərdiyi JSON + EPointRequest.apple_pay( + payment_id=body['id'], + token=body['token'], + billing_contact=body.get('billingContact'), + ) + ``` + + **Cavab formatı**: [`TokenPayResponseSchema`][integrify.epoint.schemas.response.TokenPayResponseSchema] + + Session uğurla yaradıldıqdan və istifadəçi Apple Pay pəncərəsində ödənişi + təsdiqlədikdən sonra (kart seçimi, təsdiq), SDK sizin backend-inizdəki pay + endpoint-inə (məs., `/epoint/apple`) `id`, `token` və `billingContact` göndərir. + Həmin endpoint bu sorğunu çağırıb, `resp.body`-ni olduğu kimi geri qaytarmalıdır. + `public_key` avtomatik əlavə olunur. + + Args: + payment_id: Token ödənişinin IDsi (SDK sorğusundakı `id`). + token: Apple Pay token-i (SDK sorğusundakı `token`). + billing_contact: Ödəyicinin billing məlumatı (SDK sorğusundakı `billingContact`). + """ # noqa: E501 + + @overload + def apple_pay( + self: 'EPointClientClass[_Async]', + payment_id: int | str, + token: Any, + billing_contact: Any = None, + ) -> Coroutine[Any, Any, APIResponse[TokenPayResponseSchema]]: ... + def apple_pay(self, *args: Any, **kwds: Any) -> Any: ... + + @overload + def google_pay( + self: 'EPointClientClass[_Sync]', + payment_id: int | str, + token: Any, + billing_contact: Any = None, + ) -> APIResponse[TokenPayResponseSchema]: + """Google Pay ilə ödənişi tamamlama sorğusu + + **Endpoint:** */api/1/token/google/pay* + + Example: + ```python + from integrify.epoint import EPointRequest + + # body: SDK-nın sizin `/epoint/google` endpoint-inə göndərdiyi JSON + EPointRequest.google_pay( + payment_id=body['id'], + token=body['token'], + billing_contact=body.get('billingContact'), + ) + ``` + + **Cavab formatı**: [`TokenPayResponseSchema`][integrify.epoint.schemas.response.TokenPayResponseSchema] + + Apple Pay ilə eynidir, yalnız endpoint fərqlidir və session mərhələsi yoxdur. + İstifadəçi Google Pay pəncərəsində ödənişi təsdiqlədikdən sonra, SDK sizin + backend-inizdəki pay endpoint-inə (məs., `/epoint/google`) `id`, `token` və + `billingContact` göndərir. Həmin endpoint bu sorğunu çağırıb, `resp.body`-ni + olduğu kimi geri qaytarmalıdır. `public_key` avtomatik əlavə olunur. + + Args: + payment_id: Token ödənişinin IDsi (SDK sorğusundakı `id`). + token: Google Pay token-i (SDK sorğusundakı `token`). + billing_contact: Ödəyicinin billing məlumatı (SDK sorğusundakı `billingContact`). + """ # noqa: E501 + + @overload + def google_pay( + self: 'EPointClientClass[_Async]', + payment_id: int | str, + token: Any, + billing_contact: Any = None, + ) -> Coroutine[Any, Any, APIResponse[TokenPayResponseSchema]]: ... + def google_pay(self, *args: Any, **kwds: Any) -> Any: ... + EPointRequest: 'EPointClientClass[_Sync]' = EPointClientClass(sync=True) EPointAsyncRequest: 'EPointClientClass[_Async]' = EPointClientClass(sync=False) diff --git a/packages/epoint/src/integrify/epoint/env.py b/packages/epoint/src/integrify/epoint/env.py index 115f00d..8f933fa 100644 --- a/packages/epoint/src/integrify/epoint/env.py +++ b/packages/epoint/src/integrify/epoint/env.py @@ -36,6 +36,13 @@ class API(str, Enum): SPLIT_PAY_WITH_SAVED_CARD = '/api/1/split-execute-pay' SPLIT_PAY_AND_SAVE_CARD = '/api/1/split-card-registration-with-pay' + # Apple Pay & Google Pay + CREATE_WIDGET = '/api/1/token/widget' + CREATE_TOKEN_PAYMENT = '/api/1/token/payment' # nosec: B105 + APPLE_PAY_SESSION = '/api/1/token/apple/session' + APPLE_PAY = '/api/1/token/apple/pay' + GOOGLE_PAY = '/api/1/token/google/pay' + __all__ = [ 'VERSION', diff --git a/packages/epoint/src/integrify/epoint/handlers.py b/packages/epoint/src/integrify/epoint/handlers.py index 477dc3f..7fe2b87 100644 --- a/packages/epoint/src/integrify/epoint/handlers.py +++ b/packages/epoint/src/integrify/epoint/handlers.py @@ -7,7 +7,12 @@ from integrify.epoint.helpers import generate_signature from integrify.epoint.schemas.enums import TransactionStatus, TransactionStatusExtended from integrify.epoint.schemas.request import ( + ApplePayRequestSchema, + ApplePaySessionRequestSchema, + CreateTokenPaymentRequestSchema, + CreateWidgetRequestSchema, GetTransactionStatusRequestSchema, + GooglePayRequestSchema, PayAndSaveCardRequestSchema, PaymentRequestSchema, PayoutRequestSchema, @@ -24,7 +29,10 @@ RedirectUrlResponseSchema, RedirectUrlWithCardIdResponseSchema, SplitPayWithSavedCardResponseSchema, + TokenPaymentResponseSchema, + TokenPayResponseSchema, TransactionStatusResponseSchema, + WidgetResponseSchema, ) from integrify.schemas import APIResponse, _ResponseT @@ -106,3 +114,62 @@ class SplitPayWithSavedCardPayloadHandler(BasePayloadHandler): class SplitPayAndSaveCardPayloadHandler(BasePayloadHandler): req_model = SplitPayAndSaveCardRequestSchema resp_model = RedirectUrlWithCardIdResponseSchema + + +############################################################################## +# Apple Pay & Google Pay +_ERROR_STATUSES = { + TransactionStatus.ERROR.value, + TransactionStatus.SERVER_ERROR.value, + TransactionStatus.FAILED.value, +} + + +class BaseTokenPayloadHandler(BasePayloadHandler): + """Apple/Google Pay sorğuları üçün baza handler: data-ya yalnız `public_key` əlavə olunur""" + + def pre_handle_payload(self, *args, **kwds): + return {'public_key': env.EPOINT_PUBLIC_KEY} + + +class CreateWidgetPayloadHandler(BaseTokenPayloadHandler): + req_model = CreateWidgetRequestSchema + resp_model = WidgetResponseSchema + + +class CreateTokenPaymentPayloadHandler(BasePayloadHandler): + req_model = CreateTokenPaymentRequestSchema + resp_model = TokenPaymentResponseSchema + + def handle_response(self, resp: httpx.Response) -> APIResponse[_ResponseT]: + # Uğurlu cavabda `status` field-i yoxdur, ödəniş obyekti birbaşa qayıdır + api_resp: APIResponse[TokenPaymentResponseSchema] = APIPayloadHandler.handle_response( + self, resp + ) + api_resp.ok = ( + api_resp.ok + and api_resp.body.id is not None + and api_resp.body.status not in _ERROR_STATUSES + ) + return api_resp + + +class ApplePaySessionPayloadHandler(BaseTokenPayloadHandler): + req_model = ApplePaySessionRequestSchema + resp_model = dict + + def handle_response(self, resp: httpx.Response) -> APIResponse[_ResponseT]: + # Apple merchant session obyekti olduğu kimi qaytarılır (SDK-ya ötürmək üçün) + api_resp: APIResponse[dict] = APIPayloadHandler.handle_response(self, resp) + api_resp.ok = api_resp.ok and api_resp.body.get('status') not in _ERROR_STATUSES + return api_resp + + +class ApplePayPayloadHandler(BaseTokenPayloadHandler): + req_model = ApplePayRequestSchema + resp_model = TokenPayResponseSchema + + +class GooglePayPayloadHandler(BaseTokenPayloadHandler): + req_model = GooglePayRequestSchema + resp_model = TokenPayResponseSchema diff --git a/packages/epoint/src/integrify/epoint/schemas/request.py b/packages/epoint/src/integrify/epoint/schemas/request.py index 7bfff0c..3653e47 100644 --- a/packages/epoint/src/integrify/epoint/schemas/request.py +++ b/packages/epoint/src/integrify/epoint/schemas/request.py @@ -1,8 +1,9 @@ from decimal import Decimal +from typing import Any from integrify.epoint import env from integrify.schemas import PayloadBaseModel -from pydantic import Field +from pydantic import AliasChoices, Field class MinimalPaymentRequestSchema(PayloadBaseModel): @@ -66,3 +67,40 @@ class SplitPayAndSaveCardRequestSchema(BasePaymentRequestSchema): split_user: str = Field(validation_alias='split_user_id') split_amount: Decimal description: str | None = None + + +############################################################################## +# Apple Pay & Google Pay +class CreateWidgetRequestSchema(PayloadBaseModel): + amount: Decimal + order_id: str + description: str + + +class CreateTokenPaymentRequestSchema(PayloadBaseModel): + amount: Decimal + currency: str + order_id: str + description: str | None = None + + +class ApplePaySessionRequestSchema(PayloadBaseModel): + origin: str + + +class BaseTokenPayRequestSchema(PayloadBaseModel): + id: int | str = Field(validation_alias=AliasChoices('payment_id', 'id')) + token: Any + billing_contact: Any = Field( + default=None, + validation_alias=AliasChoices('billing_contact', 'billingContact'), + serialization_alias='billingContact', + ) + + +class ApplePayRequestSchema(BaseTokenPayRequestSchema): + pass + + +class GooglePayRequestSchema(BaseTokenPayRequestSchema): + pass diff --git a/packages/epoint/src/integrify/epoint/schemas/response.py b/packages/epoint/src/integrify/epoint/schemas/response.py index e03bc6f..f459a36 100644 --- a/packages/epoint/src/integrify/epoint/schemas/response.py +++ b/packages/epoint/src/integrify/epoint/schemas/response.py @@ -1,4 +1,6 @@ +from datetime import datetime from decimal import Decimal +from typing import Any from integrify.epoint.schemas.enums import Code, TransactionStatus, TransactionStatusExtended from pydantic import BaseModel, field_validator @@ -96,3 +98,87 @@ class TransactionStatusResponseSchema(BaseWithCodeSchema): class SplitPayWithSavedCardResponseSchema(BaseResponseSchema): split_amount: Decimal | None = None """İkinci istifadəçi üçün ödəniş məbləği.""" + + +################################################################# +# Apple Pay & Google Pay +class WidgetResponseSchema(BaseModel): + """`/api/1/token/widget` sorğusunun cavabı""" + + status: str + """Əməliyyatın nəticəsi: `success` və ya `error`""" + + message: str | None = None + """Xəta baş verdikdə, xəta mesajı""" + + widget_url: str | None = None + """Apple Pay/Google Pay düymələrinin olduğu widget-in URL-i. + iframe və ya webview daxilində açılmalıdır.""" + + +class TokenPaymentResponseSchema(BaseModel): + """`/api/1/token/payment` sorğusunun cavabı: EPoint-də yaradılmış token ödənişi. + Bu obyekt frontend-də `initTokenPay`-ə `payment` parametri kimi ötürülür.""" + + # if error + status: str | None = None + """Xəta baş verdikdə, əməliyyatın statusu""" + + message: str | None = None + """Xəta baş verdikdə, xəta mesajı""" + + # if success + id: int | None = None + """Token ödənişinin EPoint-dəki IDsi. Apple/Google Pay sorğularında istifadə olunur.""" + + transaction: str | None = None + """EPoint xidmətinin əməliyyat IDsi""" + + rrn: str | None = None + """Retrieval Reference Number - unikal əməliyyat identifikatoru""" + + short_link: str | None = None + """Ödənişin qısa linki""" + + bank_order_id: str | None = None + """Bank tərəfindəki sifariş IDsi""" + + total: Decimal | None = None + """Ödənişin yekun məbləği""" + + card_name: str | None = None + """Ödəniş səhifəsində göstərilən istifadəçi adı""" + + card_mask: str | None = None + """123456******1234 formatında əks edilən kart maskası""" + + description: str | None = None + """Ödənişin təsviri""" + + merchant_order_id: str | None = None + """Tətbiqinizdə unikal əməliyyat ID (sorğuda göndərdiyiniz `order_id`)""" + + other_attr: Any = None + """Əlavə göndərdiyiniz seçimlər""" + + created_at: datetime | None = None + """Ödənişin yaradılma tarixi""" + + updated_at: datetime | None = None + """Ödənişin son yenilənmə tarixi""" + + +class TokenPayResponseSchema(BaseModel): + """Apple Pay/Google Pay ödənişinin tamamlanması sorğusunun cavabı""" + + status: str + """Əməliyyatın nəticəsi. Uğurlu olduqda: `success`""" + + message: str | None = None + """Ödənişin icra statusu haqqında mesaj""" + + result: Any = None + """Ödəniş provayderindən (PSP) gələn cavab""" + + redirect_url: str | None = None + """Yönləndirmə URL-i (məs., 3DS üçün). Hər zaman gəlmir.""" diff --git a/packages/epoint/tests/mocks.py b/packages/epoint/tests/mocks.py index 792c33c..d7ddec4 100644 --- a/packages/epoint/tests/mocks.py +++ b/packages/epoint/tests/mocks.py @@ -200,3 +200,99 @@ def epoint_mock_split_pay_and_save_card_response(): 'card_id': 'cexxxxxxxxxx', }, ) + + +############################################################################## +# Apple Pay & Google Pay +@pytest.fixture(scope='package') +def epoint_mock_create_widget_response(): + return Response( + status_code=200, + json={ + 'status': TransactionStatus.SUCCESS, + 'widget_url': 'https://epoint.az/api/1/token/widget/000001', + }, + ) + + +@pytest.fixture(scope='package') +def epoint_mock_create_widget_failed_response(): + return Response( + status_code=200, + json={'status': TransactionStatus.ERROR, 'message': MESSAGE_SERVER_ERROR}, + ) + + +@pytest.fixture(scope='package') +def epoint_mock_create_token_payment_response(): + return Response( + status_code=200, + json={ + 'id': 9998887, + 'transaction': '12345', + 'rrn': '123', + 'short_link': 'a1b2', + 'bank_order_id': 'aaa111', + 'total': 2.36, + 'card_name': 'Name', + 'card_mask': '4000********1234', + 'description': 'test description', + 'merchant_order_id': '111222333', + 'other_attr': 'other attributes', + 'created_at': '2024-10-16 12:09:10', + 'updated_at': '2024-10-16 15:00:48', + }, + ) + + +@pytest.fixture(scope='package') +def epoint_mock_create_token_payment_failed_response(): + return Response( + status_code=200, + json={'status': TransactionStatus.SERVER_ERROR, 'message': MESSAGE_SERVER_ERROR}, + ) + + +@pytest.fixture(scope='package') +def epoint_mock_apple_pay_session_response(): + return Response( + status_code=200, + json={ + 'epochTimestamp': 1729080550000, + 'expiresAt': 1729084150000, + 'merchantSessionIdentifier': 'SSH...', + 'nonce': 'abc123', + 'merchantIdentifier': 'merchant.az.epoint', + 'domainName': 'yoursite.az', + 'displayName': 'Your Site', + 'signature': 'base64signature', + }, + ) + + +@pytest.fixture(scope='package') +def epoint_mock_apple_pay_session_failed_response(): + return Response( + status_code=200, + json={'status': TransactionStatus.SERVER_ERROR, 'message': MESSAGE_SERVER_ERROR}, + ) + + +@pytest.fixture(scope='package') +def epoint_mock_token_pay_response(): + return Response( + status_code=200, + json={ + 'status': TransactionStatus.SUCCESS, + 'message': 'Payment is finished successfully', + 'result': {'code': '000'}, + }, + ) + + +@pytest.fixture(scope='package') +def epoint_mock_token_pay_failed_response(): + return Response( + status_code=200, + json={'status': TransactionStatus.ERROR, 'message': MESSAGE_TRANSACTION_FAIL}, + ) diff --git a/packages/epoint/tests/test_token_pay.py b/packages/epoint/tests/test_token_pay.py new file mode 100644 index 0000000..a20c9b7 --- /dev/null +++ b/packages/epoint/tests/test_token_pay.py @@ -0,0 +1,191 @@ +import base64 +import json +from datetime import datetime +from decimal import Decimal + +import pytest +from httpx import Response +from integrify.epoint.client import EPointClientClass +from pytest_mock import MockerFixture + + +def _decode(dry_resp: dict) -> dict: + return json.loads(base64.b64decode(dry_resp['data']['data'])) + + +@pytest.fixture(scope='module') +def epoint_dry_client(): + yield EPointClientClass(dry=True) + + +def test_create_widget_payload(epoint_dry_client: EPointClientClass, mocker: MockerFixture): + mocker.patch('integrify.epoint.env.EPOINT_PUBLIC_KEY', 'pub') + resp = epoint_dry_client.create_widget(amount=2.5, order_id='123', description='Test payment') + + assert resp['url'].endswith('/api/1/token/widget') + assert resp['data']['signature'] + assert _decode(resp) == { + 'public_key': 'pub', + 'amount': '2.5', + 'order_id': '123', + 'description': 'Test payment', + } + + +def test_create_widget_request( + epoint_client: EPointClientClass, + epoint_mock_create_widget_response: Response, + mocker: MockerFixture, +): + mocker.patch('httpx.Client.request', return_value=epoint_mock_create_widget_response) + resp = epoint_client.create_widget(amount=2.5, order_id='123', description='Test payment') + + assert resp.ok + assert resp.body.widget_url == 'https://epoint.az/api/1/token/widget/000001' + + +def test_create_widget_failed_request( + epoint_client: EPointClientClass, + epoint_mock_create_widget_failed_response: Response, + mocker: MockerFixture, +): + mocker.patch('httpx.Client.request', return_value=epoint_mock_create_widget_failed_response) + resp = epoint_client.create_widget(amount=2.5, order_id='123', description='Test payment') + + assert not resp.ok + assert resp.body.widget_url is None + + +def test_create_token_payment_payload(epoint_dry_client: EPointClientClass, mocker: MockerFixture): + mocker.patch('integrify.epoint.env.EPOINT_PUBLIC_KEY', 'pub') + resp = epoint_dry_client.create_token_payment( + amount=2.5, currency='AZN', order_id='123', description='Test payment' + ) + + assert resp['url'].endswith('/api/1/token/payment') + assert resp['data']['signature'] + data = _decode(resp) + assert data['public_key'] == 'pub' + assert data['language'] + assert data['order_id'] == '123' + assert data['description'] == 'Test payment' + + +def test_apple_pay_session_payload(epoint_dry_client: EPointClientClass, mocker: MockerFixture): + mocker.patch('integrify.epoint.env.EPOINT_PUBLIC_KEY', 'pub') + resp = epoint_dry_client.apple_pay_session(origin='https://yoursite.az') + + assert resp['url'].endswith('/api/1/token/apple/session') + assert _decode(resp) == {'public_key': 'pub', 'origin': 'https://yoursite.az'} + + +@pytest.mark.parametrize( + 'method, endpoint', + [('apple_pay', '/api/1/token/apple/pay'), ('google_pay', '/api/1/token/google/pay')], +) +def test_token_pay_payload( + epoint_dry_client: EPointClientClass, + mocker: MockerFixture, + method: str, + endpoint: str, +): + mocker.patch('integrify.epoint.env.EPOINT_PUBLIC_KEY', 'pub') + token = {'paymentData': {'data': 'xxx'}} + contact = {'givenName': 'Name'} + + resp = getattr(epoint_dry_client, method)(payment_id=1, token=token, billing_contact=contact) + assert resp['url'].endswith(endpoint) + assert _decode(resp) == { + 'public_key': 'pub', + 'id': 1, + 'token': token, + 'billingContact': contact, + } + + # SDK-dan gələn body birbaşa ötürülə bilər (id/billingContact açarları ilə) + resp2 = getattr(epoint_dry_client, method)( + **{'id': 1, 'token': token, 'billingContact': contact} + ) + assert _decode(resp2) == _decode(resp) + + +def test_create_token_payment_request( + epoint_client: EPointClientClass, + epoint_mock_create_token_payment_response: Response, + mocker: MockerFixture, +): + mocker.patch('httpx.Client.request', return_value=epoint_mock_create_token_payment_response) + resp = epoint_client.create_token_payment(amount=2.5, currency='AZN', order_id='111222333') + + assert resp.ok + assert resp.body.id == 9998887 + assert resp.body.total == Decimal('2.36') + assert resp.body.created_at == datetime(2024, 10, 16, 12, 9, 10) + + +def test_create_token_payment_failed_request( + epoint_client: EPointClientClass, + epoint_mock_create_token_payment_failed_response: Response, + mocker: MockerFixture, +): + mocker.patch( + 'httpx.Client.request', return_value=epoint_mock_create_token_payment_failed_response + ) + resp = epoint_client.create_token_payment(amount=2.5, currency='AZN', order_id='111222333') + + assert not resp.ok + assert resp.body.id is None + assert resp.body.message + + +def test_apple_pay_session_request( + epoint_client: EPointClientClass, + epoint_mock_apple_pay_session_response: Response, + mocker: MockerFixture, +): + mocker.patch('httpx.Client.request', return_value=epoint_mock_apple_pay_session_response) + resp = epoint_client.apple_pay_session(origin='https://yoursite.az') + + assert resp.ok + assert resp.body['merchantSessionIdentifier'] + + +def test_apple_pay_session_failed_request( + epoint_client: EPointClientClass, + epoint_mock_apple_pay_session_failed_response: Response, + mocker: MockerFixture, +): + mocker.patch('httpx.Client.request', return_value=epoint_mock_apple_pay_session_failed_response) + resp = epoint_client.apple_pay_session(origin='https://yoursite.az') + + assert not resp.ok + + +@pytest.mark.parametrize('method', ['apple_pay', 'google_pay']) +def test_token_pay_request( + epoint_client: EPointClientClass, + epoint_mock_token_pay_response: Response, + mocker: MockerFixture, + method: str, +): + mocker.patch('httpx.Client.request', return_value=epoint_mock_token_pay_response) + resp = getattr(epoint_client, method)(payment_id=1, token='token') + + assert resp.ok + assert resp.body.status == 'success' + assert resp.body.result == {'code': '000'} + assert resp.body.redirect_url is None + + +@pytest.mark.parametrize('method', ['apple_pay', 'google_pay']) +def test_token_pay_failed_request( + epoint_client: EPointClientClass, + epoint_mock_token_pay_failed_response: Response, + mocker: MockerFixture, + method: str, +): + mocker.patch('httpx.Client.request', return_value=epoint_mock_token_pay_failed_response) + resp = getattr(epoint_client, method)(payment_id=1, token='token') + + assert not resp.ok + assert resp.body.status == 'error' diff --git a/uv.lock b/uv.lock index 246eb5f..e9127ce 100644 --- a/uv.lock +++ b/uv.lock @@ -516,7 +516,7 @@ requires-dist = [ [[package]] name = "integrify-epoint" -version = "1.2.0" +version = "1.3.0" source = { editable = "packages/epoint" } dependencies = [ { name = "integrify-core" },