คุณจะเพิ่มการซื้อในแอพลงในแอปพลิเคชัน iOS ได้อย่างไร


257

คุณจะเพิ่มการซื้อในแอพในแอพ iOS ได้อย่างไร รายละเอียดทั้งหมดมีอะไรบ้างและมีรหัสตัวอย่างหรือไม่

นี่เป็นวิธีที่น่าสนใจสำหรับวิธีเพิ่มการซื้อในแอพในแอพ iOS


11
สิ่งที่เกี่ยวกับการอ่าน "คู่มือการเขียนโปรแกรมซื้อในแอป"?
rmaddy

คำตอบ:


554

ผู้ใช้ Swift

ผู้ใช้ Swift สามารถดูMy Swift Answer สำหรับคำถามนี้
หรือตรวจสอบคำตอบของ Yedidya Reissซึ่งแปลรหัส Objective-C นี้เป็น Swift

ผู้ใช้ Objective-C

ส่วนที่เหลือของคำตอบนี้เขียนไว้ใน Objective-C

เชื่อมต่อ App Store

  1. ไปที่appstoreconnect.apple.comและเข้าสู่ระบบ
  2. คลิกMy Appsจากนั้นคลิกแอพที่คุณต้องการจะเพิ่มการซื้อ
  3. คลิกที่Featuresส่วนหัวแล้วเลือกIn-App Purchasesทางด้านซ้าย
  4. คลิกที่+ไอคอนตรงกลาง
  5. สำหรับการกวดวิชานี้เราจะต้องเพิ่มการซื้อใน app non-consumableเพื่อโฆษณาลบเพื่อให้เลือก consumableหากคุณกำลังจะไปส่งรายการทางกายภาพให้กับผู้ใช้หรือให้พวกเขาสิ่งที่พวกเขาสามารถซื้อมากกว่าหนึ่งครั้งคุณจะเลือก
  6. สำหรับชื่ออ้างอิงใส่สิ่งที่คุณต้องการ (แต่ให้แน่ใจว่าคุณรู้ว่ามันคืออะไร)
  7. สำหรับรหัสผลิตภัณฑ์tld.websitename.appname.referencenameนี้จะใช้งานได้ดีที่สุดตัวอย่างเช่นคุณสามารถใช้com.jojodmo.blix.removeads
  8. เลือกcleared for saleจากนั้นเลือกระดับราคาเป็น 1 (99 ¢) ชั้นที่ 2 จะเท่ากับ $ 1.99 และชั้นที่ 3 จะเท่ากับ $ 2.99 รายการทั้งหมดจะพร้อมใช้งานถ้าคุณคลิกview pricing matrixฉันขอแนะนำให้คุณใช้ Tier 1 เพราะปกติแล้วคนส่วนใหญ่จะจ่ายเพื่อลบโฆษณา
  9. คลิกที่add languageปุ่มสีน้ำเงินและใส่ข้อมูล ทั้งหมดนี้จะแสดงต่อลูกค้าดังนั้นอย่านำสิ่งที่คุณไม่ต้องการให้พวกเขาเห็น
  10. สำหรับhosting content with Appleเลือกไม่มี
  11. คุณสามารถออกจากการตรวจสอบบันทึกว่างเปล่าสำหรับตอนนี้
  12. ข้ามไปscreenshot for review ตอนนี้ทุกอย่างที่เราข้ามไปเราจะกลับมา
  13. คลิก 'บันทึก'

อาจใช้เวลาสองสามชั่วโมงเพื่อให้ ID ผลิตภัณฑ์ของคุณลงทะเบียนApp Store Connectดังนั้นโปรดอดทนรอ

การตั้งค่าโครงการของคุณ

ตอนนี้คุณได้ตั้งค่าข้อมูลการซื้อในแอพของคุณแล้วใน App Store Connect ไปที่โครงการ Xcode ของคุณและไปที่ตัวจัดการแอปพลิเคชัน (ไอคอนคล้ายหน้าสีน้ำเงินที่ด้านบนของที่ซึ่งวิธีการและไฟล์ส่วนหัวของคุณ) คลิก แอปของคุณภายใต้เป้าหมาย (ควรเป็นแอพแรก) จากนั้นไปที่ทั่วไป ที่ด้านล่างคุณจะเห็นlinked frameworks and librariesคลิกสัญลักษณ์บวกเล็ก ๆ และเพิ่มกรอบStoreKit.frameworkหากคุณไม่ทำเช่นนี้การซื้อในแอปจะไม่ทำงาน!

หากคุณกำลังใช้ Objective-C เป็นภาษาสำหรับแอปของคุณคุณควรข้ามห้าขั้นตอนเหล่านี้ มิฉะนั้นถ้าคุณใช้ Swift คุณสามารถทำตามคำตอบของ Swift สำหรับคำถามนี้ได้ที่นี่หรือถ้าคุณต้องการใช้ Objective-C สำหรับรหัสการซื้อภายในแอป แต่ใช้ Swift ในแอปของคุณคุณสามารถทำสิ่งต่อไปนี้ :

  1. สร้างใหม่.h(หัว) ไฟล์ได้โดยไปที่File> New> File...( Command ⌘+ N) ไฟล์นี้จะถูกเรียกว่า " .hไฟล์ของคุณ" ในส่วนที่เหลือของการสอน

  2. เมื่อได้รับแจ้งให้คลิกสร้าง Bridging หัว นี่จะเป็นไฟล์ส่วนหัวการเชื่อมโยงของเรา หากคุณกำลังไม่ได้รับแจ้งให้ไปที่ขั้นตอนที่ 3 หากคุณจะได้รับแจ้งให้ข้ามขั้นตอนที่ 3 และตรงไปยังขั้นตอนที่ 4

  3. สร้างอีก.hชื่อไฟล์Bridge.hในโฟลเดอร์โครงการหลักจากนั้นไปที่จัดการแอพลิเคชัน (หน้าเหมือนฟ้าไอคอน) แล้วเลือกแอปของคุณในส่วนและคลิกTargets Build Settingsค้นหาตัวเลือกที่ระบุว่าSwift Compiler - การสร้างรหัสจากนั้นตั้งค่าตัวเลือกObjective-C Bridging Headerเป็นBridge.h

  4. ในไฟล์ส่วนหัวบริดจ์ของคุณเพิ่มบรรทัด#import "MyObjectiveCHeaderFile.h"โดยที่MyObjectiveCHeaderFileชื่อของไฟล์ส่วนหัวที่คุณสร้างในขั้นตอนที่หนึ่ง ตัวอย่างเช่นหากคุณตั้งชื่อไฟล์ส่วนหัวของคุณInAppPurchase.hคุณจะต้องเพิ่มบรรทัด#import "InAppPurchase.h"ลงในไฟล์ส่วนหัวของบริดจ์

  5. สร้าง Objective-C วิธี (ใหม่.m) ไฟล์ได้โดยไปที่File> New> File...( Command ⌘+ N) ชื่อมันเช่นเดียวกับส่วนหัวของแฟ้มที่คุณสร้างในขั้นตอนที่ 1 ตัวอย่างเช่นถ้าคุณเรียกว่าแฟ้มในขั้นตอนที่ 1 InAppPurchase.hคุณจะเรียกสิ่งนี้ไฟล์ใหม่InAppPurchase.m ไฟล์นี้จะถูกเรียกว่า " .mไฟล์ของคุณ" ในส่วนที่เหลือของการสอน

การเข้ารหัส

ตอนนี้เรากำลังจะได้รับการเข้ารหัสที่แท้จริง เพิ่มรหัสต่อไปนี้ลงใน.hไฟล์ของคุณ:

BOOL areAdsRemoved;

- (IBAction)restore;
- (IBAction)tapsRemoveAds;

ถัดไปคุณต้องนำเข้าStoreKitเฟรมเวิร์กลงใน.mไฟล์รวมทั้งเพิ่มSKProductsRequestDelegateและSKPaymentTransactionObserverหลัง@interfaceการประกาศของคุณ:

#import <StoreKit/StoreKit.h>

//put the name of your view controller in place of MyViewController
@interface MyViewController() <SKProductsRequestDelegate, SKPaymentTransactionObserver>

@end

@implementation MyViewController //the name of your view controller (same as above)
  //the code below will be added here
@end

และตอนนี้เพิ่มสิ่งต่อไปนี้ลงใน.mไฟล์ของคุณส่วนนี้จะซับซ้อนดังนั้นฉันขอแนะนำให้คุณอ่านความคิดเห็นในรหัส:

//If you have more than one in-app purchase, you can define both of
//of them here. So, for example, you could define both kRemoveAdsProductIdentifier
//and kBuyCurrencyProductIdentifier with their respective product ids
//
//for this example, we will only use one product

#define kRemoveAdsProductIdentifier @"put your product id (the one that we just made in App Store Connect) in here"

- (IBAction)tapsRemoveAds{
    NSLog(@"User requests to remove ads");

    if([SKPaymentQueue canMakePayments]){
        NSLog(@"User can make payments");
    
        //If you have more than one in-app purchase, and would like
        //to have the user purchase a different product, simply define 
        //another function and replace kRemoveAdsProductIdentifier with 
        //the identifier for the other product

        SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:kRemoveAdsProductIdentifier]];
        productsRequest.delegate = self;
        [productsRequest start];
    
    }
    else{
        NSLog(@"User cannot make payments due to parental controls");
        //this is called the user cannot make payments, most likely due to parental controls
    }
}

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    SKProduct *validProduct = nil;
    int count = [response.products count];
    if(count > 0){
        validProduct = [response.products objectAtIndex:0];
        NSLog(@"Products Available!");
        [self purchase:validProduct];
    }
    else if(!validProduct){
        NSLog(@"No products available");
        //this is called if your product id is not valid, this shouldn't be called unless that happens.
    }
}

- (void)purchase:(SKProduct *)product{
    SKPayment *payment = [SKPayment paymentWithProduct:product];

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

- (IBAction) restore{
    //this is called when the user restores purchases, you should hook this up to a button
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    NSLog(@"received restored transactions: %i", queue.transactions.count);
    for(SKPaymentTransaction *transaction in queue.transactions){
        if(transaction.transactionState == SKPaymentTransactionStateRestored){
            //called when the user successfully restores a purchase
            NSLog(@"Transaction state -> Restored");

            //if you have more than one in-app purchase product,
            //you restore the correct product for the identifier.
            //For example, you could use
            //if(productID == kRemoveAdsProductIdentifier)
            //to get the product identifier for the
            //restored purchases, you can use
            //
            //NSString *productID = transaction.payment.productIdentifier;
            [self doRemoveAds];
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            break;
        }
    }   
}

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
    for(SKPaymentTransaction *transaction in transactions){
        //if you have multiple in app purchases in your app,
        //you can get the product identifier of this transaction
        //by using transaction.payment.productIdentifier
        //
        //then, check the identifier against the product IDs
        //that you have defined to check which product the user
        //just purchased            

        switch(transaction.transactionState){
            case SKPaymentTransactionStatePurchasing: NSLog(@"Transaction state -> Purchasing");
                //called when the user is in the process of purchasing, do not add any of your own code here.
                break;
            case SKPaymentTransactionStatePurchased:
            //this is called when the user has successfully purchased the package (Cha-Ching!)
                [self doRemoveAds]; //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                NSLog(@"Transaction state -> Purchased");
                break;
            case SKPaymentTransactionStateRestored:
                NSLog(@"Transaction state -> Restored");
                //add the same code as you did from SKPaymentTransactionStatePurchased here
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                //called when the transaction does not finish
                if(transaction.error.code == SKErrorPaymentCancelled){
                    NSLog(@"Transaction state -> Cancelled");
                    //the user cancelled the payment ;(
                }
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
        }
    }
}

ตอนนี้คุณต้องการเพิ่มรหัสของคุณสำหรับสิ่งที่จะเกิดขึ้นเมื่อผู้ใช้เสร็จสิ้นการทำธุรกรรมสำหรับการกวดวิชานี้เราใช้การลบเพิ่มคุณจะต้องเพิ่มรหัสของคุณเองสำหรับสิ่งที่เกิดขึ้นเมื่อโหลดมุมมองแบนเนอร์

- (void)doRemoveAds{
    ADBannerView *banner;
    [banner setAlpha:0];
    areAdsRemoved = YES;
    removeAdsButton.hidden = YES;
    removeAdsButton.enabled = NO;
    [[NSUserDefaults standardUserDefaults] setBool:areAdsRemoved forKey:@"areAdsRemoved"];
    //use NSUserDefaults so that you can load whether or not they bought it
    //it would be better to use KeyChain access, or something more secure
    //to store the user data, because NSUserDefaults can be changed.
    //You're average downloader won't be able to change it very easily, but
    //it's still best to use something more secure than NSUserDefaults.
    //For the purpose of this tutorial, though, we're going to use NSUserDefaults
    [[NSUserDefaults standardUserDefaults] synchronize];
}

หากคุณไม่มีโฆษณาในแอปพลิเคชันของคุณคุณสามารถใช้สิ่งอื่นที่คุณต้องการ ตัวอย่างเช่นเราสามารถทำให้สีของพื้นหลังเป็นสีน้ำเงิน ในการทำสิ่งนี้เราต้องการที่จะใช้:

- (void)doRemoveAds{
    [self.view setBackgroundColor:[UIColor blueColor]];
    areAdsRemoved = YES
    //set the bool for whether or not they purchased it to YES, you could use your own boolean here, but you would have to declare it in your .h file

    [[NSUserDefaults standardUserDefaults] setBool:areAdsRemoved forKey:@"areAdsRemoved"];
    //use NSUserDefaults so that you can load wether or not they bought it
    [[NSUserDefaults standardUserDefaults] synchronize];
}

ตอนนี้บางแห่งในviewDidLoadวิธีการของคุณคุณจะต้องการที่จะเพิ่มรหัสต่อไปนี้:

areAdsRemoved = [[NSUserDefaults standardUserDefaults] boolForKey:@"areAdsRemoved"];
[[NSUserDefaults standardUserDefaults] synchronize];
//this will load wether or not they bought the in-app purchase

if(areAdsRemoved){
    [self.view setBackgroundColor:[UIColor blueColor]];
    //if they did buy it, set the background to blue, if your using the code above to set the background to blue, if your removing ads, your going to have to make your own code here
}

ตอนนี้คุณได้เพิ่มรหัสทั้งหมดเข้าไปในไฟล์.xibหรือของคุณstoryboardแล้วเพิ่มสองปุ่มหนึ่งคำสั่งซื้อและอีกคำหนึ่งบอกว่าคืนค่า เชื่อมtapsRemoveAds IBActionต่อกับปุ่มซื้อที่คุณเพิ่งสร้างขึ้นและrestore IBActionไปที่ปุ่มเรียกคืน การrestoreดำเนินการจะตรวจสอบว่าผู้ใช้เคยซื้อการซื้อภายในแอปหรือไม่และให้การซื้อในแอปฟรีโดยไม่ต้องซื้อ

กำลังส่งเพื่อตรวจสอบ

ถัดจากนั้นไปลงในApp Store ConnectและคลิกUsers and Accessจากนั้นคลิกที่Sandbox Testersส่วนหัวและจากนั้นคลิกสัญลักษณ์ด้านซ้ายที่จะกล่าวว่า+ Testersคุณสามารถใส่ชื่อแบบสุ่มและชื่อจริง ๆ และอีเมลไม่จำเป็นต้องเป็นของจริงคุณต้องจำได้ ใส่รหัสผ่าน (ซึ่งคุณจะต้องจำไว้) และกรอกข้อมูลที่เหลือ ฉันอยากจะแนะนำให้คุณทำDate of Birthวันที่ที่จะทำให้ผู้ใช้ 18 หรือมากกว่า App Store Territory HASที่จะอยู่ในประเทศที่ถูกต้อง ถัดไปออกจากระบบบัญชี iTunes ที่คุณมีอยู่ (คุณสามารถลงชื่อเข้าใช้อีกครั้งหลังจากบทช่วยสอนนี้)

ตอนนี้ให้รันแอปพลิเคชั่นของคุณบนอุปกรณ์ iOS ของคุณหากคุณลองใช้แอพจำลองการซื้อจะมีข้อผิดพลาดอยู่เสมอคุณจะต้องรันมันบนอุปกรณ์ iOS ของคุณ เมื่อแอพทำงานอยู่ให้แตะปุ่มซื้อ เมื่อคุณได้รับแจ้งให้เข้าสู่บัญชี iTunes ของคุณเข้าสู่ระบบในฐานะผู้ใช้ทดสอบที่เราเพิ่งสร้างขึ้น ถัดไปเมื่อมันขอให้คุณยืนยันการซื้อ 99 ¢หรืออะไรก็ตามที่คุณกำหนดระดับราคาด้วยSNAPSHOT ของหน้าจอมันคือสิ่งที่คุณจะใช้สำหรับscreenshot for reviewApp Store Connect ของคุณ ยกเลิกการชำระเงินทันที

ตอนนี้ให้ไปที่App Store เชื่อมต่อจากนั้นไปที่My Apps> >the app you have the In-app purchase on In-App Purchasesจากนั้นคลิกการซื้อในแอพและคลิกแก้ไขภายใต้รายละเอียดการซื้อในแอป เมื่อคุณทำเสร็จแล้วให้นำเข้ารูปภาพที่คุณเพิ่งถ่ายบน iPhone ของคุณไปยังคอมพิวเตอร์และอัปโหลดเป็นภาพหน้าจอเพื่อตรวจสอบจากนั้นในบันทึกการตรวจสอบใส่อีเมลและรหัสผ่านของผู้ทดสอบ นี่จะช่วยแอปเปิ้ลในกระบวนการตรวจสอบ

หลังจากคุณทำสิ่งนี้แล้วให้กลับไปที่แอปพลิเคชันบนอุปกรณ์ iOS ของคุณยังคงเข้าสู่ระบบในฐานะบัญชีผู้ใช้ทดสอบแล้วคลิกปุ่มซื้อ เวลานี้ยืนยันการชำระเงินไม่ต้องกังวลนี่จะไม่เรียกเก็บเงินจากบัญชีของคุณเงินใด ๆ ทดสอบบัญชีผู้ใช้รับการซื้อในแอปทั้งหมดฟรีหลังจากที่คุณยืนยันการชำระเงินให้แน่ใจว่าเกิดอะไรขึ้นเมื่อผู้ใช้ซื้อผลิตภัณฑ์ของคุณจริง ที่เกิดขึ้น หากไม่เป็นเช่นนั้นนั่นจะเป็นข้อผิดพลาดกับdoRemoveAdsวิธีการของคุณ ฉันขอแนะนำให้ใช้การเปลี่ยนพื้นหลังเป็นสีน้ำเงินเพื่อทดสอบการซื้อในแอปซึ่งไม่ควรเป็นการซื้อในแอปตามจริงของคุณ หากทุกอย่างได้ผลและคุณก็พร้อมที่จะไป! เพียงตรวจสอบให้แน่ใจว่าได้รวมการซื้อในแอพในไบนารีใหม่ของคุณเมื่อคุณอัปโหลดไปยังการเชื่อมต่อ App Store!


นี่คือข้อผิดพลาดทั่วไป:

เข้าสู่ระบบ: No Products Available

นี่อาจหมายถึงสี่สิ่ง:

  • คุณไม่ได้ใส่รหัสการซื้อในแอปที่ถูกต้องในรหัสของคุณ (สำหรับรหัสkRemoveAdsProductIdentifierในรหัสด้านบน
  • คุณไม่ได้ล้างการซื้อในแอพเพื่อขายในApp Store Connect
  • คุณไม่ได้รอซื้อใน app ID เพื่อลงทะเบียนในApp Store Connect รอสองสามชั่วโมงจากการสร้าง ID และปัญหาของคุณควรได้รับการแก้ไข
  • คุณกรอกข้อมูลข้อตกลงภาษีและข้อมูลธนาคารของคุณไม่เสร็จ

หากไม่ได้ผลในครั้งแรกอย่าหงุดหงิด! อย่ายอมแพ้! ฉันใช้เวลาประมาณ 5 ชั่วโมงก่อนที่ฉันจะสามารถใช้งานได้และใช้เวลาประมาณ 10 ชั่วโมงในการค้นหารหัสที่ถูกต้อง! หากคุณใช้รหัสด้านบนอย่างแน่นอนควรทำงานได้ดี รู้สึกอิสระที่จะแสดงความคิดเห็นถ้าคุณมีคำถามใด ๆที่ทุกคน

ฉันหวังว่านี่จะช่วยให้ทุกคนที่ต้องการเพิ่มการซื้อในแอพในแอปพลิเคชัน iOS ของพวกเขา ไชโย!


1
แต่ถ้าฉันไม่เพิ่มบรรทัดที่เมื่อฉันคลิกที่ปุ่มเรียกคืนไม่มีอะไรเกิดขึ้นเลยล่ะค่ะ .. ขอบคุณมากสำหรับการกวดวิชานี้;)
Ilario

1
"if ( * transaction * == SKPaymentTransactionStateRestored) {" ควรเป็นถ้า ( * transaction.transactionState * == SKPaymentTransactionStateRestored) {
Massmaker

13
แนวทางปฏิบัติที่ดีที่สุดของ Apple แนะนำให้คุณเพิ่มผู้สังเกตการณ์ธุรกรรมลงใน AppDelegate ไม่ใช่การกระทำของตัวควบคุมมุมมอง developer.apple.com/library/ios/technotes/tn2387/_index.html
Craig Pickering

3
ฉันได้รับผลิตภัณฑ์นับ 0 รายการ แต่ฉันได้ตรวจสอบ 3 เหตุผลที่เป็นไปได้ที่คุณระบุไว้ สิ่งเดียวที่อยู่ในใจหากฉันยังไม่ได้ตั้งค่าข้อมูลติดต่อข้อมูลธนาคารและข้อมูลภาษีใน "สัญญาชำระเงินแอพ ios" ภายใน iTunes เชื่อมต่อนี่อาจเป็นเหตุผลหรือไม่
Christopher Francisco

4
คุณควรอธิบายว่าในขั้นตอนที่ 9 ชื่อที่ปรากฏคือสิ่งที่จะปรากฏต่อผู้ใช้ และจะได้รับการนำเสนอในลักษณะนี้: "คุณต้องการซื้อ DISPLAY NAME ราคา $ 0.99 หรือไม่" สิ่งนี้มีความสำคัญเพราะฉันตั้งชื่อที่แสดงว่า "ลบโฆษณา" แล้วแอปของฉันถูกปฏิเสธเพราะฉันใช้ไวยากรณ์ที่ไม่เหมาะสมในป๊อปอัป! ฉันต้องเปลี่ยนชื่อที่แสดงเป็น "แพ็คเกจลบโฆษณา"
Alan Scarpa

13

เพียงแปลรหัส Jojodmo เป็น Swift:

class InAppPurchaseManager: NSObject , SKProductsRequestDelegate, SKPaymentTransactionObserver{





//If you have more than one in-app purchase, you can define both of
//of them here. So, for example, you could define both kRemoveAdsProductIdentifier
//and kBuyCurrencyProductIdentifier with their respective product ids
//
//for this example, we will only use one product

let kRemoveAdsProductIdentifier = "put your product id (the one that we just made in iTunesConnect) in here"

@IBAction func tapsRemoveAds() {

    NSLog("User requests to remove ads")

    if SKPaymentQueue.canMakePayments() {
        NSLog("User can make payments")

        //If you have more than one in-app purchase, and would like
        //to have the user purchase a different product, simply define
        //another function and replace kRemoveAdsProductIdentifier with
        //the identifier for the other product
        let set : Set<String> = [kRemoveAdsProductIdentifier]
        let productsRequest = SKProductsRequest(productIdentifiers: set)
        productsRequest.delegate = self
        productsRequest.start()

    }
    else {
        NSLog("User cannot make payments due to parental controls")
        //this is called the user cannot make payments, most likely due to parental controls
    }
}


func purchase(product : SKProduct) {

    let payment = SKPayment(product: product)
    SKPaymentQueue.defaultQueue().addTransactionObserver(self)
    SKPaymentQueue.defaultQueue().addPayment(payment)
}

func restore() {
    //this is called when the user restores purchases, you should hook this up to a button
    SKPaymentQueue.defaultQueue().addTransactionObserver(self)
    SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}


func doRemoveAds() {
    //TODO: implement
}

/////////////////////////////////////////////////
//////////////// store delegate /////////////////
/////////////////////////////////////////////////
// MARK: - store delegate -


func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {

    if let validProduct = response.products.first {
        NSLog("Products Available!")
        self.purchase(validProduct)
    }
    else {
        NSLog("No products available")
        //this is called if your product id is not valid, this shouldn't be called unless that happens.
    }
}

func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {


    NSLog("received restored transactions: \(queue.transactions.count)")
    for transaction in queue.transactions {
        if transaction.transactionState == .Restored {
            //called when the user successfully restores a purchase
            NSLog("Transaction state -> Restored")

            //if you have more than one in-app purchase product,
            //you restore the correct product for the identifier.
            //For example, you could use
            //if(productID == kRemoveAdsProductIdentifier)
            //to get the product identifier for the
            //restored purchases, you can use
            //
            //NSString *productID = transaction.payment.productIdentifier;
            self.doRemoveAds()
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
            break;
        }
    }
}


func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

    for transaction in transactions {
        switch transaction.transactionState {
        case .Purchasing: NSLog("Transaction state -> Purchasing")
            //called when the user is in the process of purchasing, do not add any of your own code here.
        case .Purchased:
            //this is called when the user has successfully purchased the package (Cha-Ching!)
            self.doRemoveAds() //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
            NSLog("Transaction state -> Purchased")
        case .Restored:
            NSLog("Transaction state -> Restored")
            //add the same code as you did from SKPaymentTransactionStatePurchased here
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
        case .Failed:
            //called when the transaction does not finish
            if transaction.error?.code == SKErrorPaymentCancelled {
                NSLog("Transaction state -> Cancelled")
                //the user cancelled the payment ;(
            }
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
        case .Deferred:
            // The transaction is in the queue, but its final status is pending external action.
            NSLog("Transaction state -> Deferred")

        }


    }
}
} 

6

คำตอบที่รวดเร็ว

สิ่งนี้มีขึ้นเพื่อเสริมคำตอบ Objective-C ของฉันสำหรับผู้ใช้ Swift เพื่อป้องกันไม่ให้คำตอบ Objective-C มีขนาดใหญ่เกินไป

ติดตั้ง

ครั้งแรกตั้งค่าการซื้อใน app บนappstoreconnect.apple.com ทำตามส่วนเริ่มต้นของคำตอบ Objective-Cของฉัน (ขั้นตอนที่ 1-13 ภายใต้ส่วนหัวของApp Store Connect ) สำหรับคำแนะนำในการทำเช่นนั้น

อาจใช้เวลาสองสามชั่วโมงเพื่อให้ ID ผลิตภัณฑ์ของคุณลงทะเบียนใน App Store Connect ดังนั้นขอให้อดทน

ตอนนี้คุณได้ตั้งค่าข้อมูลการซื้อในแอพของคุณบน App Store Connect แล้วเราต้องเพิ่มเฟรมเวิร์กของ Apple สำหรับการซื้อStoreKitในแอพลงในแอพ

เข้าสู่โปรเจ็กต์ Xcode ของคุณและไปที่ตัวจัดการแอปพลิเคชัน (ไอคอนคล้ายหน้าสีน้ำเงินที่ด้านบนของแถบด้านซ้ายที่มีไฟล์แอปของคุณอยู่) คลิกที่แอปของคุณภายใต้เป้าหมายทางซ้าย (ควรเป็นตัวเลือกแรก) จากนั้นไปที่ "ความสามารถ" ที่ด้านบน ในรายการคุณจะเห็นตัวเลือก "การซื้อในแอพ" เปิดใช้ความสามารถนี้และ Xcode จะเพิ่มStoreKitในโครงการของคุณ

การเข้ารหัส

ตอนนี้เรากำลังจะเริ่มเขียนโค้ด!

ก่อนอื่นให้สร้างไฟล์ swift ใหม่ที่จะจัดการการซื้อในแอพทั้งหมดของคุณ IAPManager.swiftฉันจะเรียกมันว่า

ในแฟ้มนี้เรากำลังจะสร้างคลาสใหม่ที่เรียกIAPManagerว่าเป็นและSKProductsRequestDelegate SKPaymentTransactionObserverที่ด้านบนตรวจสอบให้แน่ใจว่าคุณนำเข้าFoundationและStoreKit

import Foundation
import StoreKit

public class IAPManager: NSObject, SKProductsRequestDelegate,
                         SKPaymentTransactionObserver {
}

ต่อไปเราจะเพิ่มตัวแปรเพื่อกำหนดตัวระบุสำหรับการซื้อในแอพของเรา (คุณสามารถใช้อันenumซึ่งจะง่ายต่อการบำรุงรักษาหากคุณมี IAP หลายตัว)

// This should the ID of the in-app-purchase you made on AppStore Connect.
// if you have multiple IAPs, you'll need to store their identifiers in
// other variables, too (or, preferably in an enum).
let removeAdsID = "com.skiplit.removeAds"

ลองเพิ่ม initializer สำหรับคลาสของเราต่อไป:

// This is the initializer for your IAPManager class
//
// A better, and more scaleable way of doing this
// is to also accept a callback in the initializer, and call
// that callback in places like the paymentQueue function, and
// in all functions in this class, in place of calls to functions
// in RemoveAdsManager (you'll see those calls in the code below).

let productID: String
init(productID: String){
    self.productID = productID
}

ตอนนี้เรากำลังจะเพิ่มฟังก์ชั่นที่จำเป็นสำหรับSKProductsRequestDelegateและSKPaymentTransactionObserverในการทำงาน:

เราจะเพิ่มRemoveAdsManagerชั้นเรียนในภายหลัง

// This is called when a SKProductsRequest receives a response
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse){
    // Let's try to get the first product from the response
    // to the request
    if let product = response.products.first{
        // We were able to get the product! Make a new payment
        // using this product
        let payment = SKPayment(product: product)

        // add the new payment to the queue
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().add(payment)
    }
    else{
        // Something went wrong! It is likely that either
        // the user doesn't have internet connection, or
        // your product ID is wrong!
        //
        // Tell the user in requestFailed() by sending an alert,
        // or something of the sort

        RemoveAdsManager.removeAdsFailure()
    }
}

// This is called when the user restores their IAP sucessfully
private func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue){
    // For every transaction in the transaction queue...
    for transaction in queue.transactions{
        // If that transaction was restored
        if transaction.transactionState == .restored{
            // get the producted ID from the transaction
            let productID = transaction.payment.productIdentifier

            // In this case, we have only one IAP, so we don't need to check
            // what IAP it is. However, this is useful if you have multiple IAPs!
            // You'll need to figure out which one was restored
            if(productID.lowercased() == IAPManager.removeAdsID.lowercased()){
                // Restore the user's purchases
                RemoveAdsManager.restoreRemoveAdsSuccess()
            }

            // finish the payment
            SKPaymentQueue.default().finishTransaction(transaction)
        }
    }
}

// This is called when the state of the IAP changes -- from purchasing to purchased, for example.
// This is where the magic happens :)
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
    for transaction in transactions{
        // get the producted ID from the transaction
        let productID = transaction.payment.productIdentifier

        // In this case, we have only one IAP, so we don't need to check
        // what IAP it is.
        // However, if you have multiple IAPs, you'll need to use productID
        // to check what functions you should run here!

        switch transaction.transactionState{
        case .purchasing:
            // if the user is currently purchasing the IAP,
            // we don't need to do anything.
            //
            // You could use this to show the user
            // an activity indicator, or something like that
            break
        case .purchased:
            // the user successfully purchased the IAP!
            RemoveAdsManager.removeAdsSuccess()
            SKPaymentQueue.default().finishTransaction(transaction)
        case .restored:
                // the user restored their IAP!
                IAPTestingHandler.restoreRemoveAdsSuccess()
                SKPaymentQueue.default().finishTransaction(transaction)
        case .failed:
                // The transaction failed!
                RemoveAdsManager.removeAdsFailure()
                // finish the transaction
                SKPaymentQueue.default().finishTransaction(transaction)
        case .deferred:
                // This happens when the IAP needs an external action
                // in order to proceeded, like Ask to Buy
                RemoveAdsManager.removeAdsDeferred()
                break
        }
    }
}

ตอนนี้ให้เพิ่มฟังก์ชั่นบางอย่างที่สามารถใช้ในการเริ่มซื้อหรือซื้อคืน:

// Call this when you want to begin a purchase
// for the productID you gave to the initializer
public func beginPurchase(){
    // If the user can make payments
    if SKPaymentQueue.canMakePayments(){
        // Create a new request
        let request = SKProductsRequest(productIdentifiers: [productID])
        // Set the request delegate to self, so we receive a response
        request.delegate = self
        // start the request
        request.start()
    }
    else{
        // Otherwise, tell the user that
        // they are not authorized to make payments,
        // due to parental controls, etc
    }
}

// Call this when you want to restore all purchases
// regardless of the productID you gave to the initializer
public func beginRestorePurchases(){
    // restore purchases, and give responses to self
    SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().restoreCompletedTransactions()
}

จากนั้นให้เพิ่มคลาสยูทิลิตี้ใหม่เพื่อจัดการ IAP ของเรา รหัสทั้งหมดนี้อาจเป็นหนึ่งในชั้นเรียน แต่ถ้ามีหลาย ๆ ตัวทำให้มันดูสะอาดขึ้นเล็กน้อย ฉันจะสร้างคลาสใหม่ที่เรียกว่าRemoveAdsManagerและในนั้นใส่ฟังก์ชั่นบางอย่าง

public class RemoveAdsManager{

    class func removeAds()
    class func restoreRemoveAds()

    class func areAdsRemoved() -> Bool

    class func removeAdsSuccess()
    class func restoreRemoveAdsSuccess()
    class func removeAdsDeferred()
    class func removeAdsFailure()
}

ทั้งสามฟังก์ชั่นแรกremoveAds, restoreRemoveAdsและareAdsRemovedมีฟังก์ชั่นที่คุณจะเรียกร้องให้ทำการกระทำบางอย่าง IAPManagerช่วงสี่เป็นส่วนหนึ่งที่จะเรียกได้ว่า

ลองเพิ่มโค้ดลงในสองฟังก์ชันแรกremoveAdsและrestoreRemoveAds:

// Call this when the user wants
// to remove ads, like when they
// press a "remove ads" button
class func removeAds(){
    // Before starting the purchase, you could tell the
    // user that their purchase is happening, maybe with
    // an activity indicator

    let iap = IAPManager(productID: IAPManager.removeAdsID)
    iap.beginPurchase()
}

// Call this when the user wants
// to restore their IAP purchases,
// like when they press a "restore
// purchases" button.
class func restoreRemoveAds(){
    // Before starting the purchase, you could tell the
    // user that the restore action is happening, maybe with
    // an activity indicator

    let iap = IAPManager(productID: IAPManager.removeAdsID)
    iap.beginRestorePurchases()
}

และสุดท้ายเรามาเพิ่มรหัสลงในห้าฟังก์ชันสุดท้าย

// Call this to check whether or not
// ads are removed. You can use the
// result of this to hide or show
// ads
class func areAdsRemoved() -> Bool{
    // This is the code that is run to check
    // if the user has the IAP.

    return UserDefaults.standard.bool(forKey: "RemoveAdsPurchased")
}

// This will be called by IAPManager
// when the user sucessfully purchases
// the IAP
class func removeAdsSuccess(){
    // This is the code that is run to actually
    // give the IAP to the user!
    //
    // I'm using UserDefaults in this example,
    // but you may want to use Keychain,
    // or some other method, as UserDefaults
    // can be modified by users using their
    // computer, if they know how to, more
    // easily than Keychain

    UserDefaults.standard.set(true, forKey: "RemoveAdsPurchased")
    UserDefaults.standard.synchronize()
}

// This will be called by IAPManager
// when the user sucessfully restores
//  their purchases
class func restoreRemoveAdsSuccess(){
    // Give the user their IAP back! Likely all you'll need to
    // do is call the same function you call when a user
    // sucessfully completes their purchase. In this case, removeAdsSuccess()

    removeAdsSuccess()
}

// This will be called by IAPManager
// when the IAP failed
class func removeAdsFailure(){
    // Send the user a message explaining that the IAP
    // failed for some reason, and to try again later
}

// This will be called by IAPManager
// when the IAP gets deferred.
class func removeAdsDeferred(){
    // Send the user a message explaining that the IAP
    // was deferred, and pending an external action, like
    // Ask to Buy.
}

เมื่อรวมเข้าด้วยกันเราจะได้อะไรเช่นนี้:

import Foundation
import StoreKit

public class RemoveAdsManager{

    // Call this when the user wants
    // to remove ads, like when they
    // press a "remove ads" button
    class func removeAds(){
        // Before starting the purchase, you could tell the
        // user that their purchase is happening, maybe with
        // an activity indicator

        let iap = IAPManager(productID: IAPManager.removeAdsID)
        iap.beginPurchase()
    }

    // Call this when the user wants
    // to restore their IAP purchases,
    // like when they press a "restore
    // purchases" button.
    class func restoreRemoveAds(){
        // Before starting the purchase, you could tell the
        // user that the restore action is happening, maybe with
        // an activity indicator

        let iap = IAPManager(productID: IAPManager.removeAdsID)
        iap.beginRestorePurchases()
    }

    // Call this to check whether or not
    // ads are removed. You can use the
    // result of this to hide or show
    // ads
    class func areAdsRemoved() -> Bool{
        // This is the code that is run to check
        // if the user has the IAP.

        return UserDefaults.standard.bool(forKey: "RemoveAdsPurchased")
    }

    // This will be called by IAPManager
    // when the user sucessfully purchases
    // the IAP
    class func removeAdsSuccess(){
        // This is the code that is run to actually
        // give the IAP to the user!
        //
        // I'm using UserDefaults in this example,
        // but you may want to use Keychain,
        // or some other method, as UserDefaults
        // can be modified by users using their
        // computer, if they know how to, more
        // easily than Keychain

        UserDefaults.standard.set(true, forKey: "RemoveAdsPurchased")
        UserDefaults.standard.synchronize()
    }

    // This will be called by IAPManager
    // when the user sucessfully restores
    //  their purchases
    class func restoreRemoveAdsSuccess(){
        // Give the user their IAP back! Likely all you'll need to
        // do is call the same function you call when a user
        // sucessfully completes their purchase. In this case, removeAdsSuccess()
        removeAdsSuccess()
    }

    // This will be called by IAPManager
    // when the IAP failed
    class func removeAdsFailure(){
        // Send the user a message explaining that the IAP
        // failed for some reason, and to try again later
    }

    // This will be called by IAPManager
    // when the IAP gets deferred.
    class func removeAdsDeferred(){
        // Send the user a message explaining that the IAP
        // was deferred, and pending an external action, like
        // Ask to Buy.
    }

}

public class IAPManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver{

    // This should the ID of the in-app-purchase you made on AppStore Connect.
    // if you have multiple IAPs, you'll need to store their identifiers in
    // other variables, too (or, preferably in an enum).
    static let removeAdsID = "com.skiplit.removeAds"

    // This is the initializer for your IAPManager class
    //
    // An alternative, and more scaleable way of doing this
    // is to also accept a callback in the initializer, and call
    // that callback in places like the paymentQueue function, and
    // in all functions in this class, in place of calls to functions
    // in RemoveAdsManager.
    let productID: String
    init(productID: String){
        self.productID = productID
    }

    // Call this when you want to begin a purchase
    // for the productID you gave to the initializer
    public func beginPurchase(){
        // If the user can make payments
        if SKPaymentQueue.canMakePayments(){
            // Create a new request
            let request = SKProductsRequest(productIdentifiers: [productID])
            request.delegate = self
            request.start()
        }
        else{
            // Otherwise, tell the user that
            // they are not authorized to make payments,
            // due to parental controls, etc
        }
    }

    // Call this when you want to restore all purchases
    // regardless of the productID you gave to the initializer
    public func beginRestorePurchases(){
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().restoreCompletedTransactions()
    }

    // This is called when a SKProductsRequest receives a response
    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse){
        // Let's try to get the first product from the response
        // to the request
        if let product = response.products.first{
            // We were able to get the product! Make a new payment
            // using this product
            let payment = SKPayment(product: product)

            // add the new payment to the queue
            SKPaymentQueue.default().add(self)
            SKPaymentQueue.default().add(payment)
        }
        else{
            // Something went wrong! It is likely that either
            // the user doesn't have internet connection, or
            // your product ID is wrong!
            //
            // Tell the user in requestFailed() by sending an alert,
            // or something of the sort

            RemoveAdsManager.removeAdsFailure()
        }
    }

    // This is called when the user restores their IAP sucessfully
    private func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue){
        // For every transaction in the transaction queue...
        for transaction in queue.transactions{
            // If that transaction was restored
            if transaction.transactionState == .restored{
                // get the producted ID from the transaction
                let productID = transaction.payment.productIdentifier

                // In this case, we have only one IAP, so we don't need to check
                // what IAP it is. However, this is useful if you have multiple IAPs!
                // You'll need to figure out which one was restored
                if(productID.lowercased() == IAPManager.removeAdsID.lowercased()){
                    // Restore the user's purchases
                    RemoveAdsManager.restoreRemoveAdsSuccess()
                }

                // finish the payment
                SKPaymentQueue.default().finishTransaction(transaction)
            }
        }
    }

    // This is called when the state of the IAP changes -- from purchasing to purchased, for example.
    // This is where the magic happens :)
    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
        for transaction in transactions{
            // get the producted ID from the transaction
            let productID = transaction.payment.productIdentifier

            // In this case, we have only one IAP, so we don't need to check
            // what IAP it is.
            // However, if you have multiple IAPs, you'll need to use productID
            // to check what functions you should run here!

            switch transaction.transactionState{
            case .purchasing:
                // if the user is currently purchasing the IAP,
                // we don't need to do anything.
                //
                // You could use this to show the user
                // an activity indicator, or something like that
                break
            case .purchased:
                // the user sucessfully purchased the IAP!
                RemoveAdsManager.removeAdsSuccess()
                SKPaymentQueue.default().finishTransaction(transaction)
            case .restored:
                // the user restored their IAP!
                RemoveAdsManager.restoreRemoveAdsSuccess()
                SKPaymentQueue.default().finishTransaction(transaction)
            case .failed:
                // The transaction failed!
                RemoveAdsManager.removeAdsFailure()
                // finish the transaction
                SKPaymentQueue.default().finishTransaction(transaction)
            case .deferred:
                // This happens when the IAP needs an external action
                // in order to proceeded, like Ask to Buy
                RemoveAdsManager.removeAdsDeferred()
                break
            }
        }
    }

}

ท้ายสุดคุณต้องเพิ่มวิธีการบางอย่างสำหรับผู้ใช้ในการเริ่มซื้อและโทรRemoveAdsManager.removeAds()และเริ่มต้นการคืนค่าและโทรRemoveAdsManager.restoreRemoveAds()เหมือนปุ่มที่อื่น! โปรดทราบว่าตามแนวทางของ App Store คุณจำเป็นต้องให้ปุ่มเพื่อคืนค่าการซื้อบางแห่ง

กำลังส่งเพื่อตรวจสอบ

สิ่งสุดท้ายที่ต้องทำคือส่ง IAP ของคุณเพื่อตรวจสอบที่ App Store Connect! สำหรับคำแนะนำในการทำรายละเอียดที่คุณสามารถทำตามส่วนสุดท้ายของคำตอบ Objective-C ของฉันภายใต้ส่งสำหรับการตรวจสอบส่วนหัว


4

RMStoreเป็นไลบรารี่ iOS ที่มีน้ำหนักเบาสำหรับการซื้อในแอพ มันห่อ StoreKit API และให้บล็อกที่มีประโยชน์สำหรับการร้องขอแบบอะซิงโครนัส การซื้อผลิตภัณฑ์ทำได้ง่ายเพียงแค่เรียกวิธีเดียว

สำหรับผู้ใช้ขั้นสูงไลบรารีนี้ยังให้การตรวจสอบใบเสร็จรับเงินการดาวน์โหลดเนื้อหาและการคงอยู่ของธุรกรรม


-1

ฉันรู้ว่าฉันค่อนข้างช้าในการโพสต์สิ่งนี้ แต่ฉันแบ่งปันประสบการณ์ที่คล้ายกันเมื่อฉันเรียนรู้เชือกของโมเดล IAP

การซื้อในแอพเป็นหนึ่งในเวิร์กโฟลว์ที่ครอบคลุมที่สุดใน iOS ที่ใช้งานโดยกรอบงานของ Storekit เอกสารทั้งหมดมีความชัดเจนมากถ้าคุณอดทนที่จะอ่านมัน แต่เป็นขั้นสูงบ้างในลักษณะของวิชา

เพื่อสรุป:

1 - ร้องขอผลิตภัณฑ์ - ใช้คลาส SKProductRequest & SKProductRequestDelegate เพื่อออกคำขอรหัสผลิตภัณฑ์และรับพวกเขากลับมาจากร้าน itunes ของคุณเชื่อมต่อ

SKProducts เหล่านี้ควรใช้เพื่อเติม UI ร้านค้าของคุณซึ่งผู้ใช้สามารถใช้ซื้อผลิตภัณฑ์เฉพาะได้

2 - ออกคำขอการชำระเงิน - ใช้ SKPayment & SKPaymentQueue เพื่อเพิ่มการชำระเงินในคิวธุรกรรม

3 - ตรวจสอบคิวธุรกรรมสำหรับการอัพเดตสถานะ - ใช้วิธี SKYpmentTransactionObserver ProtocolTransTransactions เพื่อตรวจสอบสถานะ:

SKPaymentTransactionStatePurchasing - don't do anything
SKPaymentTransactionStatePurchased - unlock product, finish the transaction
SKPaymentTransactionStateFailed - show error, finish the transaction
SKPaymentTransactionStateRestored - unlock product, finish the transaction

4 - เรียกคืนโฟลว์ปุ่ม - ใช้ SKPaymentQueue's restoreCompletedTransactions เพื่อให้บรรลุสิ่งนี้ - ขั้นตอนที่ 3 จะดูแลส่วนที่เหลือพร้อมกับ SKPaymentTransactionObserver วิธีการต่อไปนี้:

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

นี่คือการสอนทีละขั้นตอน (เขียนโดยฉันเนื่องจากความพยายามของฉันที่จะเข้าใจ) ที่อธิบาย ในตอนท้ายมันยังมีตัวอย่างโค้ดที่คุณสามารถใช้ได้โดยตรง

นี่คืออีกหนึ่งฉันสร้างขึ้นเพื่ออธิบายบางสิ่งที่ข้อความเท่านั้นที่สามารถอธิบายในลักษณะที่ดีกว่า


21
StackOverflow เป็นเว็บไซต์สำหรับช่วยเหลือผู้อื่นและไม่ใช่เพื่อพยายามหารายได้จากพวกเขา คุณควรลบลิงค์ตัวที่สองลิงก์สุดท้ายหรือเพียงแค่โพสต์สิ่งที่ทำในบทช่วยสอนที่นี่ฟรี
Jojodmo

@Jojodmo คุณสามารถยืนยันข้อเรียกร้องของคุณด้วยแนวทางใด ๆ โดย SO? ฉันเห็นผู้คนมากมายทำการตลาด SDK ของพวกเขาเอง (แม้จ่ายเพียงครั้งเดียว) ด้วยข้อจำกัดความรับผิดชอบซึ่งฉันคิดว่ามีอยู่มากที่นี่เช่นกัน
Nirav Bhatt

12
ไม่มีแนวทางต่อต้าน แต่ถ้าคุณมาที่นี่เพื่อทำเงินคุณอาจมาที่นี่ด้วยเหตุผลที่ผิด IMO คำตอบของคุณดูเหมือนจะเน้นไปที่การให้ผู้คนลงทะเบียนในวิดีโอการสอนของคุณและไม่ได้ช่วยเหลือผู้อื่น
Jojodmo

3
นี่เป็นเพียงความรำคาญเท่านั้น
durazno
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.