diff --git a/main.go b/main.go
index 5900b65..8856da3 100644
--- a/main.go
+++ b/main.go
@@ -28,6 +28,47 @@ type OrderData struct {
} `json:"purchase_units"`
}
+type SubscriptionData struct {
+ ID string `json:"id"`
+ Status string `json:"status"`
+ // StatusUpdateTime time.Time `json:"status_update_time"`
+ PlanID string `json:"plan_id"`
+ PlanOverridden bool `json:"plan_overridden"`
+ // StartTime time.Time `json:"start_time"`
+ Quantity string `json:"quantity"`
+ ShippingAmount struct {
+ CurrencyCode string `json:"currency_code"`
+ Value string `json:"value"`
+ } `json:"shipping_amount"`
+ Subscriber struct {
+ Name struct {
+ GivenName string `json:"given_name"`
+ Surname string `json:"surname"`
+ } `json:"name"`
+ EmailAddress string `json:"email_address"`
+ PayerID string `json:"payer_id"`
+ ShippingAddress struct {
+ Name struct {
+ FullName string `json:"full_name"`
+ } `json:"name"`
+ Address struct {
+ AddressLine1 string `json:"address_line_1"`
+ AddressLine2 string `json:"address_line_2"`
+ AdminArea2 string `json:"admin_area_2"`
+ AdminArea1 string `json:"admin_area_1"`
+ PostalCode string `json:"postal_code"`
+ CountryCode string `json:"country_code"`
+ } `json:"address"`
+ } `json:"shipping_address"`
+ } `json:"subscriber"`
+ // CreateTime time.Time `json:"create_time"`
+ Links []struct {
+ Href string `json:"href"`
+ Rel string `json:"rel"`
+ Method string `json:"method"`
+ } `json:"links"`
+}
+
var (
baseURL = "https://api-m.sandbox.paypal.com"
)
@@ -42,6 +83,7 @@ func main() {
// Handlers
http.HandleFunc("/api/orders", CreateOrder)
http.HandleFunc("/api/orders/", CaptureOrder)
+ http.HandleFunc("/api/paypal/create-subscription", CreateSubscription)
http.Handle("/", http.FileServer(http.Dir("./public")))
// Channel to listen for signals
@@ -178,5 +220,80 @@ func CaptureOrder(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(result); err != nil {
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
+ return
}
}
+
+func CreateSubscription(w http.ResponseWriter, r *http.Request) {
+ log.Printf("asked to create sub")
+
+ planID := os.Getenv("PLAN_ID")
+ returnUrl := "https://suckless.org"
+ cancelUrl := "https://suckless.org"
+
+ log.Printf("This is the planid: %s", planID)
+
+ token, err := Token()
+ if err != nil {
+ http.Error(w, "Failed to get access token", http.StatusInternalServerError)
+ return
+ }
+
+ log.Printf("This is the token: %s", token)
+
+ body := map[string]interface{}{
+ "plan_id": planID,
+ "application_context": map[string]string{
+ "shipping_preference": "NO_SHIPPING",
+ "return_url": returnUrl,
+ "cancel_url": cancelUrl,
+ },
+ }
+ jsonData, err := json.Marshal(body)
+ if err != nil {
+ http.Error(w, "Server error", http.StatusInternalServerError)
+ return
+ }
+
+ log.Printf("Creating request")
+ req, err := http.NewRequest("POST", baseURL+"/v1/billing/subscriptions", bytes.NewBuffer(jsonData))
+ if err != nil {
+ http.Error(w, "Failed to create request", http.StatusInternalServerError)
+ return
+ }
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Accept", "application/json")
+ req.Header.Set("Prefer", "return=representation")
+
+ log.Printf("Sending request")
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ http.Error(w, "Failed to send request", http.StatusInternalServerError)
+ return
+ }
+ defer resp.Body.Close()
+ log.Printf("Request sent")
+
+ // Create an instance of AutoGenerated
+ // var result SubscriptionData
+ var result map[string]interface{}
+
+ // Decode the response into the AutoGenerated struct
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ http.Error(w, "Failed to decode response", http.StatusInternalServerError)
+ return
+ }
+ log.Printf("Raw JSON Response: %v", result)
+
+ // Now, `result` contains the entire structured response
+ // You can send the whole `result` back to the client, or you can selectively send fields.
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(result); err != nil {
+ http.Error(w, "Failed to encode response", http.StatusInternalServerError)
+ }
+ log.Printf("sent response to client")
+
+}
diff --git a/public/form.js b/public/form.js
index cc87420..9ad3d0b 100644
--- a/public/form.js
+++ b/public/form.js
@@ -145,6 +145,63 @@ window.paypal_order.Buttons({
},
}).render("#paypal-button-container-order");
+window.paypal_subscribe.Buttons({
+ style: { shape: 'pill', color: 'black', layout: 'vertical', label: 'subscribe' },
+ async createSubscription() {
+ try {
+ const response = await fetch("/api/paypal/create-subscription", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ userAction: "SUBSCRIBE_NOW" }),
+ });
+ const data = await response.json();
+ if (data?.id) {
+ resultMessage(`Successful subscription with ID ${data.id}...
`);
+ return data.id;
+ } else {
+ console.error(
+ { callback: "createSubscription", serverResponse: data },
+ JSON.stringify(data, null, 2),
+ );
+ // (Optional) The following hides the button container and shows a message about why checkout can't be initiated
+ const errorDetail = data?.details?.[0];
+ resultMessage(
+ `Could not initiate PayPal Subscription...
${
+ errorDetail?.issue || ""
+ } ${errorDetail?.description || data?.message || ""} ` +
+ (data?.debug_id ? `(${data.debug_id})` : ""),
+ { hideButtons: true },
+ );
+ }
+ const approvalUrl = data.links.find(link => link.rel === "approve").href;
+ window.location.href = approvalUrl;
+ } catch (error) {
+ console.error(error);
+ resultMessage(
+ `Could not initiate PayPal Subscription...
${error}`,
+ );
+ }
+ },
+ onApprove(data) {
+ /*
+ No need to activate manually since SUBSCRIBE_NOW is being used.
+ Learn how to handle other user actions from our docs:
+ https://developer.paypal.com/docs/api/subscriptions/v1/#subscriptions_create
+ */
+ if (data.orderID) {
+ resultMessage(
+ `You have successfully subscribed to the plan. Your subscription id is: ${data.subscriptionID}`,
+ );
+ } else {
+ resultMessage(
+ `Failed to activate the subscription: ${data.subscriptionID}`,
+ );
+ }
+ },
+}).render("#paypal-button-container-subscribe"); // Renders the PayPal button
+
// Example function to show a result to the user. Your site's UI library can be used instead.
function resultMessage(message) {
const container = document.querySelector("#checkout");
diff --git a/public/index.html b/public/index.html
index d0d48c0..b580f42 100644
--- a/public/index.html
+++ b/public/index.html
@@ -29,10 +29,10 @@
src="https://www.paypal.com/sdk/js?client-id=AcCW43LI1S6lLQgtLkF4V8UOPfmXcqXQ8xfEl41hRuMxSskR2jkWNwQN6Ab1WK7E2E52GNaoYBHqgIKd&components=buttons&enable-funding=card&disable-funding=paylater,venmo"
data-namespace="paypal_order"
>
-
-
-
-
+