APP 如何接入收发红包功能

使用支付宝的现金红包方案

现金红包简介

现金红包- 开发入门

现金红包是基于支付宝的支付能力推出的一款针对收发红包场景的产品,现金红包根据业务场景的不同分为 B2C 红包和 C2C 红包。通过该能力可以发送现金红包,用户领取之后现金红包可以进入到其支付宝余额中,用于消费,转账和提现。

使用流程

  1. 用户在 APP 调起支付宝客户端进行支付宝账号授权
  2. 收发红包流程:用户在 APP 上发红包,实际上是将钱先转到『企业支付宝账号』名下,等他人领取红包时,再将钱转到『个人支付宝账号』名下;如果用户没有及时领取红包,则进行资金退回操作。

服务端只是加签请求参数,实际的请求是需要支付宝客户端的SDK发起请求

APP 请求支付宝授权登录步骤

  1. 用户在 APP 请求授权绑定支付宝账户,

  2. APP 请求后端接口获取授权请求参数和签名,

    完整版授权请求参数和返回

  3. APP 调用支付宝授权登录 sdk ,授权成功后获得 auth_code 和 ali_user_id【支付宝用户标识,2088开头】,并请求后端接口保存 ali_user_id。

    完整版授权 SDK 调用方法

发红包流程

  1. 用户在 APP 点击发红包

  2. APP 请求后端接口,获取支付请求参数和签名,

  3. APP 调用『支付宝现金红包无线支付接口』,用户支付成功后,APP 获取到 ali_order_id,

    APP 支付客户端 DEMO&SDK

  4. APP 调用后端接口,传参 ali_order_id 并创建红包记录,发送红包消息。

发红包功能效果图

拼手气红包

专享红包

alipay api

资金相关

alipay.fund.trans.app.pay

现金红包无线支付接口

alipay.fund.trans.uni.transfer

单笔转账接口

alipay.fund.trans.refund:

资金退回接口

alipay.fund.trans.common.query

转账业务单据查询接口

资金消息回调接口,如资金退回成功通知、资金单据状态变更通知

资金消息

数据库建表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
-- 自家 APP 用户和支付宝用户 ID 关联表
CREATE TABLE `user_alipay` (
 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
 `user_id` bigint(20) unsigned NOT NULL DEFAULT '0',
 `ali_user_id` bigint(20) unsigned NOT NULL DEFAULT '0',
 `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 KEY `idx_user_alipay_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci

-- 发送红包记录表
CREATE TABLE `redpack` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `from_id` bigint(20) unsigned NOT NULL DEFAULT '0',
  `to_id` bigint(20) unsigned NOT NULL DEFAULT '0',
  `is_room` tinyint(1) NOT NULL DEFAULT '0',
  `amount` int(10) unsigned NOT NULL DEFAULT '0',
  `count` int(10) unsigned NOT NULL DEFAULT '0',
  `equally` tinyint(1) NOT NULL DEFAULT '0',
  `at_user_ids` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `ali_order_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `msg_id` bigint(20) unsigned NOT NULL DEFAULT '0',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `open_count` int(10) unsigned NOT NULL DEFAULT '0',
  `refund` int(10) unsigned NOT NULL DEFAULT '0',
  `ignore_failed` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `idx_redpack_ali_order_id` (`ali_order_id`),
  KEY `idx_redpack_msg_id` (`msg_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1586 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;

-- 红包领取记录表
CREATE TABLE `redpack_luck` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) unsigned NOT NULL DEFAULT '0',
  `msg_id` bigint(20) unsigned NOT NULL DEFAULT '0',
  `order_id` bigint(20) unsigned NOT NULL DEFAULT '0',
  `amount` int(10) unsigned NOT NULL DEFAULT '0',
  `ali_order_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `pay_fund_order_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0',
  `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',
  `ali_user_id` bigint(20) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1130 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;

示例代码

  1. 数据库金额的单位建议为分,直接存整数(0.01元 => 1 分)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package main

import (
    "errors"
    "fmt"
    "math/rand"
    "sort"
    "time"
)

func init () {
    rand.Seed(time.Now().UnixNano())
}

func main() {
    amount := 100000
    count := 50

    r, _ := DivideRedPack(amount, count, false)
    fmt.Println(r)

    total := 0
    for k := 0; k < count; k++ {
        total += r[k]
    }
    fmt.Printf("总金额:%d,金额差:%d\n", total, amount-total)
}

/**
 * 分割红包金额算法,返回按数量分割好的红包slice
 * @param int amount 红包金额,单位分
 * @param int count 红包数量
 * @param bool equally 是否均分
 * @return []int, error
 */
func DivideRedPack(amount, count int, equally bool) ([]int, error) {
    if amount == 0 || count == 0 {
        return []int{}, errors.New("红包金额或数量不能为0")
    }

    if amount < count {
        return []int{}, errors.New("单个红包金额不能小于 1 分钱")
    }

    // 获取红包平均值,均分时为平均数,随机时为 1 分钱
    var avg int
    if equally {
        avg = amount / count // tip: 整数除整数,结果还是整数,舍去法取整
    } else {
        avg = 1
    }

    if avg == 0 {
        avg = 1
    }

    // 红包 slice 初始化
    ret := make([]int, count)
    for k := range ret {
        ret[k] = avg
    }

    // 剩余的钱
    leftAmount := amount - count*avg

    if leftAmount == 0 {
        return ret, nil
    }

    // 剩余的钱,按照不同的方式分
    if equally {
        // 均分
        for i := 0; i < leftAmount; i++ {
            ret[i] += 1
        }
    } else {
        // 随机分配
        var luck []int
        var leftCount = count
        if leftAmount < count {
            leftCount = leftAmount
        }

        luck = make([]int, leftCount)

        // 方案一:随机范围不断缩小,不可用,因为红包数越多,越容易出现一堆 1 分钱的情况
        /*for k := range luck {
            // 最后一个红包直接承包最后剩余的金额,确保总金额正确
            if k == leftCount - 1 {
                luck[k] = leftAmount
                break
            }

            // 这里随机数最大可取到 leftAmount,则可能出现很多人只有 1 分钱的情况
            luck[k] = rand.Intn(leftAmount + 1)
            leftAmount -= luck[k]

            if leftAmount == 0 {
                break
            }
        }*/

        // 方案二:线段法,1. 在 0 ~ leftAmount 的线段中,插入 n 个节点;2. 根据节点,将线段裁成 n 个小线段
        for k := range luck {
            luck[k] = rand.Intn(leftAmount + 1)
        }
        sort.Ints(luck)
        luck[leftCount-1] = leftAmount
        for k := leftCount - 1; k > 0; k-- {
            luck[k] = luck[k] - luck[k-1]
        }

        // 方案三:按权重分配法
        /*randAmountSum := 0 // 随机金额总和
        randAmountMax := leftAmount / leftCount * 2 // 最大可领金额 = 剩余金额的平均值x2 = (剩余金额 / 剩余数量) * 2
        for k := range luck {
            luck[k] = rand.Intn(randAmountMax)
            randAmountSum += luck[k]
        }

        weightAmountSum := 0 // 按权重分配的金额总和
        for k := range luck {
            luck[k] = int(float32(luck[k]) / float32(randAmountSum) * float32(leftAmount))
            weightAmountSum += luck[k]
        }

        // 剩余金额继续分配
        if weightAmountSum < leftAmount {
            randAmountSum := leftAmount - weightAmountSum

            randAmountMax = randAmountSum / leftCount * 2
            if randAmountMax == 0 {
                randAmountMax = randAmountSum
            }

            var temp int
            for k := range luck {
                if k == (leftCount - 1) {
                    temp = randAmountSum
                } else {
                    temp = rand.Intn(randAmountMax)
                }

                luck[k] += temp
                randAmountSum -= temp
                if randAmountSum == 0 {
                    break
                }
            }
        }*/

        // 再次调整分配金额,使得更平均,否则部分人拿到很高金额,部分人拿到很低金额,有些人心境会崩溃,导致影响上班心情的哈
        sort.Ints(luck)
        for i := 0; i < leftCount>>2; i++ {
            delta := luck[leftCount-1-i]>>1 + luck[leftCount-1-i]>>4
            if delta > 0 && delta < luck[leftCount-1-i] {
                luck[i] += delta
                luck[leftCount-1-i] -= delta
            }
        }

        // 叠加到初始分配上
        for k := range luck {
            ret[k] += luck[k]
        }
    }

    if equally == false || leftAmount != count {
        // 打乱顺序
        rand.Shuffle(len(ret), func(i, j int) {
            ret[i], ret[j] = ret[j], ret[i]
        })
    }

    return ret, nil
}

sdk

官方

alipay-easysdk-php

alipay-easysdk-go-开发中

第三方

smartwalle-alipay