Внедрение быстрой интеграции платежей Alipay

Alipay

Я уже писал статью о платежах WeChat, условно говоря, оплата WeChat более хлопотна, а подача заявки на мерчант-аккаунт требует реальной квалификации. Alipay очень удобен, а документация и демонстрация достаточно полны, особенно дружественная среда песочницы, которая может реализовать быстрое развитие платежных функций.

концепция

сцена оплаты

Платежи Alipay поддерживают различные сценарии оплаты, включая оплату лицом к лицу, оплату через приложение, оплату через мобильную веб-страницу и т. д. Эта статья была оплачена лицом к лицу в качестве примера, чтобы проиллюстрировать

Оплата лично - оплата скан-кодом

Введение в сценарий: покупатель завершает платеж, используя функцию «сканирования» Alipay, сканируя QR-код продавца и т. д.

Процесс оплаты: отобразить QR-код -> отсканировать код для оплаты -> дождаться обратного звонка -> изменить статус заказа -> регулярная сверка

Сценарии применения

Пользователь открывает функцию сканирования в Alipay и сканирует QR-код, отображаемый продавцом, для оплаты. Этот режим подходит для оплаты в офлайн-магазине, оплаты лицом к лицу, торговых автоматов самообслуживания и других сценариев. Процесс показан на рисунке ниже:

image

Инструкции по применению

  1. Кассир управляет кассовой системой продавца, чтобы сгенерировать заказ Alipay и сгенерировать QR-код.
  2. Пользователь входит в систему Alipay и нажимает «Сканировать» на главной странице или нажимает «Оплата» — «Платеж со скан-кодом», чтобы войти в интерфейс скан-кода.
  3. Пользователи сканируют двухмерный код, предоставленный кассиром, проверяют сумму, подтверждают платеж.
  4. Пользователь платит через Alipay, сообщая об успехе или неудаче, а кассовая система продавца получает результат об успешности или неудаче платежа.

Смотрите ссылку для получения дополнительных инструкций:Документация по разработке платежей Alipay

начать разработку

Подготовить

  • скачать SDK, sdk&demo предоставляет базовый интерфейс платежной функции

  • Импортируйте проект, создайте новый платежный проект Alipay и скопируйте проект.

(ввести пример)

  • основные зависимости
<!-- 支付宝支付 -->
<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>3.3.87.ALL</version>
    <exclusions>
        <exclusion>
            <artifactId>commons-logging</artifactId>
            <groupId>commons-logging</groupId>
        </exclusion>
    </exclusions>
</dependency>
<!--gson-->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.5</version>
</dependency>
<!--zxing-->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.2.1</version>
</dependency>

Настроить платежную информацию

То есть настройте информацию о параметрах в zfbinfo.properties. Поскольку у меня нет официальной учетной записи, в качестве примера было помещено в песочницу следующее.

процесс настройки

  • Войтисреда песочницы
  • appidа такжеopen_api_domain: Appid копирует соответствующее содержимое информационной конфигурации и заменяет его

  • pid: Войти в личный кабинет, ID аккаунта в основной информации аккаунта - pid

  • Настройте открытые и закрытые ключи rsa

  • Загрузите указанный инструмент шифрования,Загрузка и использование инструмента сборки

  • Откройте приложение и нажмите «Сгенерировать ключ».

  • private_key: Скопируйте «приложение закрытый ключ» в Private_key

  • public_key: Скопируйте «открытый ключ приложения» в public_key.

  • Скопируйте открытый и закрытый ключи в песочницу

  • alipay_public_key: Скопируйте открытый ключ Alipay в alipay_public_key.

  • Остальные остаются прежними

проверка кода

  • Загрузите SDK, настройте zfbinfo.properties, запустите метод main() файла main.java и протестируйте код.

индивидуальный бизнес

переписывание кода

  • Обратитесь к способу оплаты в main.java и перепишите оплату лицом к лицу
public String f2fPayTradePay(@RequestBody TradePaySummary tradePaySummary) {
    // 订单列表
    List<TradePaySummary.Item> items = tradePaySummary.getItems();
    //支付二维码的访问路径
    String qrCodePath = null;

    // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
    // 需保证商户系统端不能重复,建议通过数据库sequence生成,
    String outTradeNo = "flow" + System.currentTimeMillis() + (long) (Math.random() * 10000000L);

    // (必填) 订单标题,粗略描述用户的支付目的。如“xxx品牌xxx门店消费”
    String subject = tradePaySummary.getSubject();

    // (必填) 订单总金额,单位为元,不能超过1亿元
    // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
    String totalAmount = items.stream().map(item -> new BigDecimal(item.getPrice())).reduce(new BigDecimal("0"), BigDecimal::add).toString();

    // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
    // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
    String undiscountableAmount = "0.0";

    // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
    // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
    String sellerId = "";

    // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品3件共20.00元"
    String body = String.format("购买商品%s件共%s元", items.size(), totalAmount);

    // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
    String storeId = tradePaySummary.getStoreId();

    // 业务扩展参数,目前可添加由支付宝分配的系统商编号(通过setSysServiceProviderId方法),详情请咨询支付宝技术支持
    String providerId = "2088100200300400500";
    ExtendParams extendParams = new ExtendParams();
    extendParams.setSysServiceProviderId(providerId);

    // 支付超时,线下扫码交易定义为5分钟
    String timeoutExpress = "15m";

    // 商品明细列表,需填写购买商品详细信息,
    List<GoodsDetail> goodsDetailList = new ArrayList<>();
    // 创建一个商品信息,参数含义分别为商品id(使用国标)、名称、单价(单位为分)、数量,如果需要添加商品类别,详见GoodsDetail
    List<GoodsDetail> instances = items.stream().map(item -> {
        long price = new BigDecimal(item.getPrice()).multiply(new BigDecimal(100)).longValue();
        GoodsDetail instance = GoodsDetail.newInstance(item.getGoodsId(), item.getGoodsName(), price, item.getQuantity());
        return instance;
    }).collect(Collectors.toList());
    // 创建好一个商品后添加至商品明细列表
    goodsDetailList.addAll(instances);

    // 创建支付请求builder,设置请求参数
    AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
            .setSubject(subject).setTotalAmount(totalAmount).setOutTradeNo(outTradeNo)
            .setUndiscountableAmount(undiscountableAmount).setSellerId(sellerId).setBody(body)
            .setOperatorId(tradePaySummary.getOperatorId()).setStoreId(storeId).setExtendParams(extendParams)
            .setTimeoutExpress(timeoutExpress)
            // 支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
            .setNotifyUrl(tradePayProperties.getPaySuccessCallBack())
            .setGoodsDetailList(goodsDetailList);

    // 调用tradePay方法获取当面付应答
    AlipayF2FPrecreateResult result = alipayTradeService.tradePrecreate(builder);
    switch (result.getTradeStatus()) {
        case SUCCESS:
            log.info("支付宝支付成功: )");
            AlipayTradePrecreateResponse response = result.getResponse();
            dumpResponse(response);
            // 生成二维码保存路径,创建二维码
            String filePath = String.format(tradePayProperties.getAliPayPath() + "/qr-%s.png", response.getOutTradeNo());
            // 本地绝对路径:/Users/yc/temp/qr-code/alipay/qr-123.png
            String storePath = tradePayProperties.getStorePath() + filePath;
            ZxingUtils.getQRCodeImge(response.getQrCode(), 256, storePath);
            // http请求相对路径:http://localhost:9150/static/qrcode/alipay/qr-123.png
            qrCodePath = tradePayProperties.getHttpBasePath() + filePath;
            break;

        case FAILED:
            log.error("支付宝支付失败!!!");
            break;

        case UNKNOWN:
            log.error("系统异常,订单状态未知!!!");
            break;

        default:
            log.error("不支持的交易状态,交易返回异常!!!");
            break;
    }
    return qrCodePath;
}

Путь с QR-кодом

  • конфигурация пути
trade:
  ali-pay:
    qrcode:
      store-path: /Users/yc/temp/qr-code/alipay # 本地文件绝对路径路径
      http-base-path: /static/qrcode/alipay # http访问的url路径,对应store-path
    callback-url: http://chetwhy.free.idcfengye.com/ali-pay/f2f/callback # 支付宝支付的回调地址
  • чтение конфигурации
@Component
@ConfigurationProperties(prefix = "trade.ali-pay")
@Data
public class AliPayTradeProperties {

    /**
     * 二维码
     */
    private AliPayTradeProperties.Qrcode qrcode;
    /**
     * 支付成功回调页面
     */
    private String callbackUrl;

    @Data
    public static class Qrcode{
        /**
         * qrcode存储文件路径设置
         */
        private String storePath;
        /**
         * http访问路径设置
         */
        private String httpBasePath;
    }
}
  • Конфигурация сопоставления пути к ресурсу
@Configuration
public class ResourceConfig implements WebMvcConfigurer {

    @Autowired
    private TradePayProperties tradePayProperties;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        String os = System.getProperty("os.name");
        // 将http请求路径映射到本地路径
        registry.addResourceHandler(tradePayProperties.getHttpBasePath() + "/**")
                .addResourceLocations("file:" + tradePayProperties.getStorePath() + "/");
    }
}
  • иллюстрировать

store-path: абсолютный путь, по которому QR-код хранится локально на сервере.

http-base-path: сетевой путь сервера для доступа к QR-коду.

ResourceConfig: после настройки путь http-base-path запроса http можно напрямую сопоставить с локальным путем к хранилищу, например, доступ к http:localhost:8080/static/qrcode/alipay/qr-123.png, то есть , обращаясь к /Users/yc/temp/qr-code/alipay/qr-123.png, все они указывают на один и тот же ресурс

обратный вызов платежа

Когда QR-код возвращается, пользователь сканирует QR-код и платит. Это воспринимаемое пользователем поведение, как мы узнаем, что платеж был завершен. В дополнение к активному запросу (см. main.java) рекомендуется использовать метод обратного вызова платежа, чтобы фон Alipay мог активно уведомлять фон продавца.

Проникновение в интранет

Alipay сам по себе является служебным приложением в общедоступной сети.Чтобы уведомить Alipay на нашем фоне, нам нужно доменное имя, к которому общедоступная сеть может получить доступ в фоновом режиме. При локальной отладке вы можете использовать инструмент проникновения в интрасеть, чтобы иметь прямой доступ к локальной сети, обратившись к указанному общедоступному доменному имени.

Уведомление обратного вызова — это асинхронное уведомление, которое отличается от бэкэнда запроса сокета и поддерживает длинную связь с удаленным сервером.

Ngrok CC

Раньше в платежах WeChat в качестве инструмента проникновения во внутреннюю сеть использовалось natapp. Используя пример ngrok cc на этот раз, оба являются хорошими инструментами.

Я не буду приводить здесь краткое описание использования ngrok, если вам интересно, вы можете ознакомиться с ним.Учебник на официальном сайте

Общие шаги:

  1. Регистрация Аккаунта;

  2. После входа в систему (без туннеля) нажмите «Управление туннелем» — «Открыть туннель» и выберите свободный;

  3. Управление туннелем - отредактируйте, настройте локальный порт, этот порт является портом, с которого запускается платежное приложение

  4. скачатьклиент

  5. Распаковать локалку, войти в клиент

  6. Скопируйте идентификатор туннеля, откройте терминал в текущем каталоге и введите сценарий запуска.

    ./sunny clientid 隧道id
    

интерфейс обратного вызова

Справочная документация

@Slf4j
@RestController
@RequestMapping("/ali-pay/f2f")
public class AliPayF2FController {
    @Autowired
    private TradeService tradeService;

    /**
     * 支付宝支付回调,根据文档:
     * 1.支付宝是用 POST 方式发送通知信息
     * 2.在通知返回参数列表中,除去sign、sign_type两个参数外,凡是通知返回回来的参数皆是待验签的参数。将剩下参数进行 url_decode, 然后进行字典排序,组成字符串,得到待签名字符串
     * 3.程序执行完后必须通过 PrintWriter 类打印输出"success"(不包含引号)。如果商户反馈给支付宝的字符不是 success 这7个字符,支付宝服务器会不断重发通知,直到超过 24 小时 22 分钟。一般情况下,25 小时以内完成 8 次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h)
     *
     * @return
     */
    @PostMapping("/callback")
    public void aliPayCallback(HttpServletRequest request, HttpServletResponse response) {
        log.info("接受支付宝回调");
        Enumeration<String> names = request.getParameterNames();
        Map<String, String> map = new TreeMap<>();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            String value = request.getParameter(name);
            log.info("parameter name->{},value->{}", name, value);
            if (!"sign_type".equalsIgnoreCase(name)) {
                map.put(name, value);
            }
        }
        // 异步返回结果验签
        try {
            boolean checkFlag = AlipaySignature.rsaCheckV2(map, Configs.getAlipayPublicKey(), "utf-8", Configs.getSignType());
            PrintWriter writer = response.getWriter();
            if (checkFlag) {
                writer.print("success");
            } else {
                writer.print("unSuccess");
            }
        } catch (AlipayApiException | IOException e) {
            throw new CustomException(ResultCodeEnum.ALIPAY_CALLBACK_CHECK_FAILED);
        }
    }
  
  // 其他业务接口...
}

Текущий путь доступа к mvc:/ali-pay/f2f/callback, после добавления доменного имени хоста в ngrok будет:Отступление почему.free.IDCMaple Leaf.com/Ali-боюсь есть/Send 2...

Оплатить тест

платежный интерфейс

/**
 * 支付宝支付
 * @return
 */
@PostMapping("/ali-pay")
public ResponseEntity<R> doOrder() {
    log.info("测试支付宝支付...");
    TradePaySummary summary = new TradePaySummary();
    summary.setSubject("测试支付宝当面付");
    summary.setOperatorId("yc");
    summary.setStoreId("yc");
    TradePaySummary.Item item = new TradePaySummary.Item();
    item.setGoodsId("1001");
    item.setGoodsName("测试商品");
    item.setPrice("100000");
    item.setQuantity(1);
    summary.setItems(Arrays.asList(item));
    String qrcode = aliPayFeignApi.f2fPayTradePay(summary);
    if (StringUtils.isEmpty(qrcode)) {
        return R.error().message("网络繁忙").buildResponseEntity();
    }
    // TODO 保存订单,更新状态
    return R.ok().message("测试成功").data("qrcode",qrcode).buildResponseEntity();
}

процесс тестирования

  • Заказать версию песочницыapp

  • Интерфейс запроса QR-кода

  • Нажмите на путь QR-кода, чтобы получить изображение

  • Используйте приложение для песочницы, чтобы отсканировать код для оплаты, пароль для оплаты по умолчанию — 111111, вы можете проверить деталиучетная запись песочницы(помните, что это приложение-песочница)

PS: Платформа-песочница может свободно пополнять текущий счет крупной суммой платежа и разоряться. . .


Подробный исходный код см. по адресу:GitHub.com/retreat, почему/есть…