Implement PG payments
To sell consumable products with PG payments and deliver them to users, complete the following procedure in order.
Before you begin, complete the common prerequisites.
Overall flow
In PG payments, the user pays in a browser outside the app. The app cannot tell whether the payment is complete, so after the user returns, it queries the Hive Axyl server to check the result. In step 10, the app server handles receipt verification and product delivery with the Hive Axyl Server API.
| Step | Category | What you do |
|---|---|---|
| 1 | Store console | Get credentials from the PG provider |
| 2 | Hive Console | Configure PG settings and price tiers, and register products |
| 3 | Hive Axyl SDK | Install and register the SDK modules |
| 4 | Recipe code | Copy the recipe folders |
| 5 | Hive Axyl SDK | Get the product list |
| 6 | Recipe code | Create a PurchaseOrder |
| 7 | Recipe code | Issue the payment page URL |
| 8 | App code | Open the payment page in a browser |
| 9 | Recipe code | Retrieve the receipt and prepare purchase information |
| 10 | App code, App server, Hive Axyl Server API | Verify receipts and deliver products |
| 11 | Recipe code | Finish the transaction |
| 12 | Hive Axyl SDK | Record delivery results |
| 13 | Hive Console, Hive Axyl Server API | Check the purchase history |
Finish the transaction only after product delivery is complete
Finishing the transaction in step 11 marks this purchase as finished. Restoring undelivered purchases finds only purchases that are not finished, so if you finish the transaction before delivery and the app stops midway, you cannot find that purchase again.
1. Get PG provider credentials
-
Store console Get credentials from the PG provider.
Where to check: Credentials to get from the PG provider
Get credentials from the PG provider you will use. The required values differ by PG provider.
2. Configure the Hive Console
- Hive Console Configure or check this in the Hive Console.
Register the values you got in step 1 in the Hive Console, and register price tiers and products.
| Setting | Required | Where to check |
|---|---|---|
| Set PG provider credentials and payment methods | Required | Set PG provider credentials and payment methods |
| Set price tiers | Required | Register a price tier |
| Register products | Required | Register product information |
PG payments charge the price registered in the Hive Console. If you skip setting price tiers, the payment amount is not determined.
3. Prepare SDK modules
-
Hive Axyl SDK Call the Hive Axyl SDK from the app.
Detailed procedure: Install the module, initialize, and log in
Install the required SDK modules, and then register them when you initialize the SDK. PG payments do not use a store plugin. Payment works on the basis of the logged-in user, so along with the payment module, you must also register the authentication module AddAuth() and the token module AddToken(). If you do not register the payment module, the recipe fails with a FailedPrecondition error.
- Packages to install
com.com2usplatform.hiveaxyl.core: SDK initialization and common featurescom.com2usplatform.hiveaxyl.auth: Login session and token issuance for the user who is the payercom.com2usplatform.hiveaxyl.payments: Payment server calls
The following example code registers the authentication, token, and payment modules when initializing the SDK. The example connects the payment module to the sandbox server for development and testing, so in production builds, register it with AddPayments() without sandbox: true.
4. Install the recipe code
- Recipe code Copy the recipe code into your project.
A recipe is source code that you copy into your project instead of installing as a package. Copy the following items from the axyl-samples-unity repository to Assets/Recipes/ in your Unity project.
- Items to copy
- Recipes.asmdef: Common assembly definition
- AssemblyInfo.cs: Setting that exposes internal helpers to other recipe assemblies
- Helper/: Common code shared by multiple recipes
- Payments/: Recipe body and common types
- Payments.Pg/: Code for PG
To call recipes from your app code, add Hive.Axyl.Samples.Recipes, Hive.Axyl.Samples.Recipes.Payments, and Hive.Axyl.Samples.Recipes.Payments.Pg to references in your app's assembly definition.
Platforms that provide PG payments
A PG order must specify the OS that the app client is running on with the OrderRequestOs enum. The recipe fills in Windows, Macos, Android, and Ios on Windows, macOS, Android, and iOS, respectively. On a platform that has no matching value, it cannot create the order and returns a FailedPrecondition error.
5. Get the product list
-
Hive Axyl SDK Call the Hive Axyl SDK from the app.
Detailed procedure: Get PG product information
There is no recipe for getting the product list. Get it directly with FetchPgProductsAsync() of the Hive Axyl SDK.
Put ProductId, Price, and Currency from the result as is into the PurchaseOrder in step 6. A product whose Price is empty is a product whose price could not be confirmed, so do not sell it.
6. Create a PurchaseOrder
- Recipe code Call the recipe code from the app.
PurchaseOrder is a recipe type that holds which product the user wants to purchase and at what price, and it is the starting point of every payment recipe.
using Hive.Axyl.Samples.Recipes;
// Namespace UUID that the app sets once and never changes
const string k_Namespace = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
AccountUuid.TryCompute(k_Namespace, playerId, out var accountUuid);
if (product.Price == null)
{
// Do not sell products whose price could not be confirmed.
return;
}
var order = new PurchaseOrder(
productId: product.ProductId, // Value retrieved in step 5
price: product.Price.Value,
currency: product.Currency,
country: "KR",
language: "ko",
storePlayerId: 0L, // Not used by PG
serverId: "server01",
iapPayload: "{\"character\":\"A\"}",
appVersion: Application.version,
accountUuid: accountUuid);
AccountUuid.TryCompute() is a recipe helper that creates a UUIDv5 from the logged-in user's playerId. PG has no market account token to compare against, so the comparison result in the verification response always comes back as 9. However, this value is saved in the payment history and serves as the basis for confirming which user made the payment. For the generation rules, see Generate an AccountUuid.
Put information that identifies the delivery target in iapPayload. This value comes back as is in the verification response, so it becomes the basis for deciding what to deliver to whom when you restore the purchase later.
7. Issue the payment page URL
- Recipe code Call the recipe code from the app.
When the user selects the payment button, call the recipe's InitiatePurchaseAsync(). It records the purchase intent on the server and returns the payment page URL. Because the user must be sent outside the app, the success status at this point is AwaitingExternal, not Success.
To sell multiple units at once, specify the quantity with new PgPurchaseSource(quantity). If you omit it, the quantity is treated as 1.
7.1. Start the purchase
started.Pending contains the order information, and you receive the receipt in step 9. As in the following example code, save this value to the app's purchase information store first, before checking the result status. Once you save it, you do not lose the order information of this purchase even if a later step fails.
var recipe = new ConsumablePurchaseRecipe(new PgPurchaseSource());
InitiatePurchaseOutcome started = await recipe.InitiatePurchaseAsync(order, token);
if (started.Pending != null)
{
// Save it first, regardless of the result status.
await pendingStore.SaveAsync(started.Pending);
}
if (started.Status == InitiatePurchaseStatus.UserCanceled)
{
// No order was created. Return to the in-app store.
return;
}
if (started.Status != InitiatePurchaseStatus.AwaitingExternal)
{
Debug.LogError($"{started.FailedStep}: {started.BusinessOutcome} {started.Error?.Message}");
return;
}
string payUrl = started.ExternalUrl; // Open it in step 8
var pending = started.Pending; // No receipt yet
The recipe performs the following tasks internally.
| Category | Call | Where to check |
|---|---|---|
| Hive Axyl SDK | Record the purchase intent on the server with CreatePrePurchaseAsync() | Save pre-purchase information |
| Hive Axyl SDK | Issue the payment page URL with CreatePaymentUrlAsync() | Create the payment page URL |
7.2. Manage the purchase information store
pendingStore in the example code is a store that the app creates, and it must remain even when the app is relaunched. started.Pending in this step does not have an order number yet, so save it with the account and product ID as the key. When you receive prepared.Pending containing the order number in step 9, delete this record and save it again with OrderId as the key. Delete it only after the transaction is finished successfully, and do not write receipt and token values to logs. In the usage example, SavePendingAsync() and MarkClosedAsync() of IPurchaseApplication play the same role.
8. Open the payment page in a browser
-
App code Implement this in the app.
Detailed procedure: Proceed with the payment
Open the URL returned in step 7 in a browser. The recipe only returns the URL and does not open it, because the app decides whether to use an in-app browser or an external browser.
When the user finishes the payment and returns to the app, proceed to step 9. In the usage example, IPurchaseApplication.WaitUntilVerifiableAsync() opens the payment page and waits for the user to return.
The user's return does not mean the payment succeeded
The app cannot know what happened in the browser. You can find orders whose payment is complete only by querying the Hive Axyl server in step 9, and the app server decides whether to deliver the product after verifying the receipt in step 10. Do not deliver the product just because the user returned.
9. Retrieve the receipt and prepare purchase information
- Recipe code Call the recipe code from the app.
When the user returns, call PreparePurchaseAsync(). The recipe retrieves the receipt of the order whose payment is complete from the Hive Axyl server, records the purchase result, and returns prepared.Pending filled with the receipt and the order number. The recipe does not request receipt verification in this step, so even if you receive a success result, you must not deliver the product yet.
PreparePurchaseOutcome prepared = await recipe.PreparePurchaseAsync(pending, token);
if (prepared.Pending != null)
{
// Replace the saved purchase information with the returned value.
// If the receipt was retrieved, delete the record from step 7 and save it with OrderId as the key.
await pendingStore.SaveAsync(prepared.Pending);
}
if (prepared.Status == PreparePurchaseStatus.BusinessOutcome
&& prepared.BusinessOutcome == PurchaseBusinessOutcome.NothingToRestore)
{
// The user has not finished the payment yet, or the order is already closed. This is not an error.
// Do not have the app query repeatedly; make this call again when the user requests it again.
return;
}
if (prepared.Status != PreparePurchaseStatus.Success)
{
// Do not delete the saved purchase information. Finish it in undelivered purchase restoration.
return;
}
// Pass prepared.Pending, not pending from step 7, to steps 10 and 11.
The recipe performs the following tasks internally.
| Category | Call | Where to check |
|---|---|---|
| Hive Axyl SDK | Retrieve the receipt from undelivered orders with RestorePurchasesAsync() | Get completed payment information |
| Hive Axyl SDK | Save the purchase result with RecordStorePurchaseAsync() | Save payment result data |
10. Verify receipts and deliver products
-
App code Implement this in the app.
App server Implement this on the app server.
Hive Axyl Server API Call the Hive Axyl Server API from the app server.
Detailed procedure: Verify receipts with the Hive Axyl Server API
The app server handles receipt verification and product delivery, and the recipe is not involved in this step. When the app sends prepared.Pending from step 9 to the app server, the app server verifies the receipt with the Hive Axyl Server API, delivers the product only once, and then responds to the app with the result.
10.1. Request verification and delivery from the app server
Call the app server from the app to request receipt verification and product delivery. Send Market, AxylReceipt, VerifyToken, OrderId, and StoreTransactionId of prepared.Pending and the order information in Order to the app server. The app decides how to call the app server and the response format.
VerifyAndGrantOnceAsync() in the example code is the app's method that calls the app server. In the usage example, IPurchaseApplication.VerifyAndGrantOnceAsync() plays the same role. Implement this method as follows.
- Second argument
requestType: A value that indicates whether this is a new purchase or a purchase restoration. The app decides the argument format- Example code: Passes
1, the value to put inrequestTypeof the receipt verification request, as is - Usage example: Passes
PurchaseVerificationKind.NewPurchaseorPurchaseVerificationKind.Recovery, and the app server integration code converts these values to1and2, respectively
- Example code: Passes
- Return value:
new PurchaseDeliveryConfirmation()if the app server responds with delivery completed;nullif delivery could not be confirmed because of a rejection or failure - Keep
hiveAxylTransactionId:PurchaseDeliveryConfirmationhas no field that holds this value, so keep it separately from the app server response and use it to record the delivery result in step 12
// Request receipt verification and product delivery from the app server.
PurchaseDeliveryConfirmation delivery = await VerifyAndGrantOnceAsync(
prepared.Pending,
requestType: 1, // 1: New purchase. PurchaseVerificationKind.NewPurchase in the usage example
token);
if (delivery == null)
{
// The app server did not confirm delivery.
// Keep the saved purchase information and do not finish the transaction.
return;
}
// VerifyAndGrantOnceAsync() keeps hiveAxylTransactionId from the app server response separately.
// Use it as axylTransactionId in step 12.
10.2. Verify receipts with the Hive Axyl Server API
Have the app server call POST /payment/v1/purchase/verify of the Hive Axyl Server API to verify the receipt. Put the App ID registered in the Hive Console in the X-App-Id request header, and for the address to send the request to, see Base URL. Get the access token for the app server that the call requires with Issue a token. For all request and response fields, see Verify consumable product receipts.
Put the PendingPurchase values that the app sent into the request fields as follows.
providerId:PGaxylReceipt: The receipt issued by the Hive Axyl server, contained inVerifyToken. The value retrieved in step 9productId,price,currency,country,language,serverId,iapPayload,accountUuid,appVersion: The values of the same names inOrderorderId:OrderIdstoreTransactionId:StoreTransactionIdrequestType:1, which indicates a new purchase
10.3. Deliver products and respond with delivery completed
After checking the verification response, have the app server deliver the product only once and respond to the app with the result. The app server must do the following.
- Check that
hiveAxylProductId,hiveAxylQuantity,hiveAxylPrice, andhiveAxylCurrencyin the verification response match the product, quantity, and amount sold to the user. IfhiveAxylPurchaseCancelStateis1, the payment was canceled, so do not deliver the product. - Check the app server's delivery records to see whether the purchase has already been delivered. If
hiveAxylDuplicatedistrue, you must check this. If the purchase has already been delivered, skip item 3. - Deliver the product and record delivery completion.
- Respond to the app with delivery completed. Respond with delivery completed even for purchases already delivered so that the app finishes the transaction in step 11. Also return
hiveAxylTransactionIdfrom the verification response so that it can be used to record the delivery result in step 12.
Determine the delivery target with hiveAxylPayload in the verification response. It is the iapPayload passed in step 6, returned as is.
Do not deliver products based only on values the app sent
Have the app server authenticate the requesting user's account directly and check the product and amount with the verification response. The PurchaseDeliveryConfirmation that the app creates is not a value that proves payment. The app server always decides whether to deliver.
11. Finish the transaction
- Recipe code Call the recipe code from the app.
Call ClosePurchaseAsync() after the app server responds with delivery completed. Without the delivery created in step 10, the recipe does not finish the transaction and returns Failure.
ClosePurchaseOutcome closed = await recipe.ClosePurchaseAsync(
prepared.Pending, // Value returned by step 9. You cannot finish the transaction with the Pending from step 7
delivery, // Value created in step 10 from the app server's delivery completed response
token);
if (closed.Status == ClosePurchaseStatus.Success)
{
// The purchase is complete, so delete the saved purchase information.
await pendingStore.DeleteAsync(prepared.Pending);
}
else
{
// Verification and delivery are already done; only the purchase remains open.
// Keep the saved purchase information and call again, or finish it in undelivered purchase restoration.
}
In PG payments, the transaction is finished on the Hive Axyl server, not on the device. The recipe performs the following tasks internally.
| Category | Call | Where to check |
|---|---|---|
| Hive Axyl SDK | Request purchase finalization from the server with FinalizePurchaseAsync() | Finalize the purchase |
12. Record delivery results
-
Hive Axyl SDK Call the Hive Axyl SDK from the app.
Detailed procedure: Save product delivery results
After delivery is complete, record the result on the Hive Axyl server. This call does not confirm the payment or decide whether to deliver; it only leaves a record for operations.
In AxylTransactionId, put the hiveAxylTransactionId that VerifyAndGrantOnceAsync() in 10.1 kept separately from the app server response.
using Hive.Axyl.Payments;
var payments = HiveCore.Resolve<IPaymentsService>();
await payments.ItemResultAsync(new ItemResultBody
{
AxylTransactionId = axylTransactionId, // hiveAxylTransactionId received in the app server response in 10.1 and kept separately
Status = 1, // 1: Delivery succeeded
Assets = new[]
{
new ItemResultAsset { AssetId = "gold", AssetName = "골드", Quantity = 100 },
},
});
13. Check the purchase history
-
Hive Console Configure or check this in the Hive Console.
Hive Axyl Server API Call the Hive Axyl Server API from the app server.
During operation, get the purchase history to check the payment status and delivery status.
| Category | Method | Where to check |
|---|---|---|
| Hive Console | Specify search conditions in the console and check | Specify search conditions |
| Hive Axyl Server API | Call the purchase history API from the app server | Get purchase history |
Failure handling
Recipe methods do not throw exceptions; they return result objects. Always check Status first.
Status | Meaning | What the app does |
|---|---|---|
AwaitingExternal | The payment page URL is ready. This is the success status of starting a purchase in PG payments. | Open the browser. |
Success | The step finished normally. | Proceed to the next step. |
BusinessOutcome | The Hive Axyl server rejected the request, or there is no order to retrieve. | Branch on the BusinessOutcome value. For how to handle each value, see Rejection reasons in payment recipes. |
Failure | A technical failure such as a network error or timeout. | Record Error and decide whether to retry. |
UserCanceled | The SDK reported the payment as canceled. | This is not an error, so return to the in-app store. |
AwaitingExternal and UserCanceled exist only in the step that starts the purchase. The result types of the prepare and close steps do not have them, so branching on those values does not compile.
Do not discard PendingPurchase even if it fails
PreparePurchaseOutcome and ClosePurchaseOutcome return Pending even when they fail. It is either the Pending you passed or a Pending newly filled with the values retrieved in step 9, so replace the purchase information saved in step 7 with the returned Pending so that you can finish that purchase later. Even when the app server could not confirm delivery in step 10, keep the purchase information. Delete the saved purchase information only after the transaction is finished successfully.
Next steps
To handle purchases whose payment is complete but whose delivery is not finished, see Restore undelivered PG purchases.