Добро пожаловать в этот блог!
Эта статья познакомит вас с новой главой оплаты 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 слов и напечатана вручную. Надеюсь, она вам поможет. Если вам понравилось, пожалуйста, поставьте лайк и добавьте в избранное. Спасибо.
🌊 Подпишитесь на меня, чтобы не потеряться. Если эта статья вам полезна или у вас есть какие-либо вопросы, оставьте сообщение в области комментариев, я обычно отвечу, когда увижу ее. Пожалуйста, поставьте лайк и поддержите~ 💗
Вы можете увидеть, что вам нужно войти в систему, как только вы его откроете. Возможно, у студентов есть вопросы🤔️ Почему вы можете напрямую использовать WeChat Pay на ПК, не делая этого?
Если у вас есть сомнения, просто откройте документ и посмотрите, почему.
Платежные документы продавца WeChat: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
Вы должны передать openId, чтобы играть и платить, так где я могу получить эту вещь? Вы можете только войти в систему\? ⚠️Примечание: после завершения функции входа на страницу заказа будет добавлена всплывающая функция входа в систему\ ⚠️Примечание: после завершения функции входа на страницу заказа добавляется всплывающая функция входа в систему\ ⚠️Примечание: после завершения функции входа в систему на странице заказа будет добавлена всплывающая функция входа в систему.
Вы можете видеть, что страницы здесь не совсем одинаковые, они могут быть только одинаковыми.
Он разделен на три области: верхнюю, среднюю и нижнюю. Код на стороне ПК имеет одинаковый стиль. Мы можем напрямую скопировать файл CSS на стороне ПК, используя метод CV.
Но для тех студентов, которые никогда не видели ПК, я просто выложил файл стиля. См. ниже. глобальный стиль
Сначала мы устанавливаем глобальное дополнение стиля в App.vue.
Установите отступ на 10 резиновых
<style lang="scss">
.app-container {
padding: 10px !important;
box-sizing: border-box !important;
}
</style>
существовать static
Добавлено в папку css папка\
Добавьте новый глобальный файл пользовательского стиля. global.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
@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
Файлы используются для управления Просто импортируйте файл, а затем
Обратите внимание на имя таблицы стилей.
@import 'reset';
@import 'global';
Исправлять main.js
Просто поместите Вниз сторону существования публичного CSS
ок, давайте продолжим смотреть, как играть. Вы можете видеть, что рядом с динамиком вверху есть прокрутка текста \.
Зайди в библиотеку компонентов и посмотри
Прокручивающиеся уведомления 📢
<u-notice-bar color="red" text="В этом случае используетсяJSAPIРежим вытягивания WeChatплатить Всплывающее окно пользователя для WeChatплатитьдействовать"></u-notice-bar>
Мама моя, это то же самое, что и ElementPlus, так легко радоваться, а об остальном больше не скажу!
<template>
<view class="app-container">
<view id="index" class="container">
я индекс
</view>
</view>
</template>
Остальное как на ПК, просто вылизываем
⚠️ Все коды Вниз существуют. view id="index" среди
Установить содержимое области заголовка
<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 появляются пробелы и стиль теряется.
Решение — убрать пробелы
мы будем Внизсингл Страница отображается полностью,Затем, что мы сделаем дальше, это изменим данные списка на динамическое взаимодействие.
Остальные интерфейсы API такие же, как на ПК, и их можно скопировать напрямую. Папка API стороннего проекта ПК
// axios Отправить ajax-запрос
import request from '@/utils/request';
//Запрос списка продуктов
export function getProductList() {
return request({
url: '/api/product/productList',
method: 'get'
});
}
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
import { onLoad, onShow } from '@dcloudio/uni-app'
// =========================Жизненный цикл==================== ====
onLoad(() => {
// Загрузится только один раз
console.log("onLoad")
selectProductList()
})
onShow(() => {
// Будет загружаться каждый раз
})
Наш список не нужно запрашивать повторно. Нам достаточно запросить его только один раз.
Пахнет Уху~
<img src="https://foruda.gitee.com/images/1693410126019383931/327436ed\\_5151444.png" alt="Введите картинку иллюстрировать" title="Скриншот" style="zoom:50%;" />
// Авторизация входа
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: '',// Никнейм
})
⚠️Примечание:Вичатсуществовать Сколько версий не поддерживают авторизацию и возврат имени пользователя?и Аватар может использовать только сам пользовательначальствопроходитьивходить Никнейм
// Авторизация входа - Установить аватар
const onChooseAvatar = (e) => {
console.log("onChooseAvatar", e);
const { avatarUrl } = e.detail
modal.value.avatarUrl = avatarUrl
uni.setStorageSync('avatarUrl', avatarUrl)
}
// Авторизация входа - настраивать Никнейм
const changeName = (e) => {
modal.value.nickName = e.detail.value
}
Использовать жизненный цикл onShow
Может запускаться каждый раз при доступе к странице
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, необходимый для оформления заказа.
Стиль моей мамы изменился
процесс uniapp ---> Авторизация входа в мини-программу ---> Получить код кода ---> Перейдите на серверную часть, чтобы запросить OpenId в соответствии с кодом кода.
После этого разработчик Сервера может сгенерировать пользовательское состояние входа на основе идентификатора пользователя, которое можно использовать для идентификации личности пользователя при последующей бизнес-логике середина в заимодействии.
session_key
выполняется на пользовательских данных криптографическая подпись ключ. Для обеспечения безопасности данных приложения Сервер разработчикаСеансовый ключ не должен передаваться мини-программе и не должен передаваться внешнему миру.。Внизскриншотсредиизкод Все студенты должны бороться самостоятельно
Документ авторизации апплета Uni-app: https://uniapp.dcloud.net.cn/api/plugins/login.html#login
Добавьте новый интерфейс запроса. Обратите внимание, что URL-адрес соответствует вашему собственному серверу.
Передаваемые параметры: code
、nickName Никнейм в основном используется для того, чтобы отличить пользователя мини-программы, разместившего заказ.
// Метод входаполучатьopenId
export function loginOrRegister(data) {
return request({
url: '/api/wx-pay/js-api/loginOrRegister',
method: 'post',
data
})
}
Подробные документы для входа в мини-программу: https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html
мы получаем code
После передачи кода получите прямой доступ к серверу WeChat, чтобы получить информацию для авторизации.
WechatUniAppJsApiController
Не буду вдаваться в подробности про бэкенд, думаю, это все от больших ребят 😄
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 Мы также сохранили его в локальном кэше среди
Торговая система в первую очередьвызов Долженинтерфейссуществовать Вичатплатить Фон обслуживания генерирует предварительныеплатитьзаказ на транзакцию,После возврата правильного идентификатора сеанса предварительной транзакции сгенерируйте строку транзакции и вызовите оплату в соответствии с различными сценариями, такими как Native, JSAPI и APP.
Метод запроса:
【POST】/v3/pay/transactions/jsapi
⚠️Примечание: За исключением openId, параметры точно такие же, как на ПК. Если не верите, можете сравнить.
返回изИдентификатор предварительной транзакции используется для Mini Используется, когда программа открывает окно оплаты.
писать Внизсингл Запросить адрес
существовать enums Папка Внизлапшасоздавать weChatPayJSAPI папкасуществоватьсоздавать WxJSApiType
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
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"
Они точно такие же, я их тоже скопировал из интерфейса ПК.
// Представляем декораторов
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
// 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 поле
Излишне говорить, что это то же самое, что и ПК.
// Введение в процессинг Платеж журнал успеха
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 , если честно, хахаха, я тоже прям с ПК получил.
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();
}
}
}
/*==========================================================================*/
}
Сопоставление классов сущностей для обратного вызова платежа
// 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
платить
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
// единыйJSAPIВнизсингл
export function JSAPI(productId, openId) {
return request({
'url': `/api/wx-pay/js-api/${productId}`,
'method': 'post',
'params': {
"openId" : openId
}
})
}
// инициироватьплатить
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 юаней и часы с клавиатурой.