Серия «От нуля до удовольствия»: практичное платежное приложение WeChat WeChat авторизует вход в систему и украшает страницу заказа, создает интерфейс заказа и инициирует запрос заказа
Серия «От нуля до удовольствия»: практичное платежное приложение WeChat WeChat авторизует вход в систему и украшает страницу заказа, создает интерфейс заказа и инициирует запрос заказа

1. Предисловие

Добро пожаловать в этот блог!

Эта статья познакомит вас с новой главой оплаты WeChat в мини-программе Uniapp. Являясь одним из пионеров в области мобильных платежей, WeChat Pay продолжает развиваться и внедрять инновации, чтобы предоставить пользователям и разработчикам более удобный и безопасный способ оплаты. В этой статье мы углубимся в применение и преимущества WeChat Pay в мини-программе Uniapp.

С бурным развитием мобильного Интернета Мини программа стала одной из важных платформ для предоставления пользователями информации и приобретения товаров. Wechat платит как Мини Незаменимый метод оплаты программсередина не только предоставляет пользователям быстрый и удобный метод оплаты, но и создает больше возможностей продаж для продавцов. существует этот фон Вниз, WeChat постоянно контролирует свое существование Мини программа Uniapp Сквозная интеграция для удовлетворения потребностей в платежах в различных сценариях.

В этой статье мы обсудим этапы доступа к WeChat Pay в мини-программе Uniapp и представим различные платежные функции, которые она предоставляет. Мы также проведем углубленное исследование мер безопасности WeChat Pay, чтобы гарантировать сохранность платежной информации пользователей. и средства находятся в безопасности.

будь тыРазработка мини-программыили владелец бизнеса,Или читатели, интересующиеся мобильными технологиями,Эта статья предоставит вам информацию осуществовать Мини программа Uniapp Практические знания и навыки для интеграции платежей WeChat. Давайте вместе исследуем новую главу WeChat Pay и создадим более удобную и безопасную платежную среду для пользователей.

💗 На этот раз речь пойдет о точках знаний по интерфейсу. Если вы не поняли предыдущий абзац, вы можете пойти на склад и скопировать его прямо для использования. Если у вас есть какие-либо вопросы, вы можете оставить сообщение в области комментариев. Я отвечу вам как можно скорее. Следуйте за мной, чтобы не потеряться. Если эта статья вам поможет, или если у вас есть какие-либо вопросы, оставьте сообщение в области комментариев, я обычно отвечу. увидеть это. Пожалуйста, поставьте лайк и поддержите~ 💗

В этом проекте используется стек технологий

Серверная часть: SpringBoot3.1.x, Mysql8.0, MybatisPlus.

Интерфейс: Vue3, Vite, ElementPlus.

Мини-программы: Uniapp, UviewPlus, Vue3.

Посмотреть демонстрационный адрес: Мини-программа на стороне WEB. См. область комментариев. Здесь нельзя размещать ссылки.

Эта статья содержит около 30 000 слов и напечатана вручную. Надеюсь, она вам поможет. Если вам понравилось, пожалуйста, поставьте лайк и добавьте в избранное. Спасибо.

🌊 Подпишитесь на меня, чтобы не потеряться. Если эта статья вам полезна или у вас есть какие-либо вопросы, оставьте сообщение в области комментариев, я обычно отвечу, когда увижу ее. Пожалуйста, поставьте лайк и поддержите~ 💗

1. Анализ требований к прототипу входа в систему

Вы можете увидеть, что вам нужно войти в систему, как только вы его откроете. Возможно, у студентов есть вопросы🤔️ Почему вы можете напрямую использовать WeChat Pay на ПК, не делая этого?

Если у вас есть сомнения, просто откройте документ и посмотрите, почему.

Платежные документы продавца WeChat: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml

Введите картинку иллюстрировать
Введите картинку иллюстрировать

Вы должны передать openId, чтобы играть и платить, так где я могу получить эту вещь? Вы можете только войти в систему\? ⚠️Примечание: после завершения функции входа на страницу заказа будет добавлена ​​всплывающая функция входа в систему\ ⚠️Примечание: после завершения функции входа на страницу заказа добавляется всплывающая функция входа в систему\ ⚠️Примечание: после завершения функции входа в систему на странице заказа будет добавлена ​​всплывающая функция входа в систему.

2. Анализ требований к прототипу страницы заказа

Вы можете видеть, что страницы здесь не совсем одинаковые, они могут быть только одинаковыми.

Он разделен на три области: верхнюю, среднюю и нижнюю. Код на стороне ПК имеет одинаковый стиль. Мы можем напрямую скопировать файл CSS на стороне ПК, используя метод CV.

Но для тех студентов, которые никогда не видели ПК, я просто выложил файл стиля. См. ниже. глобальный стиль

Введите картинку иллюстрировать
Введите картинку иллюстрировать

3. Глобальный стиль

Сначала мы устанавливаем глобальное дополнение стиля в App.vue.

Установите отступ на 10 резиновых

Язык кода:css
копировать
<style lang="scss">
                  .app-container {
                      padding: 10px !important;
                      box-sizing: border-box !important;
                  }
</style>

Статический стиль файла

существовать static Добавлено в папку css папка\

Добавьте новый глобальный файл пользовательского стиля. global.scss

Язык кода:scss
копировать
#index {
  margin-left: auto;
  margin-right: auto;
  padding: 0 10px 80px 10px;
  box-sizing: border-box;
}

.comm-title {
  overflow: hidden;
  clear: both;
  margin: 40px 0 30px;
}

#footer {
  background-color: #323232;
  border-top-width: 5px;
  border-top-style: solid;
  color: #999;
  width: 100%;
  overflow: hidden;
  padding-top: 30px;
}

.clear {
  clear: both;
  display: block;
  overflow: hidden;
  visibility: hidden;
  width: 0;
  height: 0;
}

#index .content {
  padding: 10px;
  box-sizing: border-box;
  box-shadow: 0 4px 30px #a5a8abcc;
  text-align: center;
  display: flex;
  flex-flow: wrap;
  justify-content: space-between;
}

#index .item {
  margin: 10px;
}

#index .orderBtn {
  position: relative;
  border: 1px solid #f3e2c6;
  background-color: #ffffff;
  color: #ff8686;
  font-weight: bold;
  border-radius: 5px;
  width: 140px;
  height: 50px;
  line-height: 50px;
  font-size: 15px;
  display: inline-block;
  text-align: center;
  text-decoration: none;
}

.current {
  border-color: #ff8686 !important;
}

.current:after {
  content: "";
  display: block;
  position: absolute;
  right: -1px;
  bottom: -1px;
  width: 28px;
  height: 28px;
  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAYAAACohjseAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAOKADAAQAAAABAAAAOAAAAAANV2hTAAADnUlEQVRoBd3ZzUsUYRwH8O/zzK6llaZkm5mkWdEhN4KEgqKkDhpU9KJpNy/hKch/QK9BdRM7SAQeSnujDgUWXqKgyBBqISKDlswwETXzbZ15eubRGbZ1dp3dmdndmQd255nnZef34Tf7G2UBD7fIta3txKs+FcfA2jwJ1HBq8jwHjMZ5DhiL8xTQCOcZYDycJ4CJcK4HroZzNdAMzrVAszhXApPBuQ6YLM5VwFRwrgGminMF0Aou64FWcVkNtAOXtUC7cFkJtBOXdUC7cVkFdAKXNUCncFkBdBKXcaDTuIwC04HLGDBduIwA04lLOzDduLQCM4FLGzBTuLQAM4lTgZL65lRzGkerWyCduQX41oL9eGvIcOzXJcdxNe2Qqi8LFPv7G4sd+wyBPsNRi4OO4giFVHsdtKpRj1IZ7Nb7sR3bM+gojvohne4E3X1Sd8ivb0Dhr3jNVqCjOH8upLO3QcuPCgtjDEp/G5SBrng2MW4b0BRuTQEwP5kwIMNJvk+60A1aekBMM0WG/LwVLHTfcHn0oC1V1AxOqrsJ6VQnSH4p2NcX0TEk7q8rhu9iL2jJUhFhi/OQn7aAfX6SeN/yrOUiYwanXotsPwxCCEiwiZ9QkQGAJQ4yfxvH9YAUVoh1bGEG8uNmsO+vEu+LmrWUQbM49Xpsbgp0V624NAnsBdlQwjPZFxVKTLdoJ3xND0EKysQEm5uA3NsU93kXs1s/TRmYDE5cbTQEzIyDVh4XpyRQBbI+ADa08nYlgSB8jb18frNYy6ZHsXivHhj9qAdutpMSMGnccjTs1yAwNwm6o0aMkC1BIK8Y7NtLPV5SdhBS/V2Q3I1ijE2EOe48MD6kr0mmkzQwVZwWFBv5wCvpH9CKY2JIFI/cIo7sB6k8AencHZCcPDHHxr4s4aaGte1JH5MCWsVp0bGfA0BkRn+m0ZL9IPxFD10BkXLEMmVkEHJPA7+tx7RtKR1NA+3CaVGy4fcAL/m0/IgYUisl4dVVbUr4DeQHl3imp8S5lTdTQLtxWsBs+B0gR0D5I0RrCq+s8qNmjp/VhiwdVwU6hdOiFv/mRGZBNu2B8qkHyrOrPIURbdryMeGfak7jLEe/ygdw3PTSTW+w0O04lcQIwoZAL+BUIGG0b8Ut6hkc/yb7/P7gfxn0Ck5kj5BO0hoO6UCP4fql8sJWFSqAXsHx75tCCenwlRfWkYbQgsik23Hqo0CtlmpB8fmlLvW2VGFa+wcOncY5YWRXPQAAAABJRU5ErkJggg==) no-repeat;
  background-size: 28px 28px;
}


.PaymentChannel_title {
  position: relative;
  display: flex;
  padding-left: 18 rpx;
  margin: 10px 0;
}

.PaymentChannel_title:before {
  content: "";
  display: block;
  position: absolute;
  left: 0;
  top: calc(50% - 10px);
  width: 4px;
  height: 18px;
  background: #fa8919;
  border-radius: 0 4px 4px 0;
}

.payButtom {
  margin: 30px 0;
  display: flex;
  justify-content: space-around;
}

Добавлен стиль HTML по умолчанию для очистки файлов стилей. reset.scss

Язык кода:scss
копировать
@charset "utf-8";
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;vertical-align:baseline;background:transparent}body{font-size:12px;line-height:160%;font-family:"Helvetica Neue",\5FAE\8F6F\96C5\9ED1,"SimHei",Tohoma;word-break:break-all;word-wrap:break-word;position:relative}ol,ul,li{list-style:none}blockquote,q{quotes:none}table{border-collapse:collapse;border-spacing:0;empty-cells:show}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}:focus{outline:0}ins,s{text-decoration:none}del{text-decoration:line-through}em,i{font-style:normal}a,img{border:0;text-decoration:none}a{text-decoration:none}a:hover{text-decoration:underline}a:focus{outline:0;-moz-outline:0}a:active{outline:0;blr:expression(this.onFocus=this.blur())}h1{font-size:36px;line-height:45px;font-weight:normal}h2{font-size:24px;line-height:30px;font-weight:normal}h3{font-size:18px;line-height:22px;font-weight:normal}h4{font-size:16px;line-height:20px;font-weight:normal}h5{font-size:14px;line-height:18px;font-weight:normal}h6{font-size:12px;line-height:16px;font-weight:normal}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}

Теперь у нас есть два файла. Если в страницу введут один-два, то хорошо, а если больше, то будет очень хлопотно и некрасиво.

Мы создаем один index.scss Файлы используются для управления Просто импортируйте файл, а затем

Обратите внимание на имя таблицы стилей.

Язык кода:css
копировать
@import 'reset';
@import 'global';

Внедрить глобальное использование

Исправлять main.js Просто поместите Вниз сторону существования публичного CSS

глобальный стиль
глобальный стиль

4. Построение страницы заказа

Введите картинку иллюстрировать
Введите картинку иллюстрировать

ок, давайте продолжим смотреть, как играть. Вы можете видеть, что рядом с динамиком вверху есть прокрутка текста \.

Зайди в библиотеку компонентов и посмотри

Прокручивающиеся уведомления 📢

Введите картинку иллюстрировать
Введите картинку иллюстрировать
Язык кода:html
копировать
<u-notice-bar color="red" text="В этом случае используетсяJSAPIРежим вытягивания WeChatплатить Всплывающее окно пользователя для WeChatплатитьдействовать"></u-notice-bar>
Введите картинку иллюстрировать
Введите картинку иллюстрировать

Мама моя, это то же самое, что и ElementPlus, так легко радоваться, а об остальном больше не скажу!

Используйте пользовательские стили

Язык кода:html
копировать
<template>
    <view class="app-container">
        <view id="index" class="container">
            я индекс
        </view>
    </view>
</template>
Введите картинку иллюстрировать
Введите картинку иллюстрировать

Следуйте за компьютером и ешьте мясо🥩

Остальное как на ПК, просто вылизываем

⚠️ Все коды Вниз существуют. view id="index" среди

Установить содержимое области заголовка

Язык кода:html
копировать
<u-notice-bar color="red" text="В этом случае используетсяJSAPIРежим вытягивания WeChatплатить Всплывающее окно пользователя для WeChatплатитьдействовать"></u-notice-bar>

<view class="PaymentChannel_title">
    <u-tooltip text="Сайт личного блога: https://yby6.com" copyText="https://yby6.com" overlay></u-tooltip>
</view>
<view class="PaymentChannel_title">
    <u-tooltip text="ПК-система WeChat платит: https://lzys522.cn/wx" copyText="https://lzys522.cn/wx" overlay></u-tooltip>
</view>
<view class="PaymentChannel_title">
    <u-tooltip text="Пример проекта блога: https://lzys522.cn" copyText="https://lzys522.cn" overlay></u-tooltip>
</view>
<view class="PaymentChannel_title"
      style="color: red;  margin-bottom: 10px;font-size: 14px;height: auto !important;">
    <u-tooltip text="Склад с открытым исходным кодом: https://gitee.com/yangbuyi/wxDemo" copyText="https://gitee.com/yangbuyi/wxDemo"
               overlay></u-tooltip>
</view>

настраиватьсерединарегиональный контент<!-- область контента --><view class="content" v-if="productList.length > 0"> <view class="item" v-for="product in productList" :key="product.id"> <a :class="'orderBtn', {current:payOrder.productId === product.id}" @click="selectItem(product.id, (product.price / 100))" href="javascript:void(0);"> {{ product.title }} ¥{{ product.price / 100 }} </a> </view> </view><u-alert description="Уведомление:Платеж успех может быть возвращен после существования списка заказов. Если возврат невозможен, пожалуйста, свяжитесь со мной: yangbuyiya». type="warning"></u-alert><view class="btn-arr"> <up-button @click="toPay()" color="#ff959b" :disabled='loading' text="подтверждатьплатитьV3"></up-button> </view>

Полный примеркод
Полный примеркод

взаимодействие

писать коллекция списков

существовать setup средиписатькод(иpcТакой же)

Введите картинку иллюстрировать
Введите картинку иллюстрировать

писать Функциональный метод

Введите картинку иллюстрировать
Введите картинку иллюстрировать

Заказать тест страницы

Обновление инструментов разработки мини-программ.

Введите картинку иллюстрировать
Введите картинку иллюстрировать

Моя мама такая красивая~

Небольшая ошибка

Из-за форматирования редактора в rpx появляются пробелы и стиль теряется.

Введите картинку иллюстрировать
Введите картинку иллюстрировать

Решение — убрать пробелы

5. Улучшите взаимодействие

мы будем Внизсингл Страница отображается полностью,Затем, что мы сделаем дальше, это изменим данные списка на динамическое взаимодействие.

Остальные интерфейсы API такие же, как на ПК, и их можно скопировать напрямую. Папка API стороннего проекта ПК

Создать запрос API

запрос продукта

Язык кода:javascript
копировать
// axios Отправить ajax-запрос
import request from '@/utils/request';

//Запрос списка продуктов
export function getProductList() {
    return request({
        url: '/api/product/productList',
        method: 'get'
    });
}

Напишите запрос на отправку, чтобы получить список товаров

Язык кода:javascript
копировать
import { getProductList } from "../../api/product";

// получить список продуктов
const selectProductList = async () => {
    const { data } = await getProductList()
    productList.value = data
    payOrder.value.productId = data[0].id
}

Написание жизненного цикла

Мы знаем, что у Vue свой жизненный цикл, и у UniApp тоже есть свой жизненный цикл. Подробную документацию можно найти по адресу: https://uniapp.dcloud.net.cn/collocation/App.html#applifecycle

Введите картинку иллюстрировать
Введите картинку иллюстрировать
Язык кода:javascript
копировать
import { onLoad, onShow } from '@dcloudio/uni-app'

// =========================Жизненный цикл==================== ====
onLoad(() => {
    // Загрузится только один раз
    console.log("onLoad")
    selectProductList()
})
onShow(() => {
    // Будет загружаться каждый раз
})

Наш список не нужно запрашивать повторно. Нам достаточно запросить его только один раз.

Тестовый просмотр

запрос продуктаданные
запрос продуктаданные

Пахнет Уху~

6. Подготовка к авторизации перед оформлением заказа в мини программе

Настройте авторизованный вход в WeChat

модальное окно

<img src="https://foruda.gitee.com/images/1693410126019383931/327436ed\\_5151444.png" alt="Введите картинку иллюстрировать" title="Скриншот" style="zoom:50%;" />

Введите картинку иллюстрировать
Введите картинку иллюстрировать

Напишите переменные для хранения имени пользователя и отображения аватара пользователя.

Язык кода:javascript
копировать
// Авторизация входа
let modal = ref({
    show: false, // Отображать ли
    content: 'Пожалуйста, нажмите на аватар и никнейм, чтобы заполнить информацию и получить полный платный сервис WeChat!',
    code: '', // Авторизация входаCode
    avatarUrl: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
    oldAvatarUrl: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
    nickName: '',// Никнейм
})

писать модальное окно

Введите картинку иллюстрировать
Введите картинку иллюстрировать

⚠️Примечание:Вичатсуществовать Сколько версий не поддерживают авторизацию и возврат имени пользователя?и Аватар может использовать только сам пользовательначальствопроходитьивходить Никнейм

Войдите и авторизируйтесь, чтобы получить аватар

Введите картинку иллюстрировать
Введите картинку иллюстрировать
Язык кода:javascript
копировать
// Авторизация входа - Установить аватар
const onChooseAvatar = (e) => {
    console.log("onChooseAvatar", e);
    const { avatarUrl } = e.detail
    modal.value.avatarUrl = avatarUrl
    uni.setStorageSync('avatarUrl', avatarUrl)
}

Войдите и авторизируйтесь, чтобы получить никнейм

Введите картинку иллюстрировать
Введите картинку иллюстрировать
Язык кода:javascript
копировать
// Авторизация входа - настраивать Никнейм
const changeName = (e) => {
    modal.value.nickName = e.detail.value
}

Подтвердите авторизацию

Использовать жизненный цикл onShow Может запускаться каждый раз при доступе к странице

Язык кода:javascript
копировать
onShow(() => {
    // Сначала проверьте, авторизован ли получатель для входа в систему.
    const storageSync = uni.getStorageSync('token');
    const nickName = uni.getStorageSync('nickName');
    const avatarUrl = uni.getStorageSync('avatarUrl');
    // В противном случае появится всплывающее окно с запросом авторизации для входа в систему.
    if ([ null, undefined, '' ].includes(storageSync)) {
        modal.value.show = true
    } else {
        modal.value.nickName = nickName
        modal.value.avatarUrl = avatarUrl
    }
})

Тестирование получения аватара и ника

После заполнения аватара и ника автоматически выскочит кнопка авторизации.

визуализации
визуализации

Разрешить отправку

Вы можете видеть, что кнопка «Отправить» появляется после того, как мы заполняем аватар и никнейм.

Введите картинку иллюстрировать
Введите картинку иллюстрировать

При анализе требований к прототипу входа мы проанализировали OpenId, необходимый для оформления заказа.

Стиль моей мамы изменился

Введите картинку иллюстрировать
Введите картинку иллюстрировать

ПолучитьOPENID

процесс uniapp ---> Авторизация входа в мини-программу ---> Получить код кода ---> Перейдите на серверную часть, чтобы запросить OpenId в соответствии с кодом кода.

Введите картинку иллюстрировать
Введите картинку иллюстрировать

иллюстрировать

  1. вызов wx.login() получать Временный код учетных данных для входа ,并回проходить到Сервер разработчика
  2. вызов auth.code2Session интерфейс в обмен на Уникальная идентификация пользователя OpenID 、 пользовательсуществовать Вичат开помещать平台账号ВнизизУникальный идентификаторUnionID(Если текущий Мини программа привязана к учетной записи открытой платформы WeChat) и сеансовый ключ session_key

После этого разработчик Сервера может сгенерировать пользовательское состояние входа на основе идентификатора пользователя, которое можно использовать для идентификации личности пользователя при последующей бизнес-логике середина в заимодействии.

На что следует обратить внимание

  1. сеансовый ключ session_key выполняется на пользовательских данных криптографическая подпись ключ. Для обеспечения безопасности данных приложения Сервер разработчикаСеансовый ключ не должен передаваться мини-программе и не должен передаваться внешнему миру.
  2. Код временного входа в систему можно использовать только один раз.

Разрешить отправкуполучатьCode

Внизскриншотсредиизкод Все студенты должны бороться самостоятельно

Документ авторизации апплета Uni-app: https://uniapp.dcloud.net.cn/api/plugins/login.html#login

получатьCodeкод
получатьCodeкод

Отправьте запрос на серверную часть, чтобы получить OpenId

Изменить wechatPay.js

Добавьте новый интерфейс запроса. Обратите внимание, что URL-адрес соответствует вашему собственному серверу.

Передаваемые параметры: code、nickName Никнейм в основном используется для того, чтобы отличить пользователя мини-программы, разместившего заказ.

Язык кода:javascript
копировать
// Метод входаполучатьopenId
export function loginOrRegister(data) {
    return request({
        url: '/api/wx-pay/js-api/loginOrRegister',
        method: 'post',
        data
    })
}
Введите картинку иллюстрировать
Введите картинку иллюстрировать

Бэкэнд-интерфейс авторизации WeChat

Подробные документы для входа в мини-программу: https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html

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

Введите картинку иллюстрировать
Введите картинку иллюстрировать

создавать WechatUniAppJsApiController

Не буду вдаваться в подробности про бэкенд, думаю, это все от больших ребят 😄

Введите картинку иллюстрировать
Введите картинку иллюстрировать
Язык кода:java
копировать
package com.yby6.controller;

import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.yby6.config.WxPayConfig;
import com.yby6.domain.wechat.LoginUser;
import com.yby6.reponse.R;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * WeChat Мини программаJS API платить
 *
 * @author Yang Shuai
 * Create By 2023/9/9
 */
@Slf4j
@RestController
@RequestMapping("/api/wx-pay/js-api")
@RequiredArgsConstructor
public class WechatUniAppJsApiController {

    private final WxPayConfig wxPayConfig;


    /**
     * WeChat Мини интерфейс входа в программу (Войдите или зарегистрируйтесь)
     *
     * @param loginUser Необходимый code
     */
    @PostMapping("loginOrRegister")
    public R loginOrRegister(@RequestBody LoginUser loginUser) {
        return R.ok(getOpenId(loginUser.getCode()));
    }

    /**
     * получить уникальный сертификат WeChat
     *
     * @param code код
     * @return {@link String}
     */
    private String getOpenId(String code) {
        String url = "https://api.weixin.qq.com/sns/jscode2session";
        Map<String, Object> map = new HashMap<>();
        map.put("appId", wxPayConfig.getAppid());
        map.put("secret", wxPayConfig.getSecret());
        map.put("js_code", code);
        map.put("grant_type", "authorization_code");
        String post = HttpUtil.post(url, map);
        log.info("WeChat возвращает: {}", post);
        JSONObject obj = JSONUtil.parseObj(post);
        String openid = obj.getStr("openid");
        if (StringUtils.isNoneBlank(openid)) {
            return openid;
        }
        throw new RuntimeException("Временные учетные данные для входаошибка");    }

}

Проверка авторизации апплета

Запустите серверную программу, откройте мини-программу, очистите все кеши и перекомпилируйте.

Появится окно авторизации. После заполнения код будет получен и отправлен на бэкенд для получения openid.

Введите картинку иллюстрировать
Введите картинку иллюстрировать

Возвращенный OpenId Мы также сохранили его в локальном кэше среди

openId
openId
Введите картинку иллюстрировать
Введите картинку иллюстрировать

7. Интерфейс заказа мини-программы

Торговая система в первую очередьвызов Долженинтерфейссуществовать Вичатплатить Фон обслуживания генерирует предварительныеплатитьзаказ на транзакцию,После возврата правильного идентификатора сеанса предварительной транзакции сгенерируйте строку транзакции и вызовите оплату в соответствии с различными сценариями, такими как Native, JSAPI и APP.

интерфейсиллюстрировать

Метод запроса:

【POST】/v3/pay/transactions/jsapi


⚠️Примечание: За исключением openId, параметры точно такие же, как на ПК. Если не верите, можете сравнить.

返回изИдентификатор предварительной транзакции используется для Mini Используется, когда программа открывает окно оплаты.

Введите картинку иллюстрировать
Введите картинку иллюстрировать

Напишите небольшой интерфейс заказа программы

писать Внизсингл Запросить адрес

существовать enums Папка Внизлапшасоздавать weChatPayJSAPI папкасуществоватьсоздавать WxJSApiType

Язык кода:java
копировать
package com.yby6.enums.weChatPayJSAPI;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * JSAPI Перечисление интерфейсов
 *
 * @author Yang Shuai
 * Create By 2023/09/10
 */
@AllArgsConstructor
@Getter
public enum WxJSApiType {

    /**
     * jsapi Внизсингл
     * POST
     */
    JSAPI_PAY("/v3/pay/transactions/jsapi");


    /**
     * тип
     */
    private final String type;
}

существоватьписать Адрес обратного платежа создавать WxJSNotifyType

Язык кода:java
копировать
package com.yby6.enums.weChatPayJSAPI;

import lombok.Getter;

/**
 * Перечисление обратного вызова JS
 * Обратный звонок получен службой продавца API интерфейс
 */
@Getter
public enum WxJSNotifyType {

    /**
     * платитьуведомить v3
     * /v1/play/callback
     * /api/wx-pay/native/notify
     */
    NATIVE_NOTIFY("/api/wx-pay/js-api/notify"),
    /**
     * Уведомление о результате возврата
     */
    REFUND_NOTIFY("/api/wx-pay/js-api/refunds/notify");

    /**
     * тип
     */
    final String type;

    WxJSNotifyType(String s) {
        this.type = s;
    }

}

⚠️Примечание: Адрес обратного звонка настраивается вами.,Я помещу обратный вызов в интерфейс существования в будущем. WechatUniAppJsApiController Внутри, поэтому адрес интерфейса обратного вызова "/api/wx-pay/js-api/notify"

писать Мини программаединый Внизсинглинтерфейс

Они точно такие же, я их тоже скопировал из интерфейса ПК.

Язык кода:java
копировать
    // Представляем декораторов
		private final CloseableHttpClient wxPayClient;
		// Внедрить услугу заказа
    private final OrderInfoService orderInfoService;
		// Представляем проверку подписи WeChat
    private final Verifier verifier;

    /**
     * Мини программаJSApi вызовединый ВнизсинглAPI,генерироватьплатить二维код
     */
    @SneakyThrows
    @PostMapping("{productId}")
    public R<Map<String, Object>> jsPayPay(@PathVariable Long productId, @RequestParam("openId") String openId) {

        // 将Никнейм拆出来用В后续我из Заказ展示
        String[] arr = openId.split("\\|");
        String openIdTep = arr[0];
        String nickName = arr.length > 1 ? arr[1] : "Мини программапользователь";        // Создать заказ
        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, nickName);
        String prepayId = orderInfo.getCodeUrl(); // prepayId
        if (StrUtil.isNotEmpty(prepayId) && "еще нетплатить".equals(orderInfo.getOrderStatus())) {
            log.info("Заказ сохранен, JSAPI сохранен");
            Map<String, Object> map = WxSignUtil.jsApiCreateSign(prepayId);
            log.info("rouseМини программаплатитьпараметр:{}", map);
            return R.ok(map);
        }


        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxJSApiType.JSAPI_PAY.getType()));
        Map<String, Object> paramsMap = new HashMap<>(14);
        paramsMap.put("appid", wxPayConfig.getAppid());
        paramsMap.put("mchid", wxPayConfig.getMchId());
        paramsMap.put("description", orderInfo.getTitle() + "-" + nickName);
        paramsMap.put("out_trade_no", orderInfo.getOrderNo());
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxJSNotifyType.NATIVE_NOTIFY.getType()));
        Map<String, Object> amountMap = new HashMap<>();
        amountMap.put("total", orderInfo.getTotalFee());
        amountMap.put("currency", "CNY");
        // Установить сумму
        paramsMap.put("amount", amountMap);
        paramsMap.put("payer", new HashMap<String, Object>() {{
            put("openid", openIdTep);
        }});
        //Преобразуем параметры в строку json
        JSONObject jsonObject = JSONUtil.parseObj(paramsMap);
        log.info("Параметры запроса ===> {0}" + jsonObject);
        StringEntity entity = new StringEntity(jsonObject.toString(), "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        JSONObject object = JSONUtil.parseObj(bodyAsString);
        response.close();
        prepayId = object.getStr("prepay_id");
        return R.ok(WxSignUtil.jsApiCreateSign(prepayId));
    }

Код можно оптимизировать. Студенты также могут оптимизировать параметры запроса вручную.

Собрать апплет для вызова параметров платежа

Подробная документация: https://pay.weixin.qq.com/docs/merchant/apis/mini-program-payment/mini-transfer-payment.html

Введите картинку иллюстрировать
Введите картинку иллюстрировать
Язык кода:java
копировать
// yangbuyi Copyright (c) https://yby6.com 2023.

package com.yby6.wechat;

import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.yby6.config.WxPayConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * WeChat Мини программа Проверка и расшифровка сообщений
 *
 * @author Yang Shuai
 * Create By 2023/09/10
 * <p>
 */
@Slf4j
@Component
public class WxSignUtil<T> {

    protected static final SecureRandom RANDOM = new SecureRandom();

    /**
     * Сгенерируйте подпись, соберите WeChat и настройте параметры платежа
     * Если вы не понимаете возвращаемые параметры Пожалуйста, посетите официальную документацию WeChat.
     *
     * @param prepayId Вичат Внизсингл返回изprepay_id
     * @return Параметры, необходимые для текущего вызова платежа
     */
    public static Map<String, Object> jsApiCreateSign(String prepayId) {
        if (StringUtils.isNotBlank(prepayId)) {
            final WxPayConfig wxPayConfig = SpringUtil.getBean(WxPayConfig.class);
            final String appid = wxPayConfig.getAppid();
            // Загрузить подпись
            String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
            String nonceStr = String.valueOf(System.currentTimeMillis());
            String packageStr = "prepay_id=" + prepayId;
            String packageSign = sign(buildMessage(appid, timeStamp, nonceStr, packageStr).getBytes(StandardCharsets.UTF_8), wxPayConfig.getPrivateKey(wxPayConfig.getPrivateKeyPath()));
            Map<String, Object> packageParams = new HashMap<>(6);
            packageParams.put("appId", appid);
            packageParams.put("timeStamp", timeStamp);
            packageParams.put("nonceStr", nonceStr);
            packageParams.put("package", packageStr);
            packageParams.put("signType", "RSA");
            packageParams.put("paySign", packageSign);
            return packageParams;
        }
        return null;
    }

    /**
     * Создать подпись
     * <p>
     * Мини программаappId
     * Временная метка
     * случайная строка
     * Расширенная строка сведений о заказе
     */
    public static String sign(byte[] message, PrivateKey privateKey) {
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(privateKey); // Загрузить закрытый ключ продавца
            sign.update(message); // UTF-8
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Текущая среда Java не поддерживает SHA256withRSA", e);
        } catch (SignatureException e) {
            throw new RuntimeException("Вычисление подписи не удалось", e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException("Неверный закрытый ключ", e);
        }
    }

    /**
     * генерироватьслучайная строка Базовые методы WeChat
     */
    protected static String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".charAt(RANDOM.nextInt("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".length()));
        }
        return new String(nonceChars);
    }

    /**
     * Сортировка в соответствии со спецификациями документа подписи внешнего интерфейса, \n — это новая строка.
     *
     * @param appId     appId
     * @param timestamp время
     * @param nonceStr  случайная строка
     * @param prepayIds prepay_id
     */
    public static String buildMessage(String appId, String timestamp, String nonceStr, String prepayIds) {
        return appId + "\n" + timestamp + "\n" + nonceStr + "\n" + prepayIds + "\n";
    }


    /**
     * Расшифровать симметрия Расшифровать
     * ссылка: <a href="https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient/blob/master/src/main/java/com/wechat/pay/contrib/apache/httpclient/util/AesUtil.java">...</a>
     *
     * @param plainText Хибун
     * @return {@link String}
     */
    public static <T> T decryptFromResource(String plainText, Class<T> clazz) {
        Map<String, Object> bodyMap = JSONUtil.toBean(plainText, Map.class);
        log.info("Личный текст Расшифровать");
        final WxPayConfig wxPayConfig = SpringUtil.getBean(WxPayConfig.class);
        //Получены данные уведомления resource узел
        Map<String, String> resourceMap = (Map) bodyMap.get("resource");
        //Зашифрованный текст данных
        String ciphertext = resourceMap.get("ciphertext");
        //случайная строка
        String nonce = resourceMap.get("nonce");
        //Дополнительные данные
        String associatedData = resourceMap.get("associated_data");
        log.info("Личный текст ===> {}", ciphertext);
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        // Используйте ключ, одноразовый номер и ассоциированные_данные.,对данные密文resource.ciphertextруководить Расшифровать,получатьJSON形式из资源对象
        String resource;
        try {
            resource = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
        log.info("обычный текст ===> {}", resource);
        return JSONUtil.toBean(resource, clazz);
    }


}

Сравнить параметры запроса сборки

Слушай, я тебе не вру. Давайте просто скопируем этот новый раздел. payer openid поле

Параметры запроса
Параметры запроса

Мини-программа обратного звонка по оплате заказа

Излишне говорить, что это то же самое, что и ПК.

Язык кода:java
копировать
    // Введение в процессинг Платеж журнал успеха
    private final WxJSAPIPayService wxJSAPIPayService;


/**
 * платитьуведомить->Вичатплатитьпроходитьплатитьуведомитьинтерфейс将пользователь Платеж уведомление об успешном сообщении торговцам
 *
 * @return {@link R}
 */
@PostMapping("/notify")
public Map<String, String> transactionCallBack(HttpServletRequest request, HttpServletResponse response) {
    Map<String, String> map = new HashMap<>(12);
    try {
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String serialNo = request.getHeader("Wechatpay-Serial");
        String signature = request.getHeader("Wechatpay-Signature");

        log.info("timestamp:" + timestamp + " nonce:" + nonce + " serialNo:" + serialNo + " signature:" + signature);
        //Обработка параметров уведомления
        String body = HttpUtils.readData(request);
        log.info("платить пароль уведомления: {} ", body);
        JSONObject jsonObject = JSONUtil.parseObj(body);
        final String requestId = jsonObject.getStr("id");

        //Проверка подписи
        WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest = new WechatPay2ValidatorForRequest(verifier, requestId, body);
        if (!wechatPay2ValidatorForRequest.validate(request)) {
            log.error("Проверка уведомления не удалась");
            //неудачный ответ
            response.setStatus(500);
            return WechatRep.fail();
        }
        // Обработать заказ
        final CallBackResource decrypt = WxSignUtil.decryptFromResource(body, CallBackResource.class);
        wxJSAPIPayService.processOrder(JSONUtil.toJsonStr(decrypt));
        log.info("Обработка обратного вызова завершена");
        response.setHeader("Content-type", ContentType.JSON.toString());
        response.getOutputStream().write(JSONUtil.toJsonStr(WechatRep.ok()).getBytes(StandardCharsets.UTF_8));
        response.flushBuffer();
    } catch (Exception e) {
        log.error("Не удалось обработать обратный вызов WeChat:", e);
    }
    // успешный ответ
    response.setStatus(200);
    return WechatRep.ok();
}

Напишите небольшую программу для записи журналов заказов.

существовать service средисоздавать WxJSAPIPayService , если честно, хахаха, я тоже прям с ПК получил.

Язык кода:java
копировать
package com.yby6.service;

import cn.hutool.json.JSONUtil;
import com.yby6.config.WxPayConfig;
import com.yby6.domain.OrderInfo;
import com.yby6.domain.wechat.CallBackResource;
import com.yby6.enums.OrderStatus;
import com.yby6.enums.weChatPayNative.WxNotifyType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author Yang Shuai
 * Create By 2023/9/9
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class WxJSAPIPayService {

    private final ReentrantLock lock = new ReentrantLock();
    private final OrderInfoService orderInfoService;
    private final PaymentInfoService paymentInfoService;

    /**
     * обратный вызов jsapi
     */
    public void processOrder(String plainText) {
        final CallBackResource data = JSONUtil.toBean(plainText, CallBackResource.class);

        log.info("Обработать заказ");
        // Специальное напоминание WeChat:
        // Проводить проверку статуса бизнес-данных перед обработкой.
        // Блокировки данных следует использовать для управления параллелизмом, чтобы избежать хаоса данных, вызванного повторным входом функции.

        // Попробуйте получить блокировки:
        // Если это удастся, он немедленно вернет true, если потерпит неудачу, он немедленно вернет false. Нет необходимости ждать снятия блокировки.
        if (lock.tryLock()) {
            try {
                // Обработка дубликатов уведомлений
                // интерфейсвызовиз幂等性:несмотря ни на чтоинтерфейсодеяловызовсколько раз,产生из结果是一致из。
                OrderInfo orderInfo = orderInfoService.lambdaQuery().eq(OrderInfo::getOrderNo, (data.getOutTradeNo())).one();

                if (null != orderInfo && !OrderStatus.NOTPAY.getType().equals(orderInfo.getOrderStatus())) {
                    log.info("Дубликат уведомления, уже Платеж успешный Ла");
                    return;
                }

                // Имитировать параллелизм уведомлений
                //TimeUnit.SECONDS.sleep(5);

                // Обновить статус заказа
                orderInfoService.lambdaUpdate().eq(OrderInfo::getOrderNo, data.getOutTradeNo()).set(OrderInfo::getOrderStatus, OrderStatus.SUCCESS.getType()).update();
                log.info("Обновить статус заказа,Номер заказа: {},Статус заказа: {}", data.getOutTradeNo(), OrderStatus.SUCCESS);
                // Запись в журнал
                paymentInfoService.createPaymentInfo(plainText);
            } finally {
                // Чтобы активно снять блокировку
                lock.unlock();
            }
        }
    }
    /*==========================================================================*/
}

Сопоставление классов сущностей для обратного вызова платежа

Язык кода:java
копировать
// yangbuyi Copyright (c) https://yby6.com 2023.

package com.yby6.domain.wechat;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * js API платить回调 *
 * @author Yang Shuai
 * Create By 2023/9/9
 */
@NoArgsConstructor
@Data
public class CallBackResource {

    /**
     * mchid
     */
    @JsonProperty("mchid")
    private String mchid;
    /**
     * appid
     */
    @JsonProperty("appid")
    private String appid;
    /**
     * сделка нет
     */
    @JsonProperty("out_trade_no")
    private String outTradeNo;
    /**
     * Идентификатор заказа транзакции
     */
    @JsonProperty("transaction_id")
    private String transactionId;
    /**
     * Тип сделки
     */
    @JsonProperty("trade_type")
    private String tradeType;
    /**
     * торговая страна
     */
    @JsonProperty("trade_state")
    private String tradeState;
    /**
     * торговая странаdesc
     */
    @JsonProperty("trade_state_desc")
    private String tradeStateDesc;
    /**
     * Тип банка
     */
    @JsonProperty("bank_type")
    private String bankType;
    /**
     * дополнительный
     */
    @JsonProperty("attach")
    private String attach;
    /**
     * 成功время
     */
    @JsonProperty("success_time")
    private String successTime;
    /**
     * Плательщик
     */
    @JsonProperty("payer")
    private PayerDTO payer;
    /**
     * количество
     */
    @JsonProperty("amount")
    private AmountDTO amount;

    /**
     * ../
     *
     * @author Yang Shuai
     * Create By 2023/05/24
     */
    @NoArgsConstructor
    @Data
    public static class PayerDTO {
        /**
         * openid
         */
        @JsonProperty("openid")
        private String openid;
    }

    /**
     * ../
     *
     * @author Yang Shuai
     * Create By 2023/05/24
     */
    @NoArgsConstructor
    @Data
    public static class AmountDTO {
        /**
         * общий
         */
        @JsonProperty("total")
        private Integer total;
        /**
         * Плательщикобщий
         */
        @JsonProperty("payer_total")
        private Integer payerTotal;
        /**
         * валюта
         */
        @JsonProperty("currency")
        private String currency;
        /**
         * платитьвалюта
         */
        @JsonProperty("payer_currency")
        private String payerCurrency;
    }
}

На этот раз новый код

新增изкод
新增изкод

Протестируйте единый интерфейс заказа мини-программы

Перезапустите мини-программу, перезапустите серверную службу, включите проникновение в интрасеть, авторизуйте вход в мини-программу и получите копию openId.

openId: o6Yr-xxxxxxxxxx

openid
openid

Измените идею серверной части и используйте инструмент отладки интерфейса для отправки запроса на заказ.

Введите картинку иллюстрировать
Введите картинку иллюстрировать

Идеально без проблем

Введите картинку иллюстрировать
Введите картинку иллюстрировать

8. Мини программа открывает окно оплаты.

uni.requestPayment(OBJECT)

платить

uni.requestPayment — это клиентский платёжный API, который объединяет различные платформы. Будь то определенная Мини-программа или существующее приложение, клиент использует этот APIвызовплатить.

Когда этот API работает на каждом конце, он автоматически преобразуется в собственный API платвызов на каждом конце.

Уведомлениеплатить требует больше, чем просто развитие клиентов,Также требуется разработка на стороне сервера. Хотя клиентское API унифицировано,Однако, чтобы подать заявку на активацию и настроить обратную заливку платы на каждой платформе, вам все равно необходимо просмотреть документ оплаты самой платформы.

Например, в WeChat есть приложения платно и Мини. Различные входы в приложения, такие как «программировать» и «H5платить», используют процесс, соответствующий uni-app. Что касается существующего приложения, вам необходимо подать заявку на получение приложения WeChat бесплатно и Мини. программаподать заявку на WeChat’s Mini программаплатить。

Подробный адрес документа: https://uniapp.dcloud.net.cn/api/plugins/payment.html#%E7%94%B3%E8%AF%B7%E6%B5%81%E7%A8%8B-2

Введите картинку иллюстрировать
Введите картинку иллюстрировать

Эти параметры такие же, как мы собрали. Дальше нам остаётся только задать параметры и запустить вызовплатить.

Напишите небольшую программу для унификации запросов заказов.

Изменить wechatPay.js

Язык кода:javascript
копировать
// единыйJSAPIВнизсингл
export function JSAPI(productId, openId) {
    return request({
        'url': `/api/wx-pay/js-api/${productId}`,
        'method': 'post',
		'params': {
			"openId" : openId
		}
    })
}			

Улучшить функцию toPay

Язык кода:javascript
копировать
// инициироватьплатить
const toPay = async () => {
  // получать Вичатплатитьсертификатсоздаватьплатить Заказ  const storageSync = uni.getStorageSync('token');
  const nickName = uni.getStorageSync('nickName');

  // Отправить Мини программаединый Внизсингл
  const {code, data} = await JSAPI(payOrder.value.productId, storageSync + "|" + nickName)

  if (code !== 200) {
    toast("Не удалось создать заказ, повторите попытку позже!")
    return
  }
  toast("Создавать Заказ успешно, подождите...")
  setTimeout(() => {
    const wx = data
    // вызов Вичатплатить弹窗    uni.requestPayment({
      provide: 'wxpay',
      timeStamp: wx.timeStamp, // 当前время
      nonceStr: wx.nonceStr, // случайная строка
      package: wx.package, // prepayId
      signType: wx.signType, // Алгоритм подписи
      paySign: wx.paySign, // платитьзнак      success: (res) => {
        loading.value = false
        toast("Платеж успехшен Пожалуйста, существует список заказов, чтобы проверить статус заказа, а функция возврата также существует в списке заказов середина", 5)
      },
      fail: (res) => {
        console.log(res);
        toast("Отменить оплату, вы можете продолжать нажимать кнопку оплаты, чтобы начать заново")
        loading.value = false
      }
    })
  }, 500)

}

Протестируйте полную мини-программу, чтобы унифицировать процесс заказа

Запустите мини-программу, запустите серверную службу, начните проникновение во внутреннюю сеть арахисовой оболочки, очистите кеш мини-программы.

Введите картинку иллюстрировать
Введите картинку иллюстрировать

Все идеально, никаких ошибок не появляется, сканируйте код и можно продолжать.

Платеж успешен

Введите картинку иллюстрировать
Введите картинку иллюстрировать
Введите картинку иллюстрировать
Введите картинку иллюстрировать
Введите картинку иллюстрировать
Введите картинку иллюстрировать

наконец

Этот вопрос исчерпан. Увидимся в следующий раз👋~

🌊 Подпишитесь на меня, чтобы не потеряться. Если эта статья вам полезна или у вас есть какие-либо вопросы, оставьте сообщение в области комментариев, я обычно отвечу, когда увижу ее. Пожалуйста, поставьте лайк и поддержите~ 💗

ЯсуществоватьучаствоватьВторой этап специального тренировочного лагеря Tencent Technology Creation 2023 года включает в себя эссе, получившие призы, которые разделят призовой фонд в 10 000 юаней и часы с клавиатурой.

Введите картинку иллюстрировать
Введите картинку иллюстрировать
boy illustration
Углубленный анализ переполнения памяти CUDA: OutOfMemoryError: CUDA не хватает памяти. Попыталась выделить 3,21 Ги Б (GPU 0; всего 8,00 Ги Б).
boy illustration
[Решено] ошибка установки conda. Среда решения: не удалось выполнить первоначальное зависание. Повторная попытка с помощью файла (графическое руководство).
boy illustration
Прочитайте нейросетевую модель Трансформера в одной статье
boy illustration
.ART Теплые зимние предложения уже открыты
boy illustration
Сравнительная таблица описания кодов ошибок Amap
boy illustration
Уведомление о последних правилах Points Mall в декабре 2022 года.
boy illustration
Даже новички могут быстро приступить к работе с легким сервером приложений.
boy illustration
Взгляд на RSAC 2024|Защита конфиденциальности в эпоху больших моделей
boy illustration
Вы используете ИИ каждый день и до сих пор не знаете, как ИИ дает обратную связь? Одна статья для понимания реализации в коде Python общих функций потерь генеративных моделей + анализ принципов расчета.
boy illustration
Используйте (внутренний) почтовый ящик для образовательных учреждений, чтобы использовать Microsoft Family Bucket (1T дискового пространства на одном диске и версию Office 365 для образовательных учреждений)
boy illustration
Руководство по началу работы с оперативным проектом (7) Практическое сочетание оперативного письма — оперативного письма на основе интеллектуальной системы вопросов и ответов службы поддержки клиентов
boy illustration
[docker] Версия сервера «Чтение 3» — создайте свою собственную программу чтения веб-текста
boy illustration
Обзор Cloud-init и этапы создания в рамках PVE
boy illustration
Корпоративные пользователи используют пакет регистрационных ресурсов для регистрации ICP для веб-сайта и активации оплаты WeChat H5 (с кодом платежного узла версии API V3)
boy illustration
Подробное объяснение таких показателей производительности с высоким уровнем параллелизма, как QPS, TPS, RT и пропускная способность.
boy illustration
Удачи в конкурсе Python Essay Challenge, станьте первым, кто испытает новую функцию сообщества [Запускать блоки кода онлайн] и выиграйте множество изысканных подарков!
boy illustration
[Техническая посадка травы] Кровавая рвота и отделка позволяют вам необычным образом ощипывать гусиные перья! Не распространяйте информацию! ! !
boy illustration
[Официальное ограниченное по времени мероприятие] Сейчас ноябрь, напишите и получите приз
boy illustration
Прочтите это в одной статье: Учебник для няни по созданию сервера Huanshou Parlu на базе CVM-сервера.
boy illustration
Cloud Native | Что такое CRD (настраиваемые определения ресурсов) в K8s?
boy illustration
Как использовать Cloudflare CDN для настройки узла (CF самостоятельно выбирает IP) Гонконг, Китай/Азия узел/сводка и рекомендации внутреннего высокоскоростного IP-сегмента
boy illustration
Дополнительные правила вознаграждения амбассадоров акции в марте 2023 г.
boy illustration
Можно ли открыть частный сервер Phantom Beast Palu одним щелчком мыши? Супер простой урок для начинающих! (Прилагается метод обновления сервера)
boy illustration
[Играйте с Phantom Beast Palu] Обновите игровой сервер Phantom Beast Pallu одним щелчком мыши
boy illustration
Maotouhu делится: последний доступный внутри страны адрес склада исходного образа Docker 2024 года (обновлено 1 декабря)
boy illustration
Кодирование Base64 в MultipartFile
boy illustration
5 точек расширения SpringBoot, супер практично!
boy illustration
Глубокое понимание сопоставления индексов Elasticsearch.
boy illustration
15 рекомендуемых платформ разработки с нулевым кодом корпоративного уровня. Всегда найдется та, которая вам понравится.
boy illustration
Аннотация EasyExcel позволяет экспортировать с сохранением двух десятичных знаков.