定制 artisanpack-ui/convertkit 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

artisanpack-ui/convertkit

Composer 安装命令:

composer require artisanpack-ui/convertkit

包简介

ConvertKit (Kit) integration for ArtisanPack UI — API client, forms, tags, sequences, and broadcasts for Laravel.

README 文档

README

ConvertKit (Kit) integration for Laravel. Ships a Kit v4 API client, a feed-driven artisanpack-ui/forms bridge, public REST endpoints for subscribe forms in any front-end, Artisan commands for reference-data sync and feed management, and a FakeConvertKit test double for consumer apps.

  • Laravel 10, 11, 12, and 13
  • PHP 8.2+
  • Kit v4 API

Installation

composer require artisanpack-ui/convertkit

Publish the config and run migrations:

php artisan vendor:publish --tag=convertkit-config
php artisan vendor:publish --tag=convertkit-migrations
php artisan migrate

Configuration

Set your Kit v4 API key in .env:

CONVERTKIT_API_KEY=your-kit-v4-api-key

Generate a key in your Kit account under Advanced → API. Verify the key can reach Kit:

php artisan convertkit:test

Full config options live in config/convertkit.php after publishing — retries, cache TTLs, forms-integration toggles, rate-limit windows, etc.

Basic Usage

Subscribers

use ArtisanPackUI\ConvertKit\Facades\ConvertKit;

// Create a subscriber
$subscriber = ConvertKit::subscribers()->create(
    email: 'jane@example.com',
    firstName: 'Jane',
    fields: [ 'company' => 'Acme' ],
);

// Find by email
$existing = ConvertKit::subscribers()->findByEmail( 'jane@example.com' );

// Apply / remove a tag
ConvertKit::subscribers()->tag( $subscriber->id, 12345 );
ConvertKit::subscribers()->untag( $subscriber->id, 12345 );

// Unsubscribe
ConvertKit::subscribers()->unsubscribe( $subscriber->id );

The convertkit() helper is equivalent to the facade:

convertkit()->subscribers()->create( 'jane@example.com' );

Forms, Tags, Custom Fields

$forms  = ConvertKit::forms()->list();          // cached; use ->refresh() to force
$tags   = ConvertKit::tags()->list();
$fields = ConvertKit::customFields()->list();

// Subscribe to a specific Kit form (applies tags server-side)
ConvertKit::forms()->subscribe(
    formId: 12345,
    email: 'jane@example.com',
    fields: [ 'company' => 'Acme' ],
    tags: [ 10, 20 ],
);

Refresh the cached reference data:

php artisan convertkit:sync            # all
php artisan convertkit:sync forms
php artisan convertkit:sync tags
php artisan convertkit:sync fields

Forms Integration

Pairs with artisanpack-ui/forms. Flip on the integration and every submission of a form the feed matches is evaluated, mapped to a Kit payload, and dispatched to a queue.

CONVERTKIT_FORMS_INTEGRATION=true
CONVERTKIT_QUEUE_CONNECTION=redis
CONVERTKIT_QUEUE=convertkit

Creating a feed

Use the REST endpoints (see below) or the CLI wizard:

php artisan convertkit:feeds create

A feed has:

  • form_id — the artisanpack-ui/forms form to listen on
  • kit_form_id — the Kit form to subscribe to (optional; leave null for a raw subscribers()->create() subscribe)
  • kit_tag_ids — Kit tag ids to apply on subscribe
  • field_map — Kit destination → submission field slug
  • conditional_logic — optional rule set that must pass before the feed fires

Field mapping

Keys are Kit destinations, values are your submission's field slugs:

{
    "email_address": "email",
    "first_name": "name",
    "company": "company_name"
}

email_address and first_name land at the top of the Kit payload; anything else (like company) becomes a Kit custom-field entry.

Conditional logic

{
    "match": "all",
    "conditions": [
        { "field": "plan", "operator": "equals", "value": "pro" },
        { "field": "email", "operator": "contains", "value": "@" }
    ]
}

Supported operators: equals, not_equals, contains, not_contains, is_empty, is_not_empty. match is all (AND) or any (OR).

REST API

All feed-admin routes sit under admin/convertkit by default and are guarded by the manage-convertkit-feeds Gate ability. Define your own gate closure:

use ArtisanPackUI\ConvertKit\Models\KitFeed;

Gate::define(
    'manage-convertkit-feeds',
    fn ( User $user, ?KitFeed $feed = null ): bool => $user->isAdmin(),
);

Feeds

Method Path Purpose
GET /admin/convertkit/feeds List feeds. Filter with ?form_id=.
POST /admin/convertkit/feeds Create a feed.
GET /admin/convertkit/feeds/{id} Show a feed.
PUT /admin/convertkit/feeds/{id} Update a feed.
DELETE /admin/convertkit/feeds/{id} Delete a feed.
POST /admin/convertkit/feeds/{id}/test Dry-run a feed against a sample submission.

Example — dry-run:

curl -X POST https://example.test/admin/convertkit/feeds/1/test \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -d '{"values": {"email": "jane@example.com", "plan": "pro"}}'

Response:

{
    "would_send": true,
    "reason": null,
    "payload": {
        "email_address": "jane@example.com",
        "fields": { "plan": "pro" }
    }
}

When conditional logic blocks the feed, would_send is false and reason is "conditional_logic". When the field map can't resolve an email, reason starts with field_map:.

Public subscribe endpoint

The public endpoint is what front-end forms POST to. Actual Kit calls run on the queue, so it always returns 202 Accepted.

POST /convertkit/subscribers

Either pass a feed_id (uses that feed's Kit form + tags) or a bare kit_form_id:

{
    "feed_id": 1,
    "email": "jane@example.com",
    "first_name": "Jane",
    "tags": [ 100 ]
}
{
    "kit_form_id": 12345,
    "email": "jane@example.com",
    "fields": { "company": "Acme" }
}

Rate limited to 10 attempts per IP per minute by default. Tune via CONVERTKIT_SUBSCRIBE_MAX_ATTEMPTS and CONVERTKIT_SUBSCRIBE_DECAY_MINUTES.

Recipes

Livewire subscribe form

use Livewire\Attributes\Validate;
use Livewire\Component;

class SubscribeForm extends Component
{
    #[Validate( 'required|email' )]
    public string $email = '';

    public bool $done = false;

    public function submit(): void
    {
        $this->validate();

        convertkit()->forms()->subscribe(
            formId: 12345,
            email: $this->email,
            tags: [ 100 ],
        );

        $this->done = true;
    }

    public function render(): string
    {
        return <<<'BLADE'
            <div>
                @if ( $done )
                    <p>Thanks — check your inbox.</p>
                @else
                    <form wire:submit="submit">
                        <input type="email" wire:model="email" required />
                        <button type="submit" wire:loading.attr="disabled">
                            Subscribe
                        </button>
                    </form>
                @endif
            </div>
        BLADE;
    }
}

React subscribe form

import { useState } from 'react';

export function SubscribeForm() {
    const [ email, setEmail ] = useState( '' );
    const [ status, setStatus ] = useState( 'idle' );

    async function submit( event ) {
        event.preventDefault();
        setStatus( 'sending' );

        const response = await fetch( '/convertkit/subscribers', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                Accept: 'application/json',
                'X-CSRF-TOKEN': document
                    .querySelector( 'meta[name="csrf-token"]' )
                    ?.content ?? '',
            },
            body: JSON.stringify( { feed_id: 1, email } ),
        } );

        setStatus( response.ok ? 'done' : 'error' );
    }

    if ( status === 'done' ) {
        return <p>Thanks — check your inbox.</p>;
    }

    return (
        <form onSubmit={ submit }>
            <input
                type="email"
                required
                value={ email }
                onChange={ ( e ) => setEmail( e.target.value ) }
            />
            <button type="submit" disabled={ status === 'sending' }>
                Subscribe
            </button>
        </form>
    );
}

Vue subscribe form

<script setup>
import { ref } from 'vue';

const email = ref( '' );
const status = ref( 'idle' );

async function submit() {
    status.value = 'sending';

    const response = await fetch( '/convertkit/subscribers', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            Accept: 'application/json',
            'X-CSRF-TOKEN':
                document.querySelector( 'meta[name="csrf-token"]' )?.content ?? '',
        },
        body: JSON.stringify( { feed_id: 1, email: email.value } ),
    } );

    status.value = response.ok ? 'done' : 'error';
}
</script>

<template>
    <p v-if="status === 'done'">Thanks — check your inbox.</p>
    <form v-else @submit.prevent="submit">
        <input v-model="email" type="email" required />
        <button type="submit" :disabled="status === 'sending'">Subscribe</button>
    </form>
</template>

Artisan Commands

Command Purpose
convertkit:test Verify the configured API key can reach Kit.
convertkit:sync [resource] Refresh cached forms/tags/fields.
convertkit:feeds list [--form=] Table of feeds, optionally filtered by form.
convertkit:feeds create Interactive wizard for creating a feed.
convertkit:feeds delete {id} Delete a feed (with confirmation).

Testing

Swap the real ConvertKit binding for a recording fake:

use ArtisanPackUI\ConvertKit\Facades\ConvertKit;

it( 'subscribes users to the newsletter', function (): void {
    $fake = ConvertKit::fake();

    $this->post( '/signup', [ 'email' => 'jane@example.com' ] )
        ->assertRedirect();

    $fake->assertSubscribed( 'jane@example.com' );
    $fake->assertTagged( 'jane@example.com', 100 );
    $fake->assertSentCount( 1 );
} );

Available assertions:

  • assertSubscribed( string $email, ?int $formId = null )
  • assertTagged( string $email, int $tagId )
  • assertNothingSent()
  • assertSentCount( int $count )

The fake never touches the network, so tests stay hermetic.

Contributing

As an open source project, this package is open to contributions from anyone. Please read through the contributing guidelines to learn more about how you can contribute to this project.

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-13

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固