В данном примере рассматривается процесс формирования ссылки на оплату, исходя из предоставленных параметров. При переходе по данной ссылке плательщик будет перенаправлен на страницу оплаты для завершения транзакции.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
package main import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io/ioutil" "net/http" "strconv" ) // Доступы к Paykeeper const server = "https://xxxxxxxxxx.server.paykeeper.ru" const user = "admin" const password = "xxxxxxxxxx" // Данные клиента const email = "" const phone = "" // Номер заказа const orderID = 9999 // Товар type Product struct { Name string `json:"name"` Price float64 `json:"price"` Quantity int `json:"quantity"` Sum float64 `json:"sum"` Tax string `json:"tax"` ItemType string `json:"item_type"` } // Получение токена func getToken(headers map[string]string) (string, error) { url := server + "/info/settings/token/" req, _ := http.NewRequest("GET", url, nil) for key, value := range headers { req.Header.Set(key, value) } resp, err := http.DefaultClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) var result map[string]interface{} if err := json.Unmarshal(body, &result); err != nil { return "", err } if token, exists := result["token"]; exists { return token.(string), nil } return "", fmt.Errorf("не удалось получить токен") } // Создание счета func createInvoice(headers map[string]string, token string, prods []Product, total float64) (string, error) { paymentData := map[string]string{ "pay_amount": fmt.Sprintf("%.2f", total), "orderid": strconv.Itoa(orderID), "service_name": fmt.Sprintf(";PKC|%s|", toJSON(prods)), "client_email": email, "client_phone": phone, "token": token, } data := toURLEncoded(paymentData) url := server + "/change/invoice/preview/" req, _ := http.NewRequest("POST", url, bytes.NewBufferString(data)) for key, value := range headers { req.Header.Set(key, value) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := http.DefaultClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() body, _ := ioutil.ReadAll(resp.Body) var result map[string]interface{} if err := json.Unmarshal(body, &result); err != nil { return "", err } if invoiceID, exists := result["invoice_id"]; exists { return invoiceID.(string), nil } return "", fmt.Errorf("ошибка при создании счета") } // Вспомогательная функция для JSON func toJSON(data interface{}) string { jsonData, _ := json.Marshal(data) return string(jsonData) } // Вспомогательная функция для URL-кодирования func toURLEncoded(data map[string]string) string { encoded := "" for key, value := range data { if encoded != "" { encoded += "&" } encoded += key + "=" + value } return encoded } // Основной процесс func main() { // Собираем массив товаров basket := []Product{ {Name: "Тортилья, 20 см, 756 г", Price: 381.0, Quantity: 5}, } var total float64 var prods []Product for _, item := range basket { sum := item.Price * float64(item.Quantity) total += sum prods = append(prods, Product{ Name: item.Name, Price: item.Price, Quantity: item.Quantity, Sum: sum, Tax: "none", ItemType: "goods", }) } // Добавляем доставку delivery := Product{ Name: "Доставка", Price: 300, Quantity: 1, Sum: 300, Tax: "none", ItemType: "service", } prods = append(prods, delivery) total += delivery.Sum // Авторизация в Paykeeper credentials := base64.StdEncoding.EncodeToString([]byte(user + ":" + password)) headers := map[string]string{ "Authorization": "Basic " + credentials, } // Получение токена и создание счета token, err := getToken(headers) if err != nil { fmt.Println("Ошибка авторизации:", err) return } invoiceID, err := createInvoice(headers, token, prods, total) if err != nil { fmt.Println("Ошибка при создании счета:", err) return } link := fmt.Sprintf("%s/bill/%s/", server, invoiceID) fmt.Println("Перейдите по ссылке:", link) } |
После перехода по ссылке link пользователь будет направлен на страницу оплаты.
На языке Go обработку запросов от PayKeeper об успешных платежах можно реализовать следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
package main import ( "crypto/md5" "encoding/hex" "fmt" "log" "net/http" "os" "time" ) func main() { http.HandleFunc("/", handleRequest) log.Println("Сервер запущен на :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } func handleRequest(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Только POST запросы разрешены", http.StatusMethodNotAllowed) return } // Фиксируем время запроса currentTime := time.Now().Format("2006-01-02 15:04:05") // Получаем данные из POST-запроса id := r.FormValue("id") sum := r.FormValue("sum") clientid := r.FormValue("clientid") orderid := r.FormValue("orderid") key := r.FormValue("key") serviceName := r.FormValue("service_name") clientEmail := r.FormValue("client_email") clientPhone := r.FormValue("client_phone") psID := r.FormValue("ps_id") batchDate := r.FormValue("batch_date") fopReceiptKey := r.FormValue("fop_receipt_key") bankID := r.FormValue("bank_id") cardNumber := r.FormValue("card_number") cardHolder := r.FormValue("card_holder") cardExpiry := r.FormValue("card_expiry") // Секретный ключ secret := "********" // Генерация MD5 хеша hashData := id + sum + clientid + orderid + secret md5Hash := md5.Sum([]byte(hashData)) expectedKey := hex.EncodeToString(md5Hash[:]) // Устанавливаем заголовок ответа w.Header().Set("Content-Type", "text/html; charset=UTF-8") // Проверяем хеш if key == expectedKey { // Генерируем ответ responseHashData := id + secret responseHash := md5.Sum([]byte(responseHashData)) response := fmt.Sprintf("OK %s", hex.EncodeToString(responseHash[:])) fmt.Fprintln(w, response) // Логируем успешный запрос logMessage := fmt.Sprintf("%s Ret=%s id=%s sum=%s key=%s, my key is %s, other params: service_name=%s, client_email=%s, client_phone=%s, ps_id=%s, batch_date=%s, fop_receipt_key=%s, bank_id=%s, card_number=%s, card_holder=%s, card_expiry=%s\n", currentTime, response, id, sum, key, expectedKey, serviceName, clientEmail, clientPhone, psID, batchDate, fopReceiptKey, bankID, cardNumber, cardHolder, cardExpiry) appendToLogFile("/tmp/paykeeperlog.txt", logMessage) } else { // Выводим сообщение об ошибке fmt.Fprintln(w, "Invalid key") } } // appendToLogFile добавляет сообщение в лог-файл func appendToLogFile(filePath, message string) { file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Printf("Ошибка при открытии лог-файла: %v", err) return } defer file.Close() if _, err := file.WriteString(message); err != nil { log.Printf("Ошибка при записи в лог-файл: %v", err) } } |
Полный перечень параметров в POST-оповещении можно посмотреть здесь.
В ответ на данный запрос должна быть отправлена строка вида:
Важно отметить, что ответ данного скрипта должен быть исключительно в указанном формате и ни байтом больше, в противном случае PayKeeper будет считать, что ваш сайт неправильно обработал оповещение об успешном платеже.
В PayKeeper возможен полный возврат платежа с помощью метода /change/payment/reverse/. Возврат могут делать только пользователи с включённой функцией возврата. Подробнее можно ознакомиться в документации JSON API. Пример запроса на полный возврат:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
package main import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" ) func main() { // Номер платежа invoiceID := "1234567890" // Доступы к Paykeeper server := "https://xxxxxxxxxx.server.paykeeper.ru" user := "admin" password := "xxxxxxxxxx" // Авторизация в Paykeeper credentials := user + ":" + password base64Credentials := base64.StdEncoding.EncodeToString([]byte(credentials)) headers := map[string]string{ "Content-Type": "application/x-www-form-urlencoded", "Authorization": "Basic " + base64Credentials, } // Получение информации о платеже infoURL := fmt.Sprintf("%s/info/invoice/byid/?id=%s", server, invoiceID) response, err := httpRequest(infoURL, "GET", headers, nil) if err != nil { fmt.Printf("Ошибка: %v\n", err) return } paymentID, ok := response["paymentid"].(string) if !ok { fmt.Println("Платеж не найден") return } // Запрос токена tokenURL := fmt.Sprintf("%s/info/settings/token/", server) tokenResponse, err := httpRequest(tokenURL, "GET", headers, nil) if err != nil { fmt.Printf("Ошибка: %v\n", err) return } token, ok := tokenResponse["token"].(string) if !ok { fmt.Println("Не удалось получить токен") return } // Запрос на возврат платежа formData := url.Values{ "id": {paymentID}, "amount": {"500"}, "partial": {"false"}, "token": {token}, } reverseURL := fmt.Sprintf("%s/change/payment/reverse/", server) reverseResponse, err := httpRequest(reverseURL, "POST", headers, []byte(formData.Encode())) if err != nil { fmt.Printf("Ошибка: %v\n", err) return } fmt.Println(reverseResponse) } func httpRequest(url, method string, headers map[string]string, data []byte) (map[string]interface{}, error) { client := &http.Client{} req, err := http.NewRequest(method, url, bytes.NewBuffer(data)) if err != nil { return nil, err } for key, value := range headers { req.Header.Set(key, value) } resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("HTTP ошибка: %s", resp.Status) } var result map[string]interface{} err = json.Unmarshal(body, &result) if err != nil { return nil, err } return result, nil } |
Менеджер перезвонит вам и расскажет про детали подключения