{\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}需要什么来着
这个 neeto-whatsapp-nano 管理 neto 应用程序中的 WhatsApp 消息 .
纳米出口 @bigbinary/neeto-whatsapp-frontend 国家防范机制一揽子计划
neeto-whatsapp-engine 铁路发展引擎.
它还对每个工作空间的WhatsApp消息进行测量. 每个工作空间都免费 每月发送信件,管理员可以从 NeetoAuth 购买预付信件包 计费. 见 WhatsApp 消息包。 。 。 .
目录
用主机应用程序开发
发动机
引擎处理WhatsApp Business API集成,包括发送和 接收信件、处理消息状态更新的 webhooks 以及管理 WhatsApp 消息模板 .
安装
-
将此行添加到您的应用程序的 Gemfile 中 :
source "NEETO_GEM_SERVER_URL" do # ..existing gems gem 'neeto-whatsapp-engine' # Use this for live development: # gem 'neeto-whatsapp-engine', path: "../neeto-whatsapp-nano" end -
然后执行:
bundle install -
将此行添加到您的应用程序
config/routes.rb文件 :mount NeetoWhatsappEngine::Engine, at: "/neeto_whatsapp" -
运行以下命令以复制从引擎到主机的迁移 应用程序 :
bundle exec rails neeto_whatsapp_engine:install:migrations -
添加迁移到数据库 :
bundle exec rails db:migrate -
添加以下环境变量:
WHATSAPP_NANO_WEBHOOK_VERIFY_TOKEN=abc123
WHATSAPP_WEBHOOK_APP_SECRET=abc123
WHATSAPP_ENABLED_ORGANIZATIONS="spinkart"
WHATSAPP_DISABLE_INBOUND_MESSAGES=true <--- To disable processing incoming messages. Defaults to false.
- 将以下关联添加到您想要关联的实体 应用程序消息
has_many :whatsapp_messages, class_name: "NeetoWhatsappEngine::Message", as: :entity, dependent: :destroy
- 覆盖获取 实体! 方法 NeetoWhatsapp Engine:: Api:V1::: 邮件控制器
# app/overrides/controllers/neeto_whatsapp_engine/api/v1/messages_controller_override.rb
# frozen_string_literal: true
NeetoWhatsappEngine::Api::V1::MessagesController.class_eval do
private
def fetch_entity!
# Fetch entity here
# example:
# @entity = @organization.bookings.find_by!(id: params[:entity_id])
end
end
- 后端曝光发送什么app消息的2个服务:
- 发送模板信件
NeetoWhatsappEngine::TemplateMessageService.new(account).process(
recipient_phone:,
template_name:,
language_code:,
entity: booking,
# Required. The workspace the message is metered against. See "WhatsApp message packs".
organization: booking.organization,
# Optional. Shown as the recipient in WhatsApp logs.
recipient_name: booking.name,
template_params: [
# This is an example on how to send params.
# Check the params required based on the template(created on meta's dashboard) which you are going to use.
{ name: "client_name", type: "text", text: booking.name },
{ name: "meeting_name", type: "text", text: booking.meeting.name },
{ name: "host_meeting_time", type: "text", text: meeting_time_in_host_time_zone },
{ name: "attendee_list", type: "text", text: attendee_list },
{ name: "meeting_location", type: "text", text: meeting_location },
{ name: "booking_id", type: "text", text: booking.sid }
],
# Optional. Only needed if the template has buttons with dynamic values.
# See the "Template buttons" section below for the accepted shape.
template_buttons: [
{ index: 0, sub_type: "url", text: booking.sid },
{ index: 1, sub_type: "quick_reply", text: "unsubscribe" },
{ index: 2, sub_type: "copy_code", text: "25OFF" }
]
)
- 对于发送自由格式的文本消息,您可以使用前端组件
WhatsAppMessages内部处理它。 但是,如果你想称呼它 手动的
NeetoWhatsappEngine::TextMessageService.new(account).process(
recipient_phone:,
entity:,
message_text: "Hello",
organization:, # Required. The workspace the message is metered against.
recipient_name: # Optional. Shown as the recipient in WhatsApp logs.
)
模板按钮
如果一个模板(在元仪表板上创建)有动态值的按钮,
通过 template_buttons。这与 template_params数字 :
参数填充正文,按钮填充按钮组件。 每个条目填充
一个按钮,匹配 index 到模板中按钮的位置.
template_buttons: [
{ index: 0, sub_type: "url", text: booking.sid },
{ index: 1, sub_type: "quick_reply", text: "unsubscribe" },
{ index: 2, sub_type: "copy_code", text: "25OFF" }
]
| 密钥 | 需求 | 说明 |
|---|---|---|
index | 没有 | 模板中按钮的0基位置. 默认条目在数组中的位置 . |
sub_type | 没有 | "url" (违约), "quick_reply",或 "copy_code"。 。 。 . |
text | 对 | 动态值. 含义取决于 sub_type (见下文). |
name | 没有 | 对于 url 按钮,其模板使用命名变量(例如:. {{promo_code}} 改为 {{1}}) (中文(简体) ). Omit for 位置变量. |
text 每个按钮类型对不同事物的映射 :
url—— 替换到模板 URL 的值{{1}}变量。 这个 基准 URL 生活在模板中; 您只在此传递变量值( 例如 a) 预订id. 百分数编码任何特殊字符.quick_reply——有效载荷在用户点击时返回到网钩中 按钮.copy_code——被窃听时代码被复制到用户剪贴板.
说明: template_buttons 保存在保留下 "template_buttons" 键入
讯息 metadata。如果您也通过该密钥英寸 metadata,这是
覆写.
WhatsApp 消息包
计量方法
- 每个工作区都得到 每月100个免费消息 (UTC) (中文(简体) ). 未使用的自由消息 别搬过来.
- 自由消息耗尽后, 发送使用 预付电文。管理员购买 这些是来自NeetoAuth的包,他们从来不是 过期.
- 一个信息是 保留 当它发送, 收费 当WhatsApp报告时
交付或阅读 已发行 如果失败.
NeetoWhatsappEngine::ReleaseStaleReservationsJob发布任何信息 30天后仍未结案. - 一旦免费和预付电文都用光了,
TemplateMessageService财务报告和审定财务报表TextMessageService提高NeetoWhatsappEngine::Errors::BalanceExhaustedError而不是发送。 这个 信件带有“ 未发送” 状态. - 两种服务都需要
organization:,信息的工作空间是 与WhatsApp日志相对并显示在其中.
建筑
组件和网络呼叫
flowchart TB
subgraph Host["Host application, e.g. NeetoCal"]
Caller["Host code, e.g. a booking confirmation"]
subgraph Engine["neeto-whatsapp-engine"]
Services["TemplateMessageService<br/>TextMessageService"]
Billing["Billing::ReserveService<br/>Billing::ChargeService<br/>Billing::ReleaseService"]
Webhooks["WebhooksController<br/>ProcessWebhookPayloadJob"]
ServerAPI["Server API<br/>POST credits, GET balances"]
Jobs["ThresholdCheckJob<br/>ReleaseStaleReservationsJob"]
DB[("messages<br/>balances<br/>credits")]
end
Pages["WhatsApp logs and Usage pages"]
end
Meta["Meta WhatsApp Cloud API"]
Auth["NeetoAuth Billing"]
LS["Lemon Squeezy"]
Admins(["Workspace admins"])
Caller -->|"process(organization:, ...)"| Services
Services -->|"reserve"| Billing
Services -->|"1. send message"| Meta
Meta -->|"2. status webhook: delivered, read, failed"| Webhooks
Webhooks -->|"charge or release"| Billing
Billing --> DB
Billing -->|"low or ran out"| Jobs
Jobs -->|"low-balance and ran-out emails"| Admins
Admins -->|"Buy messages"| Auth
Auth -->|"3. hosted checkout"| LS
LS -->|"4. order_created webhook"| Auth
Auth -->|"5. POST credits and GET balances, server token"| ServerAPI
ServerAPI --> DB
Pages --> DB
发送消息
sequenceDiagram
autonumber
participant Host as Host code
participant Svc as Message service
participant Bal as Balance (row lock)
participant Meta as Meta Cloud API
participant Job as ProcessWebhookPayloadJob
Host->>Svc: process(organization:, recipient_phone:, ...)
Svc->>Bal: ReserveService: roll the month if needed
alt free messages left this month
Bal-->>Svc: free_reserved += 1, billed_from: free
else prepaid messages left
Bal-->>Svc: prepaid_available -= 1, prepaid_reserved += 1, billed_from: prepaid
else nothing left
Bal-->>Svc: BalanceExhaustedError
Svc->>Svc: log the message as "Not sent"
Svc-->>Host: raise BalanceExhaustedError
Note over Svc: ThresholdCheckJob sends the ran-out email once
end
Svc->>Meta: send the message
alt Meta accepts it
Svc->>Svc: create the message, billing_state: reserved
else Meta returns an error
Svc->>Svc: create a failed message, then ReleaseService
end
Meta-->>Job: status webhook, processed in the background
alt delivered or read
Job->>Bal: ChargeService
Note over Job: ThresholdCheckJob sends the low-balance email once total_remaining drops to low_balance_threshold, 50 by default
else failed
Job->>Bal: ReleaseService
end
ReleaseStaleReservationsJob 每日运行在主机( 例如, UTC 04: 04)
在 NeetoCal 中发布消息 reserved 30天后, 当Meta 时
从来没有发出最终状态.
买一包
sequenceDiagram
autonumber
actor Admin as Workspace admin
participant Auth as NeetoAuth
participant LS as Lemon Squeezy
participant Eng as Engine server API
Admin->>Auth: open Buy messages, from an email or the Usage page
Auth->>Eng: GET /neeto_whatsapp/api/v1/server/balances
Eng-->>Auth: free and prepaid balances, usage_url
Admin->>Auth: choose a pack and buy
Auth->>LS: create a checkout tagged kind: whatsapp_pack
Auth-->>Admin: redirect to the hosted checkout
Admin->>LS: pay
LS-->>Admin: redirect back to the buy page
LS->>Auth: order_created webhook
Auth->>Auth: record WhatsappPackPurchase, unique on the order id
Auth->>Eng: POST /neeto_whatsapp/api/v1/server/credits (CreditJob, retried until it succeeds)
Eng->>Eng: create Credit, unique on purchase_identifier, then prepaid_available += messages
Eng-->>Auth: 200, the same credit again on a repeat
Auth-->>Admin: the buy page polls for the purchase, then shows the new balance
NeetoAuth 从自己的包配置中取出消息计数, 匹配于 柠檬Squeezy变体,从不从取出数据.
余额计数器
每个工作区都有一个 neeto_whatsapp_engine_balances 排队 每个写锁
所以同时发送的讯息不能花两次.
| 计数器 | 含义 |
|---|---|
free_used | 本月收取免费信件 |
free_reserved | 本月发送但尚未发送或失败的免费信件 |
prepaid_available | 留给使用的预付信息 |
prepaid_reserved | 发送但尚未发送或未发送的预付电文 |
period_starts_on | 免费柜台所属的月份 |
free_remaining = max(free_monthly_allowance - free_used - free_reserved, 0)
(the full allowance when period_starts_on is an earlier month)
total_remaining = free_remaining + prepaid_available
每个步骤如何移动计数器:
| 步骤 | 免费消息 | 预付电文 |
|---|---|---|
| 储备,寄送时 | free_reserved + 1 | prepaid_available - 1, (中文(简体) ), prepaid_reserved + 1 |
| 交付或阅读时收费 | free_reserved - 1, (中文(简体) ), free_used + 1 | prepaid_reserved - 1 |
| 失败或30天后释放 | free_reserved - 1 | prepaid_reserved - 1, (中文(简体) ), prepaid_available + 1 |
| 信用卡,买包 | 无 | prepaid_available + messages |
- 月份滚动 : 新月中的第一个写入重排
free_used, (中文(简体) ),free_reserved和两个电子邮件标记。 上个月保留了一条免费留言 因为上个月的柜台是 已经走了 预付电文永远不会被重置 . - 电子邮件 :
low_balance_notified_at财务报告和审定财务报表exhausted_notified_at每个 每次过关都会发电子邮件 一个信用 重新武装了 运行的电子邮件,和 余额超过阈值后, 低余额电子邮件 .
stateDiagram-v2
[*] --> reserved: sent
[*] --> not_metered: no messages left, logged as Not sent
reserved --> charged: delivered or read
reserved --> released: failed, or still reserved after 30 days
charged --> [*]
released --> [*]
not_metered --> [*]
billing_state 在留言。 因为没有留言而被拒绝的消息
没有计费状态和保留什么.
每次搬出 reserved 发生一次:服务用一个
条件更新于 billing_state = 'reserved',因此重复或不合顺序
网友不收费两次.
在主机应用程序中整合消息包
<entity> 下面是您所属的WhatsApp信件的记录,例如
预订.
-
运行量子迁移. 复制并运行引擎的迁移(参见 安装) (中文(简体) ). 它们增加了余额、贷项和账单 字段.
-
发送时通过工作空间, 处理拒绝的发送 .
organization:需要时间。 也通过recipient_name:因此什么 App 日志 显示消息给谁.def send_whatsapp_message(entity:, recipient_phone:, recipient_name:, template_name:, language_code:, template_params:) NeetoWhatsappEngine::TemplateMessageService.new(account).process( recipient_phone:, recipient_name:, organization: entity.organization, template_name:, language_code:, entity:, template_params: ) rescue NeetoWhatsappEngine::Errors::BalanceExhaustedError # No WhatsApp messages left. The message is already logged as "Not sent" and admins are emailed. Rails.logger.info("[WhatsApp] Skipped #{template_name}: no WhatsApp messages left") end -
决定谁可以看到WhatsApp日志和使用. 管理员API呼叫
authorize_whatsapp_admin!,在主机执行前提高:# app/overrides/controllers/neeto_whatsapp_engine/api/v1/admin_base_controller_override.rb NeetoWhatsappEngine::Api::V1::AdminBaseController.class_eval do private def authorize_whatsapp_admin! authorize(:settings, :can_view_whatsapp?) # use the host's own policy and permission end end -
将信件链接到他们的实体( 可选) . 信件详细显示窗格 在WhatsApp日志中,当实体响应时显示链接
whatsapp_log_link数字 :# app/models/<entity>.rb def whatsapp_log_link { label: I18n.t("<entity>.whatsapp_log_link_label"), value: sid, url: show_url } end -
命名您的模板 . 用法报告和日志显示每个模板 以其标签下
neeto_whatsapp_engine.template_labels后退为 人性化模板名称 :# config/locales/en.yml en: neeto_whatsapp_engine: template_labels: <template_name>: "Readable name of the message" -
告诉引擎使用页面在哪里. 设定
usage_path数字 环境config/constants.yml。电子邮件链接到它和 NeetoAuth 链接返回到它 :neeto_whatsapp: usage_path: "<path of the host's WhatsApp usage page>" -
安排清理工作 内
config/scheduled_jobs.yml数字 :whatsapp_release_stale_reservations: cron: "4 4 * * *" class: "NeetoWhatsappEngine::ReleaseStaleReservationsJob" queue: "low" description: "Runs daily at 04:04 UTC. Releases WhatsApp message reservations still unresolved after 30 days" -
添加WhatsApp日志和使用页 到主机的管理面板
WhatsAppLogs财务报告和审定财务报表WhatsAppUsage组件。 把使用页放在usage_path,并用 与第3步相同的许可. -
在 NeetoAuth 中启用产品 . 包装从
<auth_app_url>/admin/billing/whatsapp-packs/<product slug>子弹是 主持人的application_name小写( NeetoCal 变为neetocal),以及 它必须与产品匹配ClientApplication#slug在尼托奥特。 然后添加 产品ClientApplication名称改为whatsapp_packs.client_applications在尼托奥特config/constants.yml。 。 。 .
配置
设置在 neeto_whatsapp: 在主机内 config/constants.yml数字 :
| 密钥 | 默认 | 说明 |
|---|---|---|
usage_path | 无 | 主机WhatsApp使用页面的路径 。 在电子邮件中使用,然后返回 NeetoAuth 作为 usage_url。 。 。 . |
free_monthly_allowance | 100 | 每个工作空间每月免费留言. |
low_balance_threshold | 50 | 当免费加预付信件掉到这个号码时, 管理员会被发电子邮件 . |
引擎从主机读取产品名称 application_name (计:
实例 NeetoCal),以及来自每个组织的 NeetoAuth URL
auth_app_url。 。 。 .
电子邮件给工作空间管理员
这两封邮件都寄给每个工作空间的管理员.
- "只有50个WhatsApp消息留下" 当发送的消息带来
余额转入
low_balance_threshold。 。 。 . - "什么App通知已经停止" 在拒绝的第一个发送上发送 因为没有留言.
每发一发. 信用或每月重置武器 都有一个 买 信件 按钮( NeetoAuth WhatsApp 包页)和 a 视图使用 链接 (主机使用页面).
NeetoAuth 的服务器 API
NeetoAuth 用服务器授权令牌调用这些端点
(单位:千美元)Authorization: Token token=<server_authorization_token>) (中文(简体) ).
POST /neeto_whatsapp/api/v1/server/credits在 a 之后添加预付电文 包是付费的.- 参数 :
organization_subdomain, (中文(简体) ),purchase_identifier(柠檬挤压) 命令编号,messages, (中文(简体) ),amount。 。 。 . - 这是天真
purchase_identifier: 重复返回200添加 没什么.
- 参数 :
GET /neeto_whatsapp/api/v1/server/balances?organization_subdomains[]=<subdomain>返回每个工作空间的平衡 :- 自由使用, 自由剩余和重设日期
- 预缴款和剩余总额
- 本月发文
- 最后一笔贷款
usage_url
取消发展任务
从主机应用程序中运行这些 .
# Add prepaid messages to a workspace without a purchase
bin/rails "neeto_whatsapp_engine:credit[spinkart,500]"
# Pretend WhatsApp sent a status update for the latest outbound message (development only)
bin/rails "neeto_whatsapp_engine:simulate_status[delivered]" # charges the reserved message
bin/rails "neeto_whatsapp_engine:simulate_status[failed]" # releases it
前端软件包
安装
- 安装最新的
neeto-whatsapp-frontend使用下列命令的软件包:yarn add @bigbinary/neeto-whatsapp-frontend
发展指示
检查 前端软件包开发指南 用于开发前端软件包的分步指令.
构成部分
WhatsAppMessages (单位:千美元)源代码(中文(简体) )
此组件提供了一个用于查看和发送WhatsApp的聊天界面 用于给定实体的信息。 它显示一个可滚动的信息列表 何等App格式化、状态图标和粘性作曲形式 下边。 每10秒对消息进行实时更新的民意调查.
道具
entityId: 信息应当显示的实体的身份.entityType:实体的类型(例如,"Booking") (中文(简体) ).
使用量
import React from "react";
import { WhatsAppMessages } from "@bigbinary/neeto-whatsapp-frontend";
const App = ({ bookingId }) => (
<WhatsAppMessages
entityId={bookingId}
entityType="Booking"
// TO enable/disable the compose form to send messages. Defaults to true.
disableComposeForm={false}
/>
);
export default App;
WhatsAppLogs (单位:千美元)源代码(中文(简体) )
工作空间的WhatsApp日志:每个发送的消息,及其状态,收件人
和类型。 单击信件查看其细节,包括返回其链接
当主机定义时 whatsapp_log_link。 。 。 .
道具
breadcrumbs: Breadcrumbs 用于页眉.pageTitle: 浏览器标签标题.usagePath: 主机的路线WhatsAppUsage页面,从标题链接.helpUrl: 可选。 帮助文章链接 .
WhatsAppUsage (单位:千美元)源代码(中文(简体) )
工作空间的WhatsApp平衡:
- 免费和预付信息留下,以及信息用完后会发生什么
- 按类型分列的本月消息
- (单位:千美元) 购买信件 按钮打开 NeetoAuth WhatsApp 包页面
道具
breadcrumbs: Breadcrumbs 用于页眉.pageTitle: 浏览器标签标题.logsPath: 主机的路线WhatsAppLogs页 次.helpUrl: 可选。 帮助文章链接 .
使用量
import WhatsAppLogs from "@bigbinary/neeto-whatsapp-frontend/WhatsAppLogs";
import WhatsAppUsage from "@bigbinary/neeto-whatsapp-frontend/WhatsAppUsage";
// Guard both routes with the permission used in authorize_whatsapp_admin!.
<Route
path={routes.whatsappLogs}
render={() => (
<WhatsAppLogs
breadcrumbs={breadcrumbs}
pageTitle="WhatsApp logs"
usagePath={routes.whatsappUsage}
/>
)}
/>
<Route
path={routes.whatsappUsage}
render={() => (
<WhatsAppUsage
breadcrumbs={breadcrumbs}
logsPath={routes.whatsappLogs}
pageTitle="WhatsApp usage"
/>
)}
/>
出版指令
咨询 构建和释放软件包 关于如何出版的详细指南.