mcpField
E
extension-stripe

extension-stripe

github.com/caffeinelabs其他228.7k 次安装6 天前更新
代码仓库

关于 extension-stripe

extension-stripe 是用于在 Caffeine AI 应用中集成 Stripe 支付的 skill,提供后端配置、结账会话和支付状态操作,并说明前端结账及结果页的实现。

主要功能

  1. 01Stripe 配置
  2. 02创建结账会话
  3. 03查询支付状态
  4. 04HTTP outcall 响应转换
  5. 05前端结账与结果页

适用场景

  1. 01为应用配置 Stripe
  2. 02为商品创建结账会话
  3. 03查询会话支付状态
  4. 04将用户路由到支付结果页

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/caffeinelabs/skills --skill extension-stripe

安装后在 Claude Code 中输入/extension-stripe确认已加载。

附带文件migration/v0.x.y-to-v1.x.y.md

详细介绍

Stripe 支付集成

适用于 Caffeine AI 的 Stripe 支付扩展。

概览

此技能通过 HTTP 外呼添加 Stripe 支付支持。MixinStripe 混入提供配置、创建结账会话、支付状态检查以及 HTTP 外呼 transform 回调。前端负责处理结账流程和支付结果页面。

此处不涵盖传入的 Stripe Webhook(发往 canister 的外部 POST 请求)——请使用 extension-http-endpoints 处理 http_request / http_request_update,然后在更新路径中验证 Stripe 签名。

前置条件:必须先按照 extension-authorization 操作,因为此集成依赖它。

后端

模块 API

预制模块 mo:caffeineai-stripe/stripe.mo 提供底层 Stripe HTTP 辅助函数。请勿修改它。

import OutCall "mo:caffeineai-http-outcalls/outcall";

module {
  public type StripeConfiguration = {
    secretKey : Text;
    allowedCountries : [Text];
  };

  public type StripeState = {
    var configuration : ?StripeConfiguration;
  };

  public func initState() : StripeState;

  public type ShoppingItem = {
    currency : Text;
    productName : Text;
    productDescription : Text;
    priceInCents : Nat;
    quantity : Nat;
  };

  public func createCheckoutSession(configuration : StripeConfiguration, caller : Principal, items : [ShoppingItem], successUrl : Text, cancelUrl : Text, transform : OutCall.Transform) : async Text;

  public type StripeSessionStatus = {
    #failed : { error : Text };
    #completed : { response : Text; userPrincipal : ?Text };
  };

  public func getSessionStatus(configuration : StripeConfiguration, sessionId : Text, transform : OutCall.Transform) : async StripeSessionStatus;
};

在 main.mo 中设置

include MixinStripe(accessControlState, stripeState) 必须放在 main.mo 中,而不是自定义混入文件中。该混入会自动提供以下公共端点:

  • isStripeConfigured()
  • setStripeConfiguration(config)
  • createCheckoutSession(items, successUrl, cancelUrl)
  • getStripeSessionStatus(sessionId)
  • transform(input) — HTTP 外呼响应转换所必需

请勿重新声明这些函数。它们仅由 MixinStripe 提供。

import Stripe "mo:caffeineai-stripe/stripe";
import AccessControl "mo:caffeineai-authorization/access-control";
import MixinAuthorization "mo:caffeineai-authorization/MixinAuthorization";
import MixinStripe "mo:caffeineai-stripe/MixinStripe";
import Map "mo:core/Map";
import Iter "mo:core/Iter";
import Text "mo:core/Text";
import Runtime "mo:core/Runtime";

actor {
  // Include authorization
  let accessControlState : AccessControl.AccessControlState;
  include MixinAuthorization(accessControlState, null);
  let stripeState : Stripe.StripeState;
  include MixinStripe(accessControlState, stripeState);

  public type Product = {
    id : Text;
    // add custom fields
  };

  let products : Map.Map<Text, Product>;

  public query func getProducts() : async [Product] {
    products.values().toArray();
  };

  public shared ({ caller }) func addProduct(product : Product) : async () {
    if (not (AccessControl.hasPermission(accessControlState, caller, #admin))) {
      Runtime.trap("Unauthorized: Only admins can add products");
    };
    products.add(product.id, product);
  };

  public shared ({ caller }) func updateProduct(product : Product) : async () {
    if (not (AccessControl.hasPermission(accessControlState, caller, #admin))) {
      Runtime.trap("Unauthorized: Only admins can update products");
    };
    products.add(product.id, product);
  };

  public shared ({ caller }) func deleteProduct(productId : Text) : async () {
    if (not (AccessControl.hasPermission(accessControlState, caller, #admin))) {
      Runtime.trap("Unauthorized: Only admins can delete products");
    };
    products.remove(productId);
  };

  // Add more data and functions as needed
};

迁移链头部:

import Map "mo:core/Map";
import AccessControl "mo:caffeineai-authorization/access-control";

module {
  type Product = {
    id : Text;
  };

  type StripeConfiguration = {
    secretKey : Text;
    allowedCountries : [Text];
  };

  type StripeState = {
    var configuration : ?StripeConfiguration;
  };

  type NewActor = {
    accessControlState : AccessControl.AccessControlState;
    products : Map.Map<Text, Product>;
    stripeState : StripeState;
  };

  public func migration(_old : {}) : NewActor {
    {
      accessControlState = AccessControl.initState();
      products = Map.empty<Text, Product>();
      stripeState = { var configuration = null };
    };
  };
};

前端

Stripe 支付集成:

用法:

  1. 实现一个 PaymentSetup 组件:

    • 使用 isStripeConfigured() 和 setStripeConfiguration()
    • 检查是否已配置 Stripe 支付。
    • 如果尚未配置,则打开管理面板,并提示用户使用 StripeConfiguration 初始化 Stripe。
      • Stripe 密钥
      • 允许的国家/地区列表,格式为 ["US", "CA", "GB"] 等,参见 Stripe 文档。
    • 如果已经配置,则不要显示支付设置!
  2. 实现一个结账钩子:

    • 注意,需要对后端 createCheckoutSession 的结果进行 JSON 解析。
    • 验证解析后的会话是否包含非空的 url。如果缺失,则抛出错误且不要重定向。
    import { useMutation } from '@tanstack/react-query';
    import { useActor } from '@caffeineai/core-infrastructure';
    import { ShoppingItem } from '../backend';
    
    export type CheckoutSession = {
        id: string;
        url: string;
    };
    
    export function useCreateCheckoutSession() {
        const { actor } = useActor();
    
        return useMutation({
            mutationFn: async (items: ShoppingItem[]): Promise<CheckoutSession> => {
                if (!actor) throw new Error('Actor not available');
                const baseUrl = `${window.location.protocol}//${window.location.host}`;
                const successUrl = `${baseUrl}/payment-success`;
                const cancelUrl = `${baseUrl}/payment-failure`;
                const result = await actor.createCheckoutSession(items, successUrl, cancelUrl);
                // JSON parsing is important!
                const session = JSON.parse(result) as CheckoutSession;
                if (!session?.url) {
                    throw new Error('Stripe session missing url');
                }
                return session;
            }
        });
    }
    
  3. 实现一个 Payment 组件:

    • 使用 useCreateCheckoutSession()
    • 将 ShoppingItem[] 作为输入传入。
    • 分析 CheckoutSession 结果。
    • 将网页重定向到 CheckoutSession 中的 url:这样用户就可以完成支付。
    • 不要使用路由导航前往 Stripe URL。请使用 window.location.href。
    • 绝不要导航到 /undefined;如果缺少 session.url,则显示错误并停止。
    const session = await createCheckoutSession.mutateAsync(shoppingItems);
    if (!session?.url) throw new Error('Stripe session missing url');
    window.location.href = session.url;
    
  4. 实现 PaymentSuccess 和 PaymentFailure 组件,分别处理支付成功或失败的情况。

  5. 将两个特定路径路由到支付状态组件:

    • 将路径 "/payment-success" 路由到 PaymentSuccess。
    • 将路径 "/payment-failure" 路由到 PaymentFailure。 你需要使用 @tanstack router。
  6. 管理员视图提供一个用于配置 Stripe 的菜单。如果尚未配置,则在管理员登录时提示其配置 Stripe。

补充说明:确保产品图片在产品画布中正确渲染并调整大小。

评论

支持 Markdown