Java PC端扫码支付(一)微信支付 微信支付从配置到开发
Магазин должен активировать функцию платежей через публичный аккаунт WeChat. После этого можно получить уникальный номер магазина.
APPID можно получить из раздела "Базовая конфигурация" в разработке.
Платформа мерчанта WeChat была переопределена в 2017 году. Теперь все настройки доступны только через платформу мерчанта (pay.weixin.qq.com).
Обратная связь используется для получения информации о состоянии платежа после его завершения.
Ключ API необходим для обеспечения безопасности взаимодействия с платформой WeChat Pay.
SDK WeChat предоставляет примеры использования различных методов платежей и может служить руководством при разработке собственного интерфейса платежей.
SDK следует импортировать в проект для дальнейшей работы.## 2. Разработка
(1) Система управления магазином создаёт заказ на основе выбранных товаров пользователем. (2) После подтверждения покупки пользователем, система вызывает API "Универсального заказа" для создания предварительного платежа. (3) Система WeChat Pay генерирует предварительный платежный чек и возвращает ссылку на QR-код. (4) Система управления магазином использует полученную ссылку для генерации QR-кода (используется сторонний плагин для генерации QR-кода). (5) Пользователь открывает WeChat "Сканирование" и сканирует QR-код. Клиентская часть WeChat отправляет содержимое скана в систему WeChat Pay.(6) Система WeChat Pay получает запрос от клиента, проверяет корректность ссылки и запускает процесс оплаты пользователя, требуя его согласия.
(7) Пользователь вводит пароль в клиентской части WeChat, подтверждает оплату, после чего клиентская часть передает согласие.
(8) Система WeChat Pay завершает транзакцию оплаты на основе согласия пользователя.
(9) После завершения транзакции оплаты система WeChat Pay возвращает результат транзакции клиентской части WeChat и уведомляет пользователя через SMS или сообщение WeChat.
Клиентская часть WeChat отображает страницу с результатами транзакции оплаты.
(10) Система WeChat Pay отправляет асинхронное уведомление о результате оплаты в систему управления магазином. Система управления магазином должна ответить о получении уведомления, чтобы система WeChat Pay знала, что больше не следует повторно отправлять это уведомление.
Это официальная документация. Давайте ещё раз пройдёмся по основным моментам. Разработка простого сканирования QR-кода для PC-клиента довольно проста. Основной задачей является обращение к API "Унифицированному заказу" системы WeChat Pay, отправка информации о заказе и подписи (подпись может быть сложна, особенно при тестировании, когда могут возникнуть ошибки подписи, но SDK WeChat предоставляет методы для создания подписи, так что можно использовать готовые решения или создать свои).При успешном запросе система WeChat Pay вернёт ссылку на QR-код code_url. Обратите внимание, что это ссылка системы WeChat Pay, а не сам QR-код! Это не QR-код! Это не QR-код! Сам QR-код нужно генерировать, а не просто выводить code_url на страницу!
Хорошо, давайте посмотрим на код!
Для бизнес-логики, не связанной с платежами, я создаю отдельный класс PayController
, где реализую свою логику, такую как создание бизнес-заказа, хранение заказа в базе данных, поиск информации о заказах, проверка завершенности оплаты и т.д. Логика моего бизнеса достаточно проста, поэтому она представлена здесь только для справки.
package com.xxx.controller;
import com.xxx.pojo.ProductOrders;
import com.xxx.service.ProOrdersService;
import com.xxx.util.testPay;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.*;
@Controller
public class PayController {
@Resource
private ProOrdersService proOrdersService; // Интерфейсы для работы с заказами
/**
* Бизнес-логика, не связанная с платежами
*/
}
```Этот код представляет собой пример того, как можно организовать бизнес-логику, не связанную с платежами, используя Spring MVC и MyBatis.
```markdown
* Вызов метода для создания бизнес-заказа, сохранение информации о заказе в базе данных и возврат номера заказа
*
* @param filetxt
* @return ordernum
*/
@RequestMapping(value = "getOrder.do")
@ResponseBody
public Map<String, Object> getOrder(HttpServletRequest request, @Param("filetxt") String filetxt) {
Map<String, Object> map = new HashMap<>();
// Получаем текущего пользователя
String username = (String) request.getSession().getAttribute("username");
if (username.isEmpty()) {
map.put("type", "2");
map.put("data", "Сессия пользователя истекла, пожалуйста, войдите заново");
return map;
}
// Объект заказа, используемый для хранения информации о заказе пользователя. Здесь некоторые параметры требуются для запроса API «Унифицированного заказа», когда они понадобятся, можно будет получить эти параметры по номеру заказа из базы данных.
ProductOrders productOrders = new ProductOrders();
productOrders.setUserId(username); // Пользователь
productOrders.setOrdernumber(getOutTradeNo()); // Номер заказа — это случайно сгенерированная уникальная строка из 16 символов, используемая для соответствия заказа
productOrders.setProductId("XB001"); // Продукт
int wordnum = filetxt.trim().length(); // Количество слов
productOrders.setQuantity(wordnum); // Количество
Integer pay1 = testPay.getPay1(wordnum); // Расчет стоимости
productOrders.setTotalPrice(pay1); // Общая стоимость
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // Установка формата даты
String format = df.format(new Date()); // Преобразование формата даты
}
``````markdown
productOrders.setOrdertime(format);
productOrders.setOrderDetails(filetxt); // Текст статьи
productOrders.setStatus(0);
// Установка формата детальной информации о заказе
try {
int insert = proOrdersService.insert(productOrders);
} catch (Exception e) {
System.out.println("Exception: Ошибка при добавлении заказа");
e.printStackTrace();
}
// Упаковка значений для возврата
map.put("orderid", productOrders.getOrdernumber()); // Номер заказа
return map;
}
/**
* Поиск информации о заказе
*
* @param orderid
* @return filetxt
*/
@RequestMapping(value = "selectOrder.do")
@ResponseBody
public Map selectOrder(@Param("orderid") String orderid) {
ProductOrders productOrders = this.proOrdersService.selectByOrderId(orderid);
}
Map<String, Object> map = new HashMap<>();
map.put("wordnum", productOrders.getQuantity());
map.put("totalprice", productOrders.getTotalPrice());
map.put("filetxt", productOrders.getOrderDetails());
return map;
}
/**
* Проверка статуса платежа, это метод для проверки завершения платежа, WeChat вызывает мой обратный метод после завершения платежа, чтобы изменить статус заказа в базе данных.
* Я использую этот метод для проверки завершения платежа в базе данных.
* @param orderid
*/
@RequestMapping(value = "OrderStatus.do")
@ResponseBody
public Map SelectOrderStatus(HttpServletRequest request, @Param("orderid") String orderid) {
Map<String, Object> map = new HashMap<>();
int i = this.proOrdersService.selectOrderStatus(orderid);
if (i == 1) // успешный платеж
{
map.put("type", "SUCCESS");
return map;
}
map.put("type", "FAIL");
return map;
}
/**
* Генерация случайного номера заказа длиной 16 символов
* @return key
*/
private static String getOutTradeNo() {
SimpleDateFormat format = new SimpleDateFormat("MMddHHmmss", Locale.getDefault());
Date date = new Date();
String key = format.format(date);
Random r = new Random();
key = key + r.nextInt(1000);
return key;
}
```nextInt();
key = key.replaceAll("-", "").substring(0, 15);
return key;
}
}
Логика платежа через WeChat
Логика платежа через WeChat
1. Генерация подписи, затем упаковка в формат, требуемый API "Unified Order" (список параметров). WeChat требует XML-формат.
2. Вызов API "Unified Order" WeChat, отправка упакованного списка параметров XML-формата этому API. Успешный вызов вернёт XML-ответ, который можно распарсить в нужный формат, проверить успех запроса. Если он успешен, будет возвращён код `code_url`.
3. Затем мы генерируем QR-код с `code_url` и показываем его пользователю!
4. После завершения платежа пользователем, WeChat вызывает наш обратный метод, и мы меняем статус платежа в базе данных в зависимости от полученного ответа.Параметры, возвращаемые при вызове API "Объединенного заказа"
``````markdown
публичный статический строковый WXPAYMENTACCOUNT = "xxxxxxxxxx"; //商户号 для WeChat Official Account
публичный статический строковый APIKEY = "xxxxxxxxxxx"; //Ключ безопасности платежей для WeChat Official Account
публичный статический строковый basePath = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //Адрес запроса для единой формы заказа
публичный статический строковый notify_url = "http://www.xxxxx.com.cn/wxPayCallBack.do"; //URL обратного вызова
}
WXPayController, основной контроллер
импорт com.xxx.pojo.ProductOrders;
импорт com.xxx.pojo.Products;
импорт com.xxx.service.ProOrdersService;
импорт com.xxx.service.ProductsService;
импорт com.xxx.util.GetIPAdder;
импорт com.xxx.util.QRCodeUtil;
импорт com.xxx.conf.WXpayConfig;
импорт com.github.wxpay.sdk.WXPayConstants;
импорт com.github.wxpay.sdk.WXPayConstants.SignType;
импорт org.apache.ibatis.annotations.Param;
импорт org.springframework.stereotype.Controller;
импорт org.springframework.web.bind.annotation.RequestMapping;
импорт org.springframework.web.bind.annotation.ResponseBody;
импорт javax.annotation.Resource;
импорт javax.servlet.ServletOutputStream;
импорт javax.servlet.http.HttpServletRequest;
импорт javax.servlet.http.HttpServletResponse;
импорт java.io.*;
импорт java.net.HttpURLConnection;
импорт java.net.URL;
импорт java.util.*;
статическое импорт com.github.wxpay.sdk.WXPayUtil.*;
@Controller
публичный класс WXPayController {
@Resource
приватное ProOrdersService proOrdersService; //Интерфейс операций с заказами
@Resource
private ProductsService productsService; // Interface for product operations
/**
* Main payment method used to manage the overall payment process
*/
@RequestMapping(value = "pay")
@ResponseBody
public Map<String, String> createQRCode(HttpServletRequest request, HttpServletResponse response,
@Param("orderid") String orderid) {
Map<String, String> map = new HashMap<>();
if (orderid.isEmpty()) {
map.put("type", "2");
map.put("data", "Empty order ID");
return map;
}
ServletOutputStream sos = null;
try {
String orderInfo = createOrderInfo(orderid); // Create necessary parameters for the unified order API
String code_url = httpOrder(orderInfo); // Call the unified order API
sos = response.getOutputStream();
QRCodeUtil.encode(code_url, sos); // Call the method to generate a QR code
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
/**
* Создание заказа в унифицированном формате, который представляет собой XML-строку
* @param orderId
* @return
*/
private String createOrderInfo(String orderId) throws Exception {
return createOrderInfo(orderId, 1);
}
private String createOrderInfo(String orderId, Integer productId) throws Exception {
Products products = productsService.selectByPrimaryKey(Long.valueOf(productId)); // Объект товара
ProductOrders productOrders = this.proOrdersService.selectByOrderId(orderId); // Информация о заказе
// Создание объекта заказа
Map<String, String> map = new HashMap<>();
map.put("appid", WXpayConfig.APPID); // ID публичного аккаунта
map.put("mch_id", WXpayConfig.WXPAYMENTACCOUNT); // Номер магазина
map.put("body", productOrders.getOrderDetails()); // Описание товара
map.put("nonce_str", generateUUID());
map.put("notify_url", WXpayConfig.notify_url); // Адрес уведомления
map.put("out_trade_no", orderId); // Номер заказа
map.put("spbill_create_ip", GetIPAdder.getMyIP()); // IP-адрес терминала
map.put("trade_type", "NATIVE"); // Тип транзакции
map.put("total_fee", String.valueOf(productOrders.getTotalPrice())); // Общая сумма
String sign = createSign(map, WXpayConfig.APIKEY); // Вызов метода для создания подписи
map.put("sign", sign); // Подпись
// Преобразование объекта заказа в XML-формат
String s = null;
try {
return mapToXml(map); // Метод mapToXml является частью SDK WeChat
} catch (Exception e) {
e.printStackTrace();
}
return new String(s.getBytes("UTF-8"));
} /**
* Вызов API для единой формы заказа
* @param orderInfo
* @return
*/
private String httpOrder(String orderInfo) {
String url = WXpayConfig.basePath;
try {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
// Добавление данных
conn.setRequestMethod("POST");
conn.setDoOutput(true);
BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}```markdown
buffOutStr.write(orderInfo.getBytes("UTF-8")); // Отправка данных в UTF-8 buffOutStr.flush(); buffOutStr.close();
int responseCode = conn.getResponseCode(); // Получение кода ответа if(responseCode == HttpURLConnection.HTTP_OK){ BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); String inputLine; StringBuffer response = new StringBuffer(); while ((inputLine = in.readLine()) != null) { response.append(inputLine); } in.close(); return response.toString(); } else { return ""; } } catch (IOException e) { e.printStackTrace(); } return ""; }
```markdown
getOutputStream());
buffOutStr.write(orderInfo.getBytes("UTF-8"));
buffOutStr.flush();
buffOutStr.close();
// Получаем входной поток
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
sb.append(line);
}
Map<String, String> map = xmlToMap(sb.toString());
// Возвращаемые поля многочисленны, здесь мы берем только нужные нам поля
String returnMsg = map.get("return_msg"); // Возврат сообщения
String returnCode = map.get("return_code"); // Код состояния
String resultCode = map.get("result_code"); // Бизнес-результат
String codeUrl = map.get("code_url");
// По документации WeChat, если return_code и result_code равны SUCCESS, то будет возвращен code_url
if ("SUCCESS".equals(returnCode) && "SUCCESS".equals(resultCode)) {
return codeUrl;
} else {
return null;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
``````markdown
/**
* Callback WeChat
* After a successful payment, the WeChat server calls this method to update the order status in the database
*/
@RequestMapping(value = "/wxPayCallBack.do")
@ResponseBody
public String wxPayCallBack(HttpServletRequest request, HttpServletResponse response) {
System.out.println("Callback received successfully");
try {
InputStream inStream = request.getInputStream();
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
String result = new String(outSteam.toByteArray(), "utf-8"); // Receive return information from WeChat
Map<String, String> map = xmlToMap(result);
if (("SUCCESS").equalsIgnoreCase(map.get("result_code"))) {
// После успешного возврата меняем состояние заказа
String outTradeNo = map.get("out_trade_no");
}
this.proOrdersService.updateByOrderId(outTradeNo);
} catch (Exception e) {
}
return "УСПЕШНО";
}
/**
* Создание подписи
* Этот метод скопирован из SDK WeChat, можно также самостоятельно реализовать, важно обратить внимание на преобразование в UTF-8 после создания подписи, чтобы избежать ошибки "Ошибка подписи Body UTF-8"
*
* @param data данные для подписи
* @param key ключ API
*/
public static String createSign(final Map<String, String> data, String key) throws Exception {
return createSign(data, key, SignType.MD5);
}
``````java
/**
* Создание подписи. Обратите внимание, если в данных присутствует поле sign_type, его значение должно совпадать с параметром signType.
*
* @param data данные для подписи
* @param key ключ API
* @param signType тип подписи
* @return подпись
*/
private static String createSign(final Map<String, String> data, String key, SignType signType) throws Exception {
//Создаем отсортированный map на основе правил
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WXPayConstants.FIELD_SIGN)) {
continue;
}
if (data.get(k).trim().length() > 0) { // Если значение параметра пустое, то оно не участвует в создании подписи
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
}
sb.append("key=").append(key);
//Преобразование в UTF-8
String str = new String(sb.toString().getBytes("UTF-8"));
if (WXPayConstants.SignType.MD5.equals(signType)) {
return MD5(sb.toString()).toUpperCase();
} else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
return HMACSHA256(sb.toString(), key);
} else {
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
```Если параметры запроса к интерфейсу **Создание единой заявки** корректны, а также нет ошибок при создании подписи, мы сможем успешно получить `code_url`, что позволит нам сгенерировать QR-код для оплаты пользователями.
* Генерация QR-кода через класс-инструмент `QRCodeUtil`
Используется сторонний класс zxing, здесь используется зависимость zxing, которую вы должны скачать самостоятельно
* ```java
package com.xxx.util;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.OutputStream;
import java.util.Hashtable;
import java.util.Random;
``````java
/**
* Класс для работы с QR-кодами
*
*/
public class QRCodeUtil {
private static final String CHARSET = "utf-8";
private static final String FORMAT_NAME = "JPG";
// Размер QR-кода
private static final int QRCODE_SIZE = 300;
// Ширина логотипа
private static final int WIDTH = 60;
// Высота логотипа
private static final int HEIGHT = 60;
private static BufferedImage createImage(String content, String imgPath, boolean needCompress) throws Exception {
Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>();
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
hints.put(EncodeHintType.MARGIN, 1);
BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints);
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
}
}
if (imgPath == null || "".equals(imgPath)) {
return image;
}
// Вставка изображения
QRCodeUtil.insertImage(image, imgPath, needCompress);
return image;
}
}
``````markdown
/**
* Вставка логотипа
*
* @param source
* изображение QR-кода
* @param imgPath
* путь к изображению логотипа
* @param needCompress
* требуется ли сжатие
* @throws Exception
*/
private static void insertImage(BufferedImage source, String imgPath, boolean needCompress) throws Exception {
File file = new File(imgPath);
if (!file.exists()) {
System.err.println(imgPath + " этот файл отсутствует!");
return;
}
Image src = ImageIO.read(new File(imgPath));
int width = src.getWidth(null);
int height = src.getHeight(null);
if (needCompress) { // Сжатие логотипа
if (width > WIDTH) {
width = WIDTH;
}
if (height > HEIGHT) {
height = HEIGHT;
}
Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = tag.getGraphics();
g.drawImage(image, 0, 0, null); // отрисовка сжатого изображения
g.dispose();
src = image;
}
// вставка логотипа
Graphics2D graph = source.createGraphics();
int x = (QRCODE_SIZE - width) / 2;
int y = (QRCODE_SIZE - height) / 2;
graph.drawImage(src, x, y, width, height, null);
Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
graph.setStroke(new BasicStroke(3f));
graph.draw(shape);
graph.dispose();
}
/**
* Создание QR-кода (внутренний логотип)
*
* @param content
* содержимое
* @param imgPath
* путь к логотипу
* @param destPath
* папка назначения
* @param needCompress
* требуется ли сжатие логотипа
* @throws Exception
*/
public static void encode(String content, String imgPath, String destPath, boolean needCompress) throws Exception {
BufferedImage изображение = QRCodeUtil.create_image(содержимое, imgPath, needCompress);
mkdirs(destPath);
String файл = new Random().nextInt(99999999) + ".jpg";
ImageIO.write(изображение, FORMAT_NAME, new File(destPath + "/" + файл));
}
/**
* Когда директория не существует, метод mkdirs создаёт несколько уровней директорий автоматически, отличаясь от метода mkdir (mkdir выбросит ошибку, если родительская директория не существует)
*
* @author lanyuan Email: mmm333zzz520@163.com
* @date 2013-12-11 10:16:36
* @param destPath папка назначения
*/
public static void mkdirs(String destPath) {
File файл = new File(destPath);
// когда директория не существует, метод mkdirs создаёт несколько уровней директорий автоматически, отличаясь от метода mkdir (mkdir выбросит ошибку, если родительская директория не существует)
if (!файл.exists() && !файл.isDirectory()) {
файл.mkdirs();
}
}
/**
* Создание QR-кода (внутренний логотип)
*
* @param содержимое содержимое
* @param imgPath путь к логотипу
* @param destPath папка назначения
* @throws Exception
*/
public static void encode(String содержимое, String imgPath, String destPath) throws Exception {
QRCodeUtil.encode(содержимое, imgPath, destPath, false);
}
/**
* Создание QR-кода
*
* @param содержимое
Пожалуйста, обратите внимание, что некоторые строки кода остаются без изменения согласно правилам перевода.```markdown
/**
* Создание QR-кода
* @param content содержимое
* @param destPath путь хранения
* @param needCompress сжатие логотипа
* @throws Exception
*/
public static void encode(String content, String destPath, boolean needCompress) throws Exception {
QRCodeUtil.encode(content, null, destPath, needCompress);
}
/**
* Создание QR-кода
*
* @param content содержимое
* @param destPath путь хранения
* @throws Exception
*/
public static void encode(String content, String destPath) throws Exception {
QRCodeUtil.encode(content, null, destPath, false);
}
/**
* Создание QR-кода с встроенным логотипом
*
* @param content содержимое
* @param imgPath путь логотипа
* @param output выходной поток
* @param needCompress сжатие логотипа
* @throws Exception
*/
public static void encode(String content, String imgPath, OutputStream output, boolean needCompress)
throws Exception {
BufferedImage image = QRCodeUtil.createImage(content, imgPath, needCompress);
ImageIO.write(image, FORMAT_NAME, output);
}
/**
* Создание QR-кода
*
* @param content содержимое
* @param output выходной поток
* @throws Exception
*/
public static void encode(String content, OutputStream output) throws Exception {
QRCodeUtil.encode(content, null, output, false);
}
public static void main(String[] args) throws Exception {
String text = "тест";
QRCodeUtil.encode(text, "/Users/noahshen/Downloads/6BFAADD4-256D-447B-B742-1E1DFF11094F_meitu_1.png",
"/Users/noahshen/Downloads", true);
}
Как только пользователь завершает платеж, WeChat успешно вызывает наш метод обратного вызова, и состояние заказа в базе данных меняется на «оплачен». Таким образом, основная работа Java-сервера завершена. Но как фронтенд узнает, что пользователь завершил платеж?
Сейчас наиболее распространенный способ — это использование метода поллинга для проверки состояния платежа. В течение ограниченного времени фронтенд продолжает проверять состояние платежа. Если состояние становится «оплачен», то выполняются следующие действия. Если же в течение этого времени платеж не был завершен, выполняются действия при пропущенном платеже.
Код для проверки состояния платежа уже находится в разделе «Бизнес-логика, не связанная с платежами» данного проекта.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )