如何在NestJS中实现iOS应用的订阅功能?

我使用nestjs开发ios端app的后端.现在有一个订阅功能.
我查到的资料是, 支付前业务服务端需要调苹果的接口, 验证客户端传过来的票据.如果票据验证通过则客户端支付, 并且在数据库添加交易记录.支付后苹果支付中心会调用业务服务端的接口, 在这个接口里, 可以更新交易记录的状态, 并且更新用户的会员权限.

我不确定这个逻辑是否正确.
有没有那个大佬可以详细说说.
我使用了@apple/app-store-server-library这包, 但是这个包似乎只有回调相关的逻辑

阅读 270
avatarAI BotBETA

回答

你的逻辑基本正确,但让我们详细解释一下如何在NestJS中实现iOS应用的订阅功能,并包括一些关键步骤和注意事项。

步骤概述

  1. 客户端请求订阅

    • iOS应用用户发起订阅请求。
    • iOS应用通过Apple Pay接口完成订阅,并获取到收据(Receipt)。
  2. 验证收据

    • iOS应用将收据发送到你的NestJS后端服务器。
    • 后端服务器使用@apple/app-store-server-library包或其他方式验证收据的有效性。
    • 如果收据验证通过,后端服务器在数据库中记录订阅交易(包括收据信息、用户ID、订阅类型等)。
  3. 处理订阅回调

    • Apple会定期(如订阅状态变化、续订失败等)向你的服务器发送通知(Webhook)。
    • 你的NestJS后端服务器需要设置一个端点来接收这些通知。
    • 使用@apple/app-store-server-library包解析这些通知,并更新数据库中相应的订阅记录和用户权限。

详细实现

  1. 安装@apple/app-store-server-library

    npm install @apple/app-store-server-library
  2. 验证收据的API
    在你的NestJS控制器中创建一个API端点来接收和验证收据。

    import { Controller, Post, Body } from '@nestjs/common';
    import { verifyReceipt } from '@apple/app-store-server-library';
    import * as fs from 'fs';
    import * as path from 'path';
    
    @Controller('subscriptions')
    export class SubscriptionController {
      @Post('verify-receipt')
      async verifyReceipt(@Body() body: { receipt_data: string }) {
        const service = new verifyReceipt.Service({
          url: 'https://buy.itunes.apple.com/verifyReceipt',
          password: 'YOUR_SHARED_SECRET', // 从Apple开发者账户获取
        });
    
        try {
          const receiptData = Buffer.from(body.receipt_data, 'base64');
          const receipt = JSON.parse(receiptData.toString('utf8'));
    
          const result = await service.verify(receipt);
    
          // 在数据库中记录订阅信息
          // ...
    
          return { success: true, result };
        } catch (error) {
          return { success: false, error: error.message };
        }
      }
    }
  3. 处理Apple的Webhook回调
    创建一个API端点来接收Apple的Webhook通知。

    @Post('apple-webhook')
    async handleAppleWebhook(@Body() rawBody: any) {
      try {
        const parsedBody = JSON.parse(rawBody.toString());
    
        // 使用@apple/app-store-server-library解析通知
        const notification = verifyReceipt.Notification.fromRawBody(parsedBody);
    
        // 根据notification的类型(如RENEWAL, CANCEL, INTERRUPTION等)更新数据库中的订阅记录和用户权限
        // ...
    
        return { success: true };
      } catch (error) {
        // 处理错误
        return { success: false, error: error.message };
      }
    }
  4. 配置Webhook URL

    • 登录到你的Apple开发者账户。
    • 在App Store Connect中,为你的应用配置Webhook URL(即你的NestJS服务器接收Apple通知的端点URL)。

注意事项

  • 安全性:确保你的服务器和Apple之间的通信是安全的,使用HTTPS。
  • 错误处理:处理各种可能的错误情况,如网络错误、验证失败等。
  • 日志记录:记录所有与订阅相关的请求和响应,以便进行调试和审计。
  • 测试:在沙盒环境中彻底测试你的订阅流程,确保一切正常工作后再发布到生产环境。

通过上述步骤,你应该能够在NestJS中实现iOS应用的订阅功能。

1 个回答

安装依赖项 ↓

npm install @nestjs/common @nestjs/core @nestjs/platform-express axios

简单的实现代码(注释为文件名):

// subscription.service.ts
import { Injectable, HttpService } from '@nestjs/common';

@Injectable()
export class SubscriptionService {
    constructor(private readonly httpService: HttpService) {}

    async verifyReceipt(receipt: string): Promise<any> {
        const url = 'https://buy.itunes.apple.com/verifyReceipt';
        const response = await this.httpService.post(url, {
            'receipt-data': receipt,
            'password': 'your_shared_secret'
        }).toPromise();
        return response.data;
    }

    async handleSubscription(data: any) {
    }
}

// subscription.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { SubscriptionService } from './subscription.service';

@Controller('subscription')
export class SubscriptionController {
    constructor(private readonly subscriptionService: SubscriptionService) {}

    @Post('verify')
    async verify(@Body() body: { receipt: string }) {
        const verificationResult = await this.subscriptionService.verifyReceipt(body.receipt);
        
        if (verificationResult.status === 0) {
            await this.subscriptionService.handleSubscription(verificationResult);
            return { success: true, message: 'Subscription verified successfully.' };
        } else {
            return { success: false, message: 'Invalid receipt.' };
        }
    }
}

// webhook.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { SubscriptionService } from './subscription.service';

@Controller('webhook')
export class WebhookController {
    constructor(private readonly subscriptionService: SubscriptionService) {}

    @Post('apple')
    async handleAppleWebhook(@Body() body: any) {
        const { notificationType, data } = body;

        if (notificationType === 'RENEWAL') {
            await this.subscriptionService.handleSubscription(data);
        }
        
        return { success: true };
    }
}

// app.module.ts
import { Module, HttpModule } from '@nestjs/common';
import { SubscriptionController } from './subscription.controller';
import { SubscriptionService } from './subscription.service';
import { WebhookController } from './webhook.controller';

@Module({
    imports: [HttpModule],
    controllers: [SubscriptionController, WebhookController],
    providers: [SubscriptionService],
})
export class AppModule {}
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    await app.listen(3000);
}
bootstrap();
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏